// =====================================================
// Odinote — Context Sidebar (left side when item selected)
// =====================================================

const EMOJI_OPTIONS = ['👍','❤️','🔥','💡','✨','🎉','👀','🤔','😂','😍','🙏','💯','⚡','🚀','🎯','🤝','👏','💪','😎','🥳'];

// Icon choices for boards (Material Symbols names) — uses the shared list defined in items.jsx
const BOARD_ICON_CHOICES = [
  // General / organización
  'dashboard','folder','folder_open','category','widgets','grid_view','space_dashboard','inventory_2','label','bookmark','push_pin','tag','workspaces','topic',
  // Trabajo / productividad
  'work','business_center','lightbulb','rocket_launch','trending_up','target','task_alt','checklist','event','schedule','calendar_month','timeline','flag','priority_high',
  // Creatividad / arte
  'palette','brush','draw','format_paint','color_lens','imagesmode','photo_camera','movie','theaters','music_note','headphones','mic','piano','auto_awesome','animation',
  // Desarrollo / juegos
  'code','terminal','bug_report','memory','smart_toy','sports_esports','stadia_controller','casino','extension','joystick','swords',
  // Conocimiento / escritura
  'book','menu_book','auto_stories','school','science','biotech','functions','calculate','history_edu','translate','psychology','quiz','description','edit_note',
  // Mundo / mapas
  'map','public','travel_explore','place','explore','terrain','forest','park','landscape','castle','cottage','home','apartment','store',
  // Personas / social
  'group','person','groups','diversity_3','handshake','forum','chat','campaign','volunteer_activism',
  // Naturaleza / elementos
  'eco','local_florist','pets','water_drop','local_fire_department','bolt','wb_sunny','dark_mode','cloud','ac_unit','spa',
  // Objetos / símbolos
  'star','favorite','diamond','emoji_objects','emoji_events','military_tech','workspace_premium','shield','key','lock','visibility','build','construction','settings','tune','rocket','flight','directions_car','sailing','anchor','restaurant','local_cafe','sports_bar','cake','redeem',
];

// Color picker — separate background and header strip for columns
const STICKY_PALETTE = [
  { key: 'white',  hex: '#FFFFFF', label: 'Blanco' },
  { key: 'mist',   hex: '#ECEBEB', label: 'Gris' },
  { key: 'cream',  hex: '#FEF7E0', label: 'Crema' },
  { key: 'yellow', hex: '#F7DA84', label: 'Amarillo' },
  { key: 'orange', hex: '#F5A66B', label: 'Naranja' },
  { key: 'rose',   hex: '#FBDFDD', label: 'Rosa' },
  { key: 'red',    hex: '#E6544F', label: 'Rojo' },
  { key: 'pink',   hex: '#E58AB8', label: 'Magenta' },
  { key: 'purple', hex: '#B084D0', label: 'Púrpura' },
  { key: 'sky',    hex: '#8AC1E8', label: 'Cielo' },
  { key: 'sage',   hex: '#E8F0DA', label: 'Verde claro' },
  { key: 'green',  hex: '#90B968', label: 'Verde' },
  { key: 'dark',   hex: '#595459', label: 'Gris oscuro' },
];

// Los colores de las franjas de antes. Ya no se ofrecen —la franja se elige con
// el selector de siempre, cuatro y el arcoíris— pero hay tableros y columnas
// guardados con estos nombres, y sin esta lista se pintarían de blanco. Se queda
// como diccionario de lo antiguo, no como paleta.
const STRIP_PALETTE = [
  { key: 'navy',   hex: '#3D5A80' },
  { key: 'indigo', hex: '#6C5FAF' },
  { key: 'plum',   hex: '#955BA5' },
  { key: 'wine',   hex: '#993844' },
  { key: 'red',    hex: '#C24A45' },
  { key: 'orange', hex: '#D88040' },
  { key: 'gold',   hex: '#D5A949' },
  { key: 'olive',  hex: '#6A8546' },
  { key: 'forest', hex: '#1F4D3F' },
  { key: 'dark',   hex: '#595459' },
  { key: 'black',  hex: '#1A1A1A' },
];

// El nombre de cada color de la paleta, en el idioma de la interfaz (en
// STICKY_PALETTE solo estaba en español).
function nombreColorPaleta(p) {
  const nombres = {
    white: window.t('Blanco', 'White'), mist: window.t('Gris', 'Gray'), cream: window.t('Crema', 'Cream'),
    yellow: window.t('Amarillo', 'Yellow'), orange: window.t('Naranja', 'Orange'), rose: window.t('Rosa', 'Pink'),
    red: window.t('Rojo', 'Red'), pink: window.t('Magenta', 'Magenta'), purple: window.t('Púrpura', 'Purple'),
    sky: window.t('Cielo', 'Sky'), sage: window.t('Verde claro', 'Light green'), green: window.t('Verde', 'Green'),
    dark: window.t('Gris oscuro', 'Dark gray'), black: window.t('Negro', 'Black'),
  };
  return nombres[p.key] || p.label || p.key;
}

function resolveStickyColor(key) {
  const found = STICKY_PALETTE.find(p => p.key === key);
  if (found) return found.hex;
  if (key && key.startsWith('#')) return key;
  // Y si no está, se mira en los nombres viejos de las franjas antes de
  // rendirse. Quien tenga una columna guardada como 'navy' de hace meses la
  // sigue viendo azul en lugar de descubrirla en blanco un día cualquiera.
  const antiguo = STRIP_PALETTE.find(p => p.key === key);
  if (antiguo) return antiguo.hex;
  return '#FFFFFF';
}

// ── El tamaño de letra, en un desplegable como el de Word ──
//
// Antes eran dos botones, "aumentar" y "disminuir", en tres sitios distintos y
// con tres listas de pasos distintas: el título de un marco saltaba de 14 a 18,
// una leyenda de 11 a 13, y el texto de una nota iba por multiplicadores (1,15;
// 1,35…). Para llegar a un tamaño concreto había que ir pulsando y mirando, y
// no se sabía nunca en qué número se estaba.
//
// Ahora es un número a la vista y una lista para elegir, como en cualquier
// procesador de textos: un botón con el número, y al tocarlo un panel AL LADO de
// la barra, como el del color. Al principio era un <select> del sistema, pero su
// lista se abría hacia abajo tapando los botones de debajo, no se parecía en nada
// al resto de la aplicación y no dejaba pasar de 72. Ahora el panel trae un campo
// donde cabe cualquier tamaño y enseña cada tamaño en el nodo al pasar el ratón.
//
// Cada nodo sigue guardando su tamaño como lo guardaba (píxeles o
// multiplicador); esto solo traduce. Así los tableros que ya existen se ven
// exactamente igual que antes.
const TAMANOS_LETRA = [8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 60, 72];

// Un tamaño con decimales se enseña con uno solo (13,5), y uno entero, entero.
const numeroTamano = (px) => (Math.abs(px - Math.round(px)) < 0.05 ? String(Math.round(px)) : px.toFixed(1));

// Lo que se escriba en el campo, dentro de lo razonable: por debajo de 1 no se
// lee nada, y por encima de 400 una sola letra ya no cabe en ningún nodo.
const TAMANO_MIN = 1;
const TAMANO_MAX = 400;
const leeTamano = (t) => {
  const px = parseFloat(String(t).replace(',', '.'));
  if (!isFinite(px) || px < TAMANO_MIN || px > TAMANO_MAX) return null;
  return Math.round(px * 10) / 10;
};

// ── La vista previa al pasar el ratón ──
// Enseñar un tamaño NO puede pasar por onUpdate: cada paso quedaría en el
// historial de deshacer (que junta lo que pasa en 450 ms) y se mandaría a
// guardar y a la nube. Así que se pinta directamente en el nodo, con una
// variable CSS que solo existe mientras dura la vista previa: cada nodo lee su
// tamaño como var(--vista-…, lo guardado), y al quitarla vuelve solo a lo suyo.
// El atributo es para las leyendas, cuyo tamaño normal no está en el nodo sino
// en la hoja de estilos (y cambia de un tipo de nodo a otro).
function nodoEnPantalla(itemId) {
  return document.querySelector(`[data-item-id="${String(itemId).replace(/"/g, '\\"')}"]`);
}
function ponVistaTamano(itemId, spec, px) {
  const el = nodoEnPantalla(itemId);
  if (!el) return;
  el.style.setProperty(spec.variable, spec.aVista(px));
  el.setAttribute('data' + spec.variable.slice(1), '');
}
function quitaVistaTamano(itemId, spec) {
  const el = nodoEnPantalla(itemId);
  if (!el) return;
  el.style.removeProperty(spec.variable);
  el.removeAttribute('data' + spec.variable.slice(1));
}

// Tamaños fijos de los títulos, como los encabezados de un documento.
const NIVELES_TITULO = [
  { id: 'h1', px: 64 },
  { id: 'h2', px: 48 },
  { id: 'h3', px: 36 },
  { id: 'h4', px: 28 },
];

// El tamaño del texto de un nodo. Por dentro sigue siendo un multiplicador de
// la letra base de cada tipo (13,5 en notas, comentarios, tareas y calendario;
// 15 en la figura), que es lo que ya tienen guardado los tableros; se enseña en
// píxeles. Lo usan esta barra y la de formato de texto (TextFormatSidebar).
function specTamanoTexto(item, onUpdate) {
  const baseTexto = item.type === 'shape' || item.type === 'carpeta' ? 15 : item.type === 'separator' ? 18
    : ['note', 'comment', 'todo', 'calendar'].includes(item.type) ? 13.5 : null;
  if (!baseTexto) return null;
  return {
    valor: baseTexto * (item.textScale || 1),
    guarda: (px) => onUpdate({ textScale: px / baseTexto }),
    variable: '--vista-escala', aVista: (px) => String(px / baseTexto),
    etiqueta: window.t('Tamaño', 'Size'), titulo: window.t('Tamaño del texto', 'Text size'),
  };
}
window.specTamanoTexto = specTamanoTexto;

function SelectorTamano({ clave, spec, pane, setPane }) {
  if (!spec) return null;
  const abierto = pane === 'tamano:' + clave;
  return (
    <button
      className={`ctx-btn ctx-tamano ${abierto ? 'active' : ''}`}
      onClick={() => setPane(abierto ? null : 'tamano:' + clave)}
      title={spec.titulo}
    >
      <span className="ctx-tamano-num" aria-hidden="true">
        {numeroTamano(Math.round(spec.valor * 10) / 10)}
        <span className="material-symbols-rounded">expand_more</span>
      </span>
      <span>{spec.etiqueta}</span>
    </button>
  );
}

function PanelTamano({ spec, itemId, onCerrar }) {
  const actual = Math.round(spec.valor * 10) / 10;
  const [texto, setTexto] = React.useState(numeroTamano(actual));
  // Lo escrito en el campo y todavía no guardado. Se guarda con Enter, al salir
  // del campo o al cerrarse el panel por lo que sea (cambiar de nodo, ponerse a
  // escribir): un panel que se desmonta no avisa con ningún blur, y lo escrito
  // se perdería sin más.
  const pendiente = React.useRef(null);
  const specRef = React.useRef(spec);
  specRef.current = spec;
  React.useEffect(() => {
    setTexto(numeroTamano(Math.round(spec.valor * 10) / 10));
  }, [spec.valor]);
  React.useEffect(() => () => {
    quitaVistaTamano(itemId, specRef.current);
    if (pendiente.current != null) specRef.current.guarda(pendiente.current);
  }, []);

  const guarda = (px) => {
    pendiente.current = null;
    quitaVistaTamano(itemId, spec);
    if (Math.abs(px - spec.valor) > 0.01) spec.guarda(px);
    setTexto(numeroTamano(px));
    window.playAudioTone && window.playAudioTone('click');
  };
  const paso = (d) => {
    const base = leeTamano(texto) || actual;
    const px = Math.min(TAMANO_MAX, Math.max(TAMANO_MIN, Math.round(base) + d));
    guarda(px);
  };

  return (
    <div className="ctx-popout ctx-tamano-panel" onMouseDown={(e) => e.stopPropagation()}>
      <div className="ctx-pop-section">
        <div className="ctx-pop-title">{spec.titulo}</div>
        <div className="ctx-tamano-fila">
          <button className="ctx-tamano-paso" onClick={() => paso(-1)} title={window.t('Un punto menos', 'One point smaller')}>
            <span className="material-symbols-rounded">remove</span>
          </button>
          <input
            className="ctx-tamano-campo"
            inputMode="decimal"
            value={texto}
            aria-label={spec.titulo}
            onFocus={(e) => e.target.select()}
            onChange={(e) => {
              setTexto(e.target.value);
              const px = leeTamano(e.target.value);
              pendiente.current = px;
              if (px != null) ponVistaTamano(itemId, spec, px);
              else quitaVistaTamano(itemId, spec);
            }}
            onKeyDown={(e) => {
              // Que no llegue al lienzo: Supr o Retroceso aquí borran una
              // cifra, no el nodo.
              e.stopPropagation();
              if (e.key === 'Enter') {
                const px = leeTamano(texto);
                if (px != null) guarda(px);
                onCerrar();
              } else if (e.key === 'Escape') {
                pendiente.current = null;
                quitaVistaTamano(itemId, spec);
                onCerrar();
              }
            }}
            onBlur={() => {
              const px = pendiente.current;
              if (px != null) guarda(px);
              else setTexto(numeroTamano(Math.round(specRef.current.valor * 10) / 10));
            }}
          />
          <button className="ctx-tamano-paso" onClick={() => paso(1)} title={window.t('Un punto más', 'One point bigger')}>
            <span className="material-symbols-rounded">add</span>
          </button>
        </div>
        <div className="ctx-tamano-rejilla" onMouseLeave={() => quitaVistaTamano(itemId, spec)}>
          {TAMANOS_LETRA.map(t => (
            <button
              key={t}
              className={`ctx-tamano-op ${Math.abs(t - actual) < 0.05 ? 'active' : ''}`}
              onMouseEnter={() => ponVistaTamano(itemId, spec, t)}
              onClick={() => { guarda(t); onCerrar(); }}
            >
              {numeroTamano(t)}
            </button>
          ))}
        </div>
        <div className="ctx-tamano-pie">
          {window.t(`Pasa el ratón por un tamaño para verlo en el nodo. En el campo cabe cualquiera, de ${TAMANO_MIN} a ${TAMANO_MAX}.`,
            `Hover a size to preview it on the node. The box takes any size from ${TAMANO_MIN} to ${TAMANO_MAX}.`)}
        </div>
      </div>
    </div>
  );
}

