  :root{
    --bg:#0b0f14; --bg-elev:#121821; --bg-elev2:#1a2230; --border:#25303f;
    --text:#e8edf3; --text-dim:#8b97a8; --accent:#5b8cff; --accent2:#7ee0c0;
    --danger:#ff6b6b; --org:#f2a65a; --ok:#7ee0c0; --radius:14px;
    --shadow:0 10px 30px rgba(0,0,0,.45); --link:#5b8cff; --link-hover:#7ee0c0;
    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
  }
  /* Toggle claro/oscuro (Fase D #22, 13 jul 2026): overescribe las mismas
     variables CSS que ya usa todo el sitio, así que no hace falta duplicar
     ninguna regla — solo estos overrides sobre :root cuando el body lleva la
     clase .theme-light. "Persistido" significa durante esta sesión del
     navegador (en memoria, SITE.settings.themePreference) — coherente con la
     restricción de diseño del prototipo de no usar localStorage (sección 10
     del documento maestro); se pierde al recargar, igual que el resto del
     estado de la demo. */
  body.theme-light{
    --bg:#f4f6f9; --bg-elev:#ffffff; --bg-elev2:#eef1f6; --border:#d7dde6;
    --text:#1a2230; --text-dim:#5b6675; --shadow:0 10px 30px rgba(20,30,50,.12);
    --link:#1d4fd8; --link-hover:#0e6b50;
  }
  /* Modo alto contraste (Fase D #21): paleta deliberadamente extrema
     (negro/blanco puro + acentos muy saturados) para maximizar legibilidad,
     independiente de claro/oscuro — se puede combinar con cualquiera de los
     dos. También en memoria durante la sesión (mismo criterio que arriba). */
  body.high-contrast{
    --bg:#000000; --bg-elev:#000000; --bg-elev2:#111111; --border:#ffffff;
    --text:#ffffff; --text-dim:#e8e8e8; --accent:#ffe14d; --accent2:#4dffea;
    --danger:#ff5c5c; --ok:#4dffb8;
  }
  body.high-contrast *{ text-shadow:none !important; }
  body.high-contrast .iconbtn, body.high-contrast .labelbtn, body.high-contrast button{ border-width:2px !important; }
  /* Temas adicionales (19 jul 2026, pedido de Ed: "más opciones de tema") —
     mismo mecanismo exacto que .theme-light de arriba (overrides de las
     variables CSS de :root sobre una clase en <body>), así que ningún
     selector nuevo hace falta en el resto de la hoja de estilos. Se pueden
     combinar con .high-contrast igual que los dos temas originales
     (high-contrast se aplica después en el cascade natural si el admin
     además lo activa, aunque en la práctica alto-contraste ya cubre ese
     caso de uso por sí solo). Ver applyThemePreferences()/admin.settings.theme*
     en app.js para la lista de valores válidos de themePreference. */
  body.theme-sepia{
    --bg:#f2e8d5; --bg-elev:#faf3e3; --bg-elev2:#ece0c8; --border:#d8c9a3;
    --text:#3a2f20; --text-dim:#7a6c53; --accent:#a8672b; --accent2:#7d8f4e;
    --shadow:0 10px 30px rgba(60,40,10,.14); --link:#8a4b14; --link-hover:#5f6f32;
  }
  body.theme-ocean{
    --bg:#071a24; --bg-elev:#0d2733; --bg-elev2:#123545; --border:#1c4a5c;
    --text:#e3f4f7; --text-dim:#8bb6c2; --accent:#2fb8c6; --accent2:#5be0a8;
    --shadow:0 10px 30px rgba(0,0,0,.5); --link:#4fd3e0; --link-hover:#5be0a8;
  }
  body.theme-solarized{
    --bg:#00212b; --bg-elev:#073642; --bg-elev2:#0c4453; --border:#155263;
    --text:#eee8d5; --text-dim:#93a1a1; --accent:#b58900; --accent2:#2aa198;
    --shadow:0 10px 30px rgba(0,0,0,.5); --link:#268bd2; --link-hover:#2aa198;
  }
  body.theme-rose{
    --bg:#1c0f17; --bg-elev:#2a1620; --bg-elev2:#38202c; --border:#4d2c3a;
    --text:#f6e8ee; --text-dim:#b98fa0; --accent:#e0567e; --accent2:#e8a45c;
    --shadow:0 10px 30px rgba(0,0,0,.45); --link:#e0567e; --link-hover:#e8a45c;
  }
  /* Scrollbars modernos, sitio completo (14 jul 2026) — usa las mismas
     variables de tema que todo lo demás, así que se adapta solo entre
     oscuro/claro/alto-contraste sin reglas duplicadas. Firefox usa la
     versión corta (scrollbar-width/scrollbar-color); Chrome/Edge/Safari
     usan los pseudo-elementos ::-webkit-scrollbar*, que Firefox ignora. */
  *{ scrollbar-width:thin; scrollbar-color:var(--border) var(--bg-elev2); }
  *::-webkit-scrollbar{ width:10px; height:10px; }
  *::-webkit-scrollbar-track{ background:var(--bg-elev2); border-radius:8px; }
  *::-webkit-scrollbar-thumb{ background:var(--border); border-radius:8px; border:2px solid var(--bg-elev2); }
  *::-webkit-scrollbar-thumb:hover{ background:var(--accent); }
  *::-webkit-scrollbar-corner{ background:var(--bg-elev2); }
  /* Navegación por teclado (Fase D #21): foco visible consistente en todo el
     sitio para quien navega con Tab en vez de mouse/touch — usa :focus-visible
     para no mostrar el anillo en clics de mouse (evita el "salto visual" que
     suele molestar), pero sí en foco por teclado. */
  /* Navegación por teclado (Fase D #21): foco visible consistente en todo el
     sitio para quien navega con Tab en vez de mouse/touch — usa :focus-visible
     para no mostrar el anillo en clics de mouse (evita el "salto visual" que
     suele molestar), pero sí en foco por teclado.
     22 jul 2026 (pedido de Ed, sección 6 del plan) — el color de este anillo
     usaba var(--accent) directo, el mismo que ya usan botones y pestañas;
     Ed lo percibía como "un azul fijo" porque nunca se podía cambiar aparte
     del resto. Ahora usa --outline-custom, una variable propia que cae en
     var(--accent) si Admin → Personalización → Visual no la personalizó
     (mismo comportamiento de hoy por defecto, cero cambio visual salvo que
     se configure explícitamente). Ver applyGlobalElementColors() en app.js. */
  a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible,
  textarea:focus-visible, [tabindex]:focus-visible, .tab:focus-visible{
    outline:2px solid var(--outline-custom, var(--accent)); outline-offset:2px; border-radius:4px;
  }
  /* 17 jul 2026 (Ed: "en iPhone y otros dispositivos algunos textos son
     difíciles de leer, especialmente los links") — causa real: el sitio
     nunca definía un color para enlaces <a> en ningún lado, así que
     cualquier link "plano" (dentro del cuerpo de un artículo, "Ver video
     ↗", fuentes externas, etc.) caía en el color por defecto del
     navegador. El problema puntual: el color por defecto de un link YA
     VISITADO es un morado oscuro en la mayoría de navegadores (incluido
     iOS Safari), que sobre el fondo casi negro de este sitio (--bg) tiene
     contraste pésimo — cualquier fuente externa que el visitante ya
     hubiera abierto antes se volvía casi ilegible. Los anchors con clase
     .btn (que ya traen su propio color:#fff explícito) no se ven
     afectados — un selector de clase siempre gana contra este selector de
     elemento sin importar el orden.
     22 jul 2026 (pedido de Ed, sección 6) — mismo criterio que el outline
     de arriba: --link-custom cae en var(--link) si no se personalizó. */
  a{color:var(--link-custom, var(--link));}
  a:visited{color:var(--link-custom, var(--link));}
  a:hover, a:focus-visible{color:var(--link-hover);text-decoration-thickness:2px;}
  .pane-loading{display:flex;align-items:center;justify-content:center;gap:10px;padding:40px 0;color:var(--text-dim);font-size:13px;}
  .pane-loading .spinner{width:16px;height:16px;border-radius:50%;border:2px solid var(--border);border-top-color:var(--accent);animation:spin .7s linear infinite;}
  @keyframes spin{to{transform:rotate(360deg);}}
  /* Cargador de video de pantalla completa (14 jul 2026, opcional — ver
     SITE.settings.loader y assets/js/app.js, sección justo antes de init()).
     Se crea/destruye enteramente por JS (nunca vive en partials/app-shell.html)
     así que cuando está apagado no hay ni un solo nodo de este overlay en el
     DOM — cero costo. z-index por encima de todo lo demás en el sitio
     (incluido #walkme-overlay, el más alto anterior a 400). */
  .site-loader{position:fixed;inset:0;z-index:999999;background:#000;display:flex;align-items:center;justify-content:center;overflow:hidden;opacity:1;transition:opacity .65s ease;cursor:pointer;}
  .site-loader.fade-out{opacity:0;pointer-events:none;}
  .site-loader video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
  /* 26 jul 2026 (Round 82, punto 8) — "Cargador de video" en modo "Network
     Canvas": el z-index baja de 999999 a uno por debajo de #topbar/menús
     flotantes (pero por encima del propio #network).
     27 jul 2026 (nueva sesión, bug reportado por Ed — se veía a pantalla
     completa en Firefox/Opera de escritorio y en Chrome mobile) — ANTES
     esto dependía de un script en index.php que MEDÍA la altura real de
     #topbar en tiempo de ejecución y parcheaba top/height a mano (primero
     con reintentos fijos, después con ResizeObserver) — una carrera de
     timing contra layout/paint que no era 100% confiable en todos los
     navegadores. Fix real: index.php ya NO renderiza este bloque como
     hermano de #app (posición fija cubriendo el viewport entero) cuando el
     modo es "networkCanvas" — lo inyecta DENTRO de #content-area (que ya
     es `position:relative` y ya arranca exactamente debajo de #topbar por
     el propio layout flex-column de #app, sin necesitar ninguna medición).
     position:absolute acá (en vez de position:fixed, heredado de
     .site-loader arriba) es lo que hace que este bloque llene el espacio
     de ESE contenedor padre en vez del viewport entero — puro CSS, sin
     ninguna dependencia de timing de JS, así que el resultado es idéntico
     en cualquier navegador/dispositivo. */
  .site-loader-canvas{position:absolute;z-index:15;}
  /* Respaldo si el video falla al cargar o el navegador bloquea la
     reproducción automática (p.ej. autoplay sin silenciar) — fondo oscuro
     liso en vez de una pantalla rota/en blanco; el texto (si hay) sigue
     mostrándose igual. */
  .site-loader.site-loader-fallback{background:#0a0a0a;}
  /* 14 jul 2026 (v3, extendida v4) — apariencia configurable desde
     Admin → Configuración ("Cargador de video"): posición (arriba/centro/
     abajo), color/opacidad de texto, color/opacidad de fondo, ancho del
     recuadro (% de pantalla), alineación del texto, tamaño de fuente y
     negrita. Los VALORES reales se calculan server-side
     (rcx_loader_config()/rcx_loader_tagline_style() en index.php) y se
     aplican vía style="" inline en el propio elemento — las reglas de acá
     son solo la estructura (position:absolute + centrado horizontal +
     box-sizing:border-box heredado del `*` global, para que el padding no
     se sume por fuera del max-width) y un respaldo visual que ya coincide
     con el default pedido por Ed (pegado al borde inferior, píldora
     semitransparente "ahumada" negra, texto blanco en negrita, más grande
     que el tamaño fijo anterior de 15px, ancho de hasta 90% de la pantalla)
     por si el inline style no llegara a aplicarse por algún motivo.
     position:absolute (antes: position:relative + centrado por el flex del
     padre) es lo que permite ubicar el texto arriba/centro/abajo de forma
     independiente del video de fondo. El div sigue siendo "shrink-to-fit"
     (ancho = lo que ocupe el texto, no el 90vw completo) — max-width acá es
     solo el TOPE que evita que un texto largo se corte antes de tiempo en
     una pantalla chica, o se estire absurdo en una grande (ver v4:
     taglineBoxWidthPercent, antes un max-width:560px fijo en px). */
  /* width:max-content es necesario acá, no cosmético: sin él, un elemento
     absolutely positioned con left:50%+transform:translateX(-50%) (el truco
     de centrado) y width:auto usa un algoritmo de "shrink-to-fit" que —
     bug/particularidad conocida de CSS — calcula el "ancho disponible"
     tomando el offset de `left` como punto de partida (mitad del viewport
     acá), NO el ancho completo del contenedor. Resultado medido sin esta
     línea: en una pantalla de 360px con max-width:90vw (324px), el recuadro
     terminaba envolviendo el texto a ~206px de ancho — bastante menos de lo
     que max-width permitía, exactamente el problema que taglineBoxWidthPercent
     buscaba evitar. width:max-content fuerza el ancho intrínseco real del
     contenido (acotado igual por max-width), ignorando ese offset. */
  .site-loader-tagline{position:absolute;left:50%;bottom:56px;transform:translateX(-50%);width:max-content;z-index:1;color:#fff;background:rgba(0,0,0,.55);text-align:center;padding:14px 28px;font-size:26px;font-weight:700;line-height:1.35;text-shadow:0 2px 10px rgba(0,0,0,.6);max-width:90vw;border-radius:14px;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);}
  /* 15 jul 2026 (Ed's punch list #11 — efectos de texto del tagline del
     cargador). Puro CSS a propósito: el loader se renderiza server-side
     (index.php) para evitar el FOUC corregido el 14 jul, así que la
     animación no puede depender de que JS termine de cargar/ejecutar.
     Clase asignada por rcx_loader_tagline_effect_class() en index.php
     (o por updateTaglinePreview() en assets/js/app.js para la vista previa
     del admin — mismas clases, mismo look). */
  .tagline-fx-pulse{animation:tagline-fx-pulse 1.8s ease-in-out infinite;}
  @keyframes tagline-fx-pulse{0%,100%{transform:translateX(-50%) scale(1);}50%{transform:translateX(-50%) scale(1.06);}}
  .tagline-fx-fadeIn{animation:tagline-fx-fadein 1.4s ease-out both;}
  @keyframes tagline-fx-fadein{from{opacity:0;transform:translateX(-50%) translateY(10px);}to{opacity:1;transform:translateX(-50%) translateY(0);}}
  .tagline-fx-bounce{animation:tagline-fx-bounce 1.6s ease-in-out infinite;}
  @keyframes tagline-fx-bounce{0%,100%{transform:translateX(-50%) translateY(0);}50%{transform:translateX(-50%) translateY(-10px);}}
  /* "typewriter" necesita un ancho fijo en unidades `ch` (dimensionado al
     texto vía la variable --tw-chars, calculada server-side en
     rcx_loader_tagline_effect_style()) y steps() en vez de una curva suave,
     para que se vea como texto apareciendo letra por letra en vez de un
     simple fundido/recorte. overflow:hidden + white-space:nowrap recortan
     el texto detrás del "cursor" de ancho creciente. */
  .tagline-fx-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;width:0;max-width:100%;animation:tagline-fx-typewriter 2s steps(var(--tw-chars,20),end) 1 forwards;}
  @keyframes tagline-fx-typewriter{from{width:0;}to{width:calc(var(--tw-chars,20) * 1ch);}}
  /* Nota: .site-loader-tagline sigue centrada con left:50%/
     transform:translateX(-50%) mientras el ancho crece de 0 al valor final
     — el recuadro se expande simétricamente desde el centro a medida que
     aparecen letras, un comportamiento visual estándar y aceptado para
     este efecto (no un bug); el resto de los estilos inline
     (color/fondo/tamaño) los sigue calculando el mismo
     rcx_loader_tagline_style() de siempre, sin cambios. */
  /* 27 jul 2026 (nueva sesión, items 2/3/4) — cargador nuevo de por-área
     (Articulos/Videos/Encuestas): a diferencia de .site-loader de arriba
     (a pantalla completa, fondo #000 fijo), este vive DENTRO de la vista
     real (#view-blog/#view-videos/#view-polls, ya position:absolute;
     inset:0 — ver esas reglas más abajo) y se retira por JS en
     "rcx:site-ready" (ver maybeShowAreaLoader() en assets/js/app.js).
     Temado con var(--bg) (no un color fijo) para que se vea bien en
     cualquiera de los temas de :root de arriba, y position:absolute
     (no fixed) porque el contenedor padre YA es position:absolute — llena
     ese contenedor por herencia de box model, sin medir nada. */
  .site-area-loader{position:absolute;inset:0;z-index:5;display:flex;align-items:center;justify-content:center;background:var(--bg);transition:opacity .4s ease;}
  .site-area-loader.fade-out{opacity:0;pointer-events:none;}

  /* --- Biblioteca de tipos de cargador (item 4, ver rcx_loader_visual_html()
     en index.php y el mockup entregado a Ed) — mismas figuras que el
     mockup, temadas acá con las variables del sitio (var(--accent)/
     var(--border)/var(--bg-elev2)) en vez de colores fijos. --- */
  .lv-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
  .lv-spinner-classic{width:48px;height:48px;border-radius:50%;border:4px solid var(--border);border-top-color:var(--accent);animation:lv-spin .8s linear infinite;}
  .lv-spinner-dual{width:48px;height:48px;border-radius:50%;border:4px solid var(--accent2, var(--border));border-top-color:var(--accent);border-bottom-color:var(--accent);animation:lv-spin 1s linear infinite;}
  @keyframes lv-spin{to{transform:rotate(360deg);}}
  .lv-spinner-dots{display:flex;gap:8px;}
  .lv-spinner-dots span{width:12px;height:12px;border-radius:50%;background:var(--accent);animation:lv-dots-bounce 1.1s ease-in-out infinite;}
  .lv-spinner-dots span:nth-child(2){animation-delay:.15s;}
  .lv-spinner-dots span:nth-child(3){animation-delay:.3s;}
  @keyframes lv-dots-bounce{0%,80%,100%{transform:translateY(0);opacity:.5;}40%{transform:translateY(-10px);opacity:1;}}
  .lv-bar-indeterminate,.lv-bar-striped{width:min(280px,70%);}
  .lv-bar-track{width:100%;height:8px;border-radius:999px;background:var(--bg-elev2);border:1px solid var(--border);overflow:hidden;}
  .lv-bar-indeterminate .lv-bar-fill{width:40%;height:100%;background:var(--accent);border-radius:999px;animation:lv-bar-slide 1.3s ease-in-out infinite;}
  @keyframes lv-bar-slide{0%{transform:translateX(-120%);}100%{transform:translateX(350%);}}
  .lv-bar-striped .lv-bar-fill{width:65%;height:100%;border-radius:999px;
    background-image:linear-gradient(45deg,var(--accent) 25%,transparent 25%,transparent 50%,var(--accent) 50%,var(--accent) 75%,transparent 75%,transparent);
    background-size:24px 24px;animation:lv-bar-stripes 1s linear infinite;}
  @keyframes lv-bar-stripes{from{background-position:0 0;}to{background-position:24px 0;}}
  .lv-bar-circular{width:52px;height:52px;animation:lv-spin 1.4s linear infinite;}
  .lv-bar-circular-track{fill:none;stroke:var(--border);stroke-width:3;}
  .lv-bar-circular-fill{fill:none;stroke:var(--accent);stroke-width:3;stroke-linecap:round;stroke-dasharray:80,200;}
  .lv-skeleton-card{width:min(280px,80%);display:flex;flex-direction:column;gap:10px;}
  .lv-sk-thumb{width:100%;height:140px;border-radius:12px;background:var(--bg-elev2);}
  .lv-sk-thumb,.lv-sk-line,.lv-sk-avatar{position:relative;overflow:hidden;}
  .lv-sk-thumb::after,.lv-sk-line::after,.lv-sk-avatar::after{content:"";position:absolute;inset:0;
    background:linear-gradient(90deg,transparent,rgba(255,255,255,.08),transparent);animation:lv-shimmer 1.4s ease-in-out infinite;}
  @keyframes lv-shimmer{from{transform:translateX(-100%);}to{transform:translateX(100%);}}
  .lv-sk-line{height:12px;border-radius:6px;background:var(--bg-elev2);}
  .lv-sk-line-lg{width:80%;}
  .lv-sk-line-sm{width:50%;}
  .lv-skeleton-list{width:min(320px,85%);display:flex;flex-direction:column;gap:16px;}
  .lv-sk-row{display:flex;align-items:center;gap:12px;}
  .lv-sk-avatar{width:40px;height:40px;border-radius:50%;background:var(--bg-elev2);flex-shrink:0;}
  .lv-sk-row-lines{flex:1;display:flex;flex-direction:column;gap:8px;}
  .lv-image-icon{width:96px;height:96px;object-fit:contain;animation:lv-pulse 1.6s ease-in-out infinite;}
  .lv-image-bg{position:absolute;inset:0;background-size:cover;background-position:center;}
  @keyframes lv-pulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:.55;transform:scale(.92);}}
  .lv-gif{max-width:70%;max-height:70%;object-fit:contain;}

  /* --- item 6 (Ed): topbar/menús flotantes/burbuja de chat visibles pero
     inertes (atenuados, sin clic real) hasta que rcx:site-ready se dispara
     — ver initMenuLockUntilLoaded() en assets/js/app.js. opacity+grayscale
     en vez de pointer-events:none: el mouseover para el tooltip SÍ necesita
     seguir llegando a estos elementos; el bloqueo real del clic lo hace JS
     (capture:true + preventDefault), no CSS. --- */
  body.rcx-menus-locked #topbar-actions,
  body.rcx-menus-locked .menu-group-stack,
  body.rcx-menus-locked #chat-widget-bubble{opacity:.4;filter:grayscale(65%);}
  body.rcx-menus-locked #topbar-actions,
  body.rcx-menus-locked #topbar-actions *,
  body.rcx-menus-locked .menu-group-stack,
  body.rcx-menus-locked .menu-group-stack *,
  body.rcx-menus-locked #chat-widget-bubble,
  body.rcx-menus-locked #chat-widget-bubble *{cursor:not-allowed !important;}
  .rcx-locked-tip{position:fixed;z-index:1000000;pointer-events:none;background:rgba(0,0,0,.85);color:#fff;font-size:12px;font-weight:600;padding:6px 10px;border-radius:6px;opacity:0;transform:translateY(4px);transition:opacity .15s,transform .15s;white-space:nowrap;}
  .rcx-locked-tip.show{opacity:1;transform:translateY(0);}

  *{box-sizing:border-box;}
  html,body{margin:0;padding:0;height:100%;background:var(--bg);color:var(--text);overflow:hidden;}
  #app{position:fixed;inset:0;display:flex;flex-direction:column;}

  #topbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:linear-gradient(180deg,var(--bg-elev),rgba(18,24,33,.9));border-bottom:1px solid var(--border);z-index:20;}
  #topbar h1{font-size:15px;font-weight:600;margin:0;white-space:nowrap;display:flex;align-items:center;gap:8px;}
  #topbar h1 .dot{width:8px;height:8px;border-radius:50%;background:var(--accent);box-shadow:0 0 12px var(--accent);}
  /* 17 jul 2026 (Ed) — el título ahora navega a home (ver el listener de
     #site-title en assets/js/app.js), así que necesita leerse como
     clickeable igual que cualquier otro control de la topbar. */
  #topbar h1 #site-title{cursor:pointer;}
  #topbar h1 #site-title:hover{text-decoration:underline;}
  #topbar .spacer{flex:1;}
  /* 16 jul 2026 (pedido de Ed) — racimo de botones del topbar con scroll
     horizontal propio (mismo patrón que #admin-subnav, ver más abajo) para
     que en pantallas chicas ningún botón quede recortado fuera del
     viewport (ej. #btn-social). min-width:0 es necesario para que un
     hijo flex pueda encogerse por debajo de su ancho de contenido y active
     el overflow-x en vez de desbordar el #topbar. Los menús desplegables
     (#social-menu, #lang-menu, etc.) usan position:fixed, así que no los
     afecta el overflow de este contenedor. */
  #topbar-actions{display:flex;align-items:center;gap:10px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;min-width:0;flex-shrink:1;}
  #topbar-actions::-webkit-scrollbar{display:none;}
  .iconbtn{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text);width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;font-size:16px;transition:.15s;}
  .iconbtn:hover{border-color:var(--accent);color:var(--accent);}
  .iconbtn.active{background:var(--accent);color:#fff;border-color:var(--accent);}
  /* Botón de solo-icono, sin el "panel" (fondo/borde) del .iconbtn normal —
     usado por el botón de reloj junto a la consola de actividad reciente,
     que debe leerse como parte de esa píldora, no como un botón aparte. */
  .bare-iconbtn{background:none;border:none;color:var(--text-dim);width:32px;height:32px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;font-size:16px;border-radius:8px;transition:.15s;}
  .bare-iconbtn:hover{color:var(--accent);background:rgba(255,255,255,.06);}
  .labelbtn{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text);height:38px;padding:0 12px;border-radius:10px;display:flex;align-items:center;gap:7px;justify-content:center;cursor:pointer;flex-shrink:0;font-size:13px;font-weight:600;transition:.15s;white-space:nowrap;}
  .labelbtn:hover{border-color:var(--accent);color:var(--accent);}
  .labelbtn.active{background:var(--accent);color:#fff;border-color:var(--accent);}
  .labelbtn .ico{font-size:15px;}
  @media (max-width:520px){ .labelbtn .lbl{display:none;} .labelbtn{width:38px;padding:0;} }
  /* 13 jul 2026 (pedido de Ed) — .btn/.btn.secondary/.btn.small existían solo
     dentro de #admin-main; en el sitio público (botón "← Volver al mapa",
     "Leer completo →"/"Leer en el sitio original →" de Artículos, barra
     Organizar, etc.) esos mismos botones se renderizaban SIN estilo alguno
     (blancos, con la tipografía/borde por defecto del navegador) — la causa
     real de que no parecieran "tarjetas clicables" modernas. */
  .btn{display:inline-block;padding:10px 16px;border-radius:8px;border:none;font-weight:600;font-size:13px;cursor:pointer;background:var(--btn-custom, var(--accent));color:#fff;text-decoration:none;transition:.15s;}
  .btn:hover{background:#4577f0;}
  .btn.secondary{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text);}
  .btn.secondary:hover{border-color:var(--accent);color:var(--accent);background:var(--bg-elev);}
  .btn.small{padding:6px 12px;font-size:12px;}
  #arrange-bar{display:flex;align-items:center;gap:10px;padding:8px 14px;background:#1e3a2f;border-bottom:1px solid #2f6b4f;color:#8fe0b8;font-size:13px;flex-wrap:wrap;}
  #arrange-bar .btn.small, #arrange-bar .btn.secondary.small{padding:6px 12px;font-size:12px;}
  /* 17 jul 2026 (Ed — controles de Mapa deben ocultarse fuera de la vista
     Mapa, ver claude/article_routes_navigation_plan.md punto 4): clase
     marcada en app-shell.html sobre los controles de la topbar que solo
     tienen sentido mirando el grafo (Mapa/Libre/Centrar/Agrupar/Capas).
     showContentLayer() en app.js agrega/quita "rcx-outside-map" en
     <body> según la capa activa de #content-area. !important porque
     algunos de estos botones (#btn-arrange/#btn-reset-my-layout) ya
     tienen su propia lógica de display:none condicional (rol
     admin/disposición personal guardada) — esta regla solo debe GANAR
     cuando corresponde ocultar por sección, sin tocar ni pisar esa
     lógica existente cuando se vuelve al Mapa (no se toca ningún style
     inline, solo se agrega una regla CSS adicional que deja de aplicar
     al quitar la clase del body). #legend/#zoom-controls NO necesitan
     esta clase — viven dentro de #network, que ya se oculta entero al
     entrar a una sección (ver #content-area más abajo). */
  body.rcx-outside-map .map-only{display:none !important;}
  /* 22 jul 2026 (bug reportado por Ed, Round 58, imagen 2) — #zoom-controls
     y #chat-widget-bubble compartían la misma esquina (right:16/20px,
     bottom:16/20px) — el chat bubble (z-index:150, círculo de 56px) tapaba
     los botones +/- en cualquier vista, y era más notorio en maximizado
     porque el grafo ocupa toda la pantalla. Se sube el offset inferior lo
     suficiente para despejar el bubble en todos los casos (mobile/web,
     normal/maximizado), y se agrega soporte de lado configurable
     (.side-left, ver applyZoomControlsSide() en app.js — Admin →
     Configuración). */
  #zoom-controls{position:absolute;right:16px;bottom:92px;display:flex;flex-direction:column;gap:8px;z-index:15;}
  #zoom-controls.side-left{right:auto;left:16px;}
  #zoom-controls .iconbtn{width:42px;height:42px;font-size:20px;font-weight:700;box-shadow:0 4px 14px rgba(0,0,0,.35);}
  @media (max-width:520px){ #zoom-controls{right:10px;bottom:86px;} #zoom-controls.side-left{right:auto;left:10px;} }

  /* --- Buscador: fila propia debajo del topbar, alineado a la derecha,
     semitransparente hasta que se le da foco --- */
  #searchbar-row{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:8px 14px;pointer-events:none;}

  /* --- Agrupa la consola de actividad reciente y su botón "ver toda la
     actividad" (🕘) bien pegados entre sí (1px, a pedido de Ed 13 jul
     2026), separados del buscador por el gap normal de la fila (arriba). --- */
  #activity-cluster{display:flex;align-items:center;gap:1px;flex:1;min-width:0;max-width:560px;}

  /* --- Consola de actividad reciente (rota nodos/artículos/videos nuevos) --- */
  /* 17 jul 2026 (Ed, punto 7) — causa real encontrada: esta caja no tenía
     una altura fija, así que su alto dependía de si el ítem rotado
     actual traía o no una segunda línea (.at-tag) además del texto — un
     ítem "más grande" (con tag) hacía crecer la caja, y como
     #searchbar-row es una fila normal del documento (no position:fixed),
     eso empujaba visiblemente el resto de la página hacia abajo/arriba en
     cada rotación del ticker (cada ~4.5s). height fija + .at-body como
     columna centrada (ver abajo) garantiza que la caja nunca cambie de
     tamaño sin importar cuánto contenido traiga el ítem actual. */
  #activity-ticker{pointer-events:auto;flex:1;min-width:0;max-width:520px;height:44px;display:flex;align-items:center;gap:9px;
    background:rgba(26,34,48,.35);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,.08);
    border-radius:999px;padding:8px 16px 8px 12px;cursor:pointer;opacity:.8;transition:opacity .18s,background .18s,border-color .18s;overflow:hidden;}
  #legal-banner{display:none;align-items:center;gap:12px;padding:9px 16px;background:#3a2f14;border-bottom:1px solid #6b5420;color:#e8c97a;font-size:12.5px;line-height:1.4;flex-shrink:0;}
  #legal-banner.show{display:flex;}
  #legal-banner .txt{flex:1;}
  #legal-banner a{color:#f2d38a;text-decoration:underline;cursor:pointer;}
  #legal-banner button{background:none;border:1px solid #6b5420;color:#e8c97a;border-radius:8px;padding:3px 9px;font-size:12px;cursor:pointer;flex-shrink:0;}
  #legal-banner button:hover{background:#4a3d1c;}
  #site-footer{flex-shrink:0;padding:8px 16px;background:var(--bg-elev);border-top:1px solid var(--border);color:var(--text-dim);font-size:11px;line-height:1.5;text-align:center;}
  #site-footer a{color:var(--text-dim);text-decoration:underline;cursor:pointer;}
  /* 15 jul 2026 (Ed's punch list #10 — crédito/copyright configurable):
     bloque separado de #site-footer (aviso legal), más chico y con menos
     opacidad para quedar visualmente subordinado a ese aviso, siguiendo la
     práctica estándar de "el copyright va al pie del todo, en énfasis
     reducido". Controlado por renderCreditsUI() en app.js. */
  #site-credit{flex-shrink:0;padding:4px 16px 8px;background:var(--bg-elev);color:var(--text-dim);opacity:.7;font-size:10px;line-height:1.4;text-align:center;}
  #cookie-consent-banner{display:none;position:fixed;left:0;right:0;bottom:0;z-index:150;align-items:center;gap:12px;padding:12px 16px;background:var(--bg-elev2);border-top:1px solid var(--border);color:var(--text);font-size:12.5px;line-height:1.4;box-shadow:0 -8px 24px rgba(0,0,0,.35);}
  #cookie-consent-banner.show{display:flex;}
  #cookie-consent-banner .txt{flex:1;}
  #cookie-consent-banner a{color:var(--accent);text-decoration:underline;cursor:pointer;}
  #cookie-consent-banner button{background:var(--accent);border:none;color:#04121f;border-radius:8px;padding:7px 16px;font-size:12.5px;font-weight:700;cursor:pointer;flex-shrink:0;white-space:nowrap;}
  #cookie-consent-banner button:hover{opacity:.9;}
  @media (max-width:600px){ #cookie-consent-banner{flex-wrap:wrap;} #cookie-consent-banner button{width:100%;} }
  #walkme-overlay{display:none;position:fixed;inset:0;z-index:400;pointer-events:none;}
  #walkme-overlay.show{display:block;}
  #walkme-spotlight{position:fixed;border-radius:10px;box-shadow:0 0 0 9999px rgba(5,8,12,.72);border:2px solid var(--accent);transition:top .25s ease,left .25s ease,width .25s ease,height .25s ease;pointer-events:none;}
  #walkme-card{position:fixed;width:280px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.5);padding:16px;pointer-events:auto;transition:top .25s ease,left .25s ease;}
  #walkme-card-title{font-weight:700;font-size:14px;margin-bottom:6px;color:var(--text);}
  #walkme-card-text{font-size:12.5px;line-height:1.5;color:var(--text-dim);margin-bottom:12px;}
  #walkme-card-footer{display:flex;align-items:center;justify-content:space-between;gap:8px;}
  #walkme-step-count{font-size:11px;color:var(--text-dim);flex-shrink:0;}
  #walkme-card-footer > div{display:flex;gap:6px;}
  .ra-author-tag{display:inline-block;font-size:10px;text-transform:uppercase;letter-spacing:.04em;font-weight:700;color:#e8c97a;background:#3a2f1440;border:1px solid #6b5420;padding:2px 8px;border-radius:999px;margin-bottom:6px;}
  .comment-agree{display:flex;align-items:flex-start;gap:6px;font-size:11.5px;color:var(--text-dim);line-height:1.4;margin:8px 0;cursor:pointer;}
  .comment-agree input{margin-top:2px;flex-shrink:0;}
  .comment-agree a{color:var(--text-dim);text-decoration:underline;cursor:pointer;}
  #activity-ticker:hover{opacity:1;background:var(--bg-elev2);border-color:var(--accent);}
  #activity-ticker.empty{cursor:default;}
  #activity-ticker .at-ico{flex-shrink:0;font-size:14px;line-height:1;}
  /* 17 jul 2026 (Ed, punto 7) — columna centrada dentro de la altura fija
     de #activity-ticker: cuando el ítem actual no trae .at-tag (solo una
     línea de texto), esta línea queda centrada verticalmente en la caja
     en vez de pegada arriba. */
  #activity-ticker .at-body{min-width:0;flex:1;overflow:hidden;height:100%;display:flex;flex-direction:column;justify-content:center;}
  #activity-ticker .at-tag{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent2);white-space:nowrap;}
  #activity-ticker .at-text{font-size:12px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  @keyframes atFadeIn{from{opacity:0;transform:translateY(3px);}to{opacity:1;transform:translateY(0);}}
  #activity-ticker .at-body.animate{animation:atFadeIn .3s ease;}
  @media (max-width:760px){ #activity-ticker{max-width:none;} }
  @media (max-width:520px){ #activity-ticker .at-tag{display:none;} }
  #search-wrap{position:relative;width:220px;max-width:60vw;flex-shrink:0;pointer-events:auto;transition:width .18s ease;}
  #search-wrap:focus-within{width:280px;}
  /* En pantallas chicas el buscador arranca más angosto para dejarle sitio
     a la consola de actividad (antes se la comía por completo — ver captura
     del reporte), y se agranda al enfocarlo (más incluso que en escritorio,
     ya que en focus la consola se oculta para cederle todo el espacio). */
  #btn-activity-all{pointer-events:auto;flex-shrink:0;}
  @media (max-width:640px){
    #search-wrap{width:132px;}
    #search-wrap:focus-within{width:min(76vw,320px);}
    /* 17 jul 2026 (Ed, bug reportado en mobile: el botón 🕘 no abría el
       popup) — root cause real: esta regla apuntaba a
       "#searchbar-row:focus-within", así que CUALQUIER elemento de la fila
       con foco la disparaba, incluido el propio botón — en mobile, tocar un
       <button> suele darle foco (Android Chrome en particular), así que el
       toque disparaba :focus-within sobre la fila entera y ocultaba el
       botón A MITAD del propio toque, antes de que el click llegara a
       completarse. Se acota con :has() a que sea el INPUT de búsqueda en sí
       (#search-wrap) el que tenga foco, no cualquier hijo de la fila. */
    #searchbar-row:has(#search-wrap:focus-within) #activity-ticker{display:none;}
    #searchbar-row:has(#search-wrap:focus-within) #btn-activity-all{display:none;}
  }
  @media (max-width:420px){
    #search-wrap{width:96px;}
    #search-wrap:focus-within{width:min(80vw,280px);}
  }
  #search-icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);font-size:12px;color:var(--text-dim);cursor:pointer;line-height:1;opacity:.75;z-index:1;}
  #search-icon:hover{color:var(--text);opacity:1;}
  #search{width:100%;box-sizing:border-box;background:rgba(26,34,48,.35);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,.08);color:var(--text);padding:9px 30px 9px 30px;border-radius:999px;font-size:13px;outline:none;opacity:.65;transition:.18s;}
  #search::placeholder{color:var(--text-dim);}
  #search:hover{opacity:.85;}
  #search:focus{opacity:1;background:var(--bg-elev2);border-color:var(--accent);box-shadow:0 8px 22px rgba(0,0,0,.35);}
  #btn-search-clear{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:22px;height:22px;border-radius:50%;background:none;border:none;color:var(--text-dim);font-size:12px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;}
  #btn-search-clear:hover{color:var(--text);background:var(--bg-elev);}
  #search-suggest{display:none;position:absolute;top:calc(100% + 6px);left:0;right:0;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;max-height:60vh;overflow:auto;}
  .suggest-row{display:flex;align-items:center;gap:10px;padding:8px 10px;cursor:pointer;}
  .suggest-row:hover,.suggest-row.kbd-selected{background:var(--bg-elev);}
  .suggest-row img{width:34px;height:34px;object-fit:cover;border-radius:8px;flex-shrink:0;}
  .suggest-text{min-width:0;flex:1;}
  .suggest-title{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .suggest-sub{font-size:11px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .suggest-tag{color:var(--accent);font-weight:600;}
  .suggest-group-label{padding:6px 10px 2px;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);}
  /* 15 jul 2026 — dropdown de sugerencias reutilizado fuera de la barra de
     búsqueda pública (Admin → Nodos → nodo → Conexiones manuales, buscador
     de "Conectar con…"). Misma apariencia que #search-suggest de arriba
     pero como clase, ya que acá no hay un #search-wrap fijo — el elemento
     padre inmediato (un <div style="position:relative">) es el que da el
     ancla de posicionamiento. Las filas dentro siguen usando las mismas
     clases .suggest-row/.suggest-title/.suggest-sub de arriba, sin
     duplicar estilos. */
  .admin-suggest-dropdown{display:none;position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;box-shadow:0 10px 24px rgba(0,0,0,.35);z-index:20;max-height:260px;overflow:auto;}

  /* --- Resaltado de relaciones, compartido por todas las vistas de respaldo
     (cuadrícula, tabla, agrupada, línea de tiempo, enfocada) --- */
  .fallback-card{transition:box-shadow .15s, opacity .15s, border-color .15s, background .15s;}
  .fallback-card.related-highlight{box-shadow:0 0 0 3px var(--accent);border-color:var(--accent) !important;}
  .fallback-card.self-highlight{box-shadow:0 0 0 3px #fff;}
  .has-highlight .fallback-card:not(.related-highlight):not(.self-highlight){opacity:.4;}
  tr.fallback-card.related-highlight{box-shadow:none;background:var(--accent)26;}
  tr.fallback-card.self-highlight{box-shadow:none;background:#ffffff22;}
  /* 20 jul 2026 (pedido de Ed: tarjetas con ancho/alto fijos configurables;
     cuando el subtítulo no entra, cortar con "…" y mostrar "Leer más" abajo
     a la derecha) — ver fallbackCardMetrics()/checkFallbackCardsOverflow()
     en app.js. .fc-subtitle recorta con -webkit-line-clamp (cantidad de
     líneas calculada según el alto configurado); .fc-readmore solo se
     muestra si esa medición real (no una suposición) confirmó que el texto
     quedó cortado (.fallback-card.fc-overflowing). pointer-events:none en
     .fc-readmore: es puramente visual, el clic "atraviesa" hacia la
     tarjeta (que ya abre el perfil al hacer clic en cualquier parte), sin
     necesidad de un listener aparte. */
  .fc-subtitle{font-size:11px;color:var(--text-dim,#8b97a8);display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word;}
  .fc-readmore{display:none;position:absolute;right:8px;bottom:6px;font-size:10.5px;font-weight:700;color:var(--accent,#5b8cff);align-items:center;gap:3px;pointer-events:none;background:var(--bg-elev2,#1a2230);padding:1px 4px 1px 6px;border-radius:6px;}
  .fallback-card.fc-overflowing .fc-readmore{display:flex;}
  .fc-readmore-icon{font-size:12px;line-height:1;}

  /* --- 20 jul 2026 (pedido de Ed: efecto de agrupación en Cuadrícula,
     estilo elegido en Admin → Configuración → General) ---
     .fallback-card-group es el contenedor (una tarjeta "raíz" agrupada +
     0-2 tarjetas fantasma detrás, según el estilo); .fallback-card-group-main
     es la tarjeta real (mismo .fallback-card de siempre, misma clase para
     que el resaltado de conexiones y demás sigan aplicando si hiciera
     falta más adelante) con el badge "+N" superpuesto.
     20 jul 2026 (ronda de seguimiento, pedido de Ed: "las fantasma tapaban a
     la principal") — .fallback-card-group-main pasa a z-index:20, por
     encima de CUALQUIER z-index de fantasma (el más alto usado abajo es 4,
     del fantasma 1 del abanico) — antes estaba en 3, por debajo del
     fantasma 1 (z-index:4), así que en hover el abanico terminaba tapando a
     la principal. Ahora, sea cual sea el estilo elegido, la principal
     siempre queda al frente y las fantasma siempre detrás. */
  .fallback-card-group{position:relative;cursor:pointer;transition:opacity .15s;}
  .fallback-card-group.group-dimmed,
  .fallback-card.group-dimmed{opacity:.35;}
  .fallback-card-group-main{position:relative;z-index:20;}
  /* 20 jul 2026 (ronda de seguimiento) — ancho/alto ya no son fijos acá:
     cada fantasma los recibe inline (mismo fallbackCardMetrics() que la
     tarjeta principal, ver makeGroupedFallbackCard() en app.js) para que
     coincidan con el tamaño de tarjeta configurado en Admin → Configuración
     → General. padding/text-align/overflow igual que .fallback-card (estilo
     inline en makeFallbackCard()) para que la fantasma se vea como una
     tarjeta normal (foto + nombre en el header + descripción), no como un
     rectángulo vacío. */
  .fallback-card-ghost{position:absolute;top:0;left:0;box-sizing:border-box;padding:12px;text-align:center;overflow:hidden;border-radius:12px;background:var(--bg-elev2,#1a2230);border:1px solid var(--border,#2c3648);pointer-events:none;transition:transform .18s ease,filter .18s ease,box-shadow .18s ease;}
  .fallback-card-group-fan{margin:0 16px 8px 0;}
  /* 20 jul 2026 (pedido de Ed, ronda de seguimiento: "abanico extendido")
     — hasta 4 fantasmas físicos ahora (antes 2), con más ángulo/separación
     en reposo y un arco bien abierto hacia arriba al hacer hover — se ve
     como un abanico de naipes real, no un pequeño desliz. Solo para
     "abanico"; "espiado"/"anillo" no cambiaron. */
  .fallback-card-group-fan .fallback-card-ghost-1{transform:rotate(4deg) translate(7px,3px);z-index:4;filter:brightness(.88);}
  .fallback-card-group-fan .fallback-card-ghost-2{transform:rotate(8deg) translate(14px,6px);z-index:3;filter:brightness(.76);}
  .fallback-card-group-fan .fallback-card-ghost-3{transform:rotate(12deg) translate(21px,9px);z-index:2;filter:brightness(.64);}
  .fallback-card-group-fan .fallback-card-ghost-4{transform:rotate(16deg) translate(28px,12px);z-index:1;filter:brightness(.52);}
  .fallback-card-group-peek{margin:0 12px 8px 0;}
  .fallback-card-group-peek .fallback-card-ghost-1{transform:translate(5px,5px);z-index:2;filter:brightness(.82);}
  .fallback-card-group-peek .fallback-card-ghost-2{transform:translate(10px,10px);z-index:1;filter:brightness(.65);}
  .fallback-card-group-ring{margin:8px;}
  .fallback-card-ring{position:absolute;inset:-6px;border:2px dashed var(--accent2,#7ee0c0);border-radius:16px;pointer-events:none;z-index:0;}
  .fallback-card-count-badge{position:absolute;top:-8px;right:-8px;background:var(--accent,#5b8cff);color:#0b0f14;font-weight:800;font-size:11px;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;border:2px solid var(--bg,#0b0f14);z-index:4;}
  /* 20 jul 2026 (pedido de Ed, ronda de seguimiento: "que se vean como una
     tarjeta normal, con foto/nombre en el header/descripción") — cada
     fantasma ahora lleva su contenido real (fallbackCardGhostHtml() en
     app.js: avatar + nombre + subtítulo) siempre visible, igual que
     cualquier tarjeta normal — ya no hace falta ocultar el nombre detrás de
     un tooltip y revelarlo recién al hacer hover (esa versión, y el
     tooltip de miembros que existió antes, se quitaron por pedido de Ed:
     duplicaban "Ver miembros" del menú de clic derecho/mantener
     presionado). Lo único que cambia con :hover es que las fantasma se
     despliegan hacia afuera (ver transforms de :hover más abajo) para
     poder leerlas completas en vez de solo el borde que asoma en reposo. */
  .fallback-card-group:hover .fallback-card-ghost{box-shadow:0 10px 22px rgba(0,0,0,.4);}
  /* Abanico extendido: arco centrado arriba de la tarjeta principal — el
     fantasma 1 se abre a la izquierda, el 4 a la derecha, con más altura
     (más negativo en Y) cuanto más "adentro" del abanico está cada uno. */
  .fallback-card-group-fan:hover .fallback-card-ghost-1{transform:rotate(-22deg) translate(-46px,-52px);filter:brightness(1);}
  .fallback-card-group-fan:hover .fallback-card-ghost-2{transform:rotate(-8deg) translate(-16px,-74px);filter:brightness(1);}
  .fallback-card-group-fan:hover .fallback-card-ghost-3{transform:rotate(8deg) translate(16px,-78px);filter:brightness(1);}
  .fallback-card-group-fan:hover .fallback-card-ghost-4{transform:rotate(22deg) translate(44px,-56px);filter:brightness(1);}
  .fallback-card-group-peek:hover .fallback-card-ghost-1{transform:translate(-10px,-44px);filter:brightness(1);}
  .fallback-card-group-peek:hover .fallback-card-ghost-2{transform:translate(16px,-76px);filter:brightness(1);}

  /* --- Selector de vista (menú desplegable con varios modos) --- */
  #view-mode-wrap{position:relative;}
  #view-mode-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:270px;max-width:calc(100vw - 16px);overflow-y:auto;}
  /* 24 jul 2026 (Round 75, pedido de Ed) — antes la tarjeta seleccionada
     solo tenía un fondo verdoso tenue (.active) y, por separado, el botón
     #btn-view-mode de arriba se ponía azul sólido (retirado, ver
     updateViewModeButton() en app.js) — 2 indicadores redundantes y sin
     relación visual con el resto del sitio. Ahora .view-mode-opt usa los
     3 mismos estados idle/hover/seleccionado que ya tienen los botones de
     "Elementos del menú" (Round 69, ver .round-tab-btn/.topbar-drawer-btn/
     .menu-action-topbar-btn más abajo) — mismas 3 variables CSS
     (--menu-border-idle-custom/-hover-custom/-route-custom), configurables
     desde la misma tarjeta "Bordes de selección" que ya existe, sin sumar
     ningún campo de Admin nuevo. Se agrega un borde propio a cada opción
     (antes solo tenían una línea divisoria inferior) para que el estado
     "seleccionado" se note como un borde real, no un simple fondo. */
  .view-mode-opt{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;cursor:pointer;border:2px solid var(--menu-border-idle-custom, rgba(255,255,255,.5));border-radius:8px;margin-bottom:6px;transition:border-color .15s ease;}
  .view-mode-opt:last-child{margin-bottom:0;}
  .view-mode-opt:hover{background:var(--bg-elev);border-color:var(--menu-border-hover-custom, #fff);}
  .view-mode-opt.active{border-color:var(--menu-border-route-custom, #3aa0ff);background:var(--accent2,#7ee0c0)14;}
  .view-mode-opt .ico{font-size:16px;flex-shrink:0;line-height:1.4;}
  .view-mode-opt .vm-label{font-weight:600;font-size:13px;color:var(--text);}
  .view-mode-opt .vm-desc{font-size:11px;color:var(--text-dim);margin-top:2px;}
  #export-wrap{position:relative;}
  #social-wrap{position:relative;}
  #lang-wrap{position:relative;}
  #lang-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:180px;max-width:calc(100vw - 16px);overflow-y:auto;}
  .export-opt.active-lang{background:var(--bg-elev);}
  #export-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:270px;max-width:calc(100vw - 16px);overflow-y:auto;}
  #social-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:270px;max-width:calc(100vw - 16px);overflow-y:auto;}
  .social-menu-heading{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--text-dim);padding:10px 12px 2px;}
  .export-opt{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;cursor:pointer;border-bottom:1px solid var(--border);}
  .export-opt:last-child{border-bottom:none;}
  .export-opt:hover{background:var(--bg-elev);}
  .export-opt.disabled{opacity:.4;cursor:not-allowed;}
  .export-opt .ico{font-size:16px;flex-shrink:0;line-height:1.4;}
  .export-opt .vm-label{font-weight:600;font-size:13px;color:var(--text);}
  .export-opt .vm-desc{font-size:11px;color:var(--text-dim);margin-top:2px;}
  #more-wrap{position:relative;display:none;}
  /* 23 jul 2026 (Round 69, pedido de Ed, punto 1) — si ningún ítem del
     Menú "⋯" está habilitado desde Admin (moreMenuItemEnabled()) o el
     único contenido posible es mapOnly y ya no estamos en el Mapa, el
     botón "⋯" no debe mostrarse en absoluto en mobile — ver
     updateMoreButtonVisibility() en app.js, que agrega/quita esta clase.
     !important porque debe ganarle al display:flex que el media query de
     abajo pone en mobile. */
  #more-wrap.more-empty{display:none!important;}
  #btn-cluster{display:none;}
  #btn-cluster.show{display:flex;}
  /* 17 jul 2026 (Ed, punto 10) — mismo patrón que #btn-cluster de arriba:
     "Libre" y "Ajustar" solo tienen sentido con el grafo de nodos
     realmente dibujado (Cuadrícula/Tabla/Agrupado/Cronología/Enfoque/
     Paneles de jerarquía no tienen canvas que reacomodar/centrar). Oculto
     por default; updateHierAndFitButtonVisibility() en app.js agrega
     .show solo cuando currentViewMode==="map" && !graphFallbackMode. La
     regla body.rcx-outside-map .map-only (más abajo) sigue mandando con
     !important cuando se sale de la sección Mapa por completo. */
  #btn-hier{display:none;}
  #btn-hier.show{display:flex;}
  #btn-fit{display:none;}
  #btn-fit.show{display:flex;}
  /* 24 jul 2026 (Round 76, pedido de Ed — auditoría de Cuadrícula) — los
     gemelos redondos de Zoom In/Out (#unified-btn-zoomIn/-zoomOut) y el
     grupo "Más..." del panel de Herramientas (#tools-view-features: 5
     casillas + 4 acciones, todo dependiente de window.__network) quedaban
     visibles y clicables fuera del Mapa sin avisar nada — a diferencia de
     Libre/Ajustar/Organizar/Zoom cuadrado de arriba, nunca tuvieron su
     propia gate por vista. A diferencia de esos, estos 2 ya tienen su
     propia lógica de visibilidad (Admin para los gemelos de zoom vía
     applyMenuItemsLayout(); siempre-visible para "Más..." dentro de
     Herramientas) — en vez de reescribir esa lógica, esta clase se le
     superpone con !important cuando updateGraphOnlyExtrasVisibility() en
     app.js detecta que no estamos en Mapa real. */
  .rcx-graph-only-hidden{display:none!important;}
  /* 26 jul 2026 (Round 82, pedido de Ed, punto 4) — mismo mecanismo que
     .rcx-graph-only-hidden de arriba, para el nuevo botón "Restablecer
     vista" (#btn-reset-view/#unified-btn-resetView): se le superpone
     cuando updateResetViewButtonVisibility() en app.js detecta que no hay
     nada que restablecer (modo de vista/filtros/layout/agrupamiento/
     enfoque/búsqueda todos en su estado por defecto), sin pisar la
     visibilidad de base que decide applyMenuItemsLayout() según Admin →
     Elementos del menú. */
  .rcx-nothing-to-reset{display:none!important;}
  /* 17 jul 2026 (Ed, ronda 33) — apagado admin por botón (Personalización →
     Visual, ver TOPBAR_BUTTON_TOGGLES/applyTopbarButtonSettings() en
     app.js). !important a propósito: debe ganarle a .show/.map-only y a
     cualquier display inline puntual (ej. #btn-arrange, que ya usa
     style="display:none" propio) sin importar la vista activa. */
  .rcx-topbar-btn-off{display:none!important;}
  #more-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:250px;max-width:calc(100vw - 16px);overflow-y:auto;}
  /* En pantallas angostas, "Capas" y "Exportar" se consolidan en un solo
     botón "⋯" (#more-wrap) para que la barra superior nunca desborde ni
     obligue a hacer scroll horizontal para llegar al botón de Admin. Los
     menús propios de #layers-wrap/#export-wrap siguen existiendo en el DOM
     (solo se oculta el botón que los abre directamente; ver "Más opciones"
     en JS), ya que positionFloatingMenu los reutiliza anclados al botón ⋯. */
  @media (max-width:600px){
    #layers-wrap > button, #export-wrap > button, #social-wrap > button, #lang-wrap > button{display:none;}
    #btn-cluster.show{display:none;}
    #more-wrap{display:flex;}
  }
  #layers-wrap{position:relative;}
  #layers-menu{display:none;position:fixed;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 28px rgba(0,0,0,.45);z-index:30;width:300px;max-width:calc(100vw - 16px);overflow-y:auto;}
  #layers-tag-filter{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:8px;font-size:12px;outline:none;}
  #layers-tag-filter:focus{border-color:var(--accent);}
  .layers-section{padding:10px 12px 6px;border-bottom:1px solid var(--border);}
  .layers-taglist{max-height:180px;overflow:auto;padding:6px 6px;border-bottom:1px solid var(--border);}
  .layer-chip{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;cursor:pointer;font-size:12.5px;color:var(--text);}
  .layer-chip:hover{background:var(--bg-elev);}
  .layer-chip input{accent-color:var(--accent);}
  .layer-chip span:first-of-type{flex:1;}
  .layer-count{color:var(--text-dim);font-size:11px;}
  .layers-actions{padding:8px 12px;border-bottom:1px solid var(--border);}
  .layers-presets select, .layers-presets input[type=text]{background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);border-radius:8px;font-size:12px;padding:6px 8px;outline:none;}
  .layers-presets select:focus, .layers-presets input[type=text]:focus{border-color:var(--accent);}

  /* --- Vista de Tabla / Lista compacta --- */
  #table-view{width:100%;border-collapse:collapse;font-size:13px;}
  #table-view th{text-align:left;padding:8px 10px;color:var(--text-dim);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--bg);}
  #table-view td{padding:8px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
  #table-view tr[data-id]{cursor:pointer;}
  #table-view tr[data-id]:hover td{background:var(--bg-elev2);}
  .type-badge-mini{font-size:10px;padding:2px 8px;border-radius:999px;white-space:nowrap;}

  /* --- Vista Agrupada por categoría / Línea de tiempo (secciones) --- */
  .grouped-section h3{font-weight:700;letter-spacing:.01em;}

  /* --- Vista Enfocada --- */
  #focus-view{max-width:900px;margin:0 auto;}
  #focus-picker{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text);border-radius:8px;padding:6px 10px;font-size:13px;}

  /* --- Pestañas verticales laterales (Videos / Artículos) --- */
  .side-tab{position:absolute;top:50%;right:0;transform:translateY(-50%);z-index:18;display:flex;align-items:center;}
  .side-tab.open{z-index:19;}
  .side-tab.side-left{right:auto;left:0;flex-direction:row-reverse;}
  /* 23 jul 2026 (pedido de Ed, Problema 2.1) — "#articles-tab{top:72px}"
     vivía acá desde antes de la unificación del drawer (Round 59), de
     cuando Artículos y Videos eran 2 pestañas apiladas por separado. Por
     ser selector de ID le ganaba en especificidad a
     ".side-tab.unified-mode{top:0}" (ver más abajo) aunque esa regla sea
     más nueva, así que Artículos seguía arrancando 72px más abajo que
     Herramientas/Videos/Encuestas — bug reportado por Ed con video.
     Confirmado que el sistema viejo de pestañas apiladas ya no se usa
     (las 4 secciones tienen "unified-mode" fijo en el HTML, ver
     partials/app-shell.html) así que se borra sin reemplazo. */
  .side-tab-toggle{background:var(--tab-custom, var(--accent));color:#0b0f14;border:none;border-radius:10px 0 0 10px;padding:14px 8px;font-size:12px;font-weight:700;cursor:pointer;writing-mode:vertical-rl;text-orientation:mixed;box-shadow:-4px 0 14px rgba(0,0,0,.35);letter-spacing:.03em;}
  .side-tab.side-left .side-tab-toggle{border-radius:0 10px 10px 0;box-shadow:4px 0 14px rgba(0,0,0,.35);}
  .side-tab-panel{width:0;height:0;overflow:hidden;background:var(--bg-elev2);border-left:1px solid var(--border);transition:width .2s ease,transform .2s ease;max-height:560px;display:flex;flex-direction:column;pointer-events:none;}
  .side-tab.side-left .side-tab-panel{border-left:none;border-right:1px solid var(--border);order:-1;}
  .side-tab.open .side-tab-panel{width:300px;height:70vh;pointer-events:auto;}
  .side-tab-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:12px 14px;border-bottom:1px solid var(--border);flex-shrink:0;}
  .side-tab-header span{font-weight:700;font-size:14px;}
  .side-tab-header button{background:none;border:none;color:var(--text-dim);font-size:16px;cursor:pointer;}
  #articles-panel-viewall{flex:1;text-align:right;font-size:11.5px;font-weight:600;color:var(--accent);white-space:nowrap;}
  #articles-panel-viewall:hover{text-decoration:underline;}
  .side-panel-search-wrap{padding:10px 10px 0;flex-shrink:0;position:relative;}
  .side-panel-search-wrap input{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:7px 30px 7px 12px;border-radius:999px;font-size:12px;outline:none;transition:.15s;}
  .side-panel-search-wrap input:focus{border-color:var(--accent);}
  .side-search-clear{position:absolute;right:16px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;}
  .side-search-clear:hover{color:var(--text);background:var(--bg-elev);}
  .side-tab-body{flex:1;overflow:auto;padding:10px;}
  .video-side-item{background:var(--bg-elev);border:1px solid var(--border);border-radius:10px;padding:10px;margin-bottom:10px;cursor:pointer;}
  .video-side-item:hover{border-color:var(--accent);}
  .video-side-item h4{margin:0 0 4px;font-size:13px;}
  /* 17 jul 2026 — antes esta descripción se imprimía completa sin ningún
     tope: si se pegaba el texto largo de una descripción de YouTube (o un
     og:description largo traído por "Traer datos"), el panel lateral se
     volvía enorme (ver claude/videos_admin_plan.md, punto 1). line-clamp
     la recorta a 3 líneas con elipsis SIN necesidad de tocar el dato
     guardado — funciona también retroactivamente para descripciones ya
     guardadas de rondas anteriores. */
  .video-side-item p{margin:0;font-size:11px;color:var(--text-dim);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;line-height:1.4;max-height:4.2em;}
  .video-side-tag{display:inline-block;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--accent2);background:rgba(242,166,90,.12);padding:2px 6px;border-radius:999px;margin-bottom:5px;}
  .video-side-embed{width:100%;aspect-ratio:16/9;border:0;border-radius:8px;margin-top:8px;}
  .video-side-thumb-wrap{position:relative;width:100%;aspect-ratio:16/9;border-radius:8px;overflow:hidden;margin-top:8px;background:var(--bg-elev2);}
  .video-side-thumb-wrap img{width:100%;height:100%;object-fit:cover;display:block;}
  /* 27 jul 2026 (nueva sesión, Item 1 — Ed: "no veo el video/no hay play en
     el panel de Videos") — root cause real: esta zona de miniatura/play
     antes solo se renderizaba cuando había un `thumb` real (imagen guardada
     o derivada de YouTube); un video sin ninguna de las dos (como los que
     Ed reportó, sin miniatura configurada en Biblioteca) se quedaba SIN
     ninguna zona clicable de reproducción — la tarjeta entera navegaba a la
     página del video, y visualmente parecía "no hay video". El split
     miniatura=reproduce/resto=navega ya estaba bien implementado (ver
     renderVideoSidebarList()); lo que faltaba era este placeholder para que
     la zona de play SIEMPRE exista, con o sin imagen real. */
  .video-side-thumb-noimg{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--text-dim);}
  .video-side-play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0);transition:background .15s;}
  .video-side-item:hover .video-side-play{background:rgba(0,0,0,.15);}
  .video-side-play span{width:42px;height:42px;border-radius:50%;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;font-size:14px;color:#fff;padding-left:2px;}
  /* --- Tarjetas de preview de artículos en la pestaña lateral --- */
  .side-preview-card{display:flex;gap:10px;background:var(--bg-elev);border:1px solid var(--border);border-radius:10px;padding:8px;margin-bottom:10px;cursor:pointer;}
  .side-preview-card:hover{border-color:var(--accent);}
  .side-preview-card img{width:60px;height:60px;object-fit:cover;border-radius:8px;flex-shrink:0;}
  .side-preview-body{min-width:0;flex:1;}
  .side-preview-tag{display:inline-block;font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--accent2);background:rgba(242,166,90,.12);padding:2px 6px;border-radius:999px;margin-bottom:4px;}
  .side-preview-body h4{margin:2px 0 3px;font-size:13px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .side-preview-body p{margin:0;font-size:11px;color:var(--text-dim);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
  .side-preview-meta{margin-top:4px;font-size:10px;color:var(--text-dim);}
  @media (max-width:640px){ .side-tab.open .side-tab-panel{width:min(86vw,300px);height:60vh;} }
  /* Respaldo CSS puramente para el instante antes de que corra
     layoutSideTabs() en JS (ver assets/js/app.js), que es lo que de verdad
     decide la posición final en TODOS los anchos de pantalla desde el 13
     jul 2026 — apiladas cerca del top, separadas por un hueco configurable
     (Configuración → sideTabsGapPx, 1px por defecto) en vez de centradas.
     Este bloque de mobile solo evita un parpadeo de superposición mientras
     JS todavía no midió el alto real de #network. */
  /* 23 jul 2026 (pedido de Ed, Problema 2.1) — mismo bug que el comentario
     de arriba (#articles-tab{top:72px}), versión mobile: al ser selectores
     por ID (#video-tab/#articles-tab) le ganaban en especificidad a
     ".side-tab.unified-mode{top:0}" sin importar el orden en el archivo,
     dejando Video/Artículos con un top calculado del sistema viejo de
     pestañas apiladas en pantallas chicas. Se borra junto con la versión
     desktop de arriba — layoutSideTabs() en JS ya decide la posición real
     en todos los anchos, este bloque solo estorbaba. */
  /* Aún con el apilamiento de arriba, un contenedor MUY corto (p.ej. con el
     banner de aviso legal visible en un teléfono en horizontal) podría
     seguir sin tener espacio de sobra. En viewports de poca altura, los
     botones se hacen más compactos (menos padding/letra más chica) para
     reducir aún más el riesgo de que se toquen — layoutSideTabs() en JS
     ajusta la posición exacta con las medidas reales apenas carga la página. */
  @media (max-height:520px){
    .side-tab-toggle{padding:9px 6px;font-size:10.5px;}
  }
  .side-tab.compact .side-tab-toggle{padding:8px 5px;font-size:10px;}

  /* 17 jul 2026 (Ed) — breadcrumb real ("🏠 Mapa › Artículos") en
     #blog-topbar/#videos-topbar/#polls-topbar, en vez del botón "← Volver
     al mapa" + <h1> suelto al lado que tenían antes. Una sola regla
     compartida (clase, no id) porque las 3 secciones usan exactamente el
     mismo marcado — ver partials/app-shell.html. */
  .rcx-breadcrumb{display:flex;align-items:center;gap:8px;min-width:0;}
  .rcx-breadcrumb-home{background:none;border:none;color:var(--text-dim);font-size:14px;font-weight:600;padding:4px 2px;cursor:pointer;display:flex;align-items:center;gap:4px;border-radius:6px;}
  .rcx-breadcrumb-home:hover{color:var(--text);background:var(--bg-elev2);}
  .rcx-breadcrumb-sep{color:var(--text-dim);font-size:14px;flex-shrink:0;}
  .rcx-breadcrumb-current{font-size:18px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  /* 17 jul 2026 (Ed, punto 5) — segmento intermedio del breadcrumb cuando
     hay un tercer nivel (vista de detalle): mismo look que
     .rcx-breadcrumb-home (clickeable, vuelve a la lista) pero sin el
     ícono de casita — visualmente es un paso intermedio, no el punto de
     partida. */
  .rcx-breadcrumb-link{background:none;border:none;color:var(--text-dim);font-size:18px;font-weight:600;padding:4px 2px;cursor:pointer;border-radius:6px;}
  .rcx-breadcrumb-link:hover{color:var(--text);background:var(--bg-elev2);}

  /* 23 jul 2026 (Round 69, pedido de Ed, punto 4) — contenedor compartido
     por #blog-topbar-filters/#videos-topbar-filters/#polls-topbar-filters:
     agrupa buscador + filtros propios de cada página, siempre a la
     derecha del breadcrumb (margin-left:auto) y en una sola línea
     (flex-wrap:nowrap) — antes cada página tenía su propia fila de
     filtros debajo del breadcrumb (#blog-filters/#blog-advanced-filters,
     #videos-filters, #polls-filters, todos retirados). En mobile NO se
     apilan: se encogen (ver media query debajo) para seguir cabiendo en
     la misma fila, que ahora puede hacer scroll horizontal gracias a
     .rcx-hscroll-wrap (flechas más abajo en este archivo). */
  .topbar-filters-inline{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:nowrap;flex-shrink:0;}
  @media (max-width:640px){
    .topbar-filters-inline{gap:5px;}
    .topbar-filters-inline input[type="text"],
    .topbar-filters-inline input[type="date"]{padding:5px 8px;font-size:11px;}
    .topbar-filters-inline select{padding:3px 4px;font-size:11px;max-width:88px;}
    .topbar-filters-inline label{font-size:10px!important;gap:3px!important;}
    .topbar-filters-inline label input[type="date"]{width:78px;}
    .topbar-filters-inline .btn.small,
    .topbar-filters-inline .chip{padding:5px 8px;font-size:11px;white-space:nowrap;}
  }

  /* 23 jul 2026 (Round 69, pedido de Ed, punto 6) — flechas de scroll
     horizontal GLOBALES: cualquier fila que pueda desbordar (mobile o
     desktop) se envuelve en .rcx-hscroll-wrap > .rcx-scroll-arrow.left +
     .rcx-hscroll-row + .rcx-scroll-arrow.right. wireGlobalScrollArrows()
     en app.js detecta overflow real (row.scrollWidth > clientWidth) y
     agrega/quita .show en cada flecha según la posición de scroll — no es
     "solo mobile": aplica a #topbar-actions (barra superior) y a los 3
     topbars de página (#blog-topbar/#videos-topbar/#polls-topbar) por
     igual, en cualquier ancho de pantalla. Estilo pedido por Ed en la
     segunda revisión del mockup: menos transparentes, borde azul
     brillante, pegadas a los bordes (left:0/right:0). */
  .rcx-hscroll-wrap{position:relative;min-width:0;}
  .rcx-hscroll-row{overflow-x:auto;scrollbar-width:none;}
  .rcx-hscroll-row::-webkit-scrollbar{display:none;}
  .rcx-scroll-arrow{
    position:absolute;top:50%;transform:translateY(-50%);
    width:30px;height:30px;border-radius:50%;
    display:flex;align-items:center;justify-content:center;
    background:rgba(255,255,255,.42);
    border:3px solid var(--menu-border-route-custom, #57c2ff);
    box-shadow:0 0 8px rgba(87,194,255,.85), 0 0 2px rgba(87,194,255,.9) inset;
    color:#04121f;font-size:13px;font-weight:700;cursor:pointer;padding:0;margin:0;
    opacity:0;pointer-events:none;transition:opacity .15s ease, background .15s ease;
    z-index:6;
  }
  .rcx-scroll-arrow.left{left:0px;}
  .rcx-scroll-arrow.right{right:0px;}
  .rcx-scroll-arrow.show{opacity:1;pointer-events:auto;}
  .rcx-scroll-arrow:hover{background:rgba(255,255,255,.62);}

  /* --- Blog / Artículos --- */
  #view-blog{display:none;position:absolute;inset:0;overflow:auto;background:var(--bg);}
  /* 17 jul 2026 (Ed, ronda 33) — #blog-topbar vuelve (Round 32 lo había
     eliminado y metido el breadcrumb dentro de #blog-advanced-filters,
     junto a "Limpiar filtros"; Ed pidió deshacer eso): fila propia,
     sticky, solo para el breadcrumb, alineado a la izquierda — mismo
     patrón que #videos-topbar/#polls-topbar más abajo.
     23 jul 2026 (Round 69, pedido de Ed, punto 4) — el breadcrumb y los
     filtros/búsqueda (antes #blog-filters + #blog-advanced-filters, 2 filas
     aparte) ahora comparten esta misma fila: ver #blog-topbar-filters más
     abajo y partials/app-shell.html (los ids de cada control de filtro no
     cambiaron, solo su contenedor). padding/font-size unificados con
     #videos-topbar/#polls-topbar; overflow-x:auto + scrollbar oculta porque
     la fila entera vive dentro de .rcx-hscroll-wrap (flechas de scroll, ver
     más abajo en este archivo). */
  #blog-topbar{display:flex;align-items:center;gap:16px;padding:2px 20px;font-size:14px;border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--bg-elev);z-index:5;overflow-x:auto;scrollbar-width:none;}
  #blog-topbar::-webkit-scrollbar{display:none;}
  #blog-search-wrap{position:relative;width:220px;max-width:45vw;flex-shrink:0;}
  #blog-search{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);backdrop-filter:blur(8px);border:1px solid var(--border);color:var(--text);padding:7px 30px 7px 12px;border-radius:999px;font-size:12px;outline:none;transition:.15s;}
  #blog-search:focus{border-color:var(--accent);}
  #blog-search-clear{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;}
  #blog-search-clear:hover{color:var(--text);background:var(--bg-elev);}
  /* 23 jul 2026 (Round 69, punto 4) — antes en mobile el buscador saltaba a
     su propia fila completa (width:100%); ahora se queda en la misma línea
     que el breadcrumb (ver .topbar-filters-inline más abajo, que ya lo
     encoge) — sin este override angosto, el buscador ganaba y forzaba el
     salto de línea de todos modos. */
  @media (max-width:520px){ #blog-search-wrap{width:120px;max-width:38vw;} }
  #blog-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:18px;padding:18px 20px 40px;}
  .blog-card{background:var(--bg-elev2);border:1px solid var(--border);border-radius:14px;overflow:hidden;cursor:pointer;transition:.15s;}
  .blog-card:hover{border-color:var(--accent);transform:translateY(-2px);}
  .blog-card img{width:100%;height:150px;object-fit:cover;display:block;}
  /* 27 jul 2026 (nueva sesión, pedido de Ed, punto 2) — position:relative
     agregado acá a propósito: es el ancestro posicionado más cercano que
     .post-share-menu usa para centrarse DENTRO de la tarjeta (ver ese
     comentario más abajo y el fix en .post-share-wrap dentro de
     postSocialBarHtml() en app.js — se le quitó su propio position:relative
     para que el menú "suba" hasta acá en vez de anclarse al botón). */
  .blog-card-body{padding:14px;position:relative;}
  .blog-card-body h3{margin:6px 0;font-size:15px;}
  .blog-card-body p{margin:0;font-size:13px;color:var(--text-dim);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
  .blog-type-badge{display:inline-block;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:2px 8px;border-radius:999px;background:var(--accent);color:#0b0f14;}
  .blog-meta{font-size:11px;color:var(--text-dim);margin-top:8px;}
  /* 13 jul 2026 — fuente externa (ver production_port_status.md): mismo
     naranja que el resto de la app usa para "atención/advertencia" (rol
     Editor, badges de organización), para que se lea como "esto no es
     nuestro" a simple vista sin ser alarmante. */
  .external-badge{display:inline-block;font-size:10px;font-weight:700;letter-spacing:.02em;padding:2px 8px;border-radius:999px;background:#f2a65a22;color:#f2a65a;margin-left:6px;}
  .blog-card.external-source{border-color:#f2a65a55;}
  .side-preview-card.external-source{border-color:#f2a65a55;}
  /* 21 jul 2026 (Round 55) — pestaña de nodo "Artículos Relacionados":
     mismo .blog-card que la lista pública del blog, pero en el espacio más
     angosto del panel de un nodo (no el ancho completo del sitio), así que
     usa columnas más chicas que #blog-list en vez de min(260px). El
     mini-breadcrumb reusa .rcx-breadcrumb-link ya existente (arriba en este
     archivo) para que el botón "‹ Volver" se vea igual que el resto de los
     breadcrumbs del sitio. */
  .blog-list-mini{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px;}
  .node-articles-breadcrumb{display:flex;align-items:center;margin-bottom:14px;}
  .node-articles-breadcrumb .rcx-breadcrumb-link{font-size:13px;padding:4px 10px;}
  /* 26 jul 2026 (Round 82, pedido de Ed, punto 5) — fila alineada a la
     derecha (antes a la izquierda); la fila se duplica arriba (justo debajo
     de la línea autor/fecha) y abajo (al final, como ya estaba) — ver
     postSocialBarHtml()/renderPostDetail() en app.js. */
  .post-social-bar{display:flex;gap:6px;margin-top:10px;justify-content:flex-end;flex-wrap:wrap;}
  /* 27 jul 2026 (nueva sesión, pedido de Ed, punto 1: "los botones de
     Artículos son muy grandes") — font-size/padding/gap reducidos (antes
     12px/5px 10px/8px). Solo afecta estas 3 clases + el .rcx-read-btn
     escopeado a .post-social-bar más abajo — el .rcx-read-btn "base" (el
     que usa el modal de nodo, #modal-actions) no se toca, así que el nodo
     no cambia de tamaño, solo Artículos. */
  .post-like-btn,.post-share-btn,.post-print-btn{background:var(--bg-elev);border:1px solid var(--border);color:var(--text-dim);font-size:11px;padding:4px 8px;border-radius:999px;cursor:pointer;}
  .post-like-btn:hover,.post-share-btn:hover,.post-print-btn:hover{border-color:var(--accent);color:var(--text);}
  .post-like-btn.liked{color:#5b8cff;border-color:#5b8cff55;background:#5b8cff11;}
  /* 26 jul 2026 (Round 82, punto 3/6) — popover "Compartir" de un Artículo;
     mismas clases de contenido (.export-opt/.social-menu-heading, ver más
     arriba en este archivo) que el topbar/Nodo, solo el contenedor cambia
     porque cuelga del botón dentro de la fila del artículo en vez de la
     barra superior o el modal de perfil.
     27 jul 2026 (nueva sesión, pedido de Ed, punto 2: "compartir se
     muestra oculto/recortado") — ANTES este popover se anclaba al propio
     `.post-share-wrap` (top:calc(100% + 6px);right:0), un <span
     display:inline-block> del tamaño del botón nada más — en una tarjeta
     angosta eso lo hacía desbordar fuera de la tarjeta (recortado por el
     overflow del layout de la grilla). Ahora se ancla al ancestro
     posicionado real (.blog-card-body o #blog-detail, ambos con
     position:relative agregado en esta misma ronda) y se centra dentro de
     ese contenedor en las 2 direcciones (left/top:50% + transform) — ya no
     depende de la posición del botón que lo abrió. max-width/max-height
     relativos al 100% de ese contenedor (no al viewport) para que nunca se
     salga de la tarjeta/artículo que lo contiene. */
  .post-share-menu{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.5);z-index:20;width:230px;max-width:calc(100% - 24px);max-height:calc(100% - 16px);overflow-y:auto;text-align:left;}
  /* 25 jul 2026 (pedido de Ed: leer en voz alta) — mismo look que post-
     like-btn/post-share-btn en Artículos; dentro de #modal-actions en el
     nodo, hereda el estilo de botón redondo de ese grupo (ver reglas de
     #modal-actions button más abajo) — .reading es el estado "leyendo
     ahora" (ícono ⏹ en vez de 🔊). */
  .rcx-read-btn.reading{color:#5b8cff;border-color:#5b8cff55;background:#5b8cff11;}
  .post-social-bar .rcx-read-btn{background:var(--bg-elev);border:1px solid var(--border);color:var(--text-dim);font-size:11px;padding:4px 8px;border-radius:999px;cursor:pointer;}
  .post-social-bar .rcx-read-btn:hover{border-color:var(--accent);color:var(--text);}
  @media (max-width:480px){ .rcx-read-label{display:none;} }
  #blog-detail .post-social-bar{margin-top:20px;padding-top:16px;border-top:1px solid var(--border);}
  /* 27 jul 2026 (nueva sesión) — position:relative: mismo motivo que
     .blog-card-body de arriba, para que el popover "Compartir" del
     artículo en la página de DETALLE (no solo en la tarjeta de la lista)
     también se centre dentro de este contenedor en vez de desbordar. */
  #blog-detail{max-width:760px;margin:0 auto;padding:24px 20px 60px;position:relative;}
  #blog-detail h1{font-size:26px;margin:14px 0 6px;}
  .blog-cover{width:100%;max-height:360px;object-fit:cover;border-radius:14px;margin:14px 0;}
  /* 17 jul 2026 (Ed, artículos externos con página propia) — tarjeta
     extendida dentro de #blog-detail cuando sourceType==="external" (ver
     openPost() en app.js): mismo tamaño de texto que el cuerpo nativo
     (.html-tab-content) para que no se sienta como un tratamiento aparte. */
  .blog-detail-excerpt{font-size:15px;line-height:1.6;color:var(--text);margin:0 0 18px;}

  /* --- Videos: vista pública completa (17 jul 2026, "Ver todo") ---
     Calcado a propósito del bloque de Blog/Artículos de arriba (mismos
     nombres de variables/estructura, prefijo "videos-"/"video-page-" en vez
     de "blog-") para que ambas secciones se sientan como la misma familia
     de UI — ver claude/videos_admin_plan.md, punto 4. */
  #view-videos{display:none;position:absolute;inset:0;overflow:auto;background:var(--bg);}
  /* 23 jul 2026 (Round 69, punto 4) — breadcrumb + filtros ahora comparten
     esta fila (ver #videos-topbar-filters más abajo, misma técnica que
     #blog-topbar). padding/font-size unificados con #blog-topbar/
     #polls-topbar; overflow-x:auto + scrollbar oculta porque vive dentro
     de .rcx-hscroll-wrap. */
  #videos-topbar{display:flex;align-items:center;gap:16px;padding:2px 20px;font-size:14px;border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--bg-elev);z-index:5;overflow-x:auto;scrollbar-width:none;}
  #videos-topbar::-webkit-scrollbar{display:none;}
  #videos-search-wrap{position:relative;width:220px;max-width:45vw;flex-shrink:0;}
  #videos-page-search{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);backdrop-filter:blur(8px);border:1px solid var(--border);color:var(--text);padding:7px 30px 7px 12px;border-radius:999px;font-size:12px;outline:none;transition:.15s;}
  #videos-page-search:focus{border-color:var(--accent);}
  #videos-page-search-clear{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;}
  #videos-page-search-clear:hover{color:var(--text);background:var(--bg-elev);}
  @media (max-width:520px){ #videos-search-wrap{width:120px;max-width:38vw;} }
  #videos-page-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:18px;padding:18px 20px 40px;}
  .video-page-card{background:var(--bg-elev2);border:1px solid var(--border);border-radius:14px;overflow:hidden;cursor:pointer;transition:.15s;}
  .video-page-card:hover{border-color:var(--accent);transform:translateY(-2px);}
  .video-page-card img{width:100%;height:150px;object-fit:cover;display:block;}
  .video-page-card-noimg{width:100%;height:150px;display:flex;align-items:center;justify-content:center;font-size:36px;background:var(--bg-elev);}
  /* 27 jul 2026 (nueva sesión, pedido de Ed) — position:relative: mismo
     motivo que .blog-card-body (ver esa nota más arriba) — ancestro donde
     .post-share-menu se centra ahora que la tarjeta de Video también tiene
     su propio popover "Compartir". */
  .video-page-card-body{padding:14px;position:relative;}
  .video-page-card-body h3{margin:6px 0;font-size:15px;}
  .video-page-card-body p{margin:0;font-size:13px;color:var(--text-dim);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;}
  /* 27 jul 2026 (nueva sesión) — chip de vuelta al nodo/artículo
     relacionado (solo en entradas derivadas, ver getAllVideoEntries()) —
     mismo lenguaje visual que .video-side-tag (píldora chica), pero
     clicable e interactivo (por eso es un <button>, no un <span>). */
  .video-related-chip{display:inline-flex;align-items:center;gap:4px;margin-top:8px;background:var(--bg-elev);border:1px solid var(--border);color:var(--accent);font-size:11px;font-weight:600;padding:4px 10px;border-radius:999px;cursor:pointer;}
  .video-related-chip:hover{border-color:var(--accent);background:var(--bg-elev2);}
  /* 27 jul 2026 (nueva sesión) — position:relative: mismo motivo que
     #blog-detail — ancestro donde .post-share-menu se centra en la página
     de detalle de un video. */
  #videos-page-detail{max-width:760px;margin:0 auto;padding:24px 20px 60px;position:relative;}
  #videos-page-detail h1{font-size:26px;margin:14px 0 6px;}
  .video-detail-media{margin:14px 0;}
  .video-detail-cover{width:100%;max-height:400px;object-fit:cover;border-radius:14px;display:block;}
  .video-detail-desc{white-space:pre-wrap;line-height:1.6;color:var(--text-dim);}

  /* 17 jul 2026 (Ed — shell persistente entre Mapa/Artículos/Videos/
     Encuestas, ver claude/article_routes_navigation_plan.md punto 4):
     #content-area reemplaza el rol de flex:1/position:relative que antes
     tenía #network solo — ahora #network, #view-blog, #view-videos y
     #view-polls son "capas" hermanas dentro de #content-area, cada una
     position:absolute;inset:0 (solo una visible a la vez via
     display:none/block), así que ocupan exactamente el mismo rectángulo
     de pantalla sin importar cuál esté activa. #topbar/#searchbar-row/
     #site-footer (fuera de #content-area, ver app-shell.html) nunca se
     desmontan al cambiar de sección — antes, enterBlog()/
     enterVideosPage()/enterPolls() apagaban #view-public ENTERO. Las
     pestañas flotantes (#video-tab/#articles-tab/#polls-tab) también
     viven ahora como hermanas dentro de #content-area (antes estaban
     ADENTRO de #network) — por eso persisten en cualquier sección. */
  #content-area{flex:1;position:relative;min-height:0;overflow:hidden;}
  #network{position:absolute;inset:0;background:radial-gradient(ellipse at 50% 0%,#141b26 0%,#0b0f14 70%);}
  #network-canvas{position:absolute;inset:0;touch-action:none;overscroll-behavior:contain;}
  #network-canvas canvas{touch-action:none;}
  #loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--text-dim);font-size:14px;}
  /* 17 jul 2026 (Ed, punto 6) — ver comentario junto al markup en
     partials/app-shell.html: feedback visual breve mientras el grafo se
     construye de forma diferida al volver al Mapa por primera vez desde
     una sección cargada directamente. z-index por encima del canvas para
     que tape la construcción/asentamiento inicial en vez de mostrarlo a
     medio armar. */
  #network-lazy-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:var(--text-dim);font-size:14px;background:var(--bg);z-index:6;}

  #legend{position:absolute;left:14px;bottom:14px;right:74px;display:flex;gap:8px;z-index:10;flex-wrap:wrap;}
  .legend-item{
    display:flex;align-items:center;gap:7px;background:rgba(18,24,33,.9);backdrop-filter:blur(8px);
    border:1px solid var(--border);color:var(--text);font-size:12px;font-family:inherit;
    padding:6px 12px 6px 8px;border-radius:999px;cursor:pointer;user-select:none;transition:.15s;
  }
  .legend-item:hover{border-color:var(--accent);}
  .legend-item .legend-swatch{
    width:12px;height:12px;border-radius:50%;flex-shrink:0;background:var(--_c,#888);
    box-shadow:inset 0 0 0 1px rgba(0,0,0,.25);transition:.15s;
  }
  .legend-item.off{color:var(--text-dim);}
  .legend-item.off .legend-swatch{background:transparent!important;box-shadow:inset 0 0 0 2px var(--_c,#888);}
  .legend-item:active .legend-swatch{transform:scale(.85);}

  .chip{background:rgba(18,24,33,.9);backdrop-filter:blur(8px);border:1px solid var(--border);color:var(--text-dim);font-size:12px;padding:6px 12px;border-radius:999px;cursor:pointer;user-select:none;transition:.15s;}
  .chip.on{color:#fff;border-color:transparent;background:var(--accent);}

  /* ---- Panel de filtro por jerarquía/relaciones (aparece cuando la búsqueda
     coincide con un nodo): permite acotar el grafo a su padre inmediato, todos
     sus ascendientes, sus hijos inmediatos, todos sus descendientes, o todas
     sus conexiones. ---- */
  #node-scope-panel{
    position:absolute;left:14px;bottom:64px;z-index:11;display:none;flex-direction:column;gap:8px;
    background:rgba(18,24,33,.92);backdrop-filter:blur(8px);border:1px solid var(--border);
    border-radius:12px;padding:10px 12px;font-size:12px;color:var(--text-dim);
    max-width:min(380px,calc(100vw - 28px));
  }
  #node-scope-panel .scope-title{display:flex;align-items:center;gap:8px;color:var(--text);}
  #node-scope-panel .scope-title b{color:var(--text);}
  #node-scope-panel #scope-close{margin-left:auto;background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;padding:2px 6px;line-height:1;border-radius:6px;}
  #node-scope-panel #scope-close:hover{color:var(--text);background:var(--bg-elev);}
  #node-scope-panel .scope-chips{display:flex;flex-wrap:wrap;gap:6px;}
  .scope-chip{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text-dim);font-size:11px;padding:5px 10px;border-radius:999px;cursor:pointer;user-select:none;transition:.15s;}
  .scope-chip:hover:not([disabled]){border-color:var(--accent);color:var(--text);}
  .scope-chip.on{background:var(--accent);color:#fff;border-color:var(--accent);}
  .scope-chip[disabled]{opacity:.35;cursor:not-allowed;}

  /* ---- Popup de anotación de arista: aparece al hacer clic sobre una línea
     de conexión en la vista Mapa, con la etiqueta/motivo/fuentes cargados
     desde el panel de administración (o un texto genérico para las líneas
     de jerarquía padre/hijo, que no llevan anotación manual). ---- */
  #edge-annotation-panel{
    position:fixed;z-index:31;display:none;flex-direction:column;gap:8px;
    background:rgba(18,24,33,.96);backdrop-filter:blur(8px);border:1px solid var(--border);
    border-radius:12px;padding:12px 14px;font-size:12.5px;color:var(--text-dim);
    width:290px;max-width:calc(100vw - 16px);box-shadow:0 12px 28px rgba(0,0,0,.45);
  }
  #edge-annotation-panel .eap-title, #group-action-menu .eap-title, #group-members-panel .eap-title{display:flex;align-items:center;gap:8px;color:var(--text);font-weight:600;font-size:13px;}
  #edge-annotation-panel .eap-title span.eap-close, #group-action-menu .eap-title span.eap-close, #group-members-panel .eap-title span.eap-close{margin-left:auto;background:none;border:none;color:var(--text-dim);cursor:pointer;font-size:14px;padding:2px 6px;line-height:1;border-radius:6px;font-weight:400;}
  #edge-annotation-panel .eap-title span.eap-close:hover, #group-action-menu .eap-title span.eap-close:hover, #group-members-panel .eap-title span.eap-close:hover{color:var(--text);background:var(--bg-elev);}
  #edge-annotation-panel .eap-label{color:var(--accent);font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.02em;}
  #edge-annotation-panel .eap-rationale{white-space:pre-wrap;line-height:1.5;}
  #edge-annotation-panel .eap-nodes{display:flex;gap:6px;flex-wrap:wrap;}

  /* ---- Menú de acciones de grupo + panel de miembros (Ed, ronda 34:
     "¿qué opciones tengo al hacer clic en un grupo?") — mismo look que
     #edge-annotation-panel de arriba (position:fixed anclado al punto de
     clic, ver positionPanelAtPoint en app.js), reutilizando también sus
     clases .eap-title/.eap-close para el encabezado con botón cerrar. Las
     filas de opciones del menú reutilizan .export-opt/.vm-label/.vm-desc,
     las mismas clases que ya usan export-menu/layers-menu/social-menu, así
     que no hace falta redeclarar ese estilo acá. ---- */
  #group-action-menu, #group-members-panel{
    position:fixed;z-index:31;display:none;flex-direction:column;gap:8px;
    background:rgba(18,24,33,.96);backdrop-filter:blur(8px);border:1px solid var(--border);
    border-radius:12px;padding:12px 14px 4px;font-size:12.5px;color:var(--text-dim);
    width:290px;max-width:calc(100vw - 16px);max-height:70vh;overflow-y:auto;
    box-shadow:0 12px 28px rgba(0,0,0,.45);
  }
  #group-action-menu .export-opt{padding-left:0;padding-right:0;}
  #group-members-panel{padding-bottom:10px;}
  #group-members-panel .gmp-list{display:flex;flex-direction:column;gap:2px;}
  #group-members-panel .gmp-row{display:flex;align-items:center;gap:10px;padding:7px 4px;cursor:pointer;border-radius:8px;}
  #group-members-panel .gmp-row:hover{background:var(--bg-elev);}
  #group-members-panel .gmp-avatar{width:30px;height:30px;border-radius:50%;object-fit:cover;flex-shrink:0;background:#1a2230;}
  #group-members-panel .gmp-name{font-weight:600;font-size:12.5px;color:var(--text);}
  #group-members-panel .gmp-type{font-size:11px;color:var(--text-dim);}
  #group-members-panel .gmp-empty{padding:8px 4px;font-style:italic;}

  /* ---- Barra de estado flotante mientras hay un grupo enfocado y/o
     expandido-en-el-lugar (ver updateGroupModeBar en app.js) — se ancla
     arriba del mapa, centrada, para no tapar el resto de la barra
     superior ni la búsqueda. ---- */
  #group-mode-bar{
    position:absolute;top:10px;left:50%;transform:translateX(-50%);z-index:12;
    display:none;flex-wrap:wrap;gap:8px;justify-content:center;max-width:calc(100% - 20px);
  }
  #group-mode-bar .gmb-item{
    display:flex;align-items:center;gap:8px;background:rgba(18,24,33,.92);backdrop-filter:blur(8px);
    border:1px solid var(--border);border-radius:999px;padding:6px 8px 6px 14px;font-size:12px;color:var(--text-dim);
    box-shadow:0 6px 16px rgba(0,0,0,.35);
  }
  #group-mode-bar .gmb-item b{color:var(--text);}
  #group-mode-bar .gmb-item button{
    background:var(--accent);color:#fff;border:none;border-radius:999px;padding:5px 12px;
    font-size:11.5px;font-weight:600;cursor:pointer;white-space:nowrap;
  }
  #group-mode-bar .gmb-item button:hover{filter:brightness(1.1);}

  /* ---- Controles de la línea de tiempo animada ("Cronología de
     Conexiones"): barra fija arriba de la vista con reproducir/pausar,
     control deslizante de año y contador de nodos visibles hasta ese año. ---- */
  #timeline-controls{
    position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
    background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;
    padding:10px 14px;margin-bottom:14px;
  }
  #timeline-controls #tl-play{flex-shrink:0;width:38px;height:38px;border-radius:50%;border:none;background:var(--accent);color:#fff;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;}
  #timeline-controls #tl-play:hover{filter:brightness(1.08);}
  #timeline-controls input[type=range]{flex:1;min-width:120px;accent-color:var(--accent);}
  #timeline-controls .tl-year{font-weight:700;font-size:14px;color:var(--text);min-width:44px;text-align:center;}
  #timeline-controls .tl-count{font-size:11.5px;color:var(--text-dim);white-space:nowrap;}
  .timeline-section{transition:opacity .35s ease;}
  .timeline-section.tl-hidden{display:none;}
  .timeline-section.tl-future{opacity:.28;}

  #overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:100;padding:16px;}
  #overlay.show{display:flex;}
  #modal{width:min(680px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  @keyframes pop{from{transform:scale(.96);opacity:0;}to{transform:scale(1);opacity:1;}}
  /* 25 jul 2026 (pedido de Ed) — la fila de botones (Me gusta/Comparar/
     Imprimir/Compartir/Cerrar) pasa a ser SIEMPRE la primera línea del
     modal, alineada a la derecha, con avatar/nombre/subtítulo/badge debajo
     — tanto en Web como en Mobile (antes: una sola fila en Web, y recién
     bajaba a una segunda línea DEBAJO del avatar en pantallas angostas).
     `order:-1` en #modal-actions la saca primero visualmente sin tener que
     reordenar el HTML (que sigue teniendo avatar/títulos primero, #modal-
     actions al final, por markup/accesibilidad). flex-wrap:wrap ya no vive
     solo en el media query de mobile: se necesita siempre para que el
     avatar+títulos formen su propia fila debajo. */
  #modal-header{display:flex;flex-wrap:wrap;align-items:flex-start;gap:14px;padding:20px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #modal-actions{display:flex;align-items:center;gap:8px;flex-shrink:0;order:-1;flex-basis:100%;justify-content:flex-end;margin-bottom:4px;}
  /* 27 jul 2026 (nueva sesión, Item 4 — "Vista previa" de NODO desde
     Admin, ver openAdminNodePreview()/closeModal() en app.js) — mientras
     #modal tiene la clase .rcx-preview-mode: se oculta la fila de acciones
     reales (me gusta/comparar/imprimir/compartir — todas implican un
     POST o una URL pública, sin sentido para un nodo que puede ni estar
     guardado todavía) y se muestra el aviso #modal-preview-banner. #modal-
     close se deja SIEMPRE visible (vive fuera de #modal-actions) — la
     vista previa siempre se puede cerrar. */
  #modal.rcx-preview-mode #modal-actions{display:none !important;}
  /* 27 jul 2026 (mismo Item 4, corrección encontrada en revisión final) —
     #modal-actions solo cubre la fila de acciones del ENCABEZADO del nodo
     (me gusta/comparar/imprimir/compartir de nivel-nodo). Cada ítem DENTRO
     de una pestaña (Imágenes/Video/Documentos) dibuja su PROPIA fila de
     like/compartir (.social-row, ver itemDetailHtml()) y, si los
     comentarios están activados, su propio formulario de comentario real
     (.comments-block > [data-comment-form], ver renderCommentForm()) —
     ninguno de los dos pasaba por #modal-actions, así que quedaban
     visibles y FUNCIONALES (podían disparar un POST real de me-gusta o
     comentario) mientras se previsualizaba un nodo sin guardar. Mismo
     criterio ya aplicado en #admin-preview-body para la vista previa de
     artículos (ver site.css, clases .post-social-bar/.comments-block/
     .comment-form ahí) — se repite acá para nodos. */
  #modal.rcx-preview-mode .social-row,
  #modal.rcx-preview-mode .comments-block{display:none !important;}
  #modal-preview-banner{margin:0;padding:8px 20px;font-size:12px;color:var(--org);background:#f2a65a15;border-bottom:1px solid var(--border);}
  #modal-avatar{width:64px;height:64px;border-radius:50%;object-fit:cover;flex-shrink:0;border:2px solid var(--border);background:#222;}
  #modal-avatar.square{border-radius:14px;}
  #modal-titles{flex:1;min-width:0;}
  #modal-name{font-size:18px;font-weight:700;margin:0 0 2px;}
  #modal-sub{font-size:13px;color:var(--text-dim);margin:0;}
  #modal-type-badge{display:inline-block;margin-top:6px;font-size:11px;padding:2px 9px;border-radius:999px;font-weight:600;letter-spacing:.02em;}
  #modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;}
  #modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #modal-flag-btn{flex-shrink:0;font-size:12px;padding:7px 11px;}
  #modal-print-btn{flex-shrink:0;background:none;border:1px solid var(--border);color:var(--text-dim);font-size:15px;cursor:pointer;padding:6px 10px;line-height:1;border-radius:8px;}
  #modal-print-btn:hover{color:var(--text);background:var(--bg-elev);}
  #modal-compare-btn{flex-shrink:0;background:none;border:1px solid var(--border);color:var(--text-dim);font-size:15px;cursor:pointer;padding:6px 10px;line-height:1;border-radius:8px;}
  #modal-compare-btn:hover{color:var(--text);background:var(--bg-elev);}
  #modal-share-btn{background:none;border:1px solid var(--border);color:var(--text-dim);font-size:15px;cursor:pointer;padding:6px 10px;line-height:1;border-radius:8px;}
  #modal-share-btn:hover{color:var(--text);background:var(--bg-elev);}
  /* 26 jul 2026 (Round 82, Compartir unificado) — #modal-share-menu ya no
     contiene <button> estáticos (ver app.js/#modal-share-btn listener): se
     rellena con las mismas filas .export-opt/.social-menu-heading que
     #social-menu del topbar (ver esas clases más arriba en este archivo),
     así que el ancho/tipografía quedan iguales en los dos contextos — solo
     el contenedor (posición/fondo) sigue siendo propio de este menú porque
     cuelga de un botón dentro del modal de perfil, no de la barra superior. */
  #modal-share-menu{position:absolute;top:calc(100% + 6px);right:0;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.4);z-index:20;width:270px;max-width:calc(100vw - 16px);max-height:70vh;overflow-y:auto;}
  /* 25 jul 2026 (leer en voz alta) — mismo look que #modal-print-btn/
     #modal-share-btn (sus hermanos en la misma fila de #modal-actions);
     .rcx-read-btn.reading (línea 816) sigue aplicando encima para el
     estado "leyendo". */
  #modal-read-btn{flex-shrink:0;background:none;border:1px solid var(--border);color:var(--text-dim);font-size:12px;cursor:pointer;padding:6px 10px;line-height:1;border-radius:8px;display:flex;align-items:center;gap:4px;}
  #modal-read-btn:hover{color:var(--text);background:var(--bg-elev);}
  @media (max-width:480px){ #modal-read-btn .rcx-read-label{display:none;} }

  /* ---------- Modal de Aviso legal / Metodología (independiente, sin nodo) ---------- */
  #legal-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:110;padding:16px;}
  #legal-modal-overlay.show{display:flex;}
  #legal-modal{width:min(560px,100%);max-height:82vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #legal-modal-header{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #legal-modal-title{flex:1;font-size:16px;font-weight:700;color:var(--text);}
  #legal-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #legal-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #legal-modal-body{padding:20px;overflow-y:auto;flex:1;}
  .legal-section{margin-bottom:18px;}
  .legal-section:last-child{margin-bottom:0;}
  .legal-section h3{font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--accent2);font-weight:700;margin:0 0 6px;}
  .legal-section p{font-size:13.5px;line-height:1.6;color:var(--text);margin:0;white-space:pre-wrap;}
  @media (max-width:600px){ #legal-modal-overlay{padding:0;align-items:flex-end;} #legal-modal{width:100%;max-height:86vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Modal "Ver toda la actividad reciente" (Fase A #2) ---------- */
  #activity-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:110;padding:16px;}
  #activity-modal-overlay.show{display:flex;}
  #activity-modal{width:min(560px,100%);max-height:82vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #activity-modal-header{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #activity-modal-title{flex:1;font-size:16px;font-weight:700;color:var(--text);}
  #activity-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #activity-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #activity-modal-filters{display:flex;gap:8px;padding:14px 20px 0;flex-wrap:wrap;}
  #activity-modal-filters button{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text-dim);border-radius:999px;padding:5px 12px;font-size:12px;cursor:pointer;}
  #activity-modal-filters button.active{background:var(--accent);border-color:var(--accent);color:#fff;}
  #activity-modal-search{margin:12px 20px 0;}
  #activity-modal-search input{width:100%;box-sizing:border-box;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:8px 10px;color:var(--text);font-size:13px;}
  #activity-modal-body{padding:14px 20px 20px;overflow-y:auto;flex:1;}
  .activity-row{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--border);cursor:pointer;}
  .activity-row:last-child{border-bottom:none;}
  .activity-row:hover{background:var(--bg-elev2);}
  .activity-row .ar-ico{font-size:16px;flex-shrink:0;}
  .activity-row .ar-text{flex:1;min-width:0;}
  .activity-row .ar-tag{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--accent2);}
  .activity-row .ar-label{font-size:13px;color:var(--text);}
  @media (max-width:600px){ #activity-modal-overlay{padding:0;align-items:flex-end;} #activity-modal{width:100%;max-height:86vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Modal "Comparar Nodos" (Fase E #26) ---------- */
  #compare-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:110;padding:16px;}
  #compare-modal-overlay.show{display:flex;}
  #compare-modal{width:min(760px,100%);max-height:86vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #compare-modal-header{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #compare-modal-title{flex:1;font-size:16px;font-weight:700;color:var(--text);}
  #compare-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #compare-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #compare-modal-body{padding:16px 20px 20px;overflow-y:auto;flex:1;}
  #compare-pick-search{width:100%;box-sizing:border-box;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:9px 11px;color:var(--text);font-size:13px;margin-bottom:12px;}
  .compare-pick-row{display:flex;align-items:center;gap:10px;padding:8px 6px;border-radius:8px;cursor:pointer;}
  .compare-pick-row:hover{background:var(--bg-elev2);}
  .compare-pick-row img{width:32px;height:32px;object-fit:cover;border-radius:50%;flex-shrink:0;}
  .compare-pick-row img.square{border-radius:6px;}
  .compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
  .compare-col{min-width:0;}
  .compare-col-header{text-align:center;padding-bottom:12px;border-bottom:1px solid var(--border);margin-bottom:12px;}
  .compare-col-header img{width:64px;height:64px;object-fit:cover;border-radius:50%;margin-bottom:8px;}
  .compare-col-header img.square{border-radius:10px;}
  .compare-col-header .cch-name{font-size:14px;font-weight:700;color:var(--text);}
  .compare-col-header .cch-sub{font-size:11px;color:var(--text-dim);margin-top:2px;}
  .compare-field{margin-bottom:14px;}
  .compare-field label{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--accent2);font-weight:700;margin-bottom:4px;}
  .compare-field .val{font-size:13px;color:var(--text);white-space:pre-wrap;line-height:1.5;}
  .compare-field .val.empty{color:var(--text-dim);font-style:italic;}
  /* 15 jul 2026 — resaltado de lo que dos nodos tienen efectivamente en
     común (ver computeSharedAttributes en app.js). Un campo/etiqueta
     "shared" se marca con un borde+fondo sutil en var(--accent) en vez de
     quedar visualmente igual a un campo que simplemente no coincide. */
  .compare-field.shared{background:rgba(91,140,255,.08);border-left:3px solid var(--accent);padding:6px 8px 8px;margin-left:-8px;border-radius:0 8px 8px 0;}
  .compare-field.shared label{color:var(--accent);}
  .tag.tag-shared{border-color:var(--accent);background:rgba(91,140,255,.16);color:var(--text);font-weight:600;}
  .compare-shared-summary{font-size:12.5px;color:var(--text-dim);background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:10px;}
  .compare-shared-summary.compare-shared-direct{color:var(--accent2);border-color:var(--accent2);}
  .compare-shared-summary.compare-shared-none{font-style:italic;}
  .compare-shared-badge{flex-shrink:0;font-size:10.5px;color:var(--accent);background:rgba(91,140,255,.12);border:1px solid var(--accent);border-radius:999px;padding:3px 9px;white-space:nowrap;}
  #compare-swap-btn{display:block;margin:0 auto 14px;background:var(--bg-elev2);border:1px solid var(--border);color:var(--text-dim);border-radius:999px;padding:5px 14px;font-size:12px;cursor:pointer;}
  #compare-swap-btn:hover{color:var(--text);}
  @media (max-width:600px){ #compare-modal-overlay{padding:0;align-items:flex-end;} #compare-modal{width:100%;max-height:90vh;border-radius:var(--radius) var(--radius) 0 0;} .compare-grid{grid-template-columns:1fr;gap:18px 0;} }

  /* ---------- Popup de info de IP (17 jul 2026) ---------- */
  #ip-info-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:110;padding:16px;}
  #ip-info-modal-overlay.show{display:flex;}
  #ip-info-modal{width:min(520px,100%);max-height:86vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #ip-info-modal-header{display:flex;align-items:center;gap:14px;padding:18px 20px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #ip-info-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);font-family:monospace;}
  #ip-info-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #ip-info-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #ip-info-modal-body{padding:16px 20px 20px;overflow-y:auto;flex:1;}
  .ipinfo-map-wrap{margin-bottom:14px;}
  .ipinfo-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
  .ipinfo-field{margin-bottom:14px;min-width:0;}
  .ipinfo-field label{display:block;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--accent2);font-weight:700;margin-bottom:4px;}
  .ipinfo-field .val{font-size:13px;color:var(--text);word-break:break-word;}
  .ipinfo-field .val.empty{color:var(--text-dim);font-style:italic;}
  @media (max-width:600px){ #ip-info-modal-overlay{padding:0;align-items:flex-end;} #ip-info-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} .ipinfo-grid{grid-template-columns:1fr;gap:0;} }

  /* ---------- Medidor de "fuerza de opinión" (Fase E #27) ---------- */
  .opinion-meter{margin-bottom:16px;}
  .opinion-meter-bar{height:8px;border-radius:999px;background:var(--bg-elev2);overflow:hidden;margin-top:4px;}
  .opinion-meter-fill{height:100%;border-radius:999px;transition:width .3s ease;}
  .opinion-meter-fill.low{background:#e8c97a;}
  .opinion-meter-fill.mid{background:#f2a65a;}
  .opinion-meter-fill.high{background:var(--danger, #e15c5c);}
  .opinion-meter-label{font-size:11px;color:var(--text-dim);margin-top:4px;}

  /* ---------- Editor de imágenes reutilizable (admin): presets de tamaño,
     recorte centrado tipo "object-fit:cover", chequeo de calidad/resolución,
     y conversión de "bajar de URL" a archivo editado. ---------- */
  #img-editor-overlay{position:fixed;inset:0;background:rgba(5,8,12,.75);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:120;padding:16px;}
  #img-editor-overlay.show{display:flex;}
  #img-editor-modal{width:min(480px,100%);max-height:92vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #img-editor-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #img-editor-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #img-editor-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #img-editor-body{padding:18px;overflow-y:auto;flex:1;}
  #img-editor-canvas-wrap{background:#0d1218;border-radius:10px;overflow:hidden;display:flex;align-items:center;justify-content:center;margin-bottom:12px;border:1px solid var(--border);}
  #img-editor-canvas-wrap canvas{max-width:100%;display:block;cursor:grab;touch-action:none;}
  #img-editor-canvas-wrap canvas.dragging{cursor:grabbing;}
  .img-editor-quality{font-size:12.5px;border-radius:8px;padding:8px 10px;margin-bottom:12px;}
  .img-editor-quality.ok{background:#123a2c;color:var(--ok);}
  .img-editor-quality.warn{background:#3a2c14;color:#e0a95a;}
  .img-editor-presets{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
  .img-editor-presets button{flex:1;min-width:110px;}
  #img-editor-footer{display:flex;gap:10px;padding:14px 18px;border-top:1px solid var(--border);background:var(--bg-elev2);}
  #img-editor-footer .btn{flex:1;}
  @media (max-width:600px){ #img-editor-overlay{padding:0;align-items:flex-end;} #img-editor-modal{width:100%;max-height:92vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Editor de video reutilizable (admin): mismo look que el
     editor de imágenes de arriba — metadata + miniatura + captura de cuadro
     para archivos propios. Ver openVideoEditModal() en JS. (27 jul 2026,
     nueva sesión, Item 3 extendido "en todos lados") ---------- */
  #video-editor-overlay{position:fixed;inset:0;background:rgba(5,8,12,.75);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:120;padding:16px;}
  #video-editor-overlay.show{display:flex;}
  #video-editor-modal{width:min(480px,100%);max-height:92vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #video-editor-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #video-editor-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #video-editor-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #video-editor-body{padding:18px;overflow-y:auto;flex:1;}
  #video-editor-footer{display:flex;gap:10px;padding:14px 18px;border-top:1px solid var(--border);background:var(--bg-elev2);}
  #video-editor-footer .btn{flex:1;}
  @media (max-width:600px){ #video-editor-overlay{padding:0;align-items:flex-end;} #video-editor-modal{width:100%;max-height:92vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Cuenta de visitante (registro / inicio de sesión) ---------- */
  #btn-account.logged-in{background:var(--ok);color:#04121f;border-color:var(--ok);}
  #account-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #account-modal-overlay.show{display:flex;}
  #account-modal{width:min(380px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #account-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #account-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #account-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #account-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #account-modal-body{padding:18px;overflow-y:auto;flex:1;}
  #account-tabs{display:flex;gap:6px;margin-bottom:14px;background:var(--bg-elev2);border-radius:10px;padding:4px;}
  .account-tab{flex:1;background:none;border:none;color:var(--text-dim);font-size:12.5px;font-weight:600;padding:8px;border-radius:8px;cursor:pointer;}
  .account-tab.active{background:var(--accent);color:#04121f;}
  #account-modal-body label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin:10px 0 4px;font-weight:600;}
  #account-modal-body input{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:9px 12px;border-radius:8px;font-size:13px;outline:none;}
  #account-modal-body input:focus{border-color:var(--accent);}
  .acc-error{background:#3a1414;color:var(--danger,#e05a5a);border-radius:8px;padding:8px 10px;font-size:12px;margin-bottom:6px;}
  .acc-error.acc-notice{background:#132a3a;color:#7ec3f2;}
  .acc-error.acc-notice a{color:#bfe3ff;text-decoration:underline;}
  @media (max-width:600px){ #account-modal-overlay{padding:0;align-items:flex-end;} #account-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Aviso de expiración de sesión de admin (15 jul 2026) ---------- */
  #session-warning-overlay{position:fixed;inset:0;background:rgba(5,8,12,.75);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:200;padding:16px;}
  #session-warning-overlay.show{display:flex;}
  #session-warning-modal{width:min(360px,100%);background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);padding:20px;text-align:center;animation:pop .18s ease-out;}
  #session-warning-modal h3{margin:0 0 8px;font-size:15px;}
  #session-warning-countdown{font-size:28px;font-weight:700;color:var(--accent);margin:10px 0;font-variant-numeric:tabular-nums;}
  #session-warning-modal .btn-row{display:flex;gap:10px;margin-top:14px;}
  #session-warning-modal .btn-row .btn{flex:1;}

  /* ---------- Chat en vivo — widget flotante (15 jul 2026) ---------- */
  /* 27 jul 2026 (nueva sesión, pedido de Ed: "sacar el azul de la burbuja
     de chat") — antes usaba var(--accent) (el azul de acento del sitio),
     el único botón redondo que no seguía el mismo fondo oscuro unificado
     que ya usan .round-tab-btn/.topbar-drawer-btn (var(--menu-btn-bg-custom),
     configurable desde Personalización → Visual, con var(--bg-elev2) como
     default de fábrica si Ed no lo personalizó) — se alinea acá con ese
     mismo mecanismo para que la burbuja combine con el resto de los
     botones del sitio en vez de destacar en azul. color pasa de un oscuro
     fijo (pensado para contrastar sobre el azul) a var(--text), igual que
     .round-tab-btn, porque el fondo ya no es necesariamente claro. */
  #chat-widget-bubble{position:fixed;right:20px;bottom:20px;width:56px;height:56px;border-radius:50%;background:var(--menu-btn-bg-custom, var(--bg-elev2));color:var(--text);font-size:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:var(--shadow);z-index:150;border:none;}
  #chat-widget-bubble:hover{filter:brightness(1.08);}
  /* 16 jul 2026 (pedido de Ed) — indicador de mensaje sin leer: antes, si el
     visitante cerraba el panel, pollChatWidget() dejaba de sondear del todo
     (ver comentario en pollChatWidget en app.js) y no había ninguna señal de
     que una respuesta del staff había llegado — la respuesta SÍ se guardaba
     bien server-side, pero el visitante no tenía forma de saberlo sin volver
     a abrir el panel "por las dudas". Ahora pollChatWidgetBackground() sigue
     sondeando (a ritmo más lento) incluso con el panel cerrado, y agrega esta
     clase a la burbuja cuando llega un mensaje nuevo que no es del propio
     visitante. Se limpia al volver a abrir el panel (ver openChatWidget()). */
  #chat-widget-bubble.has-unread::after{
    content:"";position:absolute;top:2px;right:2px;width:14px;height:14px;border-radius:50%;
    background:#e05a5a;border:2px solid var(--bg);animation:chatUnreadPulse 1.6s ease-in-out infinite;
  }
  @keyframes chatUnreadPulse{
    0%,100%{ box-shadow:0 0 0 0 rgba(224,90,90,.55); }
    50%{ box-shadow:0 0 0 6px rgba(224,90,90,0); }
  }
  #chat-widget-panel{position:fixed;right:20px;bottom:88px;width:min(340px,calc(100vw - 32px));height:min(460px,calc(100vh - 140px));background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;z-index:150;}
  #chat-widget-header{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:var(--bg-elev2);border-bottom:1px solid var(--border);font-weight:700;font-size:13px;}
  /* 16 jul 2026 (pedido de Ed: botón de finalizar conversación) — agrupa el
     nuevo botón 🏁 junto al ✕ existente; ambos comparten el mismo estilo
     discreto de ícono, antes solo #chat-widget-close lo tenía. */
  #chat-widget-header-actions{display:flex;align-items:center;gap:10px;}
  .chat-widget-header-btn{background:none;border:none;color:var(--text-dim);font-size:16px;cursor:pointer;line-height:1;padding:0;}
  .chat-widget-header-btn:hover{color:var(--text);}
  #chat-widget-close{font-size:18px;}
  #chat-widget-messages{flex:1;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}
  .chat-msg{max-width:85%;padding:8px 11px;border-radius:12px;font-size:13px;line-height:1.4;}
  /* 16 jul 2026 (pedido de Ed: "usar identificadores en el chat") —
     etiqueta de remitente (Vos/Asistente/Equipo/Sistema) mostrada arriba
     del texto de cada burbuja, ver chatWidgetSenderLabel() en app.js. */
  .chat-msg-sender{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;opacity:.65;margin-bottom:2px;}
  /* 16 jul 2026 (Task #12, "smart answers") — las respuestas de relación/
     desambiguación arman varias líneas separadas por \n (ver
     chat_smart_answers_build_relationship_answer() en api/chat.php);
     pre-line preserva esos saltos sin afectar mensajes de una sola línea
     (el resto del texto sigue colapsando espacios normalmente). */
  .chat-msg-text{white-space:pre-line;}
  .chat-msg.from-visitor{align-self:flex-end;background:var(--accent);color:#04121f;border-bottom-right-radius:3px;}
  .chat-msg.from-bot,.chat-msg.from-system{align-self:flex-start;background:var(--bg-elev2);color:var(--text);border-bottom-left-radius:3px;}
  .chat-msg.from-staff{align-self:flex-start;background:#123a2c;color:var(--ok);border-bottom-left-radius:3px;}
  #chat-widget-request-human{padding:6px 12px;}
  /* 19 jul 2026 (pedido de Ed: mover "Finalizar conversación" del header a
     un botón con label, junto a "Hablar con un Admin/Editor") — mismo
     padding que #chat-widget-request-human de arriba, para que ambas filas
     se vean consistentes una debajo de la otra. #chat-widget-end ya hereda
     el estilo visual de .btn.small.secondary (ver clases genéricas más
     arriba en esta hoja); acá solo se ajusta el spacing entre el ícono y
     el label dentro del botón. */
  #chat-widget-end-wrap{padding:0 12px 6px;}
  #chat-widget-end-wrap #chat-widget-end{width:100%;display:flex;align-items:center;justify-content:center;gap:6px;}
  /* 16 jul 2026 (pedido de Ed: "puede seguir con el bot agent o request
     admin again") — reemplaza el footer normal mientras la conversación
     está terminada; display:flex se activa desde JS (toggleChatWidgetEndedUI()),
     el display:none de acá es solo el estado inicial. */
  #chat-widget-ended-options{display:none;padding:8px 12px;gap:8px;flex-wrap:wrap;border-top:1px solid var(--border);}
  #chat-widget-ended-options .btn{flex:1 1 auto;}
  #chat-widget-footer{display:flex;gap:8px;padding:10px 12px;border-top:1px solid var(--border);}
  #chat-widget-footer input{flex:1;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:9px 12px;border-radius:8px;font-size:13px;outline:none;}
  #chat-widget-footer input:focus{border-color:var(--accent);}
  /* 27 jul 2026 (nueva sesión, pedido de Ed: "remove accent (blue color)
     from chat-widget-send") — el botón heredaba el azul de acento de la
     clase genérica .btn (background:var(--accent)). Mismo tratamiento que
     #chat-widget-bubble ya recibió en la Ronda 86 (dejar de ser el único
     botón del sitio en usar el azul de acento) — mismo fondo oscuro
     unificado que .round-tab-btn/.topbar-drawer-btn/#chat-widget-bubble. */
  #chat-widget-send{background:var(--menu-btn-bg-custom, var(--bg-elev2));color:var(--text);}
  #chat-widget-send:hover{filter:brightness(1.08);}
  @media (max-width:600px){ #chat-widget-panel{right:8px;bottom:76px;width:calc(100vw - 16px);} #chat-widget-bubble{right:14px;bottom:14px;} }

  /* 16 jul 2026 (historial de chats, pedido de Ed — ver "Finalizar
     conversación" en app.js/endChatWidgetSession()) — reemplaza
     #chat-widget-messages mientras el visitante mira la lista de
     conversaciones anteriores o el transcript de una de ellas. Reusa
     .chat-msg/.chat-msg-text (mismo estilo que el hilo en vivo) para el
     transcript, así no hace falta duplicar estilos de burbujas. */
  #chat-widget-history-panel{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
  #chat-widget-history-toolbar{display:flex;align-items:center;gap:8px;padding:8px 12px;border-bottom:1px solid var(--border);font-size:12px;color:var(--text-dim);}
  #chat-widget-history-toolbar .btn{flex:0 0 auto;}
  #chat-widget-history-title{flex:1;}
  /* 20 jul 2026 (pedido de Ed: botón "Borrar todo") */
  .chat-history-clear-all-btn{flex:0 0 auto;background:transparent;border:1px dashed var(--border);color:var(--text-dim);font-size:11px;padding:4px 9px;border-radius:999px;cursor:pointer;white-space:nowrap;}
  .chat-history-clear-all-btn:hover{color:#e0567e;border-color:#e0567e;}
  #chat-widget-history-list{padding:10px 12px;display:flex;flex-direction:column;gap:6px;}
  .chat-history-empty{color:var(--text-dim);font-size:12px;padding:10px 2px;}
  .chat-history-row{background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:8px 10px;font-size:12px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;gap:8px;}
  .chat-history-row:hover{border-color:var(--accent);}
  /* 20 jul 2026 — la fila ahora tiene 2 partes: el texto (título+fecha,
     clicleable, abre el transcript) y el botón de borrado individual (su
     propio click con stopPropagation, ver renderChatWidgetHistoryList()). */
  .chat-history-row-main{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1;}
  .chat-history-preview{color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .chat-history-row .chat-history-date{color:var(--text-dim);white-space:nowrap;font-size:11px;}
  .chat-history-row-delete{flex:0 0 auto;background:transparent;border:none;color:var(--text-dim);font-size:13px;cursor:pointer;padding:2px 4px;border-radius:6px;line-height:1;}
  .chat-history-row-delete:hover{color:#e0567e;background:var(--bg-elev);}
  #chat-widget-history-transcript{flex:1;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}

  /* 16 jul 2026 (bot actions, pedido de Ed: "implementar ahora" el diseño
     de claude/chat_smart_answers_plan.md) — chips de acción (showLink/
     showRelatedNodes) que puede traer un mensaje del bot en el widget
     público, ver chatWidgetActionChipHtml() en app.js. Mismo patrón visual
     que .related-chip (chip discreto tipo "borde punteado"), coherente con
     el resto del sitio en vez de inventar un estilo nuevo. */
  .chat-msg-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;}
  .chat-action-chip{background:var(--bg-elev);border:1px dashed var(--border);border-radius:999px;padding:3px 10px;font-size:11px;cursor:pointer;color:var(--text-dim);text-decoration:none;display:inline-flex;align-items:center;gap:5px;font-family:inherit;}
  .chat-action-chip:hover{color:var(--accent);border-color:var(--accent);}
  /* 20 jul 2026 (pedido de Ed #3, botón "Más…") — borde sólido (no
     punteado) para distinguirlo de los chips de navegación (ver fuente/
     nodo relacionado/comparar) — este no lleva a ningún lado, solo revela
     más opciones en el mismo mensaje. .chat-msg-loading reemplaza los
     chips en el momento del clic, mientras se espera la respuesta del
     servidor (ver requestChatWidgetMoreOptions() en assets/js/app.js). */
  .chat-action-chip-more{border-style:solid;font-weight:600;}
  .chat-msg-loading{font-size:11px;color:var(--text-dim);font-style:italic;}
  /* 19 jul 2026 (pedido de Ed: miniaturas en los chips del chat) — mismo
     tamaño chico en los 3 lugares donde puede aparecer (fuente/relacionados,
     comparar, y opciones de desambiguación), ver chatChipThumbHtml() en
     app.js. object-fit:cover evita distorsión si la foto no es cuadrada. */
  .chat-chip-thumb{width:18px;height:18px;border-radius:50%;object-fit:cover;flex-shrink:0;}
  /* Etiquetas de sesión (acción tagSession) mostradas en Admin → Chats —
     puramente informativas, no son clicables. */
  .chat-session-tags{display:flex;flex-wrap:wrap;gap:5px;margin:4px 0;}
  .chat-session-tag{background:var(--bg-elev2);border:1px solid var(--border);border-radius:999px;padding:2px 9px;font-size:10.5px;color:var(--text-dim);}
  /* Sub-panel "⚙️ Acciones" de cada fila del editor de FAQ del chat. */
  .chat-faq-actions summary{list-style:none;}
  .chat-faq-actions summary::-webkit-details-marker{display:none;}

  /* ---------- Encuestas — modal de votar (15 jul 2026) ---------- */
  #poll-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:140;padding:16px;}
  #poll-modal-overlay.show{display:flex;}
  /* 27 jul 2026 (nueva sesión, Compartir unificado en Encuestas) —
     position:relative agregado: es el ancestro posicionado donde
     .post-share-menu (dentro de #poll-modal-share-wrap, ver
     app-shell.html) se centra — mismo criterio que .blog-card-body/
     #blog-detail/.video-page-card-body ya usan para lo mismo. */
  #poll-modal{width:min(420px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;position:relative;}
  #poll-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #poll-modal-title{flex:1;font-size:15px;font-weight:700;}
  #poll-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;}
  #poll-modal-body{padding:18px;overflow-y:auto;flex:1;}
  .poll-question-block{margin-bottom:16px;}
  .poll-question-block .poll-q-text{font-weight:600;margin-bottom:8px;display:block;}
  .poll-option-label{display:flex;align-items:center;gap:8px;padding:7px 0;font-size:13.5px;cursor:pointer;}
  .poll-side-item{padding:12px;border:1px solid var(--border);border-radius:10px;margin-bottom:10px;cursor:pointer;}
  .poll-side-item:hover{border-color:var(--accent);}
  .poll-side-item h4{margin:0 0 4px;font-size:14px;}
  .poll-opt-thumb{width:44px;height:44px;object-fit:cover;border-radius:6px;flex-shrink:0;}
  .poll-opt-thumb-sm{width:28px;height:28px;object-fit:cover;border-radius:5px;flex-shrink:0;}
  .poll-option-label .poll-opt-thumb{margin-right:2px;}

  /* ---------- Vista completa de Encuestas (15 jul 2026) ---------- */
  /* 17 jul 2026 — #view-polls nunca había tenido su propia regla de
     layout (a diferencia de #view-blog/#view-videos, que sí definían
     height:100%/overflow:auto) — dependía de ser un hijo directo de
     <body> sin overflow propio, lo cual, combinado con
     html,body{overflow:hidden} más arriba en este archivo, podía recortar
     contenido en vez de scrollear en una lista larga de encuestas. Mismo
     tratamiento que sus hermanas ahora que las tres viven dentro de
     #content-area (ver la nota en #content-area más arriba). */
  #view-polls{display:none;position:absolute;inset:0;overflow:auto;background:var(--bg);}
  /* 23 jul 2026 (Round 69, punto 4) — breadcrumb + filtros ahora comparten
     esta fila (ver #polls-topbar-filters más abajo, misma técnica que
     #blog-topbar/#videos-topbar). padding/font-size unificados;
     overflow-x:auto + scrollbar oculta porque vive dentro de
     .rcx-hscroll-wrap. */
  #polls-topbar{display:flex;align-items:center;gap:16px;padding:2px 20px;font-size:14px;border-bottom:1px solid var(--border);position:sticky;top:0;background:var(--bg-elev);z-index:5;overflow-x:auto;scrollbar-width:none;}
  #polls-topbar::-webkit-scrollbar{display:none;}
  #polls-search-wrap{position:relative;width:220px;max-width:45vw;flex-shrink:0;}
  #polls-page-search{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);backdrop-filter:blur(8px);border:1px solid var(--border);color:var(--text);padding:7px 30px 7px 12px;border-radius:999px;font-size:12px;outline:none;transition:.15s;}
  #polls-page-search:focus{border-color:var(--accent);}
  #polls-page-search-clear{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:20px;height:20px;border-radius:50%;background:none;border:none;color:var(--text-dim);font-size:11px;cursor:pointer;display:flex;align-items:center;justify-content:center;line-height:1;}
  #polls-page-search-clear:hover{color:var(--text);background:var(--bg-elev);}
  @media (max-width:520px){ #polls-search-wrap{width:120px;max-width:38vw;} }
  #polls-page-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:18px;padding:18px 20px 40px;}
  .poll-page-card{border:1px solid var(--border);border-radius:12px;padding:16px;cursor:pointer;background:var(--bg-elev);}
  .poll-page-card:hover{border-color:var(--accent);}
  .poll-page-card h3{margin:0 0 6px;font-size:15px;}
  @media (max-width:600px){ #poll-modal-overlay{padding:0;align-items:flex-end;} #poll-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* ---------- Medidor de fuerza de contraseña (14 jul 2026) ----------
     Reutilizado en: registro público, aceptar invitación, cambiar
     contraseña de admin. Ver passwordMeterHtml()/updatePasswordMeter() en
     app.js. */
  .pw-meter{margin:2px 0 10px;}
  .pw-meter-bar{height:5px;border-radius:4px;background:var(--bg-elev2);overflow:hidden;}
  .pw-meter-fill{height:100%;width:0;border-radius:4px;transition:width .18s ease,background .18s ease;background:#c0392b;}
  .pw-meter-label{font-size:11px;font-weight:700;margin-top:4px;}
  .pw-meter-checklist{list-style:none;margin:6px 0 0;padding:0;font-size:11px;color:var(--text-dim);display:flex;flex-direction:column;gap:2px;}
  .pw-meter-checklist li{padding-left:16px;position:relative;}
  .pw-meter-checklist li::before{content:"✕";position:absolute;left:0;color:var(--danger,#e05a5a);font-size:10px;top:1px;}
  .pw-meter-checklist li.ok{color:var(--text);}
  .pw-meter-checklist li.ok::before{content:"✓";color:var(--ok,#5fae5f);}

  #contact-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #contact-modal-overlay.show{display:flex;}
  #contact-modal{width:min(420px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #contact-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #contact-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #contact-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #contact-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #contact-modal-body{padding:18px;overflow-y:auto;flex:1;}
  #contact-modal-body label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin:10px 0 4px;font-weight:600;}
  #contact-modal-body input,#contact-modal-body textarea{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:9px 12px;border-radius:8px;font-size:13px;outline:none;font-family:inherit;resize:vertical;}
  #contact-modal-body input:focus,#contact-modal-body textarea:focus{border-color:var(--accent);}
  @media (max-width:600px){ #contact-modal-overlay{padding:0;align-items:flex-end;} #contact-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* 27 jul 2026 (nueva sesión, Item 4 — "Vista previa" de Artículo desde
     Admin) — mismo patrón visual que #contact-modal-*, pero MÁS ancho
     (min(820px,...) en vez de 420px) porque acá adentro va el cuerpo
     completo de un artículo, no un formulario chico. z-index más alto que
     #view-admin (5) y que el propio panel — se ve por encima de todo el
     panel de administración, nunca navega a otra pantalla. */
  #admin-preview-overlay{position:fixed;inset:0;background:rgba(5,8,12,.75);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:140;padding:16px;}
  #admin-preview-overlay.show{display:flex;}
  #admin-preview-modal{width:min(820px,100%);max-height:92vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #admin-preview-header{display:flex;align-items:center;gap:14px;padding:14px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);flex-shrink:0;}
  #admin-preview-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #admin-preview-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #admin-preview-close:hover{color:var(--text);background:var(--bg-elev);}
  #admin-preview-banner{margin:0;padding:10px 18px;font-size:12px;color:var(--org);background:#f2a65a15;border-bottom:1px solid var(--border);flex-shrink:0;}
  #admin-preview-body{padding:18px;overflow-y:auto;flex:1;}
  /* Dentro de la vista previa, ninguna acción interactiva real (me gusta,
     comentar, compartir) debe poder dispararse — el contenido puede ni
     siquiera estar guardado todavía. Se ocultan en vez de solo deshabilitar
     (más claro para el admin que "acá no se puede", ver también el aviso
     de #admin-preview-banner de arriba). */
  #admin-preview-body .post-social-bar, #admin-preview-body .comments-block, #admin-preview-body .comment-form{display:none !important;}
  @media (max-width:600px){ #admin-preview-overlay{padding:0;align-items:flex-end;} #admin-preview-modal{width:100%;max-height:92vh;border-radius:var(--radius) var(--radius) 0 0;} }

  /* 25 jul 2026 (Código QR) — mismo patrón visual que #contact-modal-*. */
  #qr-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #qr-modal-overlay.show{display:flex;}
  #qr-modal{width:min(340px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #qr-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #qr-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #qr-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #qr-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #qr-modal-body{padding:18px;overflow-y:auto;flex:1;text-align:center;}
  #qr-modal-image{background:#fff;border-radius:12px;padding:16px;display:inline-block;}
  #qr-modal-image svg{width:220px;height:220px;display:block;}
  /* 26 jul 2026 (bug reportado por Ed: el QR de la pestaña Info del nodo
     se veía en blanco, "una pastillita blanca sin nada adentro" — pasaba
     en TODOS los nodos) — root cause real: el <svg> que genera QR.generateSvg()
     solo trae un viewBox, sin width/height propios (por diseño, para poder
     escalarlo desde CSS según cada contexto — ver #qr-modal-image arriba).
     #qr-modal-image ya tenía su regla de tamaño, pero estos otros 4
     contenedores (Info del nodo, pie de artículo, y las 2 vistas previas
     de Admin) nunca la tuvieron — sin width/height ni CSS que se los dé,
     el navegador lo colapsaba a tamaño 0, dejando visible solo el fondo
     blanco + padding del contenedor (la "pastillita" que describía Ed). */
  /* 26 jul 2026 (Round 82) — la regla de #node-qr-inline/#post-qr-inline
     se retira: esos 2 contenedores ya no existen (QR sacado de la
     pestaña Info del nodo y de la vista de artículo, ver app.js). Las
     tarjetas QR de Admin (formularios de nodo/artículo) siguen igual. */
  #node-form-qr svg,#post-form-qr svg{width:120px;height:120px;display:block;}
  #qr-modal-url{word-break:break-all;font-size:11px;margin:10px 0;}
  #qr-modal-actions{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-top:6px;}

  #post-import-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #post-import-modal-overlay.show{display:flex;}
  #post-import-modal{width:min(480px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #post-import-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #post-import-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #post-import-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #post-import-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #post-import-modal-body{padding:18px;overflow-y:auto;flex:1;}
  .post-import-row{padding:8px 12px;border-bottom:1px solid var(--border);font-size:13px;}
  .post-import-row:last-child{border-bottom:none;}
  .post-import-row .skip{color:var(--text-dim);text-decoration:line-through;}
  @media (max-width:600px){ #post-import-modal-overlay{padding:0;align-items:flex-end;} #post-import-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  #invite-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #invite-modal-overlay.show{display:flex;}
  #invite-modal{width:min(380px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #invite-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #invite-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #invite-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #invite-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #invite-modal-body{padding:18px;overflow-y:auto;flex:1;}
  #invite-modal-body label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin:10px 0 4px;font-weight:600;}
  #invite-modal-body input{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:9px 12px;border-radius:8px;font-size:13px;outline:none;}
  #invite-modal-body input:focus{border-color:var(--accent);}
  @media (max-width:600px){ #invite-modal-overlay{padding:0;align-items:flex-end;} #invite-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  #change-password-modal-overlay{position:fixed;inset:0;background:rgba(5,8,12,.7);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:130;padding:16px;}
  #change-password-modal-overlay.show{display:flex;}
  #change-password-modal{width:min(380px,100%);max-height:88vh;background:var(--bg-elev);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #change-password-modal-header{display:flex;align-items:center;gap:14px;padding:16px 18px;border-bottom:1px solid var(--border);background:var(--bg-elev2);}
  #change-password-modal-title{flex:1;font-size:15px;font-weight:700;color:var(--text);}
  #change-password-modal-close{background:none;border:none;color:var(--text-dim);font-size:22px;cursor:pointer;padding:4px 8px;line-height:1;border-radius:8px;flex-shrink:0;}
  #change-password-modal-close:hover{color:var(--text);background:var(--bg-elev);}
  #change-password-modal-body{padding:18px;overflow-y:auto;flex:1;}
  #change-password-modal-body label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin:10px 0 4px;font-weight:600;}
  #change-password-modal-body input{width:100%;box-sizing:border-box;background:rgba(18,24,33,.9);border:1px solid var(--border);color:var(--text);padding:9px 12px;border-radius:8px;font-size:13px;outline:none;}
  #change-password-modal-body input:focus{border-color:var(--accent);}
  @media (max-width:600px){ #change-password-modal-overlay{padding:0;align-items:flex-end;} #change-password-modal{width:100%;max-height:88vh;border-radius:var(--radius) var(--radius) 0 0;} }

  #tabs{display:flex;border-bottom:1px solid var(--border);background:var(--bg-elev2);padding:0 12px;flex-shrink:0;overflow-x:auto;gap:2px;}
  .tab{padding:12px 10px;font-size:13px;color:var(--text-dim);cursor:pointer;border-bottom:2px solid transparent;font-weight:600;white-space:nowrap;}
  .tab.active{color:var(--text);border-bottom-color:var(--accent);}

  #modal-body{padding:20px;overflow-y:auto;}
  .pane{display:none;}
  .pane.active{display:block;}

  .field{margin-bottom:14px;}
  .field label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-dim);margin-bottom:4px;font-weight:600;}
  .field .val{font-size:14px;line-height:1.5;white-space:pre-wrap;}
  .tags{display:flex;flex-wrap:wrap;gap:6px;}
  .tag{background:var(--bg-elev2);border:1px solid var(--border);padding:3px 10px;border-radius:999px;font-size:12px;}
  .tag .tag-x{background:none;border:none;color:var(--text-dim);cursor:pointer;margin-left:4px;font-size:11px;padding:0;line-height:1;}
  .tag .tag-x:hover{color:var(--danger);}

  .connlist{display:flex;flex-direction:column;gap:8px;}
  .connitem{display:flex;align-items:center;gap:10px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;padding:8px 10px;cursor:pointer;font-size:13px;transition:.15s;}
  .connitem:hover{border-color:var(--accent);}
  .connitem img{width:28px;height:28px;border-radius:50%;object-fit:cover;flex-shrink:0;}
  .connitem .rel{margin-left:auto;color:var(--text-dim);font-size:11px;}

  .empty{color:var(--text-dim);font-size:13px;font-style:italic;padding:12px 0;}

  /* ---- media items / social features (public) ---- */
  .item-card{background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;padding:12px;margin-bottom:12px;}
  .item-card .desc{font-size:13px;color:var(--text-dim);margin:6px 0 10px;line-height:1.5;}
  .social-row{display:flex;gap:8px;align-items:center;margin:10px 0 4px;flex-wrap:wrap;}
  .like-btn{background:var(--bg-elev);border:1px solid var(--border);border-radius:999px;padding:6px 12px;font-size:12px;color:var(--text);cursor:pointer;}
  /* 16 jul 2026 — era border-color:#ff6b6b;color:#ff6b6b (rojo hardcodeado,
     inconsistente con el resto del sitio). Ed lo reportó como bug ("el
     borde se pone rojo, debe seguir el patrón de los otros botones") —
     alineado con .iconbtn:hover/.labelbtn:hover/.share-btn:hover, que ya
     usan var(--accent). Afecta consistentemente todos los usos de
     .like-btn: el 🚩 del nodo (#modal-flag-btn), el 👍 de ítem, los botones
     de login OAuth y los de ver/descargar documento — no solo el reportado. */
  .like-btn:hover{border-color:var(--accent);color:var(--accent);}
  .share-btn{background:var(--bg-elev);border:1px solid var(--border);border-radius:999px;width:30px;height:30px;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:13px;}
  .share-btn:hover{border-color:var(--accent);color:var(--accent);}
  .related-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
  .related-chip{background:var(--bg-elev);border:1px dashed var(--border);border-radius:999px;padding:3px 10px;font-size:11px;cursor:pointer;color:var(--text-dim);}
  .related-chip:hover{color:var(--accent);border-color:var(--accent);}
  .comments-block{margin-top:12px;border-top:1px solid var(--border);padding-top:10px;}
  .comment-item{background:var(--bg-elev);border-radius:8px;padding:8px 10px;margin-bottom:6px;font-size:12px;}
  .comment-item .meta{color:var(--text-dim);font-size:10px;margin-bottom:3px;}
  .comment-form input,.comment-form textarea{width:100%;box-sizing:border-box;padding:8px;margin-bottom:6px;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12px;font-family:inherit;}
  .comment-form textarea{min-height:50px;resize:vertical;}
  .comment-form button{padding:7px 14px;font-size:12px;border-radius:8px;border:none;background:var(--accent);color:#fff;cursor:pointer;}

  .doc-item{display:flex;align-items:center;gap:10px;background:var(--bg-elev);border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:13px;}
  .doc-item .ic{font-size:18px;}
  .doc-item .meta{color:var(--text-dim);font-size:11px;}
  .pdf-viewer-frame{width:100%;height:420px;border:1px solid var(--border);border-radius:8px;margin-top:8px;background:#fff;}

  .carousel{position:relative;border-radius:12px;overflow:hidden;background:#0d1218;}
  /* 14 jul 2026: el alto ya no es fijo (ver renderImagesPaneContent en
     app.js, ahora aplica min-height en vez de height) — así que el track y
     la imagen ya no fuerzan un recorte via height:100%/object-fit:cover.
     La caja crece según la foto más alta del set (el track, como flex
     container, adopta el alto del slide más alto por defecto) y cada
     imagen se centra sin distorsión dentro de esa caja vía align-items. */
  .carousel-track{display:flex;transition:transform .3s ease;}
  .carousel-slide{flex:0 0 100%;display:flex;align-items:center;justify-content:center;}
  .carousel-slide img{width:100%;height:auto;display:block;}
  .carousel-nav{position:absolute;top:50%;transform:translateY(-50%);background:rgba(0,0,0,.5);color:#fff;border:none;width:34px;height:34px;border-radius:50%;cursor:pointer;font-size:18px;}
  .carousel-nav.prev{left:10px;} .carousel-nav.next{right:10px;}
  .carousel-dots{display:flex;gap:6px;justify-content:center;margin-top:8px;}
  .carousel-dot{width:7px;height:7px;border-radius:50%;background:var(--border);cursor:pointer;}
  .carousel-dot.active{background:var(--accent);}
  .thumb-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:8px;margin:10px 0;}
  .thumb-grid img{width:100%;height:64px;object-fit:cover;border-radius:8px;cursor:pointer;border:2px solid transparent;display:block;}
  .thumb-grid img.active{border-color:var(--accent);}
  .html-tab-content{font-size:14px;line-height:1.6;}
  .html-tab-content img{max-width:100%;border-radius:8px;}
  /* 24 jul 2026 (Round 75, pedido de Ed) — descripción a nivel de pestaña
     (Imágenes/Videos/Documentos), ver tabDescriptionHtml() en app.js.
     Reusa .html-tab-content para la tipografía (mismo tratamiento que la
     pestaña HTML personalizado/Clasificación) y suma un margen inferior
     propio para separarla visualmente de la grilla/carrusel/lista que
     viene debajo. */
  .tab-description{margin-bottom:14px;}

  @media (max-width:600px){
    #topbar h1 span.label{display:none;}
    #overlay{padding:0;align-items:flex-end;}
    #modal{width:100%;max-height:92vh;border-radius:16px 16px 0 0;}
    #legend{left:8px;bottom:14px;right:64px;font-size:10px;padding:8px 10px;gap:8px;}
    #node-scope-panel{left:8px;bottom:56px;font-size:11px;padding:8px 10px;}
  }

  /* ===================== Panel de administración (demo) ===================== */
  #view-admin{position:fixed;inset:0;overflow-y:auto;overflow-x:hidden;background:var(--bg);z-index:5;max-width:100vw;}
  /* ---------- Panel de administración: barra de herramientas de 3 filas
     (14 jul 2026, pedido de Ed — reemplaza la barra plana que se envolvía
     sin orden). Fila 1 = sesión (alineada a la derecha), fila 2 = los 5
     tabs principales, fila 3 = sub-nav del grupo abierto. Filas 2 y 3 son
     scrolleables horizontalmente con flechas si el contenido no entra —
     ver .admin-scroll-row-wrap/.admin-scroll-arrow más abajo y
     setupAdminScrollRow() en app.js. Todo sticky para que el panel nunca
     pierda la navegación al scrollear una página larga. */
  #admin-toolbar{position:sticky;top:0;z-index:10;background:var(--bg-elev);border-bottom:1px solid var(--border);}

  #admin-session-row{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:4px;padding:8px 20px;border-bottom:1px solid var(--border);}
  #admin-session-row a{color:var(--text-dim);text-decoration:none;font-size:12px;padding:6px 10px;border-radius:8px;font-weight:600;cursor:pointer;white-space:nowrap;}
  #admin-session-row a:hover{background:var(--bg-elev2);color:var(--text);}
  #admin-session-row .role-badge{font-size:11px;color:var(--org);background:#f2a65a22;padding:5px 10px;border-radius:999px;white-space:nowrap;margin-right:4px;}
  #admin-session-row .role-badge.role-badge-admin{color:var(--text-dim);background:var(--bg-elev2);}
  /* 27 jul 2026 (nueva sesión, Admin → Herramientas — Revisión de Videos/
     Revisión de Imágenes) — mismo estilo de "pill" que .role-badge de
     arriba, 4 variantes de color reusando las variables ya definidas en
     :root en vez de inventar colores nuevos: warn (naranja, --org, mismo
     tono que el badge de rol Editor), danger (rojo, --danger, para
     "confirmado roto"/"huérfano"), info (azul, --accent, para "sin
     verificar" — no es un error confirmado, solo no se pudo chequear) y ok
     (verde, --ok, para "todo bien" cuando hace falta mostrarlo explícito).
  */
  .rcx-issue-badge{display:inline-block;font-size:11px;padding:3px 9px;border-radius:999px;white-space:nowrap;margin:2px 4px 2px 0;font-weight:600;}
  .rcx-issue-badge.warn{color:var(--org);background:#f2a65a22;}
  .rcx-issue-badge.danger{color:var(--danger);background:#ff6b6b22;}
  .rcx-issue-badge.info{color:var(--accent);background:#5b8cff22;}
  .rcx-issue-badge.ok{color:var(--ok);background:#7ee0c022;}
  .rcx-tools-scan-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:12px;}
  .rcx-tools-result-row{border:1px solid var(--border);border-radius:10px;padding:10px 12px;margin-bottom:8px;background:var(--bg-elev2);}
  .rcx-tools-result-row .rcx-tools-result-head{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;align-items:center;}
  .rcx-tools-result-row .rcx-tools-result-url{font-size:12px;color:var(--text-dim);word-break:break-all;margin:4px 0;}
  .rcx-tools-result-row .rcx-tools-result-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px;}
  .rcx-tools-thumb-preview{width:64px;height:40px;object-fit:cover;border-radius:6px;border:1px solid var(--border);flex-shrink:0;background:var(--bg);}
  /* 16 jul 2026 (pedido de Ed, alertas de chat) — pulso visual sobre el
     enlace "💬 N" del toolbar de admin cuando llega una solicitud de chat
     pendiente NUEVA (ver pollChatPendingBadge() en app.js). Se agrega/quita
     la clase por JS; el pulso se detiene solo al entrar a la pantalla de
     Chats (renderChatAdmin limpia la clase), así que no queda animando para
     siempre si el admin no la nota enseguida. */
  #chat-pending-badge-link.chat-badge-alert{
    background:#e05a5a;color:#fff;animation:chatBadgeAlertPulse 1.1s ease-in-out infinite;
  }
  @keyframes chatBadgeAlertPulse{
    0%,100%{ box-shadow:0 0 0 0 rgba(224,90,90,.6); }
    50%{ box-shadow:0 0 0 5px rgba(224,90,90,0); }
  }

  /* 16 jul 2026 (pedido de Ed, con captura de pantalla) — el panel de FAQ
     del chat (Configuración -> Chat en vivo) hacía crecer demasiado la
     página cuando había muchas preguntas. Alto fijo + scroll propio, mismo
     criterio ya usado en otras listas largas del panel (#chat-widget-messages,
     .table-scroll, etc.) — el buscador de arriba (#chat-faq-search) filtra
     las filas ya renderizadas, ver wireChatFaqSearch() en app.js. */
  #chat-faq-rows{max-height:420px;overflow-y:auto;padding-right:4px;margin-top:8px;}
  #chat-faq-search-count{color:var(--text-dim);}

  /* 16 jul 2026 (pedido de Ed, con captura de pantalla) — la pantalla de
     Chats (Moderación -> Chats) apilaba "Solicitudes pendientes"/"Chats
     activos"/"Historial" una debajo de otra, lo que también hacía crecer
     mucho la página. Ahora son pestañas dentro de la misma pantalla — clases
     nuevas (.chat-admin-tab/.chat-admin-pane) en vez de reusar #tabs/.tab/
     .pane (esas son del modal de detalle de nodo: .tab es una clase global
     que switchPane() consulta con querySelectorAll(".tab") en todo el
     documento, así que reusarla acá haría que la navegación por teclado del
     modal también "viera" estas pestañas si ambas vistas llegan a coexistir
     en el DOM — mejor un espacio de nombres separado aunque el estilo visual
     sea el mismo). */
  #chat-admin-tabs{display:flex;border-bottom:1px solid var(--border);margin-top:18px;gap:2px;overflow-x:auto;scrollbar-width:none;}
  #chat-admin-tabs::-webkit-scrollbar{display:none;}
  .chat-admin-tab{padding:10px 14px;font-size:13px;color:var(--text-dim);cursor:pointer;border:none;border-bottom:2px solid transparent;background:none;font-weight:600;white-space:nowrap;font-family:inherit;}
  .chat-admin-tab:hover{color:var(--text);}
  .chat-admin-tab.active{color:var(--text);border-bottom-color:var(--accent);}
  .chat-admin-tab .chat-admin-tab-count{margin-left:6px;font-size:11px;background:var(--bg-elev2);padding:1px 7px;border-radius:999px;color:var(--text-dim);}
  .chat-admin-tab.active .chat-admin-tab-count{background:var(--accent);color:#fff;}
  .chat-admin-pane{display:none;padding-top:14px;}
  .chat-admin-pane.active{display:block;}

  /* 20 jul 2026 (pedido de Ed: ordenar/filtrar/agrupar por visitante +
     borrar/papelera en Historial y "Sin buena respuesta") */
  .chat-history-toolbar-admin{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:12px;}
  .chat-history-toolbar-admin input[type="search"]{flex:1;min-width:180px;}
  .chat-history-admin-row{background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:8px;font-size:13px;}
  .chat-history-admin-row summary{display:flex;flex-wrap:wrap;align-items:center;gap:6px;}
  .chat-history-visitor-id{background:var(--bg-elev);border-radius:999px;padding:1px 8px;font-size:11px;}
  .chat-history-row-actions{margin-left:auto;display:flex;gap:6px;flex-shrink:0;}
  .chat-history-visitor-group{background:var(--bg-elev);border:1px solid var(--border);border-radius:10px;padding:8px 10px;margin-bottom:10px;}
  .chat-history-visitor-group > summary{color:var(--text);}
  .chat-history-visitor-group .chat-history-admin-row{background:var(--bg-elev2);}

  .admin-scroll-row-wrap{display:flex;align-items:stretch;max-width:100%;}
  .admin-scroll-arrow{flex:0 0 auto;width:30px;border:none;background:var(--bg-elev);color:var(--text-dim);cursor:pointer;font-size:18px;line-height:1;border-bottom:1px solid var(--border);}
  .admin-scroll-arrow:hover{color:var(--text);background:var(--bg-elev2);}

  #admin-nav{display:flex;align-items:center;gap:2px;padding:10px 16px 0;flex:1;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;max-width:100%;box-sizing:border-box;scroll-behavior:smooth;}
  #admin-nav::-webkit-scrollbar{display:none;}
  /* 27 jul 2026 (nueva sesión, pedido de Ed: "tabs reales, no botones") —
     mismo mecanismo que .rcx-zone-tab (ver más abajo): esquinas superiores
     solo, la pestaña activa sube 1px y su borde inferior se funde con su
     propio fondo en vez de mostrar el acento sólido de antes (que se veía
     igual que cualquier botón .btn del sitio). #admin-groups-wrap gana el
     border-bottom que hace de "línea base" que la pestaña activa atraviesa. */
  #admin-nav .admin-group-tab{color:var(--text-dim);text-decoration:none;font-size:13px;padding:9px 14px 8px;border-radius:8px 8px 0 0;font-weight:700;cursor:pointer;white-space:nowrap;flex:0 0 auto;border:1px solid transparent;border-bottom:none;position:relative;top:1px;}
  #admin-nav .admin-group-tab:hover{background:var(--bg-elev2);color:var(--text);}
  #admin-nav .admin-group-tab.active{color:var(--accent);background:var(--bg-elev);border-color:var(--border);border-bottom:1px solid var(--bg-elev);}
  #admin-groups-wrap{border-bottom:1px solid var(--border);}
  /* 23 jul 2026 (pedido de Ed, feature nueva) — botón "Guardar" de
     Configuración, ahora estático adentro de #admin-nav (ver comentario
     en app-shell.html) en vez de vivir solo al final del formulario.
     `position:sticky; right:0` adentro de este mismo contenedor
     (#admin-nav ya es el scroll-port horizontal, `overflow-x:auto` arriba)
     lo mantiene pegado al borde derecho SIN importar cuánto se scrollee
     la fila de pestañas — mismo truco que una "columna fija" en una tabla
     con scroll horizontal. `margin-left:auto` lo empuja a la derecha
     cuando las pestañas no llenan el ancho (caso normal, sin overflow).
     Oculto por defecto (`display:none` inline) — solo visible en la ruta
     "settings", ver adminGo() en app.js. */
  #admin-nav #btn-save-settings{
    position:sticky; right:0; margin-left:auto; flex:0 0 auto;
    font-size:12.5px; padding:8px 16px; z-index:1;
  }
  /* 27 jul 2026 (nueva sesión, pedido de Ed: extender el botón "Guardar"
     persistente de arriba a Correo/SEO/Tour guiado/Datos genéricos) — el
     botón ORIGINAL de cada una de esas 4 pantallas se oculta (el
     persistente ahora reenvía su clic ahí, ver ADMIN_PERSISTENT_SAVE_MAP
     en adminGo(), app.js) para no dejar 2 botones "Guardar" redundantes
     en la misma pantalla — mismo criterio final que ya tenía Configuración
     → General desde el Round 73. */
  .rcx-persistent-save-target{ display:none; }

  #admin-subnav-wrap{border-top:1px solid var(--border);}
  #admin-subnav{display:flex;align-items:center;gap:2px;padding:8px 16px 0;flex:1;overflow-x:auto;scrollbar-width:none;max-width:100%;box-sizing:border-box;scroll-behavior:smooth;}
  #admin-subnav::-webkit-scrollbar{display:none;}
  #admin-subnav-wrap .admin-subnav-group{display:flex;gap:2px;flex-wrap:nowrap;}
  /* 27 jul 2026 (nueva sesión, pedido de Ed: "tabs reales, no botones") —
     mismo mecanismo de flat-top + "atraviesa la línea base" que el nav
     principal de arriba y .rcx-zone-tab más abajo — nivel 2 sigue
     distinguiéndose del nivel 1 (Round 69) por tamaño de fuente/padding
     más chicos, no por un tratamiento de color distinto. */
  #admin-subnav a{color:var(--text-dim);text-decoration:none;font-size:12.5px;padding:8px 12px 7px;border-radius:8px 8px 0 0;font-weight:600;cursor:pointer;white-space:nowrap;flex:0 0 auto;border:1px solid transparent;border-bottom:none;position:relative;top:1px;}
  #admin-subnav a:hover{background:var(--bg-elev2);color:var(--text);}
  #admin-subnav a.active{color:var(--accent);background:var(--bg-elev);border-color:var(--border);border-bottom:1px solid var(--bg-elev);}
  #admin-subnav-wrap{border-bottom:1px solid var(--border);}

  @media (max-width:640px){
    #admin-session-row{justify-content:flex-start;padding:8px 12px;}
    #admin-nav{padding:8px 10px;}
    #admin-subnav{padding:6px 10px;}
  }
  /* 15 jul 2026 (Ed's punch list #7) — was capped at max-width:960px with
     margin:0 auto (centered, unused space either side on wide screens).
     Now grows to fill the viewport with a 2% side margin, per Ed's spec. */
  /* 27 jul 2026 (nueva sesión, pedido de Ed: overlay de "guardando" para
     cualquier acción de guardado en Admin) — creado dinámicamente por JS
     (rcxShowAdminSavingOverlay(), ver junto a RCX.call() en app.js) la
     primera vez que hace falta, no en el HTML estático del shell. z-index
     muy alto a propósito: por encima de cualquier modal/panel de Admin
     (el más alto usado en el resto del sitio hoy no supera 4 dígitos). */
  #rcx-admin-saving-overlay{
    display:none; position:fixed; inset:0; z-index:99999;
    background:rgba(0,0,0,.55);
    align-items:center; justify-content:center;
  }
  .rcx-admin-saving-box{
    display:flex; align-items:center; gap:12px;
    background:var(--bg-elev); border:1px solid var(--border); border-radius:12px;
    padding:18px 26px; color:var(--text); font-size:14px; font-weight:600;
    box-shadow:0 12px 40px rgba(0,0,0,.5);
  }
  .rcx-admin-saving-spinner{
    width:20px; height:20px; border-radius:50%;
    border:3px solid var(--border); border-top-color:var(--accent);
    animation:rcx-admin-saving-spin .7s linear infinite;
    flex:0 0 auto;
  }
  @keyframes rcx-admin-saving-spin{ to{ transform:rotate(360deg); } }

  #admin-main{padding:24px;max-width:none;margin:0 2% 0 2%;color:var(--text);box-sizing:border-box;}
  @media (max-width:640px){ #admin-main{padding:16px 12px;} }
  #admin-main h1{font-size:20px;margin:0 0 6px;}
  #admin-main .sub{color:var(--text-dim);font-size:13px;margin:0 0 24px;}
  #admin-main .card{background:var(--bg-elev);border:1px solid var(--border);border-radius:12px;padding:20px;margin-bottom:18px;max-width:100%;box-sizing:border-box;overflow-x:auto;}
  @media (max-width:640px){ #admin-main .card{padding:14px;} }
  /* 20 jul 2026 (pedido de Ed: "aplicar el mismo estilo a todos los card en
     admin card > h3 con el titulo o proposito de la tarjeta") — antes NO
     existia ninguna regla para el <h3> de una tarjeta de admin: la enorme
     mayoria (de ~94 usos de class="card" en assets/js/app.js) quedaba con
     el estilo por default del navegador (chico, sin peso, poco contraste
     contra el tema oscuro — ver la captura de "Respaldo completo del
     sitio"), y un punado de casos sueltos (ej. faviconPackageTitle,
     backupTitle) tampoco tenian estilo propio. Esta regla les da a TODOS
     una jerarquia visual consistente sin tocar cada instancia una por una;
     un <h3> con estilo inline puntual (fuera de #admin-main, ej. las
     tarjetas publicas de blog/video/encuestas, que ya tienen su propia
     regla) no se ve afectado por selector/especificidad. */
  #admin-main .card>h3{margin:0 0 12px;font-size:16px;font-weight:700;color:var(--text);line-height:1.3;}
  .table-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;}
  .table-scroll table{min-width:560px;}
  #admin-main label{display:block;font-size:11px;color:var(--text-dim);margin:0 0 6px;font-weight:600;text-transform:uppercase;letter-spacing:.03em;}
  #admin-main input[type=text],#admin-main input[type=password],#admin-main input[type=url],#admin-main input[type=email],#admin-main textarea,#admin-main select{
    width:100%;padding:9px 11px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:14px;margin-bottom:14px;font-family:inherit;box-sizing:border-box;
  }
  #admin-main textarea{min-height:70px;resize:vertical;}
  #admin-main input:focus,#admin-main textarea:focus,#admin-main select:focus{outline:none;border-color:var(--accent);}
  #admin-main .btn{display:inline-block;padding:10px 16px;border-radius:8px;border:none;font-weight:600;font-size:13px;cursor:pointer;background:var(--accent);color:#fff;text-decoration:none;}
  #admin-main .btn:hover{background:#4577f0;}
  #admin-main .btn.secondary{background:var(--bg-elev2);border:1px solid var(--border);color:var(--text);}
  #admin-main .btn.danger{background:#3a1c1c;color:var(--danger);border:1px solid #6b2b2b;}
  #admin-main .btn.small{padding:6px 10px;font-size:12px;}

  /* 20 jul 2026 (pedido de Ed #6b) — panel lateral que aparece al cancelar
     la alerta de "cambios sin guardar" (ver confirmDiscardIfDirty()/
     adminShowUnsavedFieldsPanel() en assets/js/app.js), con el desglose
     campo-por-campo de qué cambió, agrupado por tarjeta/sección. */
  #admin-unsaved-panel{position:fixed;top:70px;right:16px;bottom:16px;width:min(320px,calc(100vw - 32px));background:var(--bg-elev);border:1px solid var(--danger);border-radius:12px;box-shadow:var(--shadow, 0 8px 24px rgba(0,0,0,.35));z-index:150;display:flex;flex-direction:column;overflow:hidden;animation:pop .18s ease-out;}
  #admin-unsaved-panel-head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 14px;border-bottom:1px solid var(--border);background:var(--bg-elev2);color:var(--danger);font-weight:700;font-size:13px;flex-shrink:0;}
  #admin-unsaved-panel-close{background:none;border:none;color:var(--text-dim);font-size:20px;cursor:pointer;padding:2px 6px;line-height:1;border-radius:6px;flex-shrink:0;}
  #admin-unsaved-panel-close:hover{color:var(--text);background:var(--bg-elev);}
  #admin-unsaved-panel-body{padding:12px 14px;overflow-y:auto;flex:1;}
  .admin-unsaved-group{margin-bottom:14px;}
  .admin-unsaved-group h4{margin:0 0 6px;font-size:11px;text-transform:uppercase;letter-spacing:.03em;color:var(--text-dim);font-weight:700;}
  .admin-unsaved-group ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px;}
  .admin-unsaved-group li{color:var(--danger);font-size:13px;padding:6px 8px;border:1px solid var(--danger);border-radius:8px;cursor:pointer;background:rgba(255,107,107,.08);}
  .admin-unsaved-group li:hover{background:rgba(255,107,107,.18);}
  @keyframes adminUnsavedFlash{ 0%,100%{outline-color:transparent;} 25%,75%{outline-color:var(--danger);} }
  .admin-unsaved-flash{outline:2px solid transparent;outline-offset:2px;animation:adminUnsavedFlash 1.6s ease-in-out;}
  @media (max-width:640px){ #admin-unsaved-panel{top:auto;bottom:0;right:0;left:0;width:100%;max-height:60vh;border-radius:12px 12px 0 0;} }

  #admin-main table{width:100%;border-collapse:collapse;font-size:13px;}
  #admin-main th,#admin-main td{text-align:left;padding:10px 8px;border-bottom:1px solid var(--border);}
  #admin-main th{color:var(--text-dim);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.03em;}
  #admin-main .badge{display:inline-block;padding:2px 9px;border-radius:999px;font-size:11px;font-weight:600;}
  #admin-main .row-actions{display:flex;gap:6px;flex-wrap:wrap;}
  /* 17 jul 2026 (Ed, punto 5) — control ▲▼ + número de prioridad reusado en
     las filas de Blog/Artículos, Videos y Encuestas, ver
     pinOrderRowControlHtml()/wirePinOrderControls() en assets/js/app.js. */
  #admin-main .rcx-pin-ctl{display:flex;align-items:center;gap:4px;}
  /* 23 jul 2026 (pedido de Ed, Round 67, "En admin Elementos del menu esta
     todo desordenado") — tarjeta "Elementos del menú" reconstruida como
     tabla compacta (antes: un bloque vertical por ítem, ver
     drawerTabsInfo/render en app.js) — mismas filas/columnas que el
     mockup aprobado por Ed (img1: Vis./Ícono/Nombre/Tooltip/Ubicación/
     Orden/Mostrar como). */
  .menu-items-table{ font-size:12px; }
  .menu-items-table th,.menu-items-table td{ padding:6px 6px; white-space:nowrap; }
  .menu-items-table thead tr:first-child th{ border-bottom:none; }
  .menu-items-table input[type="text"],
  .menu-items-table input[type="number"]{ padding:4px 5px; font-size:12px; }
  .menu-items-table select{ padding:4px 3px; font-size:12px; }
  .menu-items-table tr.row-new{ background:rgba(110,200,255,.07); }
  .menu-items-table tr.row-new td:first-child{ box-shadow:inset 3px 0 0 var(--accent); }
  #admin-main .rcx-pin-ctl .rcx-pin-input{width:48px;padding:4px 2px;text-align:center;font-size:12px;}
  #admin-main .rcx-pin-ctl .rcx-pin-btn{padding:3px 7px;font-size:11px;line-height:1;}
  #admin-main .flash-ok{background:#123a2c;border:1px solid #1f6b4e;color:var(--ok);padding:10px 14px;border-radius:8px;margin-bottom:16px;font-size:13px;}
  /* 23 jul 2026 (pedido de Ed, feature nueva) — reemplaza el .flash-ok
     "guardado" de Configuración (arriba), que vivía adentro del HTML del
     formulario y por eso: (a) quedaba fuera de la vista si Ed guardaba
     scrolleado hacia abajo (de ahí el scrollAdminToTop() que se retira en
     esta misma ronda, ver renderSettings() en app.js), y (b) se quedaba
     pegado hasta el próximo render, sin cerrarse solo. Mismos colores
     que .flash-ok (mismo mensaje "Configuración guardada."), pero
     `position:fixed` + `top` calculado en JS (mostrarAdminSettingsSavedToast()
     en app.js, justo debajo de #admin-toolbar, que es sticky) para que
     se vea SIEMPRE sin importar el scroll, y se autodestruye sola (ver
     el setTimeout ahí mismo) sin que Ed tenga que hacer nada. */
  .admin-save-toast{
    position:fixed; left:50%; transform:translateX(-50%) translateY(-6px);
    z-index:200; background:#123a2c; border:1px solid #1f6b4e; color:var(--ok);
    padding:10px 18px; border-radius:0 0 10px 10px; font-size:13px; font-weight:600;
    box-shadow:0 8px 20px rgba(0,0,0,.35); opacity:0; pointer-events:none;
    transition:opacity .22s ease, transform .22s ease;
  }
  .admin-save-toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
  /* 15 jul 2026 (Ed's punch list #2/#3 root-cause fix) — persistent "save
     failed" banner, distinct from the transient RCX.call toast and from
     .flash-ok above. Generic on purpose so any admin form can reuse it via
     setSettingsSaveError()-style helpers instead of a bespoke error UI. */
  #admin-main .flash-error{background:#3a1414;border:1px solid #6b2b2b;color:#ff8a8a;padding:10px 14px;border-radius:8px;margin-bottom:16px;font-size:13px;font-weight:600;}
  /* Links rendered inside admin content (e.g. targetCellHtml() in Analytics/
     Clics) had no admin-specific color rule and fell back to the browser's
     default blue — clashing with the dark admin theme. Ed's punch list #4. */
  #admin-main a{color:var(--accent);}
  #admin-main a:hover{color:var(--accent2);}
  /* 15 jul 2026 (Ed's punch list #4/#9) — shared pagination bar reused by
     every paginated admin list (Nodos, Analytics' "más vistos", etc.). */
  .admin-pagination{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px;}
  .admin-pagination select{width:auto;display:inline-block;padding:4px 8px;}
  .node-thumb{width:32px;height:32px;border-radius:6px;object-fit:cover;flex-shrink:0;vertical-align:middle;margin-right:8px;background:var(--bg-elev2);}
  /* Reutiliza la misma paleta ámbar que .img-editor-quality.warn — advertencias
     no bloqueantes dentro de Configuración (p.ej. cargador de video: autoplay
     sin silenciar, o video en base64 pesado). */
  #admin-main .settings-warn{background:#3a2c14;border:1px solid #6b4e1f;color:#e0a95a;padding:10px 14px;border-radius:8px;margin:10px 0;font-size:12.5px;line-height:1.5;}
  #admin-main .hint{color:var(--text-dim);font-size:12px;margin-top:-8px;margin-bottom:14px;}
  #admin-main label{display:flex;align-items:center;}
  .info-ico{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;min-width:14px;
    border-radius:50%;background:var(--bg-elev2);border:1px solid var(--border);color:var(--text-dim);
    font-size:10px;font-weight:700;font-style:normal;text-transform:none;letter-spacing:0;
    cursor:help;margin-left:6px;line-height:1;user-select:none;}
  .info-ico:hover,.info-ico:focus{color:#fff;background:var(--accent);border-color:var(--accent);outline:none;}
  /* 24 jul 2026 (Round 75, pedido de Ed: "cada card de nivel superior en
     admin pasa a ser colapsable, con título en negrita blanco + ícono de
     info que abre un popover al hacer click") — ver initCollapsibleCards()
     en app.js para el mecanismo que arma este markup en tiempo de
     ejecución sobre las ~30 pantallas de admin. Deliberadamente separado
     de .info-ico (arriba): ese sigue siendo hover-tooltip nativo para
     campos sueltos dentro de un formulario; esto es un popover a click,
     nuevo, solo para el título de la tarjeta. */
  #admin-main .card.collapsible-card{padding:0;}
  #admin-main .collapsible-card-header{display:flex;align-items:center;gap:10px;padding:16px 20px;cursor:pointer;user-select:none;position:relative;}
  @media (max-width:640px){ #admin-main .collapsible-card-header{padding:12px 14px;} }
  #admin-main .collapsible-card-header label{margin:0;}
  #admin-main .collapsible-card-title{flex:1;font-size:15px;font-weight:700;color:#fff;line-height:1.3;}
  #admin-main .collapsible-info-btn{display:inline-flex;align-items:center;justify-content:center;width:19px;height:19px;min-width:19px;
    border-radius:50%;background:var(--bg-elev2);border:1px solid var(--border);color:var(--text-dim);
    font-size:11px;font-weight:700;cursor:pointer;flex-shrink:0;padding:0;line-height:1;}
  #admin-main .collapsible-info-btn:hover,#admin-main .collapsible-info-btn.active{color:#fff;background:var(--accent);border-color:var(--accent);}
  #admin-main .collapsible-chevron{color:var(--text-dim);font-size:13px;flex-shrink:0;transition:transform .15s ease;line-height:1;}
  #admin-main .card.collapsible-card.is-open .collapsible-chevron{transform:rotate(180deg);}
  #admin-main .collapsible-card-body{padding:0 20px 20px;}
  @media (max-width:640px){ #admin-main .collapsible-card-body{padding:0 14px 14px;} }
  #admin-main .card.collapsible-card:not(.is-open) .collapsible-card-body{display:none;}
  .collapsible-info-popover{position:absolute;z-index:60;max-width:280px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:10px 12px;font-size:12.5px;line-height:1.5;color:var(--text);box-shadow:0 8px 20px rgba(0,0,0,.35);}
  #admin-main #node-search-wrap{margin-bottom:16px;}
  #admin-main #node-search-wrap input{margin-bottom:6px;}
  #admin-main .grid2{display:grid;grid-template-columns:1fr 1fr;gap:0 16px;}
  @media(max-width:640px){#admin-main .grid2{grid-template-columns:1fr;}}
  #admin-main fieldset{border:1px solid var(--border);border-radius:10px;margin-bottom:16px;padding:14px;}
  #admin-main legend{padding:0 8px;font-size:12px;color:var(--text-dim);font-weight:600;}
  .metric-adjust{display:inline-flex;align-items:center;gap:8px;}
  .metric-adjust button{width:24px;height:24px;border-radius:6px;border:1px solid var(--border);background:var(--bg-elev2);color:var(--text);font-size:14px;line-height:1;cursor:pointer;}
  .metric-adjust button:hover{background:var(--bg-elev);border-color:var(--accent);}
  .metric-adjust button:disabled{opacity:.5;cursor:default;}
  .metric-adjust b{min-width:26px;text-align:center;font-variant-numeric:tabular-nums;}
  #admin-main .kv-row{display:flex;gap:8px;margin-bottom:8px;align-items:flex-start;}
  @media (max-width:520px){
    #admin-main .kv-row{flex-wrap:wrap;}
    #admin-main .kv-row .info-k{flex:1 1 100%;}
    #admin-main .kv-row .info-v{flex:1 1 100%;}
  }
  #admin-main .kv-row input{margin-bottom:0;}
  #admin-main .kv-row .info-k{flex:0 0 200px;}
  #admin-main .kv-row .info-v{flex:1;margin-bottom:0;min-height:38px;resize:vertical;}
  #admin-main .kv-row .remove-btn{margin-top:0;}
  #admin-main .remove-btn{background:none;border:1px solid var(--border);color:var(--danger);border-radius:8px;cursor:pointer;width:36px;height:36px;flex-shrink:0;}
  /* 19 jul 2026 (editor HTML para Campos de información, ver addInfoRow()
     en app.js) — #info-rows .kv-row ya NO es una fila de 2 columnas
     (clave + valor lado a lado): la barra de herramientas del editor
     enriquecido necesita el ancho completo, así que esta lista override el
     layout genérico de arriba (misma especificidad — #id + .clase en ambas
     reglas — así que el orden en el archivo decide, y esta viene después).
     info-kv-top es la fila angosta de arriba (clave + botón quitar);
     info-v-wrap es el bloque de ancho completo de abajo (barra + editor). */
  #info-rows .kv-row{display:block;}
  #info-rows .info-kv-top{display:flex;gap:8px;align-items:center;margin-bottom:6px;}
  #info-rows .info-kv-top .info-k{flex:1;margin-bottom:0;}
  #info-rows .info-v-wrap{width:100%;}
  /* white-space:pre-wrap (a diferencia del .rte-editor genérico de arriba,
     pensado para HTML nuevo armado con <p>/<br> reales): los valores de
     Info YA existentes en producción son texto plano con saltos de línea
     literales ("\n"), nunca pasaron por un editor enriquecido — sin esto,
     el contenteditable los mostraría colapsados en una sola línea corrida
     al abrir un nodo viejo para editar, aunque el dato en sí no cambie
     hasta que se guarde. Coincide con .field .val (site.css), que ya usa
     pre-wrap para esos mismos valores en el perfil público. */
  #info-rows .rte-editor.info-v{min-height:64px;white-space:pre-wrap;}
  /* Placeholder del editor vacío (contenteditable no tiene atributo
     placeholder nativo) — mismo truco CSS estándar :empty + ::before,
     data-placeholder ya viene puesto en el div desde addInfoRow(). */
  #info-rows .rte-editor.info-v:empty::before{content:attr(data-placeholder);color:var(--text-dim);}
  #admin-login-wrap{display:flex;align-items:center;justify-content:center;min-height:100vh;}
  #admin-login-wrap form{background:var(--bg-elev);border:1px solid var(--border);border-radius:14px;padding:32px;width:min(360px,90vw);}
  #admin-login-wrap .error{background:#3a1c1c;border:1px solid #6b2b2b;color:#ff9a9a;padding:10px 12px;border-radius:8px;font-size:13px;margin-bottom:16px;}
  #admin-login-wrap .hint{color:var(--text-dim);font-size:12px;margin-top:16px;text-align:center;}

  .tab-row{display:flex;align-items:center;gap:8px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:8px;flex-wrap:wrap;}
  .tab-row .type-badge{font-size:10px;padding:3px 9px;border-radius:999px;background:#25303f;color:var(--text-dim);white-space:nowrap;}
  .tab-row input[type=text]{flex:1;min-width:120px;margin:0;}
  .tab-row select{flex:1;min-width:140px;margin:0;}
  .tab-row label{display:flex;align-items:center;gap:4px;font-size:11px;font-weight:400;text-transform:none;white-space:nowrap;color:var(--text-dim);}
  .tab-editor{background:var(--bg-elev2);border:1px dashed var(--accent);border-radius:10px;padding:14px;margin:8px 0 16px;}
  .rte-toolbar{display:flex;gap:4px;margin-bottom:8px;flex-wrap:wrap;position:relative;}
  .rte-toolbar button{background:var(--bg-elev);border:1px solid var(--border);color:var(--text);border-radius:6px;padding:5px 9px;cursor:pointer;font-size:12px;}
  /* 24 jul 2026 (Round 75, pedido de Ed) — "más alto mientras edito, sin
     guardar nada". `resize:vertical` nativo del navegador ya alcanza para
     el ajuste fino a mano (aparece un tirador en la esquina inferior
     derecha); `overflow:auto` es requisito del propio navegador para que
     ese resize funcione en un contenteditable, y de paso agrega scroll
     propio si el contenido crece más que el alto elegido. `max-height`
     evita que alguien arrastre el editor más alto que la pantalla y pierda
     de vista la barra de herramientas/el botón Guardar. Ver
     rteResizeButtonHtml()/.rte-resize-btn más abajo para el botón de un
     clic — ambos mecanismos solo tocan style.height inline, conviven sin
     pisarse. Nada de esto persiste: es puramente CSS + una clase de
     sesión, se pierde al recargar. */
  .rte-editor{min-height:140px;background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;padding:10px;color:var(--text);font-size:13px;resize:vertical;overflow:auto;max-height:70vh;}
  .rte-editor.rte-expanded{min-height:420px;}
  #info-rows .rte-editor.info-v.rte-expanded,
  .rte-editor.chat-faq-answer.rte-expanded{min-height:220px;}
  .rte-resize-btn{margin-left:auto;}
  .rte-resize-btn.active{background:var(--accent);color:#fff;border-color:var(--accent);}
  /* 24 jul 2026 (Round 76, pedido de Ed) — "Resaltar" (color y/o efecto)
     dentro de los 6 editores de texto enriquecido. Ver rteHighlightButtonHtml()/
     openRteHighlightPopover() en app.js. */
  .rte-highlight-btn.active{background:var(--accent);color:#fff;border-color:var(--accent);}
  .rte-highlight-popover{position:absolute;z-index:60;width:min(320px,90vw);background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:12px;font-size:12.5px;line-height:1.5;color:var(--text);box-shadow:0 8px 20px rgba(0,0,0,.35);}
  .rte-highlight-popover input[type="color"]{width:100%;height:30px;padding:2px;border:1px solid var(--border);border-radius:6px;background:var(--bg-elev);cursor:pointer;}
  .rte-highlight-popover select{width:100%;}
  /* El <mark> real que queda guardado dentro del HTML del contenido — el
     color de fondo lo pone style.backgroundColor inline (elegido en el
     popover), acá solo el look base (padding/radio) para que se note como
     un resaltado intencional aunque no se elija color (solo efecto).
     IMPORTANTE (bug corregido 24 jul 2026): <mark> es un elemento nativo
     de HTML con fondo amarillo por defecto en la hoja de estilos propia
     del navegador. Sin este reset explícito, cuando el admin elige "Sin
     color" (sin backgroundColor inline) el navegador igual pintaba ese
     amarillo por defecto — pareciendo que "siempre asigna color" aunque
     el código nunca lo haya puesto. background-color:transparent acá
     asegura que "Sin color" se vea realmente sin color; el inline
     backgroundColor (cuando sí se elige uno) sigue ganando por
     especificidad normal. */
  mark.rcx-hl{padding:.05em .2em;border-radius:3px;color:inherit;background-color:transparent;}
  /* Interruptor maestro compartido con Efectos de atención (Admin →
     Personalización → Visual → "Desactivar todos los efectos del sitio") —
     togglead por applyAttentionEffects() en app.js. A diferencia de
     ATTENTION_FX_TARGETS (elementos fijos que esa función recorre y les
     saca la clase directamente), estas clases viven insertadas dentro de
     HTML de contenido guardado (nodo/post/FAQ) — no hay un selector fijo
     que recorrer, así que el apagado se hace acá, a nivel body, con
     !important. */
  body.rcx-fx-master-off .rcx-fx-pulse,
  body.rcx-fx-master-off .rcx-fx-bounce,
  body.rcx-fx-master-off .rcx-fx-glow,
  body.rcx-fx-master-off .rcx-fx-colorshift,
  body.rcx-fx-master-off .rcx-fx-shimmer{ animation:none!important; }
  /* 20 jul 2026 (Markdown/HTML en "Respuesta del bot", ver chatFaqRowHtml()
     en app.js) — el .rte-editor genérico de arriba asume 140px de alto
     mínimo (pensado para Info/Blog, campos únicos); una fila de FAQ es más
     chica (equivalente al <textarea rows="2"> que reemplaza) y puede haber
     muchas en la misma pantalla con scroll propio (#chat-faq-rows) — un
     alto menor evita que 3-4 preguntas llenen toda la pantalla. Mismo
     truco de placeholder que .info-v (contenteditable no tiene atributo
     placeholder nativo). */
  .rte-editor.chat-faq-answer{min-height:52px;}
  .rte-editor.chat-faq-answer:empty::before{content:attr(data-placeholder);color:var(--text-dim);}
  /* 20 jul 2026 (menciones @node/@article/@poll dentro de CUALQUIER
     .rte-editor — ver wireRteMentions()/confirmMention() en app.js). El
     chip queda "atómico" (contenteditable=false) e inline dentro del texto;
     el desplegable de búsqueda vive en <body> (position:fixed, un solo
     elemento reutilizado por editor) para no quedar recortado por el
     overflow:hidden/scroll de la tarjeta que contiene al editor. */
  .mention-chip{display:inline-block;background:var(--accent2,#2b7a78);color:#fff;border-radius:999px;padding:1px 9px;font-size:12px;text-decoration:none;white-space:nowrap;cursor:pointer;transition:background-color .12s,color .12s;}
  /* 20 jul 2026 (pedido de Ed, con captura de pantalla: "el color del
     mouseover de los chips no deja leer") — antes solo aclaraba el mismo
     fondo teal con filter:brightness(1.12), lo que en varios temas (sobre
     todo alto contraste/sepia/ocean) dejaba texto blanco sobre un teal
     clarito, difícil de leer. Fondo explícito #dfeaf3 (pedido por Ed) +
     texto oscuro fijo para contraste garantizado en cualquier tema. */
  .mention-chip:hover{background:#dfeaf3;color:#0b2942;}
  .mention-dropdown{display:none;position:fixed;z-index:400;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.4);max-height:220px;overflow-y:auto;min-width:180px;max-width:320px;}
  .mention-dropdown.show{display:block;}
  .mention-dd-item{padding:7px 12px;font-size:13px;color:var(--text);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .mention-dd-item.active,.mention-dd-item:hover{background:var(--accent,#3a8fd6);color:#fff;}
  .mention-dd-empty{padding:7px 12px;font-size:12px;color:var(--text-dim);}
  .comment-mod-row{background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;padding:10px 12px;margin-bottom:8px;font-size:13px;}
  .comment-mod-row.pending{border-left:3px solid var(--org);}
  .comment-mod-row.approved{border-left:3px solid var(--ok);}

/* ============================================================
   Efectos de atención (16 jul 2026, pedido de Ed: "poder hacer que el
   botón de ? tuviese el efecto de pulse... el efecto sería solamente para
   lo que esté dentro del botón o pestaña... así no romper el layout").
   Ver applyAttentionEffects()/ATTENTION_FX_TARGETS en assets/js/app.js y
   claude/attention_effects_plan.md (doc del proyecto) para el diseño
   completo. Todas las animaciones de acá usan SOLO transform/filter/opacity
   — nunca width/height/margin/padding — porque esas tres son las únicas
   propiedades que corren en el compositor del navegador sin disparar
   reflow, que es justamente la garantía que pidió Ed: el layout no se
   mueve un píxel, pase lo que pase con la animación.
   Alcance construido en esta ronda (Categoría A, confirmada por Ed): botón
   "?" del Recorrido guiado, las 3 pestañas fijas de navegación (Videos/
   Artículos/Encuestas), las 2 pestañas fijas del modal de nodo (Info/
   Conexiones), y la burbuja del chat. El resto de ICON_DEFAULTS (topbar,
   badges de nodo, botones de "me gusta") queda documentado como posible
   extensión futura con el mismo patrón — no se construyó porque Ed no lo
   pidió explícitamente, ver production_port_status.md de esta ronda. */

/* Grupo "Movimiento" — mutuamente excluyente con el otro Pulse/Rebote (un
   elemento nunca tiene las dos clases a la vez, ver applyAttentionEffects()). */
@keyframes rcxFxPulse{
  0%,100%{ transform:scale(1); }
  50%{ transform:scale(1.18); }
}
.rcx-fx-pulse{ animation:rcxFxPulse 1.1s ease-in-out infinite; display:inline-block; }

@keyframes rcxFxBounce{
  0%,20%,50%,80%,100%{ transform:translateY(0); }
  40%{ transform:translateY(-5px); }
  60%{ transform:translateY(-2px); }
}
.rcx-fx-bounce{ animation:rcxFxBounce 1.4s ease-in-out infinite; display:inline-block; }

/* Grupo "Filtro" — mutuamente excluyente entre sí, combinable con el grupo
   de Movimiento de arriba (ej. Pulse + Brillo a la vez). */
@keyframes rcxFxGlow{
  0%,100%{ filter:drop-shadow(0 0 0 rgba(91,140,255,0)); }
  50%{ filter:drop-shadow(0 0 6px var(--accent)); }
}
.rcx-fx-glow{ animation:rcxFxGlow 1.6s ease-in-out infinite; }

/* Cambiar color: los íconos son emoji de texto plano, no SVG — `color` no
   los tiñe. filter:hue-rotate()+saturate() sí afecta el render de emoji en
   casi todos los navegadores (ver nota en attention_effects_plan.md). */
@keyframes rcxFxColorShift{
  0%{ filter:hue-rotate(0deg) saturate(1); }
  50%{ filter:hue-rotate(150deg) saturate(2.2); }
  100%{ filter:hue-rotate(360deg) saturate(1); }
}
.rcx-fx-colorshift{ animation:rcxFxColorShift 2.2s linear infinite; }

@keyframes rcxFxShimmer{
  0%,100%{ filter:brightness(1) drop-shadow(0 0 0 rgba(255,255,255,0)); }
  50%{ filter:brightness(1.6) drop-shadow(0 0 4px rgba(255,255,255,.8)); }
}
.rcx-fx-shimmer{ animation:rcxFxShimmer 1.3s ease-in-out infinite; }

/* Punto/badge — independiente de los dos grupos de arriba, siempre
   combinable (no anima el glyph, solo agrega un indicador superpuesto). Se
   posiciona relative en el propio elemento (ya son botones/pestañas, no
   debería chocar con estilos existentes) para poder anclar el ::after. */
.rcx-fx-badge{ position:relative; }
.rcx-fx-badge::after{
  content:""; position:absolute; top:-2px; right:-2px; width:8px; height:8px;
  border-radius:50%; background:var(--accent2); box-shadow:0 0 0 2px var(--bg-elev);
  pointer-events:none;
}

/* Accesibilidad (pedido explícito del plan, no de Ed directamente, pero
   evita un problema real): un visitante con "reducir movimiento" activado
   en su sistema/navegador no ve ninguna de las animaciones de arriba — el
   punto/badge se deja (no es movimiento, es un indicador estático). */
@media (prefers-reduced-motion: reduce){
  .rcx-fx-pulse, .rcx-fx-bounce, .rcx-fx-glow, .rcx-fx-colorshift, .rcx-fx-shimmer{
    animation:none !important;
  }
}

/* ===== 22 jul 2026 (pedido de Ed) — vista maximizada del grafo + panel
   flotante de herramientas (reemplaza los botones sueltos de la topbar en
   vista Mapa, normal y maximizada). Ver enterMaximize()/exitMaximize()/
   relocateGraphToolsIntoPanel() en app.js. ===== */

/* --- 22 jul 2026 (pedido de Ed, resto del punto 3) — modo Topbar vs
   Vertical de las pestañas de áreas (Videos/Artículos/Encuestas), Web y
   Mobile configurables por separado (ver applyAreaTabsDisplayMode() en
   app.js). En modo topbar, el MISMO elemento .side-tab se reubica dentro
   de #topbar-actions y se comporta como un dropdown más (mismo patrón
   visual que #view-mode-wrap/#layers-wrap): el botón queda en línea en la
   topbar, y el panel se despliega debajo en vez de deslizar desde el
   borde de la pantalla. --- */
.side-tab.tab-topbar-mode{
  position:relative; top:auto; left:auto; right:auto; bottom:auto; transform:none;
  display:inline-flex; z-index:auto;
}
.side-tab.tab-topbar-mode .side-tab-toggle{
  writing-mode:horizontal-tb; text-orientation:mixed; border-radius:10px;
  box-shadow:none; padding:8px 12px; font-size:13px;
}
.side-tab.tab-topbar-mode .side-tab-panel{
  position:absolute; top:calc(100% + 6px); right:0; left:auto;
  width:300px; height:0; max-height:70vh; border:1px solid var(--border);
  border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,.4);
}
.side-tab.tab-topbar-mode.open .side-tab-panel{ height:auto; }

/* --- Vista maximizada: overlay de página completa, no Fullscreen API
   nativa (iOS Safari no la soporta en elementos arbitrarios). --- */
body.is-maxed #topbar,
body.is-maxed #searchbar-row,
body.is-maxed #site-footer{ display:none; }
body.is-maxed #content-area{
  position:fixed; inset:0; z-index:100;
}

/* 23 jul 2026 (pedido de Ed, Round 67, Problema nuevo 3) — #exit-max (botón
   de cierre dedicado) se retira: Maximizar pasa a ser un botón de 2 estados
   (⛶/🗗), sin necesitar posicionamiento propio — ver toggleMaximize()/
   updateMaximizeButtons() en app.js. Su gemelo flotante vive adentro de
   #unified-tab-stack-herramientas y hereda el posicionamiento de ese grupo
   (applyMenuGroupsPosition()). */

#max-context{
  position:fixed; top:14px; left:16px; z-index:130;
  background:var(--bg-elev); border:1px solid var(--border); border-radius:8px;
  padding:8px 12px; font-size:12.5px; color:var(--text-dim);
  box-shadow:var(--shadow);
}
body.is-maxed #max-context.show{ display:block; }

/* --- Panel flotante y colapsable de herramientas del grafo. Mismo
   lenguaje visual que .side-tab (video/articles/polls) pero z-index propio
   para no competir por el mismo borde sin querer (ver checkPositionConflict()
   en app.js para el aviso cuando sí coinciden). --- */
#tools-tab{
  position:absolute; top:50%; right:0; transform:translateY(-50%);
  z-index:25; display:none; align-items:center;
  background:var(--accent); color:#0b0f14; border-radius:10px 0 0 10px;
  padding:14px 8px; cursor:pointer; box-shadow:-4px 0 14px rgba(0,0,0,.35);
  transition:right .2s ease, left .2s ease;
}
#tools-tab .ico{ font-size:16px; display:block; }
#tools-tab.side-left{ right:auto; left:0; border-radius:0 10px 10px 0; box-shadow:4px 0 14px rgba(0,0,0,.35); }
/* 22 jul 2026 (bug reportado por Ed, Round 58) — antes también dependía de
   #network.tools-panel-active, una clase que nunca se llegaba a activar
   desde el JS: en la práctica el tab de herramientas quedaba invisible en
   vista normal (solo aparecía maximizado). .map-only ya restringe esto a
   vista Mapa (ver body.rcx-outside-map .map-only{display:none !important}
   más arriba) — no hace falta ninguna condición extra acá. */
#tools-tab{ display:flex; }
#tools-panel.open ~ #tools-tab, #tools-tab.panel-open{ right:250px; }
#tools-tab.side-left.panel-open{ right:auto; left:250px; }

#tools-panel{
  position:absolute; top:0; right:0; height:100%; width:300px; z-index:24;
  background:var(--bg-elev); border-left:1px solid var(--border);
  transform:translateX(100%); transition:transform .2s ease;
  display:flex; flex-direction:column; padding:16px 12px; overflow-y:auto;
  box-shadow:-8px 0 30px rgba(0,0,0,.4);
}
#tools-panel.side-left{ right:auto; left:0; border-left:none; border-right:1px solid var(--border);
  transform:translateX(-100%); box-shadow:8px 0 30px rgba(0,0,0,.4); }
#tools-panel.open{ transform:translateX(0); }
@media (max-width:640px){ #tools-panel{ width:min(84vw,300px); } }

/* 23 jul 2026 (pedido de Ed, Problema 2.2) — #tools-panel-flip-side pasa
   de estar solo (align-self:flex-end) a compartir fila con Centrar/Agrupar
   dentro de #tools-panel-toprow — se le saca el margin-bottom/align-self
   propios, ahora los da el contenedor de la fila. */
#tools-panel-toprow{ display:flex; align-items:center; gap:8px; margin-bottom:10px; }
#tools-panel-flip-side{
  background:none; border:1px solid var(--border); color:var(--text-dim);
  font-size:14px; padding:5px 9px; border-radius:8px; cursor:pointer; flex:none;
}
#tools-panel-flip-side:hover{ color:var(--text); background:var(--bg-elev2); }
/* Centrar/Agrupar reubicados a la fila fija: comparten el espacio restante
   a partes iguales, en vez de apilarse a lo ancho como el resto del panel. */
#tools-panel-toprow #btn-fit,
#tools-panel-toprow #btn-cluster{ flex:1; width:auto; }
#tools-panel-body{ display:flex; flex-direction:column; gap:4px; }
/* Los controles reubicados (Vista/Capas/Agrupar/etc.) heredan su propio
   estilo de botón (.labelbtn/.iconbtn/.view-mode-wrap) — acá solo se ajusta
   el layout para que se apilen verticalmente en vez de en fila. */
#tools-panel-body .tools-group-body > *{ width:100%; }
#tools-panel-body .view-mode-wrap{ position:relative; }
/* 23 jul 2026 (pedido de Ed, Problema 2.2) — acordeón de los 4 grupos
   (Vista/Capas/Compartir/Más...). Cerrado = altura 0 + overflow oculto
   (no display:none, para que la transición de altura se vea); abierto =
   altura automática. El truco de max-height (en vez de height directo,
   que no anima con "auto") alcanza para el contenido real de estos 4
   grupos — si algún día un grupo creciera muchísimo, subir el tope. */
.tools-group{ border-bottom:1px solid var(--border); }
.tools-group:last-child{ border-bottom:none; }
.tools-group-head{
  width:100%; display:flex; align-items:center; gap:8px; background:none; border:none;
  color:var(--text); font-size:13px; font-weight:700; padding:10px 2px; cursor:pointer;
  text-align:left;
}
.tools-group-head:hover{ color:var(--accent); }
.tools-group-head .tgh-chev{ margin-left:auto; color:var(--text-dim); font-size:11px; transition:transform .15s ease; }
.tools-group.open .tools-group-head .tgh-chev{ transform:rotate(180deg); }
.tools-group-body{ max-height:0; overflow:hidden; transition:max-height .2s ease; }
.tools-group.open .tools-group-body{ max-height:none; overflow:visible; padding-bottom:12px; }
/* 22 jul 2026 (bug reportado por Ed: "las herramientas no se muestran en
   paneles como habíamos acordado") — Round 56 solo REUBICÓ estos 3
   controles (Vista/Capas/Exportar) tal cual, sin aplanarlos: seguían
   siendo un botón que abre un menú desplegable (display:none por
   defecto, position:fixed calculado por JS). El mockup aprobado por Ed
   mostraba estas opciones SIEMPRE visibles adentro del panel, sin sub-
   clic — acá se fuerza justamente eso: display/position ganan por sobre
   el estilo inline que useFloatingMenu() sigue alternando al hacer clic
   (queda como no-op inofensivo), y se le saca la apariencia de "menú
   flotante" (borde/sombra/ancho fijo) para que se vea como contenido
   plano del panel, no como un popup. El contenido se pre-puebla una sola
   vez desde relocateGraphToolsIntoPanel() en app.js — si no, quedaría
   vacío hasta el primer clic. */
#tools-panel-body #view-mode-menu,
#tools-panel-body #layers-menu,
#tools-panel-body #export-menu{
  display:block !important; position:static !important;
  background:transparent; border:none; box-shadow:none;
  width:100%; max-width:100%; margin-top:4px; padding:0; overflow:visible;
}
/* 24 jul 2026 (Round 76) — #btn-view-mode se retiró del selector de abajo:
   el botón entero se eliminó (ver updateViewModeButton() en app.js), así
   que esta regla ya no tiene nada que hacer por ese id. */
#tools-panel-body #btn-layers,
#tools-panel-body #btn-export{
  width:100%; justify-content:flex-start;
}
#tools-panel-body #export-menu{ position:static; margin-top:4px; }

@media (max-width:640px){
  #tools-panel{ width:min(84vw,250px); }
  #tools-tab.panel-open{ right:min(84vw,250px); }
  #tools-tab.side-left.panel-open{ left:min(84vw,250px); }
}

/* --- Modal de advertencia por conflicto de posición (Admin →
   Configuración), reutiliza las mismas variables de tema que #modal/
   #account-modal. --- */
#position-conflict-overlay{
  position:fixed; inset:0; z-index:140; background:rgba(5,8,12,.7);
  backdrop-filter:blur(3px); display:none; align-items:center; justify-content:center; padding:16px;
}
#position-conflict-overlay.show{ display:flex; }
#position-conflict-modal{
  width:min(420px,100%); background:var(--bg-elev); border:1px solid var(--border);
  border-radius:var(--radius); box-shadow:var(--shadow); padding:20px;
}
#position-conflict-text{ font-size:13.5px; color:var(--text); line-height:1.5; margin:0 0 16px; }
#position-conflict-actions{ display:flex; justify-content:flex-end; gap:8px; }

/* ===== 22 jul 2026 (Round 59, pedido de Ed) — drawer único: fusiona el
   panel de herramientas (Round 56) con las 3 pestañas de Videos/Artículos/
   Encuestas en un solo mecanismo — un stack de botones redondos que abre
   UN panel, con una fila de accesos adentro para cambiar de sección sin
   cerrar. Reutiliza las 4 piezas de contenido existentes tal cual
   (#tools-panel-body, #video-panel-body, #articles-panel-body,
   #polls-panel-body) — nada de su lógica interna se reescribió, solo se
   unificó la geometría/entrada externa. Ver initUnifiedDrawer() en
   app.js. ===== */

/* ===== 23 jul 2026 (pedido de Ed, Round 67, Problema nuevo 4) — el stack
   único #unified-tab-stack se separa en 2 grupos independientes-pero-
   coordinados: #unified-tab-stack-herramientas (🛠️/Maximizar/Zoom,
   .map-only = solo en vista Mapa) y #unified-tab-stack-paginas
   (Artículos/Videos/Encuestas + gemelos sueltos, visible en todo el
   sitio). Ambos comparten la clase .menu-group-stack; top/left/right los
   calcula applyMenuGroupsPosition() en app.js (lado/alineación/
   desplazamiento/orden configurables por grupo desde Admin) — acá solo
   viven las propiedades estructurales que no dependen de esa cuenta. ===== */
.menu-group-stack{
  position:fixed; z-index:26; display:flex; flex-direction:column; gap:10px; margin:0; padding:0;
  transition:top .2s ease, left .2s ease, right .2s ease, opacity .22s ease, transform .22s ease;
}
/* Separador (línea blanca fina) entre los 2 grupos cuando comparten lado y
   alineación y Ed activó "Línea divisora" — su posición/visibilidad la
   calcula applyMenuGroupsPosition() (top/left/right inline). */
#menu-groups-divider{
  position:fixed; z-index:26; width:44px; height:2px; background:rgba(255,255,255,.35);
  border-radius:2px; pointer-events:none; transition:top .2s ease;
}
/* 23 jul 2026 (pedido de Ed, último pedido) — si los 2 grupos no entran sin
   superponerse (o es mobile y se salen del viewport), applyMenuGroupsPosition()
   agrega .shrink a los 2 contenedores: los botones bajan de 44px a 32px.
   Ver mockup_round67_v3.html (función layout()) para el algoritmo. */
.menu-group-stack.shrink .round-tab-btn{ width:32px; height:32px; font-size:14px; }
/* 23 jul 2026 (pedido de Ed, Round 67, Problema nuevo 4.A) — al abrir un
   pane, SOLO el grupo que lo disparó esconde sus botones de tipo drawer
   (que "pasan a ser" la fila .access-btn de arriba del panel) — ver
   updateUnifiedDrawerUI() en app.js, que agrega .drawer-open solo al
   contenedor correspondiente. Los .round-action-btn (Maximizar/Zoom en
   herramientas; los gemelos sueltos en paginas) NO se esconden: no abren
   ningún pane, no duplican ninguna fila de acceso. */
.menu-group-stack.drawer-open > .round-tab-btn:not(.round-action-btn){ display:none; }
/* 23 jul 2026 (Round 68, pedido de Ed, punto 4) — bug reproducido y
   confirmado con Playwright sobre el sitio real: la regla de arriba solo
   escondía los botones del grupo QUE ABRIÓ el panel — el OTRO grupo se
   quedaba flotando en su lugar de siempre y, como los 2 comparten lado
   por defecto, terminaba tapando visualmente el panel recién abierto
   (capturas en claude/round68_issues_plan.md). Ed pidió la solución más
   simple: mientras CUALQUIER panel esté abierto, los 2 stacks completos
   (incluidos los .round-action-btn que la regla de arriba no tocaba) se
   ocultan con un fundido + deslizamiento leve — ver updateUnifiedDrawerUI()
   en app.js, que agrega .any-panel-open a los 2 contenedores por igual
   (no solo al que abrió) mientras anyDrawerSectionOpen() sea true. Se
   usa opacity/transform (no display:none) para que la transición se
   pueda animar — .menu-group-stack ya tiene esas 2 propiedades en su
   transition de arriba. */
.menu-group-stack.any-panel-open{
  opacity:0; transform:translateX(14px); pointer-events:none;
}
/* 23 jul 2026 (pedido de Ed, feature nueva) — panel de fondo semi-
   transparente OPCIONAL detrás de cada grupo flotante, configurable por
   grupo desde Admin → "Posición de los grupos flotantes" → "Fondo del
   grupo" (habilitado/deshabilitado, color, opacidad, padding interno,
   margen al borde — ver menuGroupBgConfig() en app.js). z-index un punto
   por debajo de .menu-group-stack (26) para quedar siempre detrás de los
   botones sin importar el orden real en el DOM. Tamaño/posición
   (top/left/right/width/height) los calcula applyMenuGroupsPosition()
   junto con el resto del layout del grupo — no hay tamaño fijo acá,
   porque el panel tiene que quedar justo detrás de N botones visibles,
   que varía según qué ítems estén activos/visibles en cada grupo.
   Oculto por defecto (display:none inline en el HTML) — cero cambio
   visual hasta que Ed lo active. Mockup aprobado: mockup_round72_group_bg.html. */
.menu-group-bg{
  position:fixed; z-index:25; border-radius:16px; pointer-events:none;
  transition:top .2s ease, left .2s ease, right .2s ease, width .2s ease, height .2s ease, opacity .22s ease, transform .22s ease;
}
/* Mismo fundido + deslizamiento que .menu-group-stack.any-panel-open,
   para que el panel de fondo desaparezca en sincro con los botones cuando
   se abre cualquier drawer (ver updateUnifiedDrawerUI() en app.js). */
.menu-group-bg.any-panel-open{
  opacity:0; transform:translateX(14px);
}
/* 23 jul 2026 (pedido de Ed, Problema 3) — antes convivían 3 tratamientos
   distintos de fondo/selección para botones del mismo sistema de menú:
   .round-tab-btn (celeste siempre, aro blanco solo si activo),
   #unified-toggle-btn (oscuro fijo, sin variable — retirado en Round 67
   junto con el botón ☰), y .topbar-drawer-btn (hereda de .iconbtn: oscuro
   en reposo pero SE RELLENA de celeste sólido al activarse, ver
   .iconbtn.active más arriba — sin aro). Se unifican los 2 que quedan a un
   solo fondo oscuro neutro (--menu-btn-bg-custom, configurable desde
   Admin → Personalización → Visual → "CSS de elementos globales", cae en
   var(--bg-elev2) = igual que antes si no se personaliza) y un solo
   mecanismo de selección: aro + brillo (--menu-btn-ring-custom, cae en
   blanco), NUNCA relleno de color — ver override de .topbar-drawer-btn.active
   más abajo, que pisa el relleno de .iconbtn.active solo para estos
   botones (no toca .iconbtn.active en general, por si algún otro botón
   fuera de este sistema de menú depende del relleno celeste a propósito). */
.round-tab-btn{
  width:44px; height:44px; border-radius:50%; border:1px solid var(--border); cursor:pointer; margin:0; padding:0;
  background:var(--menu-btn-bg-custom, var(--bg-elev2)); color:var(--text); font-size:18px;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 4px 14px rgba(0,0,0,.4);
  transition:transform .15s ease, box-shadow .15s ease, width .15s ease, height .15s ease, font-size .15s ease;
}
@media (max-width:640px){ .round-tab-btn{ width:38px; height:38px; font-size:16px; } }
.round-tab-btn:hover{ transform:scale(1.18); box-shadow:0 6px 20px rgba(0,0,0,.5); }
/* 23 jul 2026 (pedido de Ed, Round 67, punto "*** Otra cosa") — brillo
   blanco alrededor del botón/pestaña seleccionado, consistente en los 3
   estilos de botón del sistema de menú (antes solo .round-tab-btn tenía
   aro; .access-btn y .topbar-drawer-btn no brillaban). */
.round-tab-btn.active{ outline:2px solid var(--menu-btn-ring-custom, #fff); outline-offset:2px; box-shadow:0 0 0 4px rgba(255,255,255,.18), 0 4px 14px rgba(0,0,0,.4); }
/* Gemelos de topbar de Elementos del menú: pisan el relleno celeste de
   .iconbtn.active (definido arriba, ~línea 212) con el mismo fondo oscuro
   + aro/brillo que el resto del sistema de menú, en vez de cambiar de color. */
.topbar-drawer-btn.active{ background:var(--menu-btn-bg-custom, var(--bg-elev2)); border-color:var(--border); color:var(--text); outline:2px solid var(--menu-btn-ring-custom, #fff); outline-offset:2px; box-shadow:0 0 0 4px rgba(255,255,255,.18); }
.topbar-drawer-btn{ background:var(--menu-btn-bg-custom, var(--bg-elev2)); }

/* 23 jul 2026 (Round 69, pedido de Ed, punto 2) — sistema de borde
   unificado para TODOS los "gemelos" de menú (flotante Y topbar):
   blanco en reposo, blanco brillante en hover o mientras el panel/menú
   está abierto (.active — ya se usaba para esto, ver reglas arriba), y
   azul SOLO cuando el botón corresponde a la ruta realmente activa en la
   URL (.is-route-active, calculado por updateMenuRouteActiveState() en
   app.js a partir de showContentLayer()). El "azul que solo se veía en
   mobile" que reportó Ed NO era este sistema — no existía ningún
   tratamiento de "ruta activa" — sino el aro genérico de accesibilidad
   :focus-visible (ver ~línea 87 de este archivo, que se deja intacto:
   sigue siendo necesario para navegación por teclado; :focus-visible
   típicamente se activa al tocar en touch pero no al hacer click con
   mouse, de ahí la diferencia percibida). .is-route-active es un estado
   nuevo, deliberado, independiente de foco. Los 3 colores son
   configurables desde Admin → Elementos del menú → "Bordes de
   selección" (ver MENU_BORDER_COLOR_VARS/applyMenuBorderColors() en
   app.js) — el azul de ruta usa un valor fijo propio (#3aa0ff) en vez de
   var(--accent) a propósito, porque --accent cambia de color según el
   tema visual elegido (ver los distintos body.theme-* al inicio de este
   archivo) y Ed quiere que el estado "en esta ruta" se vea igual (azul)
   sin importar el tema. */
.round-tab-btn,
.topbar-drawer-btn,
.menu-action-topbar-btn{
  border-color: var(--menu-border-idle-custom, rgba(255,255,255,.5));
}
.round-tab-btn:hover,
.topbar-drawer-btn:hover,
.menu-action-topbar-btn:hover,
.round-tab-btn.active,
.topbar-drawer-btn.active,
.menu-action-topbar-btn.active{
  border-color: var(--menu-border-hover-custom, #fff);
}
.menu-action-topbar-btn:hover{ color:var(--text); }
.round-tab-btn.is-route-active,
.topbar-drawer-btn.is-route-active,
.menu-action-topbar-btn.is-route-active{
  border-color: var(--menu-border-route-custom, #3aa0ff) !important;
  box-shadow: 0 0 0 3px rgba(58,160,255,.38), 0 4px 14px rgba(0,0,0,.4);
}
.round-tab-btn.is-route-active.active{
  outline-color: var(--menu-border-route-custom, #3aa0ff);
}
.topbar-drawer-btn.is-route-active.active,
.menu-action-topbar-btn.is-route-active.active{
  outline: 2px solid var(--menu-border-route-custom, #3aa0ff);
  outline-offset: 2px;
}

/* Geometría unificada de los 4 paneles (Herramientas/Videos/Artículos/
   Encuestas): mismo tamaño y posición que #tools-panel ya tenía, para que
   los otros 3 (antes 70vh/300px con .side-tab-panel) se vean idénticos y
   ocupen exactamente el mismo lugar — solo uno visible a la vez. */
.side-tab.unified-mode{
  position:absolute; top:0; right:0; height:100%; bottom:auto; transform:none; z-index:24;
}
.side-tab.unified-mode.side-left{ right:auto; left:0; }
.side-tab.unified-mode .side-tab-toggle{ display:none; } /* pill vieja: reemplazada por #unified-tab-stack */
.side-tab.unified-mode .side-tab-panel{
  position:relative; width:0; height:100%; max-height:none; overflow:hidden;
  border-left:1px solid var(--border); border-right:none;
  transition:width .2s ease;
}
.side-tab.unified-mode.side-left .side-tab-panel{ border-left:none; border-right:1px solid var(--border); }
.side-tab.unified-mode.open .side-tab-panel{ width:300px; overflow-y:auto; }
@media (max-width:640px){ .side-tab.unified-mode.open .side-tab-panel{ width:min(84vw,300px); } }

/* Fila de accesos (Herramientas/Artículos/Videos/Encuestas), idéntica
   dentro de cada uno de los 4 paneles — solo se ve la del panel visible. */
.unified-access-row{ display:flex; align-items:stretch; border-bottom:1px solid var(--border); margin:0 0 10px; }
.access-btn{
  flex:1; display:flex; flex-direction:column; align-items:center; gap:3px;
  background:none; border:none; color:var(--text-dim); font-size:10px; padding:9px 4px;
  cursor:pointer; border-bottom:2px solid transparent;
}
.access-btn.active{ color:var(--accent); border-bottom-color:var(--accent); background:rgba(255,255,255,.03); }
.access-btn:hover:not(.active){ background:var(--bg-elev2); }
/* 23 jul 2026 (pedido de Ed, Round 67, Problema nuevo 4.A) — "cuando
   estemos en herramientas las pestañas de Artículos, Videos y Encuestas no
   deben estar visibles y viceversa": la fila de accesos se filtra según
   qué GRUPO disparó el pane (ver updateUnifiedDrawerUI() en app.js), no
   solo qué sección está activa — con el pane cerrado (ninguna clase
   group-*) se ven los 4, igual que antes. */
.unified-access-row.group-herramientas .access-btn:not([data-target="tools"]){ display:none; }
.unified-access-row.group-paginas .access-btn[data-target="tools"]{ display:none; }
/* 23 jul 2026 (pedido de Ed, Problema 2.3 — mobile: unificar controles
   flotantes) — el stack redondo flotante (#unified-tab-stack) y esta fila
   de accesos hacían el mismo trabajo a la vez mientras el drawer estaba
   abierto (botones duplicados, se superponían al contenido en viewports
   angostos — ver video de Ed). Ed aprobó explícitamente la opción de
   "esconder los botones" flotantes al abrir el panel; esta fila ya
   cumplía visualmente el rol de "fila de pestañas arriba" que pidió (con
   ícono + nombre + selección), solo le faltaba el ✕ para cerrar — que es
   lo que agrega este botón nuevo (mismo lugar que Ed marcó en su imagen
   de referencia, integrado a la izquierda de la fila). Con esto, la fila
   de accesos queda como ÚNICO control de sección+cierre mientras el panel
   está abierto — ver regla de #unified-tab-stack.drawer-open más abajo. */
.access-close-btn{
  flex:none; width:34px; display:flex; align-items:center; justify-content:center;
  background:none; border:none; border-right:1px solid var(--border); color:var(--text-dim);
  font-size:15px; cursor:pointer;
}
.access-close-btn:hover{ color:var(--text); background:var(--bg-elev2); }

/* 22 jul 2026 (pedido de Ed) — Minimapa: miniatura fija en la esquina
   inferior izquierda (no compite con #zoom-controls, que vive abajo a la
   derecha), oculto por defecto. */
#minimap-canvas{
  position:absolute; left:14px; bottom:16px; z-index:14;
  width:160px; height:110px; border:1px solid var(--border); border-radius:8px;
  background:rgba(10,14,19,.85); box-shadow:0 4px 14px rgba(0,0,0,.35);
}
@media (max-width:520px){ #minimap-canvas{ width:120px; height:84px; left:10px; bottom:10px; } }

/* 22 jul 2026 (pedido de Ed) — "Widget embebible" (/embed): oculta toda
   la cadera del sitio (topbar, buscador, footer, drawer, banner legal,
   burbuja de chat) — queda solo el grafo (Mapa) + controles de zoom,
   pensado para incrustarse en un <iframe> fuera del sitio. */
body.rcx-embed-mode #topbar,
body.rcx-embed-mode #searchbar-row,
body.rcx-embed-mode #site-footer,
body.rcx-embed-mode #legal-banner,
body.rcx-embed-mode #unified-tab-stack-herramientas,
body.rcx-embed-mode #unified-tab-stack-paginas,
body.rcx-embed-mode #menu-groups-divider,
body.rcx-embed-mode #chat-widget-bubble,
body.rcx-embed-mode #chat-widget-panel{
  display:none !important;
}
body.rcx-embed-mode #content-area{ inset:0; top:0; }

/* 22 jul 2026 (pedido de Ed) — modo de visualización (ícono/texto/ambos)
   para los botones redondos del stack y sus gemelos de topbar — un ícono
   solo mantiene la forma circular de siempre.
   23 jul 2026 (Round 68, pedido de Ed, punto 1) — "Ícono y texto" se
   separa en 2 modos EXPLÍCITOS, elegibles por igual en Flotante y Barra
   superior: "inside" (píldora ancha, ícono+texto uno al lado del otro —
   antes esto era el único comportamiento de topbar, sin poder elegir
   "debajo" ahí) y "below" (el botón mantiene su forma/tamaño de siempre —
   círculo en flotante, cuadrado redondeado en topbar — y el nombre
   flota aparte, centrado DEBAJO, sin afectar el layout/gap del stack ni
   de la barra — antes esto solo existía para los flotantes, Round 67
   punto 2). "Solo texto" sigue siendo píldora en los 2 casos (no hay
   ícono del cual "colgar" nada).
   BUG encontrado en Round 68 (no solo el reporte visual de Ed — se
   confirmó en el código): applyMenuItemsLayout() calculaba el modo
   elegido pero NUNCA agregaba la clase .tab-display-* al botón, así que
   "Ícono y texto" terminaba embutiendo los 2 <span> DENTRO del
   círculo/cuadrado fijo de "solo ícono" (44px/38px) sin píldora ni
   etiqueta debajo — la causa real de que el texto rompiera el botón
   (ver claude/round68_issues_plan.md). Corregido: applyMenuItemsLayout()
   ahora sí aplica la clase correspondiente a cada botón.
   23 jul 2026 (Round 70, bug real reportado por Ed con captura) — estas
   reglas nunca cubrieron a .menu-action-topbar-btn (Inicio/Maximizar/
   Redes/Idioma/Recorrido/Cuenta — ver #btn-map-home/#btn-social/#btn-lang/
   #btn-walkme/#btn-account/#btn-maximize en partials/app-shell.html), solo
   a .round-tab-btn (flotante) y .topbar-drawer-btn (Artículos/Videos/
   Encuestas/Herramientas en la topbar). applyMenuItemsLayout() SÍ les
   agrega la clase .tab-display-* correcta a los 6 por igual (confirmado
   leyendo el código — no es un bug de JS), pero como el selector CSS no
   los incluía, el texto quedaba embutido en la caja fija de 38px de
   .iconbtn sin importar el modo elegido en Admin — mismo síntoma que el
   bug de Round 68 de arriba, pero para una clase distinta que quedó
   afuera esa vez. Reproducido en vivo contra el sitio real antes de
   arreglar (los 2 modos, Píldora y Debajo, ambos rotos por igual) y
   confirmado el fix de la misma forma. */
.round-tab-btn.tab-display-text,
.topbar-drawer-btn.tab-display-text,
.menu-action-topbar-btn.tab-display-text,
.round-tab-btn.tab-display-inside,
.topbar-drawer-btn.tab-display-inside,
.menu-action-topbar-btn.tab-display-inside{
  width:auto; min-width:44px; border-radius:22px; padding:0 14px; gap:6px;
}
.topbar-drawer-btn.tab-display-text,
.topbar-drawer-btn.tab-display-inside,
.menu-action-topbar-btn.tab-display-text,
.menu-action-topbar-btn.tab-display-inside{
  min-width:38px; border-radius:19px; padding:0 12px;
}
/* "below" en FLOTANTE: el círculo mantiene su tamaño/forma de siempre, el
   texto flota aparte (position:absolute) DEBAJO, centrado — no afecta el
   layout/gap del stack (Round 67 punto 2). Funciona porque
   .menu-group-stack no clipea overflow. .menu-action-topbar-btn NUNCA vive
   en el flotante (solo tiene gemelo de topbar), así que no hace falta
   agregarla acá — ver la variante de TOPBAR más abajo. */
.round-tab-btn.tab-display-below{ position:relative; overflow:visible; }
.round-tab-btn.tab-display-below .rtb-text{
  position:absolute; top:100%; left:50%; transform:translateX(-50%); margin-top:5px;
  font-size:10px; font-weight:700; white-space:nowrap; text-align:center;
  color:var(--text); background:rgba(10,14,19,.78); padding:2px 7px; border-radius:6px;
  pointer-events:none; box-shadow:0 2px 8px rgba(0,0,0,.3);
}
/* "below" en TOPBAR (Round 68, punto 1, nuevo): NO se puede usar el mismo
   truco de position:absolute+overflow visible que en flotante —
   #topbar-actions tiene overflow-y:hidden a propósito (scroll horizontal
   propio de esa fila, 16 jul 2026) y clipearía cualquier cosa que se
   salga verticalmente del botón (confirmado con Playwright en esta misma
   ronda: la etiqueta quedaba invisible, recortada). En vez de pelear con
   ese overflow, el botón mismo crece en alto — ícono arriba, etiqueta
   abajo, los 2 DENTRO de su propia caja (flex-direction:column) — el
   contenedor flex de la barra (align-items:center) se acomoda solo a la
   altura del hijo más alto, sin necesidad de que nada se salga de su caja.
   23 jul 2026 (Round 70) — .menu-action-topbar-btn agregada acá también,
   mismo criterio que arriba. */
.topbar-drawer-btn.tab-display-below,
.menu-action-topbar-btn.tab-display-below{
  width:44px; height:auto; min-height:38px; flex-direction:column; justify-content:center;
  gap:2px; padding:6px 2px; border-radius:10px;
}
.topbar-drawer-btn.tab-display-below .rtb-text,
.menu-action-topbar-btn.tab-display-below .rtb-text{
  font-size:9px; font-weight:700; color:var(--text-dim); white-space:normal;
  text-align:center; line-height:1.15; max-width:52px;
}
.round-tab-btn .rtb-text,
.topbar-drawer-btn .rtb-text,
.menu-action-topbar-btn .rtb-text{ font-size:12px; font-weight:700; white-space:nowrap; }
.round-tab-btn .rtb-icon,
.topbar-drawer-btn .rtb-icon,
.menu-action-topbar-btn .rtb-icon{ font-size:16px; line-height:1; }
.round-tab-btn.tab-display-below .rtb-text{ font-size:10px; }

/* 26 jul 2026 (Round 83, Zonas de Artículos + Archivo, pedido de Ed) —
   pestañas de zona (Recientes/Destacados | Archivados), tanto en el sitio
   público (#blog-zone-tabs, dentro de #view-blog) como en Admin →
   Contenido → Blog / Artículos (#posts-zone-tabs). Mismo lenguaje visual
   que #admin-subnav (pill oscura, activa con borde/color de acento) para
   que se sienta como el resto del panel en vez de un componente nuevo. */
/* 27 jul 2026 (nueva sesión, pedido de Ed: "tabs reales, no botones") —
   antes cada pestaña era una píldora suelta (border-radius:9px en las 4
   esquinas, activa = borde de acento), sin relación visual con el panel
   de abajo. Ahora: esquinas superiores solo (flat-top), una línea base
   compartida en el contenedor (.rcx-zone-tabs) que la pestaña ACTIVA
   "atraviesa" (mismo truco que #admin-nav/#admin-subnav más abajo — la
   pestaña sube 1px y su propio borde inferior queda del mismo color que
   su fondo, así se funde con lo que sigue en vez de mostrar una línea
   cortando por el medio). */
.rcx-zone-tabs{ display:flex; gap:2px; flex-wrap:wrap; margin:0 0 14px; border-bottom:1px solid var(--border); }
.rcx-zone-tab{
  background:transparent; color:var(--text-dim); border:1px solid transparent; border-bottom:none;
  border-radius:8px 8px 0 0; padding:8px 16px; font-size:12.5px; font-weight:600;
  cursor:pointer; white-space:nowrap; position:relative; top:1px;
}
.rcx-zone-tab:hover{ color:var(--text); background:var(--bg-elev2); }
.rcx-zone-tab.active{
  color:var(--accent); background:var(--bg-elev);
  border-color:var(--border); border-bottom:1px solid var(--bg-elev);
}
.rcx-zone-tab .count{
  display:inline-block; background:var(--border); color:var(--text-dim);
  font-size:10.5px; font-weight:700; padding:1px 7px; border-radius:10px; margin-left:6px;
}
.rcx-zone-tab.active .count{ color:var(--accent); }
#blog-zone-tabs{ padding:0 20px 0; }

/* 26 jul 2026 (Round 83, Iconos — pedido de Ed: "Nombre + imagen del
   ícono en uso + campo de entrada" en una sola lista continua, sin la
   grilla de 2 columnas ni el corte "Iconos con efectos de atención") —
   fila unificada de Personalización → Visual → Iconos. .rcx-icon-preview
   se reutiliza también en la columna ÍCONO (ahora de solo lectura) de la
   tabla "Elementos del menú", Configuración → General. */
.rcx-icon-row{ padding:9px 0; border-bottom:1px solid var(--border); }
.rcx-icon-row:last-child{ border-bottom:none; }
.rcx-icon-row-main{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.rcx-icon-row-main label{ flex:0 0 230px; text-transform:none; font-size:12px; }
.rcx-icon-preview{
  display:inline-flex; align-items:center; justify-content:center;
  width:30px; height:30px; flex:0 0 auto; font-size:16px; line-height:1;
  border:1px solid var(--border); border-radius:8px; background:var(--bg-elev);
}
.rcx-icon-preview svg{ width:18px; height:18px; }
.rcx-icon-row-main input[type="text"]{ flex:1 1 220px; min-width:140px; }
.rcx-icon-row-main button[data-fx-toggle]{ flex:0 0 auto; }