function ContextSidebar({
  item, lang, onUpdate, onDelete, onDuplicate, onOpen, backlinks, onGoToBacklink,
  onClose, isColChild, onStartEdit, callbacks, editando,
}) {
  const [pane, setPane] = React.useState(null); // 'color' | 'emoji' | 'comments' | 'rename' | null
  // «Listo» del panel de color: se cierra el panel que lo contiene.
  React.useEffect(() => {
    const cierra = () => setPane(null);
    window.addEventListener('odi-color-listo', cierra);
    return () => window.removeEventListener('odi-color-listo', cierra);
  }, []);
  // En la ruleta, el elemento cuyo selector de color está abierto.
  const [colorRuleta, setColorRuleta] = React.useState(null);
  const [focusedRowVersion, setFocusedRowVersion] = React.useState(0);
  const [iconQuery, setIconQuery] = React.useState('');

  React.useEffect(() => {
    window._notifyFocusedRowChanged = () => setFocusedRowVersion(v => v + 1);
    return () => { window._notifyFocusedRowChanged = null; };
  }, []);

  // El panel abierto (la lista de lenguajes, un color…) se cierra al cambiar de
  // nodo o al entrar o salir de escribir. Antes se quedaba colgado ahí: elegías
  // un lenguaje, te ibas a otra cosa y la lista seguía puesta encima del
  // lienzo, tapando, sin nada que la cerrara.
  const modo = `${item ? item.id : ''}|${editando ? 1 : 0}|${item && item._editingTitle ? 1 : 0}`;
  const modoPrevio = React.useRef(modo);
  React.useEffect(() => {
    if (modoPrevio.current !== modo) {
      modoPrevio.current = modo;
      setPane(null);
    }
  }, [modo]);

  if (!item || window._calendarDayMenuOpen) return null;

  // Cada tamaño que se puede tocar en este nodo: qué número enseñar, cómo
  // guardarlo en el campo de siempre (píxeles o multiplicador) y con qué
  // variable se previsualiza. El botón y el panel leen los dos de aquí.
  const tamanos = {};
  if (item.type === 'frame') {
    tamanos.marco = {
      valor: item.titleSize || 14,
      guarda: (px) => onUpdate({ titleSize: px }),
      variable: '--vista-titulo', aVista: (px) => px + 'px',
      etiqueta: window.t('Título', 'Title'), titulo: window.t('Tamaño del título', 'Title size'),
    };
  }
  if (item.showCaption === true) {
    // Sin tamaño propio, la leyenda mide lo que diga la hoja de estilos para
    // ese tipo de nodo (11 en una imagen, 15 en una nota…): se enseña el que
    // se ve de verdad, no un número inventado.
    let visto = item.captionSize;
    if (!visto) {
      const el = nodoEnPantalla(item.id);
      const cap = el && el.querySelector('.node-caption');
      visto = cap ? Math.round(parseFloat(getComputedStyle(cap).fontSize) * 10) / 10 : 11;
    }
    tamanos.leyenda = {
      valor: visto || 11,
      guarda: (px) => onUpdate({ captionSize: px }),
      variable: '--vista-leyenda', aVista: (px) => px + 'px',
      etiqueta: window.t('Leyenda', 'Caption'), titulo: window.t('Tamaño de la leyenda', 'Caption size'),
    };
  }
  const specTexto = specTamanoTexto(item, onUpdate);
  if (specTexto) tamanos.texto = specTexto;

  const isText = ['note','comment','doc'].includes(item.type);
  const isColumn = item.type === 'column';
  const isBoard = item.type === 'board';
  const isLink = item.type === 'link';
  const isTodo = item.type === 'todo';
  const isImage = item.type === 'image';
  const isTable = item.type === 'table';
  const isAudio = item.type === 'audio';
  const isComment = item.type === 'comment';
  const isCalendar = item.type === 'calendar';
  const isColor = item.type === 'color';
  const isFile = item.type === 'file';
  const isDoc = item.type === 'doc';
  const isFrame = item.type === 'frame';
  const isBigTitle = item.type === 'bigtitle';
  const isMap = item.type === 'map';
  const isShape = item.type === 'shape';
  const isSeparator = item.type === 'separator';
  const isCarpeta = item.type === 'carpeta';
  const isRuleta = item.type === 'ruleta';
  const isGaleria = item.type === 'galeria';
  const isProgreso = item.type === 'progreso';
  const isCode = item.type === 'code';
  const isTimer = item.type === 'timer';
  // Mientras se escribe código, la barra se queda en lo justo: volver, y
  // comentar lo que esté seleccionado. Todo lo demás (colores, duplicar,
  // eliminar) estorba con las manos en el teclado, y una de esas es
  // irreversible.
  const editandoCodigo = isCode && editando;

  // ── "Abrir la carpeta del archivo" ──
  //
  // Aparece cuando hay algún archivo de verdad al que llevar, y hay dos formas
  // de tenerlo: la ruta de donde salió (se apunta al añadirlo, ver
  // window.rutaDeArchivo en items.jsx) o la copia que la bóveda escribe en su
  // carpeta `media`. El proceso principal prueba las dos y abre la primera que
  // exista, así que aquí basta con saber que hay algo que probar.
  //
  // Un `src` que empieza por `data:` no cuenta: eso es el archivo metido dentro
  // de la nota, que no existe en ninguna carpeta. Uno de internet tampoco.
  const srcLocal = typeof item.src === 'string' && item.src.trim() &&
    !item.src.startsWith('data:') && !/^https?:/.test(item.src);
  const puedeAbrirCarpeta = !!((item.rutaOrigen || srcLocal) &&
    window.electronAPI && window.electronAPI.mostrarEnCarpeta);

  const abreLaCarpeta = () => {
    window.playAudioTone && window.playAudioTone('click');
    window.electronAPI.mostrarEnCarpeta({
      ruta: item.rutaOrigen,
      src: item.src,
      carpeta: window.odiCarpetaProyecto || null,
    }).then((r) => {
      if (r && r.ok) return;
      const motivo = r && r.motivo;
      window.showToast && window.showToast(
        motivo === 'no-esta'
          ? window.t('El archivo ya no está en esa carpeta. Lo que ves aquí sigue guardado en la nota.',
                     'The file is not in that folder any more. What you see here is still saved in the note.')
          : motivo === 'sin-ruta'
          ? window.t('Este archivo vive dentro de la nota, no en una carpeta. Con una bóveda abierta, los que añadas se guardan también como archivos.',
                     'This file lives inside the note, not in a folder. With a vault open, the ones you add are also saved as files.')
          : window.t('No se pudo abrir la carpeta.', 'The folder could not be opened.'),
        'error'
      );
    });
  };
  const isDraw = item.type === 'draw';
  const isCurrentlyCropping = isImage && callbacks?.croppingId === item.id;
  // Dibujando: la barra se convierte en la caja de herramientas del lápiz y
  // nada más. Es el mismo patrón del recorte de una imagen.
  const isDrawingNow = isDraw && callbacks?.drawingId === item.id;

  const DRAW_COLORS = [
    '#1A1A1A', '#595459', '#FFFFFF', '#E6544F',
    '#D88040', '#DDAF2C', '#90B968', '#3CA59E',
    '#3D5A80', '#6C5FAF', '#955BA5', '#E58AB8',
  ];
  const DRAW_WIDTHS = [2, 4, 8, 14, 22];

  const downloadDocPdf = () => {
    if (!window.html2pdf) return;
    const title = (window.pickLang ? window.pickLang(item.title, lang) : '') || 'documento';
    const bodyHtml = (window.pickLang ? window.pickLang(item.body, lang) : '') || '';
    const el = document.createElement('div');
    el.style.cssText = 'padding:24px;font-family:Georgia,serif;color:#1a1a1a;line-height:1.55;';
    el.innerHTML = `<h1 style="font-size:22px;margin:0 0 12px;">${title}</h1>` + bodyHtml;
    window.html2pdf().set({ filename: `${title}.pdf`, margin: 12, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4' } }).from(el).save();
  };
  const tableCell = isTable && window._focusedTableCell && window._focusedTableCell.itemId === item.id
    ? window._focusedTableCell : null;

  // "Estirar": ajusta la altura del nodo a la de su contenido real (para
  // comentarios/notas largos que se cortan). Mide con la altura en auto.
  const fitHeightToContent = () => {
    const wrap = document.querySelector(`.item[data-item-id="${item.id}"]`) ||
                 document.querySelector(`[data-item-id="${item.id}"]`);
    if (!wrap) return;
    const card = wrap.querySelector('.item-card') || wrap.firstElementChild;
    if (!card) return;
    const prev = wrap.style.height;
    wrap.style.height = 'auto';
    const target = Math.max(60, Math.round(card.scrollHeight + 6));
    wrap.style.height = prev;
    // Además devuelve la nota al ajuste automático (deshace el tamaño manual)
    onUpdate({ h: target, manualH: false });
    window.playAudioTone && window.playAudioTone('click');
  };

  const reactions = item.reactions || {};
  const comments = item.comments || [];

  const addReaction = (emoji) => {
    const next = { ...reactions };
    next[emoji] = (next[emoji] || 0) + 1;
    onUpdate({ reactions: next });
  };

  const addComment = (text) => {
    const t = text.trim();
    if (!t) return;
    const id = `cm-${Date.now()}-${Math.floor(Math.random()*9999)}`;
    onUpdate({ comments: [...comments, {
      id,
      author: 'Anonymous',
      text: t,
      time: Date.now(),
    }] });
  };

  return (
    <>
      <div className="ctx-side">
        <button
          className="ctx-close"
          onClick={()=>{
            if (isCurrentlyCropping) {
              callbacks.setCroppingId(null);
              return;
            }
            // Dibujando, "atrás" guarda: salir tirando lo dibujado sin avisar
            // sería la peor forma de perder un rato de trabajo. Para tirarlo
            // está el botón de descartar, que sí pregunta.
            if (isDrawingNow) {
              if (pane) { setPane(null); return; }
              callbacks.saveDrawing();
              return;
            }
            // Step 1: if a sub-pane is open, just close it
            if (pane) { setPane(null); return; }
            // Step 2: if a table cell is focused, deselect cell to go back to table-level menu
            if (tableCell && tableCell.clearCellSelection) { tableCell.clearCellSelection(); return; }
            // Step 3: otherwise close the sidebar entirely
            onClose();
          }}
          title={window.t('Atrás', 'Back')}
        >
          <span className="material-symbols-rounded">arrow_back</span>
        </button>

        {editandoCodigo ? (
          /* Escribiendo código: solo volver (el botón de arriba) y comentar.
             Nada más, que con las manos en el teclado el resto sobra. */
          <button
            className="ctx-btn"
            onClick={()=>{ const f = window[`__codigoComentar_${item.id}`]; if (f) f(); }}
            title={window.t('Comentar o descomentar lo seleccionado', 'Comment or uncomment the selection')}
          >
            <span className="material-symbols-rounded">comment</span>
            <span>{window.t('Comentar', 'Comment')}</span>
          </button>
        ) : isCurrentlyCropping ? (
          <button
            className="ctx-btn"
            onClick={() => callbacks.setCroppingId(null)}
            title={window.t('Listo', 'Done')}
          >
            <span className="material-symbols-rounded">done</span>
            <span>{window.t('Listo', 'Done')}</span>
          </button>
        ) : isDrawingNow ? (
          <>
            <button
              className={`ctx-btn ${callbacks.drawTool === 'pen' ? 'active' : ''}`}
              onClick={()=>callbacks.setDrawTool('pen')}
              title={window.t('Lápiz', 'Pen')}
            >
              <span className="material-symbols-rounded">edit</span>
              <span>{window.t('Lápiz', 'Pen')}</span>
            </button>

            <button
              className={`ctx-btn ${callbacks.drawTool === 'move' ? 'active' : ''}`}
              onClick={()=>callbacks.setDrawTool('move')}
              title={window.t('Mover trazos', 'Move strokes')}
            >
              <span className="material-symbols-rounded">near_me</span>
              <span>{window.t('Mover', 'Move')}</span>
            </button>

            <button
              className={`ctx-btn ${callbacks.drawTool === 'eraser' ? 'active' : ''}`}
              onClick={()=>callbacks.setDrawTool('eraser')}
              title={window.t('Borrador', 'Eraser')}
            >
              <span className="material-symbols-rounded">ink_eraser</span>
              <span>{window.t('Borrar', 'Erase')}</span>
            </button>

            <button
              className={`ctx-btn ${pane === 'drawColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'drawColor' ? null : 'drawColor')}
              title={window.t('Color', 'Color')}
            >
              <div className="ctx-color-chip" style={{ background: callbacks.drawColor, border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Color', 'Color')}</span>
            </button>

            <button
              className={`ctx-btn ${pane === 'drawWidth' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'drawWidth' ? null : 'drawWidth')}
              title={window.t('Grosor', 'Thickness')}
            >
              <span className="material-symbols-rounded">line_weight</span>
              <span>{window.t('Grosor', 'Width')}</span>
            </button>

            <button
              className={`ctx-btn ${pane === 'drawPressure' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'drawPressure' ? null : 'drawPressure')}
              title={window.t('Cómo varía el trazo', 'How the stroke varies')}
            >
              <span className="material-symbols-rounded">stylus_note</span>
              <span>{window.t('Trazo', 'Stroke')}</span>
            </button>

            <button
              className={`ctx-btn ${pane === 'drawEstab' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'drawEstab' ? null : 'drawEstab')}
              title={window.t('Estabilizador: menos temblor al dibujar con ratón', 'Stabiliser: less wobble when drawing with a mouse')}
            >
              <span className="material-symbols-rounded">timeline</span>
              <span>{[window.t('Estabilizar', 'Stabilise'), window.t('Suave', 'Light'), window.t('Medio', 'Medium'), window.t('Fuerte', 'Strong')][callbacks.drawEstabilizador || 0]}</span>
            </button>

            <div className="ctx-sep-h"/>

            <div style={{ display: 'flex', gap: 4 }}>
              <button
                className="ctx-btn"
                disabled={!callbacks.canDrawUndo}
                style={{ flex: 1, opacity: callbacks.canDrawUndo ? 1 : 0.35 }}
                onClick={()=>callbacks.drawUndo()}
                title={window.t('Deshacer', 'Undo')}
              >
                <span className="material-symbols-rounded">undo</span>
              </button>
              <button
                className="ctx-btn"
                disabled={!callbacks.canDrawRedo}
                style={{ flex: 1, opacity: callbacks.canDrawRedo ? 1 : 0.35 }}
                onClick={()=>callbacks.drawRedo()}
                title={window.t('Rehacer', 'Redo')}
              >
                <span className="material-symbols-rounded">redo</span>
              </button>
            </div>

            <div className="ctx-sep-h"/>

            <button
              className="ctx-btn"
              onClick={()=>{
                window.customConfirm(window.t('¿Descartar lo dibujado en esta sesión?', 'Discard what you drew in this session?'))
                  .then(ok => { if (ok) callbacks.discardDrawing(); });
              }}
              title={window.t('Descartar', 'Discard')}
            >
              <span className="material-symbols-rounded">close</span>
              <span>{window.t('Descartar', 'Discard')}</span>
            </button>

            <button
              className="ctx-btn ctx-btn-primary"
              onClick={()=>callbacks.saveDrawing()}
              title={window.t('Guardar', 'Save')}
            >
              <span className="material-symbols-rounded">check</span>
              <span>{window.t('Guardar', 'Save')}</span>
            </button>
          </>
        ) : (
          <>
            {/* Botón de editar universal: entra al modo edición del nodo (para las
                notas, títulos, comentarios, etc. muestra las opciones de texto).
                Se omite en imagen (se edita con doble clic / recorte) y en nodos
                sin edición de contenido (color, audio, archivo). */}
            {!tableCell && onStartEdit && ['note','comment','bigtitle','todo','link','board','column','frame','shape','separator'].includes(item.type) && (
              <button
                className="ctx-btn"
                onClick={()=>{ onStartEdit(); window.playAudioTone && window.playAudioTone('click'); }}
                title={window.t('Editar', 'Edit')}
              >
                <span className="material-symbols-rounded">edit</span>
                <span>{window.t('Editar', 'Edit')}</span>
              </button>
            )}
            {/* Un dibujo no tiene fondo de tarjeta: su color es el de la tinta,
                y va en su propio botón (abajo) porque se aplica a los trazos.
                Y un bloque de código tampoco: su azul oscuro con el degradado
                es lo que lo hace reconocible de un vistazo. Ahí se cambia el
                título y su barra, que van en sus propios botones. */}
            {!tableCell && !isImage && !isDraw && !isCode && !isRuleta && (
              <button
            className={`ctx-btn ${(pane === 'color' || pane === 'colorHex') ? 'active' : ''}`}
            onClick={()=> isColor ? setPane(pane === 'colorHex' ? null : 'colorHex') : setPane(pane === 'color' ? null : 'color')}
            title={window.t('Color', 'Color')}
          >
            <div className="ctx-color-chip" style={{ background: isColor ? (item.hex || '#56B3A7') : isSeparator && !item.color ? 'var(--ink-2)' : isCarpeta && !item.color ? '#F7DA84' : resolveStickyColor(item.color || 'white'), border: '1.5px solid var(--line-soft)' }}/>
            <span>{window.t('Color', 'Color')}</span>
          </button>
        )}

        {puedeAbrirCarpeta && (
          <button
            className="ctx-btn"
            onClick={abreLaCarpeta}
            title={window.t('Abrir la carpeta donde está el archivo', 'Open the folder where the file is')}
          >
            <span className="material-symbols-rounded">folder_open</span>
            <span>{window.t('Abrir la carpeta', 'Open the folder')}</span>
          </button>
        )}

        {isShape && (
          <button
            className="ctx-btn"
            onClick={() => {
              // La media de los dos lados, no el mayor ni el menor: subir al
              // mayor se come lo que haya al lado y bajar al menor encoge tanto
              // que parece que se ha roto algo. Con la media la figura se queda
              // donde estaba y del tamaño que ya tenía.
              const lado = Math.max(80, Math.round(((item.w || 200) + (item.h || 160)) / 2));
              onUpdate({ w: lado, h: lado, manualH: true });
              window.playAudioTone && window.playAudioTone('click');
            }}
            title={window.t('Igualar los lados: el círculo vuelve a ser un círculo', 'Even the sides: the circle becomes a circle again')}
          >
            <span className="material-symbols-rounded">aspect_ratio</span>
            <span>{window.t('Igualar', 'Even out')}</span>
          </button>
        )}

        {/* La carpeta: cambiarla, abrirla en el Explorador y volver a mirarla. El
            nodo escucha estos avisos por su id (ver Carpeta.jsx). */}
        {isCarpeta && window.electronAPI && window.electronAPI.carpetaLeer && (
          <>
            <button className="ctx-btn" onClick={() => window.dispatchEvent(new CustomEvent('odi-carpeta-elegir', { detail: item.id }))} title={window.t('Elegir otra carpeta', 'Choose another folder')}>
              <span className="material-symbols-rounded">folder_open</span>
              <span>{window.t('Carpeta', 'Folder')}</span>
            </button>
            {/* El nombre tiene MENÚ PROPIO, como el título del reloj: se pulsa
                aquí, se pasa a escribirlo y a la izquierda sale la barra de
                texto, con su color y su negrita. */}
            <button
              className="ctx-btn"
              onClick={() => { setPane(null); onUpdate({ _editingTitle: true }); }}
              title={window.t('Cambiar el nombre que se ve en el lienzo', 'Change the name shown on the canvas')}
            >
              <span className="material-symbols-rounded">edit</span>
              <span>{window.t('Nombre', 'Name')}</span>
            </button>
            {item.ruta && (
              <button
                className="ctx-btn"
                onClick={async () => {
                  const r = await window.electronAPI.carpetaAbrir(item.ruta);
                  if (!r || !r.ok) window.showToast && window.showToast(window.t('Esa carpeta ya no está donde estaba.', 'That folder is no longer where it was.'), 'error');
                }}
                title={window.t('Abrirla en el Explorador', 'Open it in Explorer')}
              >
                <span className="material-symbols-rounded">open_in_new</span>
                <span>{window.t('Abrir', 'Open')}</span>
              </button>
            )}
            {item.ruta && (
              <button
                className="ctx-btn"
                onClick={(e) => {
                  // La flecha da una vuelta entera con rebote: sin eso, leer
                  // una carpeta que no ha cambiado no se notaba y parecía que
                  // el botón no hacía nada.
                  const flecha = e.currentTarget.querySelector('.material-symbols-rounded');
                  if (flecha && flecha.animate) {
                    flecha.animate([{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],
                      { duration: 700, easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)' });
                  }
                  window.playAudioTone && window.playAudioTone('click');
                  window.dispatchEvent(new CustomEvent('odi-carpeta-leer', { detail: item.id }));
                }}
                title={window.t('Volver a mirar lo que hay dentro', 'Look inside again')}
              >
                <span className="material-symbols-rounded">refresh</span>
                <span>{window.t('Actualizar', 'Refresh')}</span>
              </button>
            )}
          </>
        )}

        {/* La galería: añadir fotos, verlas en grande, cambiar entre rejilla y
            pila, y ordenarlas. El nodo escucha los avisos por su id (ver
            Galeria.jsx). */}
        {isGaleria && (() => {
          const nFotos = (item.fotos || []).length;
          const pila = item.vista === 'pila';
          return (
            <>
              <button className="ctx-btn" onClick={() => window.dispatchEvent(new CustomEvent('odi-galeria-anadir', { detail: item.id }))} title={window.t('Añadir fotos', 'Add photos')}>
                <span className="material-symbols-rounded">add_photo_alternate</span>
                <span>{window.t('Añadir', 'Add')}</span>
              </button>
              {nFotos > 0 && (
                <button className="ctx-btn" onClick={() => window.dispatchEvent(new CustomEvent('odi-galeria-ver', { detail: item.id }))} title={window.t('Verlas en grande', 'View them full screen')}>
                  <span className="material-symbols-rounded">slideshow</span>
                  <span>{window.t('Ver', 'View')}</span>
                </button>
              )}
              <button
                className="ctx-btn"
                onClick={() => { onUpdate({ vista: pila ? 'rejilla' : 'pila' }); window.playAudioTone && window.playAudioTone('click'); }}
                title={pila ? window.t('Ponerlas en rejilla', 'Show them as a grid') : window.t('Apilarlas como fotos de papel', 'Stack them like paper photos')}
              >
                <span className="material-symbols-rounded">{pila ? 'filter_none' : 'grid_view'}</span>
                <span>{pila ? window.t('Pila', 'Stack') : window.t('Rejilla', 'Grid')}</span>
              </button>
              {nFotos > 1 && (
                <button className={`ctx-btn ${pane === 'galeriaFotos' ? 'active' : ''}`} onClick={() => setPane(pane === 'galeriaFotos' ? null : 'galeriaFotos')} title={window.t('Ordenar o quitar fotos', 'Reorder or remove photos')}>
                  <span className="material-symbols-rounded">photo_library</span>
                  <span>{window.t('Fotos', 'Photos')}</span>
                </button>
              )}
            </>
          );
        })()}

        {/* La ruleta: girarla y editar sus elementos. */}
        {isRuleta && (
          <>
            {(window.ruletaActivos ? window.ruletaActivos(item) : (item.elementos || [])).length >= 2 && (
              <button className="ctx-btn" onClick={() => window.dispatchEvent(new CustomEvent('odi-ruleta-girar', { detail: item.id }))} title={window.t('Girar la ruleta', 'Spin the wheel')}>
                <span className="material-symbols-rounded">cached</span>
                <span>{window.t('Girar', 'Spin')}</span>
              </button>
            )}
            <button className={`ctx-btn ${pane === 'ruletaElementos' ? 'active' : ''}`} onClick={() => setPane(pane === 'ruletaElementos' ? null : 'ruletaElementos')} title={window.t('Los elementos de la ruleta', 'The wheel items')}>
              <span className="material-symbols-rounded">format_list_bulleted</span>
              <span>{window.t('Elementos', 'Items')}</span>
            </button>
            <button className={`ctx-btn ${pane === 'ruletaOpciones' ? 'active' : ''}`} onClick={() => setPane(pane === 'ruletaOpciones' ? null : 'ruletaOpciones')} title={window.t('Cuánto dura el giro, quitar al que sale y los últimos resultados', 'How long it spins, removing the winner and the latest results')}>
              <span className="material-symbols-rounded">tune</span>
              <span>{window.t('Opciones', 'Options')}</span>
            </button>
          </>
        )}

        {/* El progreso: qué cuenta, el proyecto entero o este tablero con lo que
            tenga dentro. Un solo botón que cambia de uno a otro. */}
        {isProgreso && (
          <button
            className="ctx-btn"
            onClick={() => { onUpdate({ alcance: item.alcance === 'tablero' ? 'proyecto' : 'tablero' }); window.playAudioTone && window.playAudioTone('click'); }}
            title={item.alcance === 'tablero'
              ? window.t('Cuenta este tablero: pulsa para contar el proyecto entero', 'Counts this board: click to count the whole project')
              : window.t('Cuenta el proyecto entero: pulsa para contar solo este tablero', 'Counts the whole project: click to count only this board')}
          >
            <span className="material-symbols-rounded">{item.alcance === 'tablero' ? 'dashboard' : 'folder_special'}</span>
            <span>{item.alcance === 'tablero' ? window.t('Tablero', 'Board') : window.t('Proyecto', 'Project')}</span>
          </button>
        )}

        {/* El separador: el estilo de la línea y dónde va el título. */}
        {isSeparator && (
          <>
            <button
              className={`ctx-btn ${pane === 'sepLinea' ? 'active' : ''}`}
              onClick={() => setPane(pane === 'sepLinea' ? null : 'sepLinea')}
              title={window.t('Estilo y grosor de la línea', 'Line style and thickness')}
            >
              <span className="material-symbols-rounded">line_style</span>
              <span>{window.t('Línea', 'Line')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={() => {
                const orden = ['left', 'center', 'right'];
                const i = orden.indexOf(item.align || 'left');
                onUpdate({ align: orden[(i + 1) % 3] });
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Título a la izquierda, en medio o a la derecha', 'Title left, centre or right')}
            >
              <span className="material-symbols-rounded">
                {item.align === 'center' ? 'format_align_center' : item.align === 'right' ? 'format_align_right' : 'format_align_left'}
              </span>
              <span>{window.t('Alinear', 'Align')}</span>
            </button>
          </>
        )}

        {isShape && (
          <button
            className={`ctx-btn ${pane === 'figura' ? 'active' : ''}`}
            onClick={()=>setPane(pane === 'figura' ? null : 'figura')}
            title={window.t('Cambiar la forma', 'Change the shape')}
          >
            <span className="material-symbols-rounded">category</span>
            <span>{window.t('Forma', 'Shape')}</span>
          </button>
        )}

        <button
          className={`ctx-btn ${pane === 'emoji' ? 'active' : ''}`}
          onClick={()=>setPane(pane === 'emoji' ? null : 'emoji')}
          title={window.t('Reacciones', 'Reactions')}
        >
          <span className="material-symbols-rounded">add_reaction</span>
          <span>{window.t('Reacciones', 'React')}</span>
        </button>

        <button
          className={`ctx-btn ${pane === 'comments' ? 'active' : ''}`}
          onClick={()=>setPane(pane === 'comments' ? null : 'comments')}
          title={window.t('Comentarios', 'Comments')}
        >
          <span className="material-symbols-rounded">chat_bubble</span>
          <span>{window.t('Comentar', 'Comment')}</span>
          {comments.length > 0 && <div className="ctx-badge">{comments.length}</div>}
        </button>

        {isTodo && (
          <>
            <button
              className={`ctx-btn ${item.showTitle !== false ? 'active' : ''}`}
              onClick={()=>onUpdate({ showTitle: item.showTitle === false })}
              title={window.t('Mostrar/ocultar título', 'Toggle title')}
            >
              <span className="material-symbols-rounded">title</span>
              <span>{window.t('Título', 'Title')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'dueDate' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'dueDate' ? null : 'dueDate')}
              title={window.t('Fecha límite', 'Due date')}
            >
              <span className="material-symbols-rounded">calendar_today</span>
              <span>{window.t('Para', 'Due')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'assign' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'assign' ? null : 'assign')}
              title={window.t('Asignar', 'Assign')}
            >
              <span className="material-symbols-rounded">person</span>
              <span>{window.t('Asignar', 'Assign')}</span>
            </button>
            <div className="ctx-sep-h"/>
            <button
              className="ctx-btn"
              onClick={()=>{
                let activeRowIdx = -1;
                if (window._focusedTodoRow && window._focusedTodoRow.todoId === item.id) {
                  activeRowIdx = (item.items || []).findIndex(r => r.id === window._focusedTodoRow.rowId);
                }
                if (activeRowIdx === -1 && item.items && item.items.length > 0) {
                  activeRowIdx = 0;
                }
                if (activeRowIdx !== -1) {
                  const nextItems = (item.items || []).map((r, i) =>
                    i === activeRowIdx ? { ...r, indent: Math.max(0, Math.min(2, (r.indent || 0) + 1)) } : r
                  );
                  onUpdate({ items: nextItems });
                }
              }}
              title={window.t('Aumentar sangría', 'Indent')}
            >
              <span className="material-symbols-rounded">format_indent_increase</span>
              <span>{window.t('Indentar', 'Indent')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={()=>{
                let activeRowIdx = -1;
                if (window._focusedTodoRow && window._focusedTodoRow.todoId === item.id) {
                  activeRowIdx = (item.items || []).findIndex(r => r.id === window._focusedTodoRow.rowId);
                }
                if (activeRowIdx === -1 && item.items && item.items.length > 0) {
                  activeRowIdx = 0;
                }
                if (activeRowIdx !== -1) {
                  const nextItems = (item.items || []).map((r, i) =>
                    i === activeRowIdx ? { ...r, indent: Math.max(0, Math.min(2, (r.indent || 0) - 1)) } : r
                  );
                  onUpdate({ items: nextItems });
                }
              }}
              title={window.t('Reducir sangría', 'Outdent')}
            >
              <span className="material-symbols-rounded">format_indent_decrease</span>
              <span>{window.t('Desindentar', 'Outdent')}</span>
            </button>
            <div className="ctx-sep-h"/>
          </>
        )}

        {isLink && (
          <>
            <button
              className={`ctx-btn ${item.showPreview !== false ? 'active' : ''}`}
              onClick={()=>{
                // Igual que el documento: al quitar la vista previa el nodo se
                // vuelve compacto (fila tipo marcador), y al activarla recupera
                // altura para que la miniatura no quede aplastada.
                const turningOff = item.showPreview !== false;
                if (isColChild) {
                  onUpdate({ showPreview: !turningOff });
                } else {
                  onUpdate(turningOff
                    ? { showPreview: false, h: 76 }
                    : { showPreview: true, h: Math.max(item.h || 0, 260) });
                }
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Alternar vista previa', 'Toggle preview')}
            >
              <span className="material-symbols-rounded">image</span>
              <span>{window.t('Vista previa', 'Preview')}</span>
            </button>
            <button
              className={`ctx-btn ${item.showInfo !== false ? 'active' : ''}`}
              onClick={()=>onUpdate({ showInfo: item.showInfo === false })}
              title={window.t('Mostrar informacion', 'Show info')}
            >
              <span className="material-symbols-rounded">link</span>
              <span>{window.t('Info', 'Info')}</span>
            </button>
            <button
              className={`ctx-btn ${item.showCaption === true ? 'active' : ''}`}
              onClick={()=>{
                const show = item.showCaption !== true;
                onUpdate({ showCaption: show, h: show ? (item.h || 230) + 46 : Math.max(120, (item.h || 230) - 46) });
              }}
              title={window.t('Leyenda', 'Caption')}
            >
              <span className="material-symbols-rounded">notes</span>
              <span>{window.t('Leyenda', 'Caption')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={()=>item.url && window.open(item.url, '_blank', 'noopener,noreferrer')}
              title={window.t('Abrir enlace', 'Open link')}
            >
              <span className="material-symbols-rounded">open_in_new</span>
              <span>{window.t('Abrir', 'Open')}</span>
            </button>
          </>
        )}

        {isDraw && (
          <>
            <button
              className="ctx-btn"
              onClick={()=>{ callbacks.enterDrawMode(item.id); window.playAudioTone && window.playAudioTone('click'); }}
              title={window.t('Seguir dibujando', 'Keep drawing')}
            >
              <span className="material-symbols-rounded">gesture</span>
              {/* "Editar" y no "Dibujar": el mismo botón que en el resto de
                  nodos, porque hace lo mismo — entrar a modificarlo. */}
              <span>{window.t('Editar', 'Edit', 'Modifier', 'Bearbeiten', 'Modifica', 'Editar', '编辑', '編集', '편집', 'تعديل', 'Изменить')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'inkColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'inkColor' ? null : 'inkColor')}
              title={window.t('Color de la tinta', 'Ink colour')}
            >
              <div className="ctx-color-chip" style={{
                background: (item.strokes && item.strokes[0] && item.strokes[0].color) || '#1A1A1A',
                border: '1.5px solid var(--line-soft)',
              }}/>
              <span>{window.t('Color', 'Color')}</span>
            </button>
          </>
        )}

        {(isBoard || isColumn) && (
          <button
            className={`ctx-btn ${pane === 'boardIcon' ? 'active' : ''}`}
            onClick={()=>setPane(pane === 'boardIcon' ? null : 'boardIcon')}
            title={window.t('Cambiar icono', 'Change icon')}
          >
            <span className="material-symbols-rounded">{item.icon || (isColumn ? 'view_column' : 'dashboard')}</span>
            <span>{window.t('Icono', 'Icon')}</span>
          </button>
        )}

        {isBoard && (
          <button
            className={`ctx-btn ${item.showPreview !== false ? 'active' : ''}`}
            onClick={()=>{
              const show = item.showPreview === false; // toggling ON
              if (show) {
                onUpdate(isColChild ? { showPreview: true } : { showPreview: true, w: 300, h: 240 });
              } else {
                onUpdate(isColChild ? { showPreview: false } : { showPreview: false, w: 300, h: 62 });
              }
            }}
            title={window.t('Vista previa', 'Preview')}
          >
            <span className="material-symbols-rounded">{item.showPreview === false ? 'crop_square' : 'grid_view'}</span>
            <span>{window.t('Vista previa', 'Preview')}</span>
          </button>
        )}

        {isAudio && (
          <>
            <button
              className={`ctx-btn ${item.loop === true ? 'active' : ''}`}
              onClick={()=>onUpdate({ loop: item.loop !== true })}
              title={window.t('Bucle', 'Loop')}
            >
              <span className="material-symbols-rounded">loop</span>
              <span>{window.t('Bucle', 'Loop')}</span>
            </button>
            <button
              className={`ctx-btn ${item.showCaption === true ? 'active' : ''}`}
              onClick={()=>{
                const show = item.showCaption !== true;
                onUpdate({ showCaption: show, h: show ? (item.h || 140) + 40 : Math.max(100, (item.h || 140) - 40) });
              }}
              title={window.t('Leyenda', 'Caption')}
            >
              <span className="material-symbols-rounded">notes</span>
              <span>{window.t('Leyenda', 'Caption')}</span>
            </button>
          </>
        )}

        {/* Title toggle for the title-bearing nodes (table has its own below) */}
        {(isLink || isAudio || isBoard || isColumn) && (
          <button
            className={`ctx-btn ${item.showTitle !== false ? 'active' : ''}`}
            onClick={()=>onUpdate({ showTitle: item.showTitle === false })}
            title={window.t('Mostrar/ocultar título', 'Toggle title')}
          >
            <span className="material-symbols-rounded">title</span>
            <span>{window.t('Título', 'Title')}</span>
          </button>
        )}

        {/* Nodo de tiempo: qué cuenta, el nombre, y los dos colores que se
            tocan. La DURACIÓN ya no está aquí a propósito: se escribe en el
            propio reloj del nodo. Poner tres minutos es lo más corto que se
            hace con este nodo y mandarlo a un panel lateral lo convertía en
            lo más largo. */}
        {isTimer && (
          <>
            <button
              className={`ctx-btn ${pane === 'timerModo' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'timerModo' ? null : 'timerModo')}
              title={window.t('Cuenta atrás, cronómetro o pomodoro', 'Countdown, stopwatch or pomodoro')}
            >
              <span className="material-symbols-rounded">timer</span>
              <span>{window.nombreModoTiempo ? window.nombreModoTiempo(item.modoTiempo) : 'Tiempo'}</span>
            </button>
            {/* El título tiene MENÚ PROPIO, como el del bloque de código: se
                pulsa aquí, se pasa a escribirlo, y al lado salen su color, su
                negrita y su alineación. */}
            <button
              className="ctx-btn"
              onClick={()=>{
                setPane(null);
                onUpdate({ _editingTitle: true });
              }}
              title={window.t('Poner nombre al reloj', 'Name the timer')}
            >
              <span className="material-symbols-rounded">label</span>
              <span>{window.t('Título', 'Title')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'timerBarColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'timerBarColor' ? null : 'timerBarColor')}
              title={window.t('Color de la barra', 'Bar colour')}
            >
              <div className="ctx-color-chip" style={{ background: item.barColor || '#E6544F', border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Barra', 'Bar')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'timerNumeros' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'timerNumeros' ? null : 'timerNumeros')}
              title={window.t('Color de los números', 'Numbers colour')}
            >
              <div className="ctx-color-chip" style={{ background: item.numberColor || '#1A1A1A', border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Números', 'Numbers')}</span>
            </button>
          </>
        )}

        {/* Nodo de calendario: lo que se puede ajustar a gusto. El tamaño del
            texto de los días usa el mismo botón que las notas (item.textScale);
            el del mes y el año va aparte porque se leen a distancias distintas
            —los días de cerca, la cabecera de un vistazo desde lejos. */}
        {isCalendar && (
          <>
            <button
              className={`ctx-btn ${pane === 'calNumeros' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'calNumeros' ? null : 'calNumeros')}
              title={window.t('Color de los números de los días', 'Day numbers colour')}
            >
              <div className="ctx-color-chip" style={{ background: item.numberColor || '#595459', border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Números', 'Numbers')}</span>
            </button>
            {/* Encendido por defecto, como estaba. Quitarlo deja el día de hoy
                como cualquier otro: en un calendario que se usa para planear
                otro mes, ese verde es una mancha que no dice nada. */}
            <button
              className={`ctx-btn ${item.hoyMarcado !== false ? 'active' : ''}`}
              onClick={()=>{ onUpdate({ hoyMarcado: item.hoyMarcado === false }); window.playAudioTone && window.playAudioTone('click'); }}
              title={window.t('Marcar el día de hoy en verde', 'Highlight today in green')}
            >
              <span className="material-symbols-rounded">today</span>
              <span>{window.t('Hoy', 'Today')}</span>
            </button>
            {/* Los ajustes de Google que tienen sentido aquí, y el .ics para
                pasar el calendario a Google, Outlook o Apple, o traerlo. */}
            <button
              className={`ctx-btn ${pane === 'calAjustes' ? 'active' : ''}`}
              onClick={() => setPane(pane === 'calAjustes' ? null : 'calAjustes')}
              title={window.t('Ajustes del calendario', 'Calendar settings')}
            >
              <span className="material-symbols-rounded">settings</span>
              <span>{window.t('Ajustes', 'Settings')}</span>
            </button>
          </>
        )}

        {/* Nodo de código: el lenguaje, y los dos colores que sí se tocan.
            El del fondo no está a propósito: el azul oscuro con su degradado
            ES el nodo, y dejarlo cambiar lo convierte en otra cosa. */}
        {isCode && (
          <>
            <button
              className={`ctx-btn ${pane === 'codeLang' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'codeLang' ? null : 'codeLang')}
              title={window.t('Lenguaje', 'Language')}
            >
              <span className="material-symbols-rounded">code</span>
              <span>{(window.lenguajeCodigo ? window.lenguajeCodigo(item.codeLang).nombre : 'Código')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={()=>onStartEdit && onStartEdit()}
              title={window.t('Escribir', 'Edit')}
            >
              <span className="material-symbols-rounded">edit</span>
              <span>{window.t('Escribir', 'Edit')}</span>
            </button>
            {/* El título tiene MENÚ PROPIO: se pulsa aquí y se pasa a
                escribirlo, con su color y su negrita al lado. Antes había dos
                botones de color sueltos en este mismo menú —uno para el título
                y otro para la barra— y no se sabía cuál era cuál. Aquí queda
                uno solo, el de la barra, y lo del título vive dentro del
                título. */}
            <button
              className="ctx-btn"
              onClick={()=>{
                setPane(null);
                // Los dos modos no pueden estar a la vez: si se venía de
                // escribir código, se sale de ahí antes de entrar al título.
                callbacks && callbacks.endEdit && callbacks.endEdit();
                onUpdate({ _editingTitle: true });
              }}
              title={window.t('Poner nombre al bloque', 'Name the block')}
            >
              <span className="material-symbols-rounded">label</span>
              <span>{window.t('Título', 'Title')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'codeBarColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'codeBarColor' ? null : 'codeBarColor')}
              title={window.t('Color de la barra', 'Bar colour')}
            >
              <div className="ctx-color-chip" style={{ background: item.barColor || '#243352', border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Color', 'Colour')}</span>
            </button>
          </>
        )}

        {/* Color node: toggle showing the HEX code */}
        {isColor && (
          <button
            className={`ctx-btn ${item.showHex !== false ? 'active' : ''}`}
            onClick={()=>onUpdate({ showHex: item.showHex === false })}
            title={window.t('Mostrar HEX', 'Show HEX')}
          >
            <span className="ctx-letter" style={{fontWeight:800, fontSize:13}}>#</span>
            <span>HEX</span>
          </button>
        )}

        {/* File node: preview + info toggles (Info only matters with preview on) */}
        {isFile && (
          <>
            <button
              className={`ctx-btn ${item.showPreview === true ? 'active' : ''}`}
              onClick={()=>{
                const show = item.showPreview !== true;
                if (show) {
                  const ext = (item.fileType || (item.name ? item.name.split('.').pop() : '') || '').toLowerCase();
                  const isXls = ['xlsx','xls','ods','csv'].includes(ext);
                  if (isXls) onUpdate({ showPreview: true, w: 420, h: 280 });           // landscape sheet
                  else onUpdate({ showPreview: true, h: Math.max(Math.round((item.w || 230) * 1.3), 300) });
                } else {
                  // back to a small square (compact icon) state
                  onUpdate({ showPreview: false, showInfo: false, w: 200, h: 190 });
                }
              }}
              title={window.t('Vista previa', 'Preview')}
            >
              <span className="material-symbols-rounded">image</span>
              <span>{window.t('Vista previa', 'Preview')}</span>
            </button>
            {item.showPreview === true && (
              <button
                className={`ctx-btn ${item.showInfo === true ? 'active' : ''}`}
                onClick={()=>onUpdate({ showInfo: item.showInfo !== true })}
                title={window.t('Info', 'Info')}
              >
                <span className="material-symbols-rounded">info</span>
                <span>{window.t('Info', 'Info')}</span>
              </button>
            )}
            {/* Cambiar el archivo se pide desde aquí. El doble clic sobre la
                tarjeta ahora abre el archivo, que es lo que uno espera. */}
            {item.src && (
              <button
                className="ctx-btn"
                onClick={()=>onUpdate({ _replaceFile: true })}
                title={window.t('Cambiar archivo', 'Replace file')}
              >
                <span className="material-symbols-rounded">swap_horiz</span>
                <span>{window.t('Cambiar', 'Replace')}</span>
              </button>
            )}
          </>
        )}

        {/* Doc node: preview toggle (off → logo only) + download as PDF */}
        {isDoc && (
          <>
            <button
              className={`ctx-btn ${item.showPreview !== false ? 'active' : ''}`}
              onClick={()=>onUpdate({ showPreview: item.showPreview === false })}
              title={window.t('Vista previa', 'Preview')}
            >
              <span className="material-symbols-rounded">image</span>
              <span>{window.t('Vista previa', 'Preview')}</span>
            </button>
            <button className="ctx-btn" onClick={downloadDocPdf} title={window.t('Descargar PDF', 'Download PDF')}>
              <span className="material-symbols-rounded">download</span>
              <span>{window.t('Descargar', 'Download')}</span>
            </button>
          </>
        )}

        {/* Caption ("leyenda") toggle for image / board / comment / calendar / color / file */}
        {(isImage || isBoard || isComment || isCalendar || isColor || isFile) && (
          <button
            className={`ctx-btn ${item.showCaption === true ? 'active' : ''}`}
            onClick={()=>{
              const show = item.showCaption !== true;
              const dH = isImage ? 0 : 44; // image caption is an overlay (no extra height)
              onUpdate({ showCaption: show, h: show ? (item.h || 200) + dH : Math.max(80, (item.h || 200) - dH) });
            }}
            title={window.t('Leyenda', 'Caption')}
          >
            <span className="material-symbols-rounded">notes</span>
            <span>{window.t('Leyenda', 'Caption')}</span>
          </button>
        )}

        {/* Table — no cell selected: show table-level options */}
        {isTable && !tableCell && (
          <>
            <button
              className={`ctx-btn ${item.showTitle === true ? 'active' : ''}`}
              onClick={()=>onUpdate({ showTitle: item.showTitle !== true })}
              title={window.t('Mostrar/ocultar título', 'Toggle title')}
            >
              <span className="material-symbols-rounded">title</span>
              <span>{window.t('Título', 'Title')}</span>
            </button>
          </>
        )}

        {/* Table — cell selected: show cell-level options */}
        {isTable && tableCell && (
          <>
            <button
              className={`ctx-btn ${pane === 'tblStyle' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'tblStyle' ? null : 'tblStyle')}
              title={window.t('Estilo', 'Style')}
            >
              <span className="material-symbols-rounded">text_format</span>
              <span>{window.t('Estilo', 'Style')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'tblColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'tblColor' ? null : 'tblColor')}
              title={window.t('Color', 'Color')}
            >
              <div className="ctx-color-chip" style={{ background: tableCell.cellData?.color || '#FFFFFF', border: '1.5px solid var(--line-soft)' }}/>
              <span>{window.t('Color', 'Color')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={()=>{ setPane(null); tableCell.startFormula && tableCell.startFormula(); }}
              title={window.t('Fórmula (= en la celda)', 'Formula (= in cell)')}
            >
              <span style={{fontFamily:'serif', fontStyle:'italic', fontWeight:700, fontSize:14}}>fx</span>
              <span>{window.t('Fórmula', 'Formula')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'tblAlign' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'tblAlign' ? null : 'tblAlign')}
              title={window.t('Alineación', 'Alignment')}
            >
              <span className="material-symbols-rounded">format_align_center</span>
              <span>{window.t('Alineación', 'Align')}</span>
            </button>
            <div className="ctx-sep-h"/>
            <button
              className="ctx-btn"
              onClick={()=>tableCell.addCol()}
              title={window.t('Añadir columna', 'Add column')}
            >
              <span className="material-symbols-rounded">add_box</span>
              <span>{window.t('Columna', 'Column')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={()=>tableCell.addRow()}
              title={window.t('Añadir fila', 'Add row')}
            >
              <span className="material-symbols-rounded">add_row_below</span>
              <span>{window.t('Añadir fila', 'Add row')}</span>
            </button>
            <div className="ctx-sep-h"/>
            <button
              className="ctx-btn danger"
              onClick={()=>tableCell.removeCol()}
              title={window.t('Eliminar columna', 'Remove column')}
            >
              <span className="material-symbols-rounded">indeterminate_check_box</span>
              <span>{window.t('Col -', 'Col -')}</span>
            </button>
            <button
              className="ctx-btn danger"
              onClick={()=>tableCell.removeRow()}
              title={window.t('Eliminar fila', 'Remove row')}
            >
              <span className="material-symbols-rounded">remove</span>
              <span>{window.t('Fila -', 'Row -')}</span>
            </button>
          </>
        )}

        {isBoard && (
          <button
            className={`ctx-btn`}
            onClick={()=>onOpen && onOpen()}
            title={window.t('Abrir', 'Open')}
          >
            <span className="material-symbols-rounded">open_in_full</span>
            <span>{window.t('Abrir', 'Open')}</span>
          </button>
        )}
        {/* Sacar el tablero a su propio proyecto (ver convierteEnProyecto en
            Canvas.jsx). No dentro de una columna: ahí el tablero no es un nodo
            del lienzo y no tiene de dónde salir volando. */}
        {isBoard && !isColChild && callbacks && callbacks.convertirEnProyecto && (
          <button
            className="ctx-btn"
            onClick={() => callbacks.convertirEnProyecto(item.id)}
            title={window.t('Convertir este tablero en un proyecto aparte', 'Turn this board into its own project')}
          >
            <span className="material-symbols-rounded">rocket_launch</span>
            <span>{window.t('Proyecto', 'Project')}</span>
          </button>
        )}

        {isImage && (
          <>
            <button
              className="ctx-btn"
              onClick={()=>onUpdate({ _triggerImagePick: true })}
              title={window.t('Cambiar imagen', 'Change image')}
            >
              <span className="material-symbols-rounded">swap_horiz</span>
              <span>{window.t('Cambiar', 'Change')}</span>
            </button>
            <button
              className={`ctx-btn ${callbacks?.croppingId === item.id ? 'active' : ''}`}
              onClick={() => {
                if (callbacks) {
                  callbacks.setCroppingId(callbacks.croppingId === item.id ? null : item.id);
                }
              }}
              title={window.t('Recortar imagen', 'Crop image')}
            >
              <span className="material-symbols-rounded">crop</span>
              <span>{window.t('Recortar', 'Crop')}</span>
            </button>
            {/* ── Ajustar: la imagen recupera su proporción ──
                Estirar la caja de una imagen la estira (a propósito: se pidió
                poder deformarlas). Esto la devuelve a su forma real: el lado que
                sobra se recorta hasta la proporción de la imagen —tal como esté
                recortada— y la caja conserva su centro, así que no salta de sitio.

                Antes este botón se llamaba "Restaurar" y además borraba el
                recorte, así que para arreglar la proporción había que perder
                el recorte. Eso ahora es un botón aparte, justo aquí debajo. */}
            <button
              className="ctx-btn"
              onClick={() => {
                const ratio = item.naturalRatio;
                if (!ratio || !item.w || !item.h) return;
                const c = item.crop || { x: 0, y: 0, w: 100, h: 100 };
                const aspecto = ratio * (c.w / c.h);
                let w = item.w, h = item.h;
                // El lado que sobra es el que se recorta; el otro no se toca.
                if (aspecto > item.w / item.h) h = Math.max(20, Math.round(item.w / aspecto));
                else w = Math.max(20, Math.round(item.h * aspecto));
                if (w === item.w && h === item.h) return;
                // Centrada: la imagen estaba en medio de su caja, así que la
                // caja nueva se queda con el mismo centro y la imagen no se mueve.
                onUpdate({
                  w, h,
                  x: Math.round((item.x || 0) + (item.w - w) / 2),
                  y: Math.round((item.y || 0) + (item.h - h) / 2),
                });
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Ajustar la caja a la imagen, sin huecos', 'Fit the box to the image, no gaps')}
            >
              <span className="material-symbols-rounded">aspect_ratio</span>
              <span>{window.t('Ajustar', 'Fit')}</span>
            </button>
            {/* Quitar el recorte: lo que hacía el antiguo "Restaurar". Solo sale
                si la imagen está recortada, que es cuando tiene sentido. */}
            {item.crop && (item.crop.x !== 0 || item.crop.y !== 0 || item.crop.w !== 100 || item.crop.h !== 100) && (
              <button
                className="ctx-btn"
                onClick={() => {
                  const ratio = item.naturalRatio || (item.w && item.h ? item.w / item.h : 1);
                  onUpdate({
                    crop: { x: 0, y: 0, w: 100, h: 100 },
                    h: Math.max(60, Math.round(item.w / ratio))
                  });
                  window.playAudioTone && window.playAudioTone('click');
                }}
                title={window.t('Quitar el recorte y volver a la imagen entera', 'Remove the crop and go back to the whole image')}
              >
                <span className="material-symbols-rounded">crop_free</span>
                <span>{window.t('Sin recorte', 'Uncrop')}</span>
              </button>
            )}
            {/* Sacar la paleta de la imagen: se guarda entre las paletas del
                selector de color (para usarla en cualquier proyecto) y se deja
                al lado un nodo de paleta, para tenerla a la vista en el tablero. */}
            {window.odiPaletaDeImagen && item.src && (
              <button
                className="ctx-btn"
                onClick={async () => {
                  try {
                    const src = window.resolveMediaSrc ? window.resolveMediaSrc(item.src) : item.src;
                    const colores = await window.odiPaletaDeImagen(src, 6);
                    if (!colores.length) {
                      window.showToast && window.showToast(window.t('En esta imagen no hay colores que sacar.', 'There are no colours to take from this image.'));
                      return;
                    }
                    window.odiGuardaPaleta(window.t('De la imagen', 'From the image'), colores);
                    if (callbacks && callbacks.ponNodoAlLado) {
                      callbacks.ponNodoAlLado(item.id, {
                        type: 'swatch', w: 320, h: 112,
                        title: { es: 'Paleta', en: 'Palette' },
                        colors: colores.map(hex => ({ hex })),
                      });
                    }
                    window.playAudioTone && window.playAudioTone('create');
                    window.showToast && window.showToast(window.t(
                      'Paleta sacada: está al lado de la imagen y entre las paletas del selector de color.',
                      'Palette taken: it is next to the image and among the colour picker palettes.'));
                  } catch (e) {
                    window.showToast && window.showToast(window.t('No se pudieron leer los colores de esta imagen.', 'Could not read the colours of this image.'));
                  }
                }}
                title={window.t('Sacar los colores de esta imagen', 'Take the colours of this image')}
              >
                <span className="material-symbols-rounded">palette</span>
                <span>{window.t('Paleta', 'Palette')}</span>
              </button>
            )}
            <button
              className={`ctx-btn ${item.flipH ? 'active' : ''}`}
              onClick={() => {
                onUpdate({ flipH: !item.flipH });
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Voltear horizontalmente (efecto espejo)', 'Flip horizontally (mirror)')}
            >
              <span className="material-symbols-rounded">flip</span>
              <span>{window.t('Espejo H', 'Flip H')}</span>
            </button>
            <button
              className={`ctx-btn ${item.flipV ? 'active' : ''}`}
              onClick={() => {
                onUpdate({ flipV: !item.flipV });
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Voltear verticalmente (espejo arriba/abajo)', 'Flip vertically (top/bottom mirror)')}
            >
              <span className="material-symbols-rounded" style={{ transform: 'rotate(90deg)' }}>flip</span>
              <span>{window.t('Espejo V', 'Flip V')}</span>
            </button>
          </>
        )}

        {isFrame && (
          <>
            {/* (El botón "Editar" universal ya entra a editar el título del marco,
                así que aquí solo queda el tamaño del título.) */}
            <SelectorTamano clave="marco" spec={tamanos.marco} pane={pane} setPane={setPane}/>
          </>
        )}

        {isBigTitle && (
          <>
            <button
              className={`ctx-btn ${pane === 'bigtitleColor' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'bigtitleColor' ? null : 'bigtitleColor')}
              title={window.t('Color del texto', 'Text color')}
            >
              <div className="ctx-letter" style={{
                fontWeight: 800,
                fontSize: 14,
                color: item.textColor || '#1A1A1A',
                background: item.textColor ? 'none' : 'linear-gradient(90deg, #1A1A1A, #E6544F, #90B968)',
                WebkitBackgroundClip: item.textColor ? 'none' : 'text',
                WebkitTextFillColor: item.textColor ? 'none' : 'transparent',
                backgroundClip: item.textColor ? 'none' : 'text',
              }}>
                {item.textColor && item.textColor !== 'inherit' ? <span style={{color: item.textColor}}>A</span> : 'A'}
              </div>
              <span>{window.t('Color', 'Color')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'bigtitleAlign' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'bigtitleAlign' ? null : 'bigtitleAlign')}
              title={window.t('Alineación del texto', 'Text alignment')}
            >
              <span className="material-symbols-rounded">format_align_center</span>
              <span>{window.t('Alineación', 'Align')}</span>
            </button>
            <button
              className={`ctx-btn ${pane === 'bigtitleNivel' ? 'active' : ''}`}
              onClick={()=>setPane(pane === 'bigtitleNivel' ? null : 'bigtitleNivel')}
              title={window.t('Tamaño del título', 'Title size')}
            >
              <span className="material-symbols-rounded">format_size</span>
              <span>{window.t('Nivel', 'Heading')}</span>
            </button>
          </>
        )}

        {isMap && (
          <>
            <button
              className="ctx-btn"
              onClick={() => {
                onUpdate({ _editingTitle: true });
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Cambiar título', 'Change title')}
            >
              <span className="material-symbols-rounded">title</span>
              <span>{window.t('Título', 'Title')}</span>
            </button>
            <button
              className="ctx-btn"
              onClick={() => {
                onStartEdit && onStartEdit();
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Editar enlace o dirección', 'Edit link or address')}
            >
              <span className="material-symbols-rounded">pin_drop</span>
              <span>{window.t('Dirección', 'Address')}</span>
            </button>
            <button
              className={`ctx-btn ${item.showCaption === true ? 'active' : ''}`}
              onClick={()=>{
                const show = item.showCaption !== true;
                onUpdate({ showCaption: show, h: show ? (item.h || 280) + 40 : Math.max(160, (item.h || 280) - 40) });
                if (show) {
                  setTimeout(() => {
                    const el = document.querySelector(`[data-item-id="${item.id}"] .map-caption-input .rich-caption`);
                    if (el) el.focus();
                  }, 50);
                }
                window.playAudioTone && window.playAudioTone('click');
              }}
              title={window.t('Leyenda', 'Caption')}
            >
              <span className="material-symbols-rounded">notes</span>
              <span>{window.t('Leyenda', 'Caption')}</span>
            </button>
          </>
        )}

        {/* Tamaño de letra de la leyenda, en cualquier nodo que la tenga puesta. */}
        <SelectorTamano clave="leyenda" spec={tamanos.leyenda} pane={pane} setPane={setPane}/>

        {/* Tamaño del texto del nodo: multiplica todo el texto y convive con
            los títulos, las negritas y demás formato. En la figura también. */}
        <SelectorTamano clave="texto" spec={tamanos.texto} pane={pane} setPane={setPane}/>

        {/* Estirar: ajustar altura al contenido (útil en comentarios/notas largos) */}
        {['note','comment','todo','link'].includes(item.type) && (
          <button className="ctx-btn" onClick={fitHeightToContent} title={window.t('Ajustar la altura al contenido', 'Fit height to content')}>
            <span className="material-symbols-rounded">height</span>
            <span>{window.t('Estirar', 'Fit height')}</span>
          </button>
        )}

        <div className="ctx-sep-h"/>

        {/* Girar. El tirador redondo del nodo sirve para cualquier ángulo;
            esto es para el caso de siempre — de 90 en 90 — sin pulso. Doble
            clic vuelve a dejarlo derecho, igual que el tirador. */}
        <button
          className={`ctx-btn ${item.rot ? 'active' : ''}`}
          onClick={()=>onUpdate({ rot: (((item.rot || 0) + 90) % 360) })}
          onDoubleClick={()=>onUpdate({ rot: 0 })}
          title={window.t('Girar 90° · doble clic lo endereza', 'Rotate 90° · double-click to straighten')}
        >
          <span className="material-symbols-rounded">rotate_right</span>
          <span>{item.rot ? `${Math.round(item.rot)}°` : window.t('Girar', 'Rotate')}</span>
        </button>
        {/* El candado: bloqueado no se mueve, no se estira ni se borra. */}
        <button
          className={`ctx-btn ${item.bloqueado ? 'active' : ''}`}
          onClick={() => { onUpdate({ bloqueado: !item.bloqueado }); window.playAudioTone && window.playAudioTone('click'); }}
          title={item.bloqueado
            ? window.t('Quitar el candado (Ctrl+Shift+L)', 'Unlock (Ctrl+Shift+L)')
            : window.t('Bloquear: no se mueve, no se estira ni se borra (Ctrl+Shift+L)', 'Lock: it cannot be moved, resized or deleted (Ctrl+Shift+L)')}
        >
          <span className="material-symbols-rounded">{item.bloqueado ? 'lock' : 'lock_open'}</span>
          <span>{item.bloqueado ? window.t('Bloqueado', 'Locked') : window.t('Bloquear', 'Lock')}</span>
        </button>
        {/* Elegido uno solo de un grupo (segundo clic): sacarlo de él. */}
        {item.grupo && (
          <button className="ctx-btn" onClick={() => { onUpdate({ grupo: null }); window.playAudioTone && window.playAudioTone('click'); }}
            title={window.t('Sacar este nodo de su grupo', 'Take this node out of its group')}>
            <span className="material-symbols-rounded">link_off</span>
            <span>{window.t('Sacar del grupo', 'Ungroup this')}</span>
          </button>
        )}
        <button className="ctx-btn" onClick={onDuplicate} title={window.t('Duplicar', 'Duplicate')}>
          <span className="material-symbols-rounded">content_copy</span>
          <span>{window.t('Duplicar', 'Duplicate')}</span>
        </button>
        <button className="ctx-btn danger" onClick={item.bloqueado ? undefined : onDelete} disabled={!!item.bloqueado}
          title={item.bloqueado ? window.t('Está bloqueado: quítale el candado para borrarlo', 'It is locked: unlock it to delete it') : window.t('Eliminar', 'Delete')}>
          <span className="material-symbols-rounded">delete</span>
          <span>{window.t('Eliminar', 'Delete')}</span>
        </button>
        {/* Quién apunta a este nodo. Es la mitad que le falta a un enlace: sin
            esto, el origen sabe del destino pero el destino no sabe de nadie, y
            no puedes ver de qué depende lo que estás a punto de cambiar. */}
        {backlinks && backlinks.length > 0 && (
          <button
            className={`ctx-btn ${pane === 'backlinks' ? 'active' : ''}`}
            onClick={() => setPane(pane === 'backlinks' ? null : 'backlinks')}
            title={window.t('Nodos que enlazan aquí', 'Nodes linking here')}
          >
            <span className="material-symbols-rounded">hub</span>
            <span>{backlinks.length} {window.t('refs', 'refs')}</span>
          </button>
        )}
          </>
        )}
      </div>

      {/* Popout panes */}
      {pane === 'backlinks' && (
        <div className="ctx-popout odi-backlinks">
          <div className="ctx-pop-title">
            {window.t('Referenciado desde', 'Referenced from')}
          </div>
          {backlinks.map(b => (
            <button
              key={`${b.canvasId}:${b.itemId}`}
              className="odi-backlink"
              onClick={() => { onGoToBacklink && onGoToBacklink(b); setPane(null); }}
            >
              <span className="odi-backlink-text">“{b.linkText}”</span>
              <span className="odi-backlink-path">{b.path.join(' / ')}</span>
            </button>
          ))}
        </div>
      )}
      {pane && pane.startsWith('tamano:') && tamanos[pane.slice(7)] && (
        <PanelTamano
          key={item.id + pane}
          spec={tamanos[pane.slice(7)]}
          itemId={item.id}
          onCerrar={() => setPane(null)}
        />
      )}
      {pane === 'ruletaElementos' && (() => {
        const elementos = Array.isArray(item.elementos) ? item.elementos : [];
        const colores = ['#E6544F', '#F7DA84', '#90B968', '#3D5A80', '#955BA5', '#F2A65A', '#5BB5A2', '#E58FB0'];
        const cambia = (id, patch) => onUpdate({ elementos: elementos.map(e => e.id === id ? { ...e, ...patch } : e) });
        const fuera = elementos.filter(e => e.fuera).length;
        // Varios de golpe: una lista pegada (uno por línea) entra entera.
        const anadeVarios = (textos) => {
          const limpios = textos.map(t => String(t || '').trim()).filter(Boolean);
          if (!limpios.length) return;
          const nuevos = limpios.map((t, k) => ({
            id: `el-${Date.now()}-${k}-${Math.floor(Math.random() * 9999)}`,
            texto: t,
            color: colores[(elementos.length + k) % colores.length],
          }));
          // Con otros elementos, el elegido de antes ya no dice nada.
          onUpdate({ ultimo: null, elementos: [...elementos, ...nuevos] });
          window.playAudioTone && window.playAudioTone('create');
        };
        // Mezclar cambia el orden de las porciones (no quién sale: eso ya es
        // al azar), para que dos parecidos no queden siempre juntos.
        const mezcla = () => {
          const c = elementos.slice();
          for (let i = c.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); const t = c[i]; c[i] = c[j]; c[j] = t; }
          onUpdate({ ultimo: null, elementos: c });
          window.playAudioTone && window.playAudioTone('click');
        };
        return (
          <div className="ctx-popout ruleta-panel" onMouseDown={(e) => e.stopPropagation()}>
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Elementos de la ruleta', 'Wheel items')}</div>
              <div className="ruleta-lista">
                {elementos.map(el => (
                  <React.Fragment key={el.id}>
                  <div className={`ruleta-fila ${el.fuera ? 'fuera' : ''}`} title={el.fuera ? window.t('Ya salió: no está en la rueda', 'Already out: not on the wheel') : undefined}>
                    {/* El color abre el selector de toda la aplicación: cualquier
                        color, con el historial. Antes un toque pasaba al
                        siguiente de una lista fija y no se podía elegir uno a
                        gusto (lo pidió el usuario). */}
                    <button
                      className={`ruleta-fila-color ${colorRuleta === el.id ? 'abierto' : ''}`}
                      // Como se pinta en la rueda: suavizado (ver ruletaSuave).
                      style={{ background: window.ruletaSuave ? window.ruletaSuave(el.color) : el.color }}
                      onClick={() => setColorRuleta(colorRuleta === el.id ? null : el.id)}
                      title={window.t('Cambiar el color', 'Change the colour')}
                    />
                    {/* Una foto para su porción: se encoge y va dentro del
                        elemento (ver ruletaEncogeFoto en Ruleta.jsx). */}
                    <label className={`ruleta-fila-foto ${el.imagen ? 'con-foto' : ''}`}
                      title={el.imagen ? window.t('Cambiar la foto', 'Change the photo') : window.t('Poner una foto', 'Add a photo')}
                      style={el.imagen ? { backgroundImage: `url(${el.imagen})` } : undefined}>
                      {!el.imagen && <span className="material-symbols-rounded">add_photo_alternate</span>}
                      <input type="file" accept="image/*" style={{ display: 'none' }}
                        onChange={async (e) => {
                          const f = e.target.files && e.target.files[0];
                          e.target.value = '';
                          if (!f || !window.ruletaEncogeFoto) return;
                          const src = await window.ruletaEncogeFoto(f);
                          if (src) { cambia(el.id, { imagen: src }); window.playAudioTone && window.playAudioTone('create'); }
                        }}/>
                    </label>
                    <input
                      className="ruleta-fila-texto"
                      value={el.texto}
                      onChange={(e) => cambia(el.id, { texto: e.target.value })}
                      onKeyDown={(e) => e.stopPropagation()}
                    />
                    {el.imagen && (
                      <button className="ruleta-fila-quitar" onClick={() => cambia(el.id, { imagen: null })} title={window.t('Quitar la foto', 'Remove the photo')}>
                        <span className="material-symbols-rounded">hide_image</span>
                      </button>
                    )}
                    {el.fuera && (
                      <button className="ruleta-fila-quitar volver" onClick={() => cambia(el.id, { fuera: false })} title={window.t('Volver a meterlo en la rueda', 'Put it back on the wheel')}>
                        <span className="material-symbols-rounded">undo</span>
                      </button>
                    )}
                    <button className="ruleta-fila-quitar" onClick={() => onUpdate({ ultimo: null, elementos: elementos.filter(e => e.id !== el.id) })} title={window.t('Quitar', 'Remove')}>
                      <span className="material-symbols-rounded">close</span>
                    </button>
                  </div>
                  {colorRuleta === el.id && window.SelectorColor && (
                    <div className="ruleta-fila-selector">
                      <window.SelectorColor
                        valor={el.color}
                        onCambio={(c) => cambia(el.id, { color: c })}
                        colores={['#E6544F', '#F7DA84', '#90B968', '#3D5A80']}
                        tam={22}
                      />
                    </div>
                  )}
                  </React.Fragment>
                ))}
              </div>
              <input
                className="ruleta-nuevo"
                placeholder={window.t('Nuevo elemento y Intro (o pega una lista)', 'New item and Enter (or paste a list)')}
                onKeyDown={(e) => {
                  e.stopPropagation();
                  if (e.key !== 'Enter' || !e.target.value.trim()) return;
                  anadeVarios([e.target.value]);
                  e.target.value = '';
                }}
                onPaste={(e) => {
                  // Una lista pegada (de una nota, de Excel…): un elemento por línea.
                  const pegado = (e.clipboardData && e.clipboardData.getData('text')) || '';
                  const lineas = pegado.split(/\r?\n|\t/).map(s => s.trim()).filter(Boolean);
                  if (lineas.length < 2) return;
                  // Que no lo coja también el lienzo como una nota pegada.
                  e.preventDefault(); e.stopPropagation();
                  anadeVarios(lineas);
                }}
              />
              <div className="ruleta-acciones">
                {elementos.length >= 2 && (
                  <button className="ruleta-accion" onClick={mezcla} title={window.t('Cambiar el orden de las porciones', 'Shuffle the order of the slices')}>
                    <span className="material-symbols-rounded">casino</span>{window.t('Mezclar', 'Shuffle')}
                  </button>
                )}
                {fuera > 0 && (
                  <button className="ruleta-accion" onClick={() => { onUpdate({ elementos: elementos.map(e => e.fuera ? { ...e, fuera: false } : e) }); window.playAudioTone && window.playAudioTone('click'); }}
                    title={window.t('Volver a meter en la rueda a todos los que salieron', 'Put everyone who came out back on the wheel')}>
                    <span className="material-symbols-rounded">restore</span>{window.t('Volver a meter a todos', 'Put them all back')} ({fuera})
                  </button>
                )}
              </div>
            </div>
          </div>
        );
      })()}
      {pane === 'ruletaOpciones' && (() => {
        const duracion = item.duracion || 'normal';
        const historial = Array.isArray(item.historial) ? item.historial : [];
        const hora = (t) => { try { return new Date(t).toLocaleTimeString(window.currentLang || undefined, { hour: '2-digit', minute: '2-digit' }); } catch (e) { return ''; } };
        return (
          <div className="ctx-popout ruleta-panel" onMouseDown={(e) => e.stopPropagation()}>
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Cuánto gira', 'How long it spins')}</div>
              <div className="ruleta-duraciones">
                {[['corta', window.t('Corto', 'Short')], ['normal', window.t('Normal', 'Normal')], ['larga', window.t('Largo', 'Long')]].map(([k, nombre]) => (
                  <button key={k} className={`ruleta-duracion ${duracion === k ? 'activa' : ''}`} onClick={() => onUpdate({ duracion: k })}>{nombre}</button>
                ))}
              </div>
            </div>
            <div className="ctx-pop-section">
              {/* Para sorteos y turnos: el que sale se va de la rueda al
                  girar la siguiente vez (se puede volver a meter). */}
              <label className="ruleta-interruptor">
                <input type="checkbox" checked={!!item.quitaGanador} onChange={(e) => onUpdate({ quitaGanador: e.target.checked })}/>
                <span>
                  <b>{window.t('Quitar al que sale', 'Remove the winner')}</b>
                  <small>{window.t('Al girar otra vez, el anterior se va de la rueda: para sorteos y turnos.', 'When you spin again, the previous one leaves the wheel: for draws and turns.')}</small>
                </span>
              </label>
            </div>
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Últimos resultados', 'Latest results')}</div>
              {historial.length ? (
                <div className="ruleta-historial">
                  {historial.map((h, k) => (
                    <div key={k} className="ruleta-historial-fila">
                      <i style={{ background: h.color ? (window.ruletaSuave ? window.ruletaSuave(h.color) : h.color) : '#E1DFE3' }}/>
                      <span>{h.texto}</span>
                      <small>{hora(h.t)}</small>
                    </div>
                  ))}
                </div>
              ) : (
                <div className="ruleta-historial-vacio">{window.t('Todavía no ha salido nadie.', 'Nothing has come out yet.')}</div>
              )}
              {historial.length > 0 && (
                <button className="ruleta-accion" onClick={() => onUpdate({ historial: [] })}>
                  <span className="material-symbols-rounded">delete_sweep</span>{window.t('Borrar los resultados', 'Clear the results')}
                </button>
              )}
            </div>
          </div>
        );
      })()}
      {pane === 'galeriaFotos' && (() => {
        const fotos = Array.isArray(item.fotos) ? item.fotos : [];
        const mueve = (k, d) => {
          const j = k + d;
          if (j < 0 || j >= fotos.length) return;
          const c = fotos.slice();
          const t = c[k]; c[k] = c[j]; c[j] = t;
          onUpdate({ fotos: c });
          window.playAudioTone && window.playAudioTone('click');
        };
        return (
          <div className="ctx-popout galeria-panel" onMouseDown={(e) => e.stopPropagation()}>
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Fotos de la galería', 'Gallery photos')} · {fotos.length}</div>
              <div className="galeria-lista">
                {fotos.map((f, k) => (
                  <div className="galeria-fila" key={f.id}>
                    {/* Con miniatura: con cien fotos, pintar las enteras aquí
                        volvía lento el panel. */}
                    {window.GaleriaMiniImg
                      ? <window.GaleriaMiniImg foto={f} caja={40} className="galeria-fila-mini"/>
                      : <img className="galeria-fila-mini" src={window.displayMediaSrc ? window.displayMediaSrc(f) : f.src} alt="" draggable={false}/>}
                    <span className="galeria-fila-nombre">
                      {k === 0 ? window.t('Portada', 'Cover') : window.t(`Foto ${k + 1}`, `Photo ${k + 1}`)}
                    </span>
                    <button className="galeria-fila-boton" disabled={k === 0} onClick={() => mueve(k, -1)} title={window.t('Antes', 'Earlier')}>
                      <span className="material-symbols-rounded">arrow_upward</span>
                    </button>
                    <button className="galeria-fila-boton" disabled={k === fotos.length - 1} onClick={() => mueve(k, 1)} title={window.t('Después', 'Later')}>
                      <span className="material-symbols-rounded">arrow_downward</span>
                    </button>
                    <button
                      className="galeria-fila-boton quitar"
                      onClick={() => window.dispatchEvent(new CustomEvent('odi-galeria-quitar', { detail: { id: item.id, foto: f.id } }))}
                      title={window.t('Quitar', 'Remove')}
                    >
                      <span className="material-symbols-rounded">close</span>
                    </button>
                  </div>
                ))}
              </div>
            </div>
          </div>
        );
      })()}
      {pane === 'sepLinea' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Estilo de la línea', 'Line style')}</div>
            <div className="ctx-lang-list">
              {(window.ESTILOS_SEPARADOR || []).map(e => {
                const activo = (item.estiloLinea || 'continua') === e;
                const nombre = {
                  continua: window.t('Continua', 'Solid'),
                  discontinua: window.t('Discontinua', 'Dashed'),
                  punteada: window.t('Punteada', 'Dotted'),
                  // 'Doble' a secas ya está en el diccionario común como el de las flechas
                  // de doble sentido, y salía 'Two-way'.
                  doble: window.t('Doble línea', 'Double line'),
                }[e];
                return (
                  <button
                    key={e}
                    className={`ctx-lang-item ${activo ? 'active' : ''}`}
                    onClick={() => { onUpdate({ estiloLinea: e }); window.playAudioTone && window.playAudioTone('click'); }}
                  >
                    <span className="sep-muestra" style={{ borderTop: window.bordeSeparador(e, item.grosor, 'currentColor') }}/>
                    <span>{nombre}</span>
                    {activo && <span className="material-symbols-rounded">check</span>}
                  </button>
                );
              })}
            </div>
            <div className="ctx-pop-sub">{window.t('Grosor', 'Thickness')}</div>
            <div className="ctx-lang-list">
              {[[1, window.t('Fina', 'Thin')], [2, window.t('Media', 'Medium')], [4, window.t('Gruesa', 'Thick')], [8, window.t('Muy gruesa', 'Very thick')]].map(([g, nombre]) => {
                const activo = (item.grosor || 2) === g;
                return (
                  <button
                    key={g}
                    className={`ctx-lang-item ${activo ? 'active' : ''}`}
                    onClick={() => { onUpdate({ grosor: g }); window.playAudioTone && window.playAudioTone('click'); }}
                  >
                    <span className="sep-muestra" style={{ borderTop: window.bordeSeparador(item.estiloLinea || 'continua', g, 'currentColor') }}/>
                    <span>{nombre}</span>
                    {activo && <span className="material-symbols-rounded">check</span>}
                  </button>
                );
              })}
            </div>
          </div>
        </div>
      )}
      {pane === 'color' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{isColumn ? (window.t('Color de la franja', 'Strip color')) : isSeparator ? window.t('Color de la línea', 'Line colour') : isCarpeta ? window.t('Color de la carpeta', 'Folder colour') : (window.t('Color de fondo', 'Background'))}</div>
            {/* Cuatro y el arcoiris, como la barra de herramientas. Antes eran
                trece cuadrados: una rejilla de trece colores parecidos obliga a
                comparar, y comparar cuesta mas que elegir. Los cuatro cubren lo
                de siempre y el resto esta en la ultima casilla. */}
            <window.SelectorColor
              entero
              valor={item.color === 'transparent' ? 'transparent' : (window.resolveStickyColor ? window.resolveStickyColor(item.color || 'white') : item.color)}
              onCambio={(c) => onUpdate({ color: c })}
              colores={window.COLORES_ODINOTE_FONDO}
              incluirTransparente={!!(isFrame || isBigTitle || item.type === 'note')}
              tam={26}
            />
          </div>
        </div>
      )}

      {pane === 'timerModo' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Qué cuenta', 'What it counts')}</div>
            <div className="ctx-lang-list">
              {(window.MODOS_TIEMPO || []).map(m => (
                <button
                  key={m}
                  className={`ctx-lang-item ${(item.modoTiempo || 'cuentaAtras') === m ? 'active' : ''}`}
                  /* Cambiar de modo pone el reloj a cero: dejar corriendo la
                     cuenta de otro modo enseñaría un número que no significa
                     nada en el que acabas de elegir. */
                  onClick={()=>{ onUpdate({ modoTiempo: m, arrancadoEn: null, acumulado: 0, faseDescanso: false, rondas: 0 }); setPane(null); window.playAudioTone && window.playAudioTone('click'); }}
                >
                  <span>{window.nombreModoTiempo ? window.nombreModoTiempo(m) : m}</span>
                  {(item.modoTiempo || 'cuentaAtras') === m && <span className="material-symbols-rounded">check</span>}
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {pane === 'calAjustes' && (() => {
        const interruptor = (clave, encendido, texto, valorSiApagado) => (
          <button
            className={`ctx-lang-item ${encendido ? 'active' : ''}`}
            onClick={() => { onUpdate({ [clave]: encendido ? valorSiApagado : !valorSiApagado }); window.playAudioTone && window.playAudioTone('click'); }}
          >
            <span>{texto}</span>
            {encendido && <span className="material-symbols-rounded">check</span>}
          </button>
        );
        const exporta = () => {
          const texto = window.CalLogica.aICS(item.events || {}, window.t('Calendario de Oddinote', 'Oddinote calendar'), lang);
          const url = URL.createObjectURL(new Blob([texto], { type: 'text/calendar;charset=utf-8' }));
          const a = document.createElement('a');
          a.href = url; a.download = 'calendario.ics';
          document.body.appendChild(a); a.click(); a.remove();
          setTimeout(() => URL.revokeObjectURL(url), 2000);
        };
        const importa = (e) => {
          const archivo = e.target.files && e.target.files[0];
          e.target.value = '';
          if (!archivo) return;
          archivo.text().then((texto) => {
            const nuevos = window.CalLogica.deICS(texto);
            const n = Object.values(nuevos).reduce((t, l) => t + l.length, 0);
            if (!n) { window.showToast && window.showToast(window.t('Ese archivo no trae ningún evento.', 'That file has no events.'), 'error'); return; }
            const juntos = { ...(item.events || {}) };
            for (const k of Object.keys(nuevos)) juntos[k] = [...(Array.isArray(juntos[k]) ? juntos[k] : []), ...nuevos[k]];
            onUpdate({ events: juntos });
            window.showToast && window.showToast(window.t(`${n} eventos importados.`, `${n} events imported.`));
          });
        };
        const primer = item.primerDia == null ? 1 : item.primerDia;
        return (
          <div className="ctx-popout">
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Ajustes del calendario', 'Calendar settings')}</div>
              <div className="ctx-pop-sub">{window.t('La semana empieza en', 'Week starts on')}</div>
              <div className="ctx-lang-list">
                {[[1, window.t('Lunes', 'Monday')], [0, window.t('Domingo', 'Sunday')], [6, window.t('Sábado', 'Saturday')]].map(([d, nombre]) => (
                  <button key={d} className={`ctx-lang-item ${primer === d ? 'active' : ''}`} onClick={() => onUpdate({ primerDia: d })}>
                    <span>{nombre}</span>
                    {primer === d && <span className="material-symbols-rounded">check</span>}
                  </button>
                ))}
              </div>
              <div className="ctx-pop-sub">{window.t('Ver', 'Show')}</div>
              <div className="ctx-lang-list">
                {interruptor('finesDeSemana', item.finesDeSemana !== false, window.t('Fines de semana', 'Weekends'), true)}
                {interruptor('numSemana', item.numSemana === true, window.t('Números de semana', 'Week numbers'), false)}
                {interruptor('formato24', item.formato24 !== false, window.t('Reloj de 24 horas', '24-hour clock'), true)}
              </div>
              <div className="ctx-pop-sub">{window.t('Duración de un evento nuevo', 'New event length')}</div>
              <div className="ctx-lang-list">
                {[30, 60, 90, 120].map(m => (
                  <button key={m} className={`ctx-lang-item ${(item.duracion || 60) === m ? 'active' : ''}`} onClick={() => onUpdate({ duracion: m })}>
                    <span>{m < 60 ? m + ' min' : (m / 60) + ' h'}</span>
                    {(item.duracion || 60) === m && <span className="material-symbols-rounded">check</span>}
                  </button>
                ))}
              </div>
              <div className="ctx-pop-sub">{window.t('Pasar a otros calendarios', 'Move to other calendars')}</div>
              <div className="ctx-lang-list">
                <button className="ctx-lang-item" onClick={exporta}>
                  <span>{window.t('Exportar .ics', 'Export .ics')}</span>
                  <span className="material-symbols-rounded">download</span>
                </button>
                <label className="ctx-lang-item">
                  <span>{window.t('Importar .ics', 'Import .ics')}</span>
                  <span className="material-symbols-rounded">upload</span>
                  <input type="file" accept=".ics,text/calendar" style={{ display: 'none' }} onChange={importa}/>
                </label>
              </div>
            </div>
          </div>
        );
      })()}
      {pane === 'calNumeros' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de los números', 'Numbers colour')}</div>
            <window.SelectorColor
              entero
              valor={item.numberColor || '#595459'}
              onCambio={(c) => onUpdate({ numberColor: c })}
              colores={window.COLORES_ODINOTE_FONDO}
              tam={26}
            />
            <button
              className="btn"
              style={{marginTop: 8, width: '100%', justifyContent: 'center', fontSize: 11.5}}
              onClick={()=>{ onUpdate({ numberColor: null }); window.playAudioTone && window.playAudioTone('click'); }}
              title={window.t('Volver al color de siempre', 'Back to the default colour')}
            >
              {window.t('Color por defecto', 'Default colour')}
            </button>
          </div>
        </div>
      )}

      {pane === 'timerBarColor' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de la barra', 'Bar colour')}</div>
            <window.SelectorColor
              entero
              valor={item.barColor || '#E6544F'}
              onCambio={(c) => onUpdate({ barColor: c })}
              colores={window.COLORES_ODINOTE_FONDO}
              tam={26}
            />
          </div>
        </div>
      )}

      {pane === 'timerNumeros' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de los números', 'Numbers colour')}</div>
            <window.SelectorColor
              entero
              valor={item.numberColor || '#1A1A1A'}
              onCambio={(c) => onUpdate({ numberColor: c })}
              colores={window.COLORES_ODINOTE_TEXTO || window.COLORES_ODINOTE_FONDO}
              tam={26}
            />
            <button
              className="btn"
              style={{marginTop: 8, width: '100%', justifyContent: 'center', fontSize: 11.5}}
              onClick={()=>{ onUpdate({ numberColor: null }); window.playAudioTone && window.playAudioTone('click'); }}
              title={window.t('Sacarlo otra vez del color del fondo', 'Back to the default colour')}
            >
              {window.t('Color por defecto', 'Default colour')}
            </button>
          </div>
        </div>
      )}

      {pane === 'codeLang' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Lenguaje', 'Language')}</div>
            {/* Lista y no rejilla: aquí se busca un nombre concreto, y una
                lista se lee de un vistazo de arriba abajo. */}
            <div className="ctx-lang-list">
              {(window.LENGUAJES_CODIGO || []).map(l => (
                <button
                  key={l.id}
                  className={`ctx-lang-item ${(item.codeLang || 'javascript') === l.id ? 'active' : ''}`}
                  /* Se elige y se cierra, como cualquier desplegable. Dejarlo
                     abierto lo convertía en un panel pegado encima del lienzo
                     que no se sabía cómo quitar. */
                  onClick={()=>{ onUpdate({ codeLang: l.id }); setPane(null); window.playAudioTone && window.playAudioTone('click'); }}
                >
                  <span>{window.nombreLenguaje ? window.nombreLenguaje(l) : l.nombre}</span>
                  {(item.codeLang || 'javascript') === l.id && <span className="material-symbols-rounded">check</span>}
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {pane === 'codeBarColor' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de la barra', 'Bar colour')}</div>
            <window.SelectorColor
              entero
              valor={item.barColor || '#243352'}
              onCambio={(c) => onUpdate({ barColor: c })}
              colores={window.COLORES_ODINOTE_FONDO}
              tam={26}
            />
          </div>
        </div>
      )}

      {pane === 'figura' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Forma', 'Shape')}</div>
            {/* Las cuatro dibujadas de verdad, no con nombres: "rombo" y
                "hexágono" hay que traducirlos en la cabeza, y un dibujo de
                veintiocho píxeles no. */}
            <div className="ctx-figura-grid">
              {(window.FIGURAS || []).map(f => (
                <button
                  key={f}
                  className={`ctx-figura-swatch ${(item.figura || 'circulo') === f ? 'active' : ''}`}
                  onClick={()=>{ onUpdate({ figura: f }); window.playAudioTone && window.playAudioTone('click'); }}
                  title={f}
                >
                  {window.FiguraSVG && (
                    <window.FiguraSVG figura={f} w={34} h={26} relleno="var(--paper)" />
                  )}
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {pane === 'colorHex' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color', 'Color')}</div>
            {/* El selector de toda la aplicación: su arcoíris abre el panel
                con la rueda, los códigos HEX/RGB/HSL y el cuentagotas. Antes
                aquí había la ventana de colores de Windows y un campo suelto. */}
            {window.SelectorColor && (
              <window.SelectorColor
                entero
                valor={/^#[0-9a-fA-F]{6}$/.test(item.hex || '') ? item.hex : '#56B3A7'}
                onCambio={(c) => { if (/^#[0-9a-fA-F]{6}$/.test(c || '')) onUpdate({ hex: c.toUpperCase() }); }}
              />
            )}
          </div>
        </div>
      )}

      {pane === 'boardIcon' && (() => {
        const q = iconQuery.trim().toLowerCase();
        const list = q ? BOARD_ICON_CHOICES.filter(ic => ic.includes(q)) : BOARD_ICON_CHOICES;
        const seen = new Set();
        const icons = list.filter(ic => { if (seen.has(ic)) return false; seen.add(ic); return true; });
        return (
          <div className="ctx-popout">
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Icono', 'Icon')}</div>
              <input
                className="ctx-icon-search"
                placeholder={window.t('Buscar icono…', 'Search icon…')}
                value={iconQuery}
                onChange={(e)=>setIconQuery(e.target.value)}
                autoFocus
              />
              <div className="ctx-icon-grid ctx-icon-grid-scroll">
                {icons.map(ic => (
                  <button
                    key={ic}
                    className={`ctx-icon-btn ${(item.icon || 'dashboard') === ic ? 'active' : ''}`}
                    onClick={()=>onUpdate({ icon: ic })}
                    title={ic}
                  >
                    <span className="material-symbols-rounded">{ic}</span>
                  </button>
                ))}
                {icons.length === 0 && <div className="ctx-icon-empty">{window.t('Sin resultados','No results')}</div>}
              </div>
            </div>
          </div>
        );
      })()}

      {pane === 'emoji' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Reacciones', 'Reactions')}</div>
            <div className="ctx-emoji-grid">
              {EMOJI_OPTIONS.map(e => (
                <button key={e} className="ctx-emoji-btn" onClick={()=>addReaction(e)}>{e}</button>
              ))}
            </div>
            {Object.keys(reactions).length > 0 && (
              <>
                <div className="ctx-pop-sub">{window.t('Actuales', 'Current')}</div>
                <div className="ctx-reactions-current">
                  {Object.entries(reactions).map(([emoji, count]) => (
                    <button
                      key={emoji}
                      className="ctx-react-pill"
                      onClick={()=>{
                        const next = { ...reactions };
                        if (count <= 1) delete next[emoji];
                        else next[emoji] = count - 1;
                        onUpdate({ reactions: next });
                      }}
                      title={window.t('Clic para quitar', 'Click to remove')}
                    >
                      <span style={{fontSize:13}}>{emoji}</span>
                      <span>{count}</span>
                    </button>
                  ))}
                </div>
              </>
            )}
          </div>
        </div>
      )}

      {pane === 'comments' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t(`Comentarios (${comments.length})`, `Comments (${comments.length})`)}</div>
            <div className="ctx-comments-list">
              {comments.length === 0 && (
                <div className="ctx-comments-empty">
                  {window.t('Sin comentarios aún', 'No comments yet')}
                </div>
              )}
              {comments.map(c => (
                <div key={c.id} className="ctx-comment">
                  <div className="ctx-comment-head">
                    <div className="ctx-comment-avatar">A</div>
                    <span className="ctx-comment-author">{c.author}</span>
                    <span className="ctx-comment-time">
                      {new Date(c.time).toLocaleString(lang, { hour: '2-digit', minute: '2-digit' })}
                    </span>
                    <button
                      className="ctx-comment-del"
                      onClick={()=>onUpdate({ comments: comments.filter(cm => cm.id !== c.id) })}
                      title={window.t('Eliminar comentario', 'Delete comment')}
                    >
                      <span className="material-symbols-rounded">close</span>
                    </button>
                  </div>
                  <div className="ctx-comment-body">{c.text}</div>
                </div>
              ))}
            </div>
            <CommentComposer onSubmit={addComment} lang={lang}/>
          </div>
        </div>
      )}

      {pane === 'dueDate' && (() => {
        let activeRowIdx = -1;
        if (window._focusedTodoRow && window._focusedTodoRow.todoId === item.id) {
          activeRowIdx = (item.items || []).findIndex(r => r.id === window._focusedTodoRow.rowId);
        }
        if (activeRowIdx === -1 && item.items && item.items.length > 0) {
          activeRowIdx = 0;
        }
        const hasActiveRow = activeRowIdx !== -1;
        const liveRow = hasActiveRow ? item.items[activeRowIdx] : null;
        const rowDate = liveRow?.dueDate || '';
        const updateLiveRowMeta = (patch) => {
          if (!hasActiveRow) return;
          const nextItems = (item.items || []).map((r, i) => i === activeRowIdx ? { ...r, ...patch } : r);
          onUpdate({ items: nextItems });
        };
        return (
          <div className="ctx-popout">
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">
                {window.t('Fecha límite', 'Due date')}
                {!hasActiveRow && <div style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{window.t('Enfoca una tarea primero', 'Focus a task first')}</div>}
              </div>
              {hasActiveRow && (
                <>
                  <label className="ctx-field">
                     <span>{window.t('Fecha', 'Date')}</span>
                     <input
                       type="date"
                       value={rowDate}
                       onChange={(e)=>{ updateLiveRowMeta({ dueDate: e.target.value || null }); window._notifyFocusedRowChanged?.(); }}
                     />
                  </label>
                  {rowDate && (
                    <button className="ctx-btn danger" style={{marginTop:6}} onClick={()=>{ updateLiveRowMeta({ dueDate: null }); window._notifyFocusedRowChanged?.(); }}>
                      <span className="material-symbols-rounded">close</span>
                      <span>{window.t('Quitar', 'Remove')}</span>
                    </button>
                  )}
                </>
              )}
            </div>
          </div>
        );
      })()}

      {pane === 'assign' && (() => {
        let activeRowIdx = -1;
        if (window._focusedTodoRow && window._focusedTodoRow.todoId === item.id) {
          activeRowIdx = (item.items || []).findIndex(r => r.id === window._focusedTodoRow.rowId);
        }
        if (activeRowIdx === -1 && item.items && item.items.length > 0) {
          activeRowIdx = 0;
        }
        const hasActiveRow = activeRowIdx !== -1;
        const liveRow = hasActiveRow ? item.items[activeRowIdx] : null;
        const rowAssignee = liveRow?.assignee || '';
        const updateLiveRowMeta = (patch) => {
          if (!hasActiveRow) return;
          const nextItems = (item.items || []).map((r, i) => i === activeRowIdx ? { ...r, ...patch } : r);
          onUpdate({ items: nextItems });
        };
        return (
          <div className="ctx-popout">
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">
                {window.t('Asignar a', 'Assign to')}
                {!hasActiveRow && <div style={{fontSize:10,color:'var(--ink-3)',marginTop:2}}>{window.t('Enfoca una tarea primero', 'Focus a task first')}</div>}
              </div>
              {hasActiveRow && (
                <label className="ctx-field">
                  <span>{window.t('Persona', 'Person')}</span>
                  <input
                     type="text"
                     value={rowAssignee}
                     placeholder={window.t('Nombre…', 'Name…')}
                     onChange={(e)=>{ updateLiveRowMeta({ assignee: e.target.value || null }); window._notifyFocusedRowChanged?.(); }}
                  />
                </label>
              )}
            </div>
          </div>
        );
      })()}

      {pane === 'tblStyle' && tableCell && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Estilo', 'Style')}</div>
            <div style={{display:'flex', gap:4, flexWrap:'wrap'}}>
              {[
                { k: 'bold',      icon: 'format_bold', label: 'B' },
                { k: 'italic',    icon: 'format_italic', label: 'I' },
                { k: 'underline', icon: 'format_underlined', label: 'U' },
                { k: 'strike',    icon: 'format_strikethrough', label: 'S' },
              ].map(b => (
                <button
                  key={b.k}
                  className={`ctx-btn ${tableCell.cellData?.[b.k] ? 'active' : ''}`}
                  onClick={()=>tableCell.updateCell({ [b.k]: !tableCell.cellData?.[b.k] })}
                  style={{minWidth: 36}}
                  title={b.label}
                >
                  <span className="material-symbols-rounded">{b.icon}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {pane === 'tblColor' && tableCell && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de celda', 'Cell color')}</div>
            <div className="ctx-sticky-grid">
              <button
                className={`ctx-sticky-swatch ${!tableCell.cellData?.color ? 'active' : ''}`}
                style={{ background: 'transparent', border: '1.5px dashed var(--line-soft)' }}
                onClick={()=>tableCell.updateCell({ color: null })}
                title={window.t('Ninguno', 'None')}
              />
              {STICKY_PALETTE.map(p => (
                <button
                  key={p.key}
                  className={`ctx-sticky-swatch ${tableCell.cellData?.color === p.hex ? 'active' : ''}`}
                  style={{ background: p.hex, border: p.hex === '#FFFFFF' ? '1.5px solid var(--line-soft)' : '1.5px solid var(--line)' }}
                  onClick={()=>tableCell.updateCell({ color: p.hex })}
                  title={nombreColorPaleta(p)}
                />
              ))}
            </div>
            <div style={{ marginTop: '10px', paddingTop: '10px', borderTop: '1.5px solid var(--line-soft)' }}>
              <div style={{ fontSize: '10px', fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: '6px' }}>
                {window.t('Otro color', 'Another colour')}
              </div>
              <window.SelectorColor
                valor={window.esColorLibre && window.esColorLibre(tableCell.cellData?.color) ? tableCell.cellData?.color : ''}
                onCambio={(c) => tableCell.updateCell({ color: c })}
                tam={22}
              />
            </div>
          </div>
        </div>
      )}

      {pane === 'tblAlign' && tableCell && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Alineación', 'Alignment')}</div>
            <div style={{display:'flex', gap: 4}}>
              {['left','center','right','justify'].map(a => (
                <button
                  key={a}
                  className={`ctx-btn ${(tableCell.cellData?.align || 'left') === a ? 'active' : ''}`}
                  onClick={()=>tableCell.updateCell({ align: a })}
                  style={{minWidth: 36}}
                  title={a}
                >
                  <span className="material-symbols-rounded">{`format_align_${a}`}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {/* ── Paneles del dibujo ── */}
      {pane === 'drawColor' && isDrawingNow && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">
              {callbacks.selectedStrokeId
                ? window.t('Color de este trazo', 'This stroke’s colour')
                : window.t('Color de la tinta', 'Ink colour')}
            </div>
            <div style={{ marginTop: '6px' }}>
              <window.SelectorColor
                entero
                valor={callbacks.drawColor}
                onCambio={(c) => callbacks.setDrawColor(c)}
              />
            </div>
            <p style={{ margin: '8px 0 0', fontSize: 10.5, lineHeight: 1.35, color: 'var(--text-soft)' }}>
              {callbacks.selectedStrokeId
                ? window.t('Cambia el trazo elegido y la tinta siguiente.', 'Changes the picked stroke and the next ink.')
                : window.t('Elige un trazo con “Mover” para recolorearlo.', 'Pick a stroke with “Move” to recolour it.')}
            </p>
          </div>
        </div>
      )}

      {pane === 'drawWidth' && isDrawingNow && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Grosor del trazo', 'Stroke thickness')}</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 6 }}>
              {DRAW_WIDTHS.map(w => (
                <button
                  key={w}
                  className={`ctx-btn ${callbacks.drawWidth === w ? 'active' : ''}`}
                  onClick={()=>callbacks.setDrawWidth(w)}
                  style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-start', padding: '4px 8px' }}
                  title={`${w} px`}
                >
                  <span style={{
                    display: 'inline-block', width: 44, height: Math.max(2, w),
                    borderRadius: 999, background: 'currentColor', flex: 'none',
                  }}/>
                  <span style={{ fontSize: 11 }}>{w}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}

      {pane === 'drawPressure' && isDrawingNow && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Cómo varía el trazo', 'How the stroke varies')}</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 6 }}>
              {[
                { id: 'auto',     icon: 'auto_awesome', label: window.t('Automático', 'Automatic'),  hint: window.t('Lápiz → presión · ratón → velocidad', 'Pen → pressure · mouse → speed') },
                { id: 'pressure', icon: 'stylus',       label: window.t('Presión', 'Pressure'),      hint: window.t('Solo con lápiz digital', 'Digital pen only') },
                { id: 'speed',    icon: 'speed',        label: window.t('Velocidad', 'Speed'),       hint: window.t('Despacio engorda, rápido afina', 'Slow thickens, fast thins') },
                { id: 'uniform',  icon: 'horizontal_rule', label: window.t('Uniforme', 'Uniform'),   hint: window.t('Siempre el mismo grosor', 'Always the same width') },
              ].map(m => (
                <button
                  key={m.id}
                  className={`ctx-btn ${callbacks.drawPressureMode === m.id ? 'active' : ''}`}
                  onClick={()=>callbacks.setDrawPressureMode(m.id)}
                  style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-start', padding: '5px 8px', textAlign: 'left' }}
                  title={m.hint}
                >
                  <span className="material-symbols-rounded" style={{ fontSize: 16, flex: 'none' }}>{m.icon}</span>
                  <span style={{ fontSize: 11, lineHeight: 1.2 }}>{m.label}</span>
                </button>
              ))}
            </div>
            <p style={{ margin: '8px 0 0', fontSize: 10.5, lineHeight: 1.35, color: 'var(--text-soft)' }}>
              {window.t(
                'Sin lápiz digital el ratón no informa de presión: “Velocidad” es su equivalente.',
                'Without a digital pen the mouse reports no pressure: “Speed” is its stand-in.'
              )}
            </p>
          </div>
        </div>
      )}

      {pane === 'drawEstab' && isDrawingNow && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Estabilizador', 'Stabiliser')}</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginTop: 6 }}>
              {[
                { n: 0, icon: 'gesture',      label: window.t('Sin estabilizar', 'Off'),  hint: window.t('La línea sigue a la mano tal cual', 'The line follows your hand as it is') },
                { n: 1, icon: 'timeline',     label: window.t('Suave', 'Light'),         hint: window.t('Quita el temblor pequeño', 'Removes the small wobble') },
                { n: 2, icon: 'timeline',     label: window.t('Medio', 'Medium'),        hint: window.t('Para dibujar con ratón', 'For drawing with a mouse') },
                { n: 3, icon: 'show_chart',   label: window.t('Fuerte', 'Strong'),       hint: window.t('Curvas muy limpias; la línea va más por detrás', 'Very clean curves; the line trails further behind') },
              ].map(m => (
                <button
                  key={m.n}
                  className={`ctx-btn ${(callbacks.drawEstabilizador || 0) === m.n ? 'active' : ''}`}
                  onClick={()=>callbacks.setDrawEstabilizador(m.n)}
                  style={{ flexDirection: 'row', gap: 8, justifyContent: 'flex-start', padding: '5px 8px', textAlign: 'left' }}
                  title={m.hint}
                >
                  <span className="material-symbols-rounded" style={{ fontSize: 16, flex: 'none' }}>{m.icon}</span>
                  <span style={{ fontSize: 11, lineHeight: 1.2 }}>{m.label}</span>
                </button>
              ))}
            </div>
            <p style={{ margin: '8px 0 0', fontSize: 10.5, lineHeight: 1.35, color: 'var(--text-soft)' }}>
              {window.t(
                'La línea va atada al puntero con una cuerda: mientras el ratón se mueve dentro del aro, no se mueve. Se ve el aro mientras dibujas.',
                'The line is tied to the pointer with a string: while the mouse moves inside the ring, it does not move. You see the ring while drawing.'
              )}
            </p>
          </div>
        </div>
      )}

      {pane === 'inkColor' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color de la tinta', 'Ink colour')}</div>
            <div style={{ marginTop: '6px' }}>
              {/* Recolorea el dibujo entero: es lo que se espera de un nodo ya
                  guardado. Para teñir un solo trazo se entra a dibujar. */}
              <window.SelectorColor
                entero
                valor={((item.strokes || [])[0] || {}).color || '#1A1A1A'}
                onCambio={(c) => onUpdate({ strokes: (item.strokes || []).map(s => ({ ...s, color: c })) })}
              />
            </div>
          </div>
        </div>
      )}

      {pane === 'bigtitleColor' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Color del texto', 'Text color')}</div>
            <div style={{ marginTop: '6px' }}>
              <window.SelectorColor
                entero
                valor={item.titleColor || item.textColor || '#1A1A1A'}
                onCambio={(c) => onUpdate({ textColor: c })}
              />
            </div>
            <button
              className="btn"
              style={{ marginTop: 8, width: '100%', justifyContent: 'center', fontSize: 11.5 }}
              onClick={()=>{
                onUpdate({ textColor: 'inherit' });
              }}
            >
              <span className="material-symbols-rounded" style={{ fontSize: 14 }}>format_clear</span>
              {window.t('Color por defecto', 'Default color')}
            </button>
          </div>
        </div>
      )}

      {pane === 'bigtitleNivel' && (() => {
        const letra = window.letraDeTitulo ? window.letraDeTitulo(item) : 0;
        return (
          <div className="ctx-popout">
            <div className="ctx-pop-section">
              <div className="ctx-pop-title">{window.t('Tamaño del título', 'Title size')}</div>
              <div className="niveles-titulo">
                {NIVELES_TITULO.map(n => (
                  <button
                    key={n.id}
                    className={`ctx-btn niveles-titulo__boton niveles-titulo__boton--${n.id} ${Math.abs(letra - n.px) < 1 ? 'active' : ''}`}
                    onClick={() => { onUpdate(window.cajaDeTitulo(item, n.px)); window.playAudioTone && window.playAudioTone('click'); }}
                    title={n.px + ' px'}
                  >
                    {n.id.toUpperCase()}
                  </button>
                ))}
              </div>
            </div>
          </div>
        );
      })()}

      {pane === 'bigtitleAlign' && (
        <div className="ctx-popout">
          <div className="ctx-pop-section">
            <div className="ctx-pop-title">{window.t('Alineación del texto', 'Text alignment')}</div>
            <div style={{ display: 'flex', gap: '6px', marginTop: '4px' }}>
              {['left', 'center', 'right'].map(a => (
                <button
                  key={a}
                  className={`ctx-btn ${(item.align || 'center') === a ? 'active' : ''}`}
                  onClick={()=>onUpdate({ align: a })}
                  style={{ minWidth: 36 }}
                  title={a}
                >
                  <span className="material-symbols-rounded">{`format_align_${a}`}</span>
                </button>
              ))}
            </div>
          </div>
        </div>
      )}




    </>
  );
}

// Aquí vivía ColorTabs: dos pestañas, "Fondo" con trece colores y "Franja" con
// once, cada una con su rejilla escrita a mano. Se quedó sin usar cuando el
// color pasó a elegirse con el selector único —cuatro colores y el arcoíris,
// igual en toda la aplicación— y llevaba tiempo aquí sin que nada la pintara.
// Un componente que no se usa no es inofensivo: se lee como si fuera la manera
// de hacer las cosas, y la siguiente pantalla se escribe copiándolo.

function CommentComposer({ onSubmit, lang }) {
  const [draft, setDraft] = React.useState('');
  const submit = () => {
    if (!draft.trim()) return;
    onSubmit(draft);
    setDraft('');
  };
  return (
    <div className="ctx-comment-composer">
      <textarea
        value={draft}
        onChange={(e)=>setDraft(e.target.value)}
        placeholder={window.t('Escribe un comentario…', 'Write a comment…')}
        onKeyDown={(e)=>{
          if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
        }}
      />
      <button className="btn btn-primary press" onClick={submit} disabled={!draft.trim()}>
        {window.t('Enviar', 'Send')}
      </button>
    </div>
  );
}

window.ContextSidebar = ContextSidebar;
window.PanelTamano = PanelTamano;
window.STICKY_PALETTE = STICKY_PALETTE;
window.resolveStickyColor = resolveStickyColor;
window.EMOJI_OPTIONS = EMOJI_OPTIONS;
