/* ============================================================
   SHIVAIA — style.css v2 (Corte 6 UI)
   Estética: minimalismo fino. Marcellus solo para la marca SHIVAIA;
   Inter para todo lo demás. Acento: azul loto (--accent) — cambiar
   esa única variable revierte/ajusta toda la paleta de acento.
   Bugs corregidos respecto a v1:
   - Texto de respuestas con color fijo #222/#333 (ilegible en oscuro)
   - .btn-send:hover global que rompía el botón en modo oscuro
   - Bloques de modo claro duplicados
   ============================================================ */

:root {
  --bg: #0e0e10;
  --sidebar: #111113;
  --panel: #121214;
  --soft: #1a1a1e;

  --text: #ececec;
  --muted: #9ca3af;

  --border: rgba(255, 255, 255, 0.06);
  --accent: #4a66b0;          /* azul loto */
  --accent-soft: rgba(74, 102, 176, 0.18);

  --bubble-user: #1c1c21;     /* globo del usuario, oscuro */

  --nodal: #e0a92b;           /* color de la red nodal "pensando"; el dial lo
                                 interpola hacia un ámbar más oscuro en claro */

  /* Inter sale de fuentes.css (autoalojada, 11-09-2026). Montserrat se fue:
     era respaldo de Inter y con Inter cargada no se pintaba nunca. */
  --font-ui: "Inter", system-ui, sans-serif;
  --font-brand: "Marcellus", serif;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* 29-ago: Safari en iOS "autoinfla" el texto de bloques que considera
     principales al rotar o al hacer zoom, y lo hace con un factor propio que
     no está en ninguna hoja de estilos. El resultado es una tipografía que no
     coincide con la del escritorio y cajas que se desbordan a lo ancho. Con
     100% se le pide que respete los tamaños declarados. No afecta al zoom
     manual del usuario, que sigue disponible. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  font-family: var(--font-ui);
  background: var(--bg);
  color: var(--text);
  height: 100vh;
  height: 100dvh; /* móvil: respeta la UI del navegador, no corta el input */
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--accent-soft);
}

/* Scrollbars finos y silenciosos */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(128, 128, 128, 0.25) transparent;
}
*::-webkit-scrollbar { width: 6px; }
*::-webkit-scrollbar-thumb {
  background: rgba(128, 128, 128, 0.25);
  border-radius: 3px;
}
*::-webkit-scrollbar-track { background: transparent; }

/* LAYOUT */
.app {
  display: flex;
  height: 100vh;
  height: 100dvh;
  /* 29-ago: en horizontal la muesca del iPhone queda a un costado, y ahí el
     ancho de pantalla (852px) es MAYOR que el corte de 768px, así que la
     media query de móvil no se dispara. Por eso el resguardo lateral va aquí
     y no allí. En cualquier equipo sin muesca —el Dell, el A05— estos valores
     son 0 y la caja queda idéntica a como estaba. */
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

/* SIDEBAR */
.sidebar {
  width: 260px;
  background: var(--sidebar);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: transform 0.25s ease;
}

.sidebar-header {
  padding: 12px;
  border-bottom: 1px solid var(--border);
}

.logo {
  font-weight: bold;
  display: block;
  margin-bottom: 10px;
}

.btn-new-chat {
  width: 100%;
  padding: 8px;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 500;
  font-size: 15px;
  transition: border-color 0.2s ease, background 0.2s ease;
}

.btn-new-chat:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.sidebar-chats {
  flex: 1;
  padding: 10px;
  overflow-y: auto;
  min-height: 0;
}

.chat-item {
  position: relative;
  width: 100%;
  padding: 10px 12px;
  margin-bottom: 4px;
  border-radius: 12px;
  cursor: pointer;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  overflow: hidden;
  transition: background 0.2s ease, box-shadow 0.25s ease;
  /* En móvil, tocar un chat con el nombre recortado abre su nombre completo
     (ver setupChatTooltip en app.js). Sin esto, mantener el dedo un instante
     de más despierta la lupa de selección de texto de iOS y el menú nativo
     de Android sobre la lista. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.chat-item-title {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-item:hover {
  background: rgba(128, 128, 128, 0.12);
}

/* Item activo: en vez del recuadro clásico, un TRAZO de luz a la izquierda
   (pincelada) sobre un cristal translúcido con leve resplandor. Estética
   propia de SHIVAIA: nada de borde duro, algo distinto a lo habitual. */
.chat-item.active {
  background:
    linear-gradient(
      90deg,
      var(--accent-soft) 0%,
      rgba(255, 255, 255, 0.045) 38%,
      transparent 100%
    );
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}

.chat-item.active::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 18%;
  bottom: 18%;
  width: 3px;
  border-radius: 3px;
  background: linear-gradient(to bottom, var(--accent), #7b91d6);
  box-shadow:
    0 0 6px var(--accent),
    0 0 14px var(--accent-soft);
  animation: brushGlow 3.2s ease-in-out infinite;
}

@keyframes brushGlow {
  0%, 100% { opacity: 0.78; }
  50%      { opacity: 1; }
}

/* Botón de borrar chat — discreto, aparece al hover o en el item activo. */
.chat-delete {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.18s ease, transform 0.18s ease,
              background 0.18s ease, color 0.18s ease;
}

.chat-item:hover .chat-delete,
.chat-item.active .chat-delete {
  opacity: 0.6;
  transform: scale(1);
}

.chat-delete:hover {
  opacity: 1;
  background: rgba(220, 80, 80, 0.16);
  color: #f1657f;
}

.sidebar-footer {
  padding: 10px;
  border-top: 1px solid var(--border);
}

.user-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.user-avatar {
  width: 30px;
  height: 30px;
  flex: 0 0 30px; /* no se encoge con nombres largos */
  background: #dcdeb7;
  color: #555555;
  font-weight: 600;
  font-size: 14px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden; /* recorta la foto de perfil al círculo */
}
.user-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.user-name {
  font-weight: 500;
  font-size: 13px;           /* antes heredaba un tamaño mayor: quedaba feo */
  line-height: 1.3;
  min-width: 0;              /* permite que el ellipsis funcione en flex */
  overflow: hidden;
  text-overflow: ellipsis;   /* nombres/correos largos → "María Fernanda G…" */
  white-space: nowrap;
}

.sidebar-logo {
  width: 36px;
  height: 36px;
  object-fit: contain;
  display: block;
  margin-bottom: 10px;
}

/* MAIN */
.main-content {
  flex: 1;
  /* 26-ago. Un ítem flex tiene `min-width: auto`: se niega a encogerse por
     debajo del ancho mínimo de su contenido. En el teléfono eso hacía que
     .main-content midiera 402px dentro de un viewport de 352 y arrastrara
     toda la app a scroll horizontal. Con 0 puede encogerse y el contenido
     interno se acomoda con sus propios max-width. En escritorio no cambia
     nada: allí sobra espacio y este mínimo nunca se alcanza. */
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* HEADER */
.header {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 10px;
  border-bottom: 1px solid var(--border);
}

.header-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.header-logo {
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.header-title {
  font-family: var(--font-brand);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: 0.3px;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 8px;
}

.hamburger-btn {
  background: none;
  border: none;
  color: var(--text);
  font-size: 20px;
  cursor: pointer;
  /* 29-ago: el glifo ☰ medía ~20x20px de superficie tocable. En un teléfono
     eso obliga a apuntar con la yema exactamente al borde superior de la
     pantalla, que es justo donde iOS captura sus propios gestos: el dedo
     acertaba a la barra de estado del sistema y no al botón. 44x44 es el
     mínimo que pide Apple (y 48 el que pide Android; 44 con el aire del
     header cumple los dos). El glifo NO cambia de tamaño: sólo crece la
     superficie que lo rodea, centrada. */
  min-width: 44px;
  min-height: 44px;
  align-items: center;
  justify-content: center;
  padding: 0;
  /* Compensación óptica: al pasar de 20px a 44px de ancho, el glifo se
     separaba del borde izquierdo más que el resto de la cabecera. Este
     margen negativo lo devuelve a su sitio sin achicar el área tocable. Y el
     de la derecha evita que la marca SHIVAIA se corra 13px hacia el centro:
     los 11px que se solapan con ella no molestan —el título es un <span>, no
     un botón— y de paso agrandan el blanco tocable. */
  margin-left: -11px;
  margin-right: -11px;
}

.theme-btn,
.fx-btn {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  transition: background 0.2s ease, opacity 0.2s ease;
}

.theme-btn:hover,
.fx-btn:hover {
  background: rgba(128, 128, 128, 0.12);
}

/* CHAT AREA */
.chat-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* MESSAGES */
.messages {
  flex: 1;
  overflow-y: auto;
  display: flex;
  justify-content: center;
  padding: 60px 20px 24px;
}

.messages-inner {
  width: 100%;
  max-width: 720px;
  display: flex;
  flex-direction: column;
  gap: 28px;
  min-height: 100%;
  /* CORTE 6 UI (Opus 4.8) — aire antes del input. Va AQUÍ (en el contenido),
     no en .messages: el padding-bottom del contenedor con scroll se ignora
     al llegar al final, por eso los 170px anteriores no se notaban. */
  padding-bottom: 110px;
}

/* Aparición suave de cada mensaje */
.message {
  display: flex;
  line-height: 1.6;
  animation: msg-in 0.35s ease both;
  /* FIX (13 ago): el tooltip de la fila de acciones (Copiar, Escuchar,
     Guardar) caía DETRÁS del mensaje siguiente. La animación de entrada deja
     un transform aplicado, y un transform crea contexto de apilamiento: el
     z-index del tooltip quedaba encerrado dentro de su propio mensaje, así
     que los mensajes posteriores lo tapaban por orden de documento. Se
     levanta el mensaje entero mientras se lo está mirando. */
  position: relative;
}
.message:hover,
.message:focus-within {
  z-index: 5;
}

@keyframes msg-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.user-message {
  justify-content: flex-end;
}

/* Columna que envuelve contenido + fila de acciones (hora/copiar), para que
   las acciones queden DEBAJO de la burbuja y no al costado (el .message es
   flex en fila). */
.msg-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-width: 100%;
}
.user-message .msg-col {
  max-width: 75%;
  align-items: flex-end;
}

.user-message .message-content {
  background: var(--bubble-user);
  padding: 12px 16px;
  border-radius: 18px;
  border: 1px solid var(--border);
  max-width: 100%;
}

/* Fila de acciones: hora + copiar + escuchar + guardar en el Cuaderno.

   CAMBIO DEL 17-AGO. Antes era `opacity: 0` y solo se revelaba al pasar el
   cursor. Dos problemas: en pantalla táctil el hover directamente no
   existe, así que en el teléfono los iconos no aparecían nunca hasta que
   uno tocaba por casualidad; y aun en escritorio, obligaba a descubrir un
   gesto. Pensando en gente mayor, ahora están SIEMPRE presentes, muy
   tenues, y se afirman al posar el cursor.

   El mecanismo se conserva entero: lo único que cambia es el valor de
   reposo, de 0 a 0.45. No hay riesgo de salto de maquetación porque
   `min-height: 18px` ya reservaba el sitio aunque los iconos fueran
   invisibles.

   El hover se envuelve en @media (hover: hover) a propósito: en algunos
   Android el estado de :hover se queda PEGADO después de tocar, y la fila
   quedaba iluminada al 1 para siempre en un mensaje al azar. Con la
   consulta, en táctil ese camino ni existe. `:focus-within` queda fuera de
   la consulta: es la ruta del teclado y debe funcionar en todas partes. */
.message-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  min-height: 18px;
  opacity: 0.45;
  transition: opacity 0.18s ease;
}
@media (hover: hover) and (pointer: fine) {
  .message:hover .message-actions {
    opacity: 1;
  }
}
.message:focus-within .message-actions {
  opacity: 1;
}
.user-message .message-actions {
  justify-content: flex-end;
}
.message-time {
  font-size: 11px;
  color: var(--muted);
  user-select: none;
}
.message-copy {
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 2px 5px;
  border-radius: 6px;
  transition: background 0.15s ease, color 0.15s ease;
}
.message-copy:hover {
  background: rgba(128, 128, 128, 0.14);
  color: var(--text);
}
.message-copy.copied {
  color: #4caf7d;
}

.assistant-message {
  gap: 12px;
  align-items: flex-start;
}

.assistant-icon {
  width: 32px;
  height: 32px;
  background: var(--accent);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  flex-shrink: 0;
  margin-top: 2px;
}

.message-content {
  max-width: 100%;
  line-height: 1.7;
  font-size: 15px;
}

/* SHIVAIA sin globo */
.assistant-message .message-content {
  background: transparent !important;
  padding: 0 !important;
  border: none !important;
  border-radius: 0 !important;
  box-shadow: none !important;
}

/* Indicador de espera: respiración serena */
.thinking {
  color: var(--muted);
  letter-spacing: 3px;
  animation: breathe 1.6s ease-in-out infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 0.9; }
}

/* Indicador de espera: red nodal animada (motivo del logo) */
.nodal-loader { padding: 4px 0 2px; }
.nodal-loader svg { overflow: visible; display: block; }
.nodal-loader circle {
  fill: var(--nodal);
  transform-box: fill-box;
  transform-origin: center;
  filter: drop-shadow(0 0 2.4px var(--nodal));
  animation: nodepulse 1.9s ease-in-out infinite;
}
.nodal-loader line {
  stroke: var(--nodal);
  stroke-width: 0.8;
  opacity: 0.14;
  animation: linepulse 1.9s ease-in-out infinite;
}
@keyframes nodepulse {
  0%, 100% { opacity: 0.22; transform: scale(0.6); }
  50%      { opacity: 1;    transform: scale(1.2); }
}
@keyframes linepulse {
  0%, 100% { opacity: 0.08; }
  50%      { opacity: 0.42; }
}
@media (prefers-reduced-motion: reduce) {
  .nodal-loader circle, .nodal-loader line { animation: none; opacity: 0.6; transform: none; }
}

/* RESPUESTAS ESTRUCTURADAS
   (los colores heredan del tema — legibles en claro Y oscuro) */
.assistant-message-structured {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.assistant-message-structured .section {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.assistant-message-structured h3 {
  font-size: 14px;
  font-weight: 600;
  margin: 0;
  color: var(--text);
  letter-spacing: 0.2px;
}

.assistant-message-structured p {
  font-size: 15px;
  line-height: 1.75;
  margin: 0;
  color: var(--text);
  opacity: 0.92;
}

.assistant-message-structured strong {
  font-weight: 600;
}

.assistant-message-structured em {
  font-style: italic;
  opacity: 0.95;
}

/* Regla fina: reemplaza los guiones crudos ("---") por un hilo del color
   nodal de SHIVAIA que se desvanece en ambos extremos — un trazo de luz,
   no una línea dura. Sobrio por defecto (opacidad baja); se lee igual en
   claro y oscuro porque --nodal cambia con el dial. */
.assistant-message-structured hr.rule {
  border: none;
  height: 1px;
  width: 100%;
  margin: 2px 0;
  background: linear-gradient(90deg,
      transparent 0%,
      var(--nodal) 50%,
      transparent 100%);
  opacity: 0.28;
  transition: opacity 0.5s ease;
}

/* Durante el efecto de escritura, el hilo no "salta" antes de tiempo: queda
   invisible y se enciende suave al terminar de revelarse la respuesta. */
.assistant-message-structured.revealing hr.rule {
  opacity: 0;
}

.assistant-message-structured ul {
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.assistant-message-structured li {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text);
  opacity: 0.92;
}

.assistant-message-structured li::marker {
  color: var(--accent);
}

/* INPUT */
.input-area {
  display: flex;
  justify-content: center;
  padding: 16px 20px 24px;
  /* CORTE 6 UI (Opus 4.8) — sin línea dura: el degradado separa con suavidad
     para que el final del mensaje no choque contra una regla horizontal. */
  background: linear-gradient(to top, var(--bg), transparent);
}

.input-wrapper {
  width: 100%;
  max-width: 720px;
  display: flex;
  gap: 10px;
  background: var(--panel);
  padding: 12px 16px;
  border-radius: 16px;
  border: 1px solid var(--border);
  align-items: flex-end;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.input-wrapper:focus-within {
  /* CORTE 6 UI (Opus 4.8) — opción 4 "convivencia": se conserva el anillo
     azul de foco y se le suma el resplandor dorado hacia abajo. */
  border-color: var(--accent);
  box-shadow:
    0 0 0 3px var(--accent-soft),
    0 20px 56px -14px rgba(224, 169, 43, 0.52);
}

#message-input {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--text);
  outline: none;
  resize: none;
  font-size: 15px;
  line-height: 1.6;
  min-height: 24px;
  max-height: 200px;
  font-family: inherit;
  padding: 2px 0;
}

.btn-send {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  background: #2d2d2d;
  color: white;
  transition: all 0.2s ease;
  font-size: 28px;
  font-weight: 900;
  line-height: 1;
  transform: translateY(-1px);
}

.btn-send:hover {
  transform: scale(1.08);
  background: var(--accent);
}

.btn-send:active {
  transform: scale(0.95);
}

/* Estado "detener": el mismo botón cambia a ■ mientras se espera respuesta. */
.btn-send.is-stop {
  background: #b23b3b;
  font-size: 18px;
}

.btn-send.is-stop:hover {
  background: #d14b4b;
  transform: scale(1.08);
}

/* EMPTY STATE */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  flex: 1;
  gap: 10px;
  padding: 60px 20px;
  color: var(--muted);
}

.empty-state h2 {
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
}

.empty-state p {
  font-size: 14px;
}

/* OVERLAY */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 5;
}

.sidebar-overlay.visible {
  display: block;
}

/* MOBILE */
@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    transform: translateX(-100%);
    z-index: 10;
  }

  .sidebar.open {
    transform: translateX(0);
  }
}

/* =========================
   THEME: LIGHT MODE — ELIMINADO (era código muerto)
   La luminosidad la maneja el dial (applyLuminosity en app.js), que reescribe
   las variables CSS inline sobre <body>; NUNCA se agrega la clase `light`, así
   que estas reglas no disparaban jamás. Si en el futuro se quisiera un modo
   claro con estilos propios (no sólo variables), togglear una clase desde
   applyLuminosity y reintroducir aquí únicamente lo que el dial no pueda
   expresar con variables.
   ========================= */

/* =========================
   CORTE 6 UI (Opus 4.8) — ENTRADA y TOOLTIP
   ========================= */

/* Entrada: destello de luz dorada + aparición suave de toda la app */
#enter-flash {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background: radial-gradient(circle at 50% 40%, rgba(224, 169, 43, 0.26), transparent 62%);
  opacity: 0;
}
body.entering #enter-flash { animation: enter-bloom 1200ms ease-out forwards; }
body.entering .app       { animation: app-enter 1500ms ease-out both; }
@keyframes enter-bloom {
  0%   { opacity: 0; }
  20%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes app-enter {
  0%   { opacity: 0; transform: scale(0.992); }
  100% { opacity: 1; transform: scale(1); }
}

/* Tooltip propio, redondeado (reemplaza el title nativo cuadrado).
   El texto se inyecta por data-tip desde app.js. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: max-content;
  max-width: 230px;
  white-space: normal;
  background: var(--soft);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 8px 11px;
  border-radius: 10px;
  font-size: 12px;
  line-height: 1.45;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.22);
  opacity: 0;
  transform: translateY(-4px);
  pointer-events: none;
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 40;
}
[data-tip]:hover::after,
[data-tip]:focus-visible::after { opacity: 1; transform: translateY(0); }

/* FIX (13 ago) — fila de acciones (Copiar, Escuchar, Guardar).
   1) Se salían de la pantalla. El tooltip general ancla su borde DERECHO al
      del botón y crece hacia la izquierda; en los mensajes de Shivaia esos
      botones viven pegados al margen izquierdo, así que el texto se cortaba
      contra el borde. Ahí se ancla al revés: crece hacia adentro. En los
      mensajes del usuario las acciones están a la derecha y el anclaje
      original ya era el correcto, no se toca.
   2) Menos peso visual: son etiquetas de apoyo, no avisos. Un punto más
      chico y sombra apenas insinuada.

   ENMIENDA (18 ago) — el texto de dentro del globo estaba en var(--muted).
   Aquello se decidió cuando la fila de acciones vivía en opacity: 0 y solo
   asomaba al pasar el cursor: el tooltip era un accidente raro y convenía
   que pesara poco. Desde el 17 de ago la fila está SIEMPRE visible (0.45 en
   escritorio, 0.6 en móvil), así que sus tooltips se leen a diario y el
   gris sobre el fondo suave no daba contraste suficiente. Vuelve a
   var(--text), que es lo que usa la regla genérica de arriba y, por tanto,
   el tooltip del micrófono: los dos globos hablan ahora con la misma voz.
   Lo que NO cambia y es deliberado: el cuerpo más chico (11.5px), el
   relleno ajustado (6px 9px) y la sombra leve. El problema era de
   legibilidad, no de tamaño. */
.assistant-message .message-actions [data-tip]::after {
  left: 0;
  right: auto;
}
.message-actions [data-tip]::after {
  color: var(--text);
  font-size: 11.5px;
  padding: 6px 9px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}

/* Tooltip del nombre de chat (sidebar). El contenedor .sidebar-chats tiene
   overflow-y:auto, lo que fuerza recorte horizontal: un ::after anclado al
   item queda cortado por el scroll. Solución: un tooltip FLOTANTE único,
   position:fixed, colgado del <body> y posicionado por JS al hover (queda
   fuera de todo contenedor con overflow). Mismo look que .btn (data-tip):
   fondo suave, borde redondeado, sombra. Muestra el nombre completo
   envuelto en varias líneas, nunca con "…". */
#chat-tip-float {
  position: fixed;
  z-index: 80;
  width: max-content;
  max-width: 230px;
  white-space: normal;
  background: var(--soft);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 8px 11px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: bold;
  line-height: 1.45;
  box-shadow: 0 10px 26px rgba(0, 0, 0, 0.22);
  pointer-events: none;
  opacity: 0;
  transform: translateX(-4px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
#chat-tip-float.visible { opacity: 1; transform: translateX(0); }
@media (prefers-reduced-motion: reduce) {
  #chat-tip-float { transition: opacity 0.18s ease; transform: none; }
}

/* FIX (6 jul): la flecha de enviar vive pegada al borde inferior de la
   ventana — su tooltip (enviar ↑ / detener ■ / cuota en gris con contador)
   salía hacia abajo y el borde lo cortaba, en escritorio y en móvil.
   Se muestra ARRIBA del botón. */
.btn-send[data-tip]::after {
  top: auto;
  bottom: calc(100% + 8px);
  transform: translateY(4px);
}
.btn-send[data-tip]:hover::after,
.btn-send[data-tip]:focus-visible::after { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  body.entering #enter-flash { animation: none; opacity: 0; }
  body.entering .app { animation: none; }
}

/* CORTE 6 UI (Opus 4.8) — efecto de escritura: ocultar viñetas/ítems de lista
   hasta que su texto empieza a revelarse (evita ver viñetas vacías al pie). */
.revealing li { display: none; }
.revealing li.revealed { display: list-item; }

/* =========================================================
   HEADER (Opus 4.8) — dial de luminosidad
   ========================================================= */

/* Hamburguesa: solo en móvil (en escritorio la barra lateral es fija) */
.hamburger-btn { display: none; }

/* Dial de luminosidad (reemplaza el botón claro/oscuro) */
.lum-dial { display: inline-flex; align-items: center; gap: 7px; }
.lum-gly { font-size: 12px; color: var(--muted); width: 13px; text-align: center; }
.lum-dial input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 104px;
  height: 4px;
  border-radius: 3px;
  background: linear-gradient(to right, #45474f, #c9c9c9);
  cursor: pointer;
  outline: none;
}
.lum-dial input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 13px; height: 13px; border-radius: 50%;
  background: #e0a92b;
  border: 2px solid rgba(255, 255, 255, 0.55);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.lum-dial input[type="range"]::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: #e0a92b;
  border: 2px solid rgba(255, 255, 255, 0.55);
}

@media (max-width: 768px) {
  .hamburger-btn { display: inline-flex; }
  .lum-dial input[type="range"] { width: 74px; }
  /* Que el cuadro de chat no quede pegado/cortado contra el borde inferior. */
  .input-area { padding-bottom: max(20px, env(safe-area-inset-bottom)); }
  /* 17-ago: en el teléfono no hay hover, así que el valor de reposo es el
     único que se ve nunca. Sube un punto para que se lea a pleno sol sin
     tocar el escritorio. Si en el A05 sigue flojo, el número a mover es
     este, no el de arriba. */
  .message-actions { opacity: 0.6; }
}

/* =========================
   MODAL DE CONFIRMACIÓN (in-app, reemplaza a window.confirm)
   Hereda el tema del dial de luminosidad (var(--panel/border/text/muted/accent)).
   ========================= */
.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(0, 0, 0, 0.5);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.modal-overlay.visible { opacity: 1; }

.modal-panel {
  width: 100%;
  max-width: 380px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 18px;
  padding: 22px 22px 18px;
  box-shadow:
    0 24px 64px -16px rgba(0, 0, 0, 0.55),
    0 0 0 1px rgba(255, 255, 255, 0.03),
    0 0 44px -12px var(--accent-soft);
  transform: translateY(8px) scale(0.98);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.modal-overlay.visible .modal-panel {
  transform: translateY(0) scale(1);
}

.modal-title {
  font-family: var(--font-brand);
  font-size: 18px;
  letter-spacing: 0.3px;
  color: var(--text);
  margin-bottom: 8px;
}
.modal-message {
  font-size: 14px;
  line-height: 1.55;
  color: var(--muted);
  margin-bottom: 20px;
}
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}
.modal-btn {
  border: none;
  cursor: pointer;
  font-size: 14px;
  padding: 9px 16px;
  border-radius: 10px;
  transition: background 0.15s ease, transform 0.12s ease,
              color 0.15s ease, filter 0.15s ease;
}
.modal-btn:active { transform: scale(0.97); }
.modal-cancel {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
}
.modal-cancel:hover {
  background: rgba(128, 128, 128, 0.12);
}
.modal-confirm {
  background: var(--accent);
  color: #fff;
}
.modal-confirm:hover { filter: brightness(1.08); }
.modal-confirm.danger { background: #b23b3b; }
.modal-confirm.danger:hover { background: #d14b4b; }

@media (prefers-reduced-motion: reduce) {
  .modal-overlay, .modal-panel { transition: none; }
}

/* ==========================================================
   LOGIN — FASE 1 (modal de iniciar sesión / crear cuenta)
   Hereda la estética del modal (panel, dial de luminosidad).
   ========================================================== */

.login-panel { max-width: 360px; }

.user-row { cursor: pointer; border-radius: 10px; transition: background 0.15s ease; }
.user-row:hover { background: rgba(128, 128, 128, 0.10); }

/* Botón oficial "Continuar con Google" (fondo claro, spec de Google) */
.google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  padding: 10px 14px;
  border-radius: 10px;
  background: #ffffff;
  color: #1f1f1f;
  border: 1px solid #dadce0;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  cursor: pointer;
  transition: box-shadow 0.15s ease, background 0.15s ease;
}
.google-btn:hover { background: #f7f8f8; box-shadow: 0 1px 3px rgba(0,0,0,0.15); }
.google-btn svg { flex: 0 0 auto; }

.login-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0 14px;
  color: var(--muted);
  font-size: 12px;
}
.login-divider::before,
.login-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.login-field { display: block; margin-bottom: 12px; }
.login-field span {
  display: block;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 5px;
}
.login-field input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.login-field input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.login-error {
  min-height: 18px;
  font-size: 12.5px;
  color: #d14b4b;
  margin: 2px 0 10px;
}

.login-submit { width: 100%; padding: 11px 16px; }
.login-submit:disabled { opacity: 0.6; cursor: default; }

.login-switch {
  display: block;
  width: 100%;
  margin-top: 12px;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
  text-align: center;
  transition: color 0.15s ease;
}
.login-switch:hover { color: var(--text); }

/* ============================================================
   SHIVAIA 1.5 — M2 UI: CUOTA AGOTADA
   1) Botón de enviar en gris, no clickeable (disabled + .is-quota).
   2) Aviso de cuota dentro del modal de login (visitantes).
   3) Pop-up SHIVAIA de agradecimiento (usuarios con sesión).
   ============================================================ */

/* Envío deshabilitado por cuota: gris, sin hover, sin cursor de acción. */
.btn-send:disabled,
.btn-send.is-quota {
  background: var(--soft);
  color: var(--muted);
  cursor: not-allowed;
  opacity: 0.55;
}
.btn-send:disabled:hover,
.btn-send.is-quota:hover {
  background: var(--soft);
  transform: translateY(-1px); /* neutraliza el scale del hover normal */
}
.btn-send:disabled:active { transform: translateY(-1px); }

/* Aviso de cuota arriba del modal de login (visitantes). */
.quota-notice {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text);
  background: var(--accent-soft);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 16px;
}

/* Botón "mantener sesión cerrada": mismo lenguaje discreto que login-switch. */
.login-switch.quota-keep {
  margin-top: 2px;
  opacity: 0.85;
}

/* Pop-up de cuota agotada con sesión: logo + agradecimiento, centrado. */
.quota-panel {
  max-width: 380px;
  text-align: center;
}
.quota-logo {
  width: 72px;
  height: 72px;
  object-fit: contain;
  display: block;
  margin: 0 auto 14px;
  opacity: 0.95;
}
.quota-title {
  font-family: var(--font-brand);
  font-size: 20px;
}
.quota-actions {
  justify-content: center;
}

/* (14-sep · P6) Las dos salidas del aviso de cuota, visibles.
   Eran `.modal-btn` a secas —gris sobre panel casi blanco— y
   desaparecían al lado del «Volver al chat» sólido. Contorno dorado:
   se leen como acción y no compiten con el primario. */
.quota-actions #quota-codigo,
.quota-actions #quota-planes {
  background: var(--nodal-soft, rgba(185, 136, 20, 0.12));
  border: 1px solid var(--nodal, #b98814);
  color: var(--nodal, #b98814);
  font-weight: 500;
}
.quota-actions #quota-codigo:hover,
.quota-actions #quota-planes:hover {
  background: var(--nodal, #b98814);
  color: #fff;
}


/* ============================================================================
   ============================================================================
   SHIVAIA — CAPA "EL HORIZONTE"   (agosto 2026)

   Sistema visual nuevo: la interfaz deja de ser plana y pasa a tener
   espesor. Una sola fuente de luz —una línea dorada bajo la cabecera— y
   todo lo demás es consecuencia: cada plano tiene canto encendido arriba,
   filo oscuro abajo y sombra larga. Sin vidrio, sin desenfoque, sin borde
   parejo.

   POR QUÉ ESTÁ AL FINAL Y NO INTEGRADO ARRIBA
   Todo lo anterior en este archivo son meses de decisiones finas, muchas
   con su historia escrita al lado. Reescribirlo era el riesgo que no vale
   la pena correr. Esta capa REDEFINE lo que necesita y no toca el resto.
   Para revertir: borrar desde esta línea hasta el final del archivo.

   QUÉ NECESITA DE app.js
   · applyLuminosity() reescrito, porque el claro nuevo es CÁLIDO y los
     cantos de luz se dan vuelta y pasan a ser sombras. Sin eso, el dial
     funciona a medias: el extremo oscuro se ve bien, el claro no.
   · El armado del velo del sidebar (clase `veil-armed` en <body>).
   · Las etiquetas de la fila de acciones (span.lbl) y el botón de plegado.
   Lo demás —incluido el tamaño de la red nodal— se resuelve acá.

   NO REQUIERE CAMBIOS DE MARCADO. Todo lo que parecía necesitar un <div>
   nuevo se resuelve con pseudo-elementos sobre lo que ya existe.
   ============================================================================
   ============================================================================ */

/* ---------------------------------------------------------------------------
   TOKENS DE PROFUNDIDAD
   Segundo :root, a propósito: gana sobre el de arriba sin borrarlo.
   Los valores son la semilla del extremo oscuro. applyLuminosity() los
   reescribe todos sobre <body> en cuanto se mueve el dial.
   --------------------------------------------------------------------------- */
:root {
  --sunk: #050506;              /* plano hundido: el sidebar */
  --bg-deep: #08080a;           /* fondo del plano de lectura, abajo */

  --hollow-a: #15151a;          /* relieve de botón, arriba */
  --hollow-b: #0d0d10;          /* relieve de botón, abajo */
  --panel-a: #16161b;           /* campo de escritura, arriba */
  --panel-b: #0e0e12;           /* campo de escritura, abajo */
  --bubble-a: #17171d;          /* globo del usuario, arriba */
  --bubble-b: #101014;          /* globo del usuario, abajo */
  --send-a: #2b2d36;            /* botón de enviar, centro */
  --send-b: #14151a;            /* botón de enviar, borde */

  --edge: rgba(255, 255, 255, 0.085);        /* canto de luz superior */
  --edge-strong: rgba(255, 255, 255, 0.14);
  --filo: rgba(0, 0, 0, 0.8);                /* filo oscuro inferior */
  --ring: rgba(0, 0, 0, 0.05);               /* contorno: crece con la luz */
  --cast: rgba(0, 0, 0, 0.95);               /* sombra proyectada */

  --veil-a: rgba(5, 5, 6, 0.74);             /* diafragma del sidebar */
  --veil-b: rgba(5, 5, 6, 0.90);

  --hz-core: rgba(224, 169, 43, 0.52);       /* el horizonte */
  --hz-soft: rgba(224, 169, 43, 0.14);
  --hz-dot: rgba(242, 202, 107, 0.8);
  --fall: rgba(224, 169, 43, 0.055);         /* caída de luz bajo el horizonte */
  --sky: rgba(74, 102, 176, 0.15);           /* halo azul loto desde arriba */
  --phone-sky: #0a0c18;         /* teléfono: el negro se abre hacia azul oscuro */

  --hair: rgba(236, 236, 236, 0.13);         /* canto vertical del plano */
  --hair-soft: rgba(236, 236, 236, 0.07);
  --nodal-hair: rgba(224, 169, 43, 0.34);
  --nodal-glow: rgba(224, 169, 43, 0.32);
  --tool-hover: rgba(255, 255, 255, 0.05);
  --peligro: #f1657f;           /* eliminar: lo único irreversible */
  --peligro-soft: rgba(241, 101, 127, 0.34);
}

/* ---------------------------------------------------------------------------
   EL PLANO HUNDIDO — sidebar
   No está al lado del plano de lectura: está detrás. Fondo más oscuro, un
   leve retroceso, y la sombra que el plano de lectura le proyecta encima.
   --------------------------------------------------------------------------- */
.sidebar {
  position: relative;
  background: var(--sunk);
  border-right: none;
}

/* 26-ago · REGRESIÓN DEL TELÉFONO, arreglada aquí y no arriba.
   La regla de encima (`position: relative`, necesaria para que el plano
   hundido apile su velo y su ::after) tiene la misma especificidad que la
   del bloque MOBILE de la línea ~764 y aparece DESPUÉS: las media queries
   no suman especificidad, así que ganaba el orden. Resultado: en el
   teléfono el sidebar seguía ocupando su lugar en el flujo aunque el
   translateX(-100%) lo sacara de la vista. Ocupaba 211px, .main-content
   402, y la app medía 613 dentro de un viewport de 352 — todo flotando,
   con scroll lateral.
   Se restituye el fuera-de-flujo DESPUÉS de la regla que lo pisaba, que es
   el único lugar donde queda a salvo del orden. No se toca el bloque de la
   línea 764 (sigue siendo válido y es el que documenta la intención) ni el
   `position: relative` del escritorio. */
@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    z-index: 10;
  }
}

/* El retroceso, aplicado a los tres bloques que ya existen para no tener que
   envolverlos en un contenedor nuevo. Solo horizontal: sin escalado, que
   abriría huecos verticales entre ellos. */
.sidebar-header,
.sidebar-chats,
.sidebar-footer {
  transform: translateX(-7px);
}

.sidebar-header,
.sidebar-footer {
  border-color: transparent;
}

/* EL DIAFRAGMA. Aire, no vidrio: es el propio fondo del plano hundido,
   más denso hacia la derecha. Al acercar el cursor se retira.

   ARRANCA APAGADO (sin `veil-armed`). Al entrar, el sidebar se lee entero
   y nadie piensa que está mal dibujado; el velo se arma después de la
   primera interacción, cuando el usuario ya sabe qué hay ahí. Mover el
   ratón no cuenta como interactuar: eso se decide en app.js. */
.sidebar::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg,
      var(--veil-a) 0%, var(--veil-a) 60%, var(--veil-b) 100%);
  transition: opacity 0.42s cubic-bezier(0.4, 0, 0.2, 1);
}
body.veil-armed .sidebar::after { opacity: 1; }
body.veil-armed .sidebar:hover::after,
body.veil-armed .sidebar:focus-within::after { opacity: 0.05; }

/* En el teléfono no hay cursor: un velo que solo se retira al acercar el
   ratón dejaría el sidebar tenue para siempre. Allí no existe. */
@media (max-width: 768px) {
  .sidebar::after { display: none; }
  .sidebar-header, .sidebar-chats, .sidebar-footer { transform: none; }
}

/* Botones del sidebar con relieve. "Cuaderno del Caminante" lo inyecta
   cuaderno-ui.js con la misma clase, así que los dos quedan iguales: si uno
   tiene relieve y el otro no, el segundo se lee como colgado. */
.btn-new-chat {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  border: none;
  box-shadow: inset 0 1px 0 var(--edge), 0 10px 18px -12px var(--cast);
  padding: 11px 14px;
  transition: box-shadow 0.25s ease, transform 0.18s ease;
}
.btn-new-chat:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 12px 22px -12px var(--cast),
              0 0 0 1px var(--nodal-hair);
  transform: translateY(-1px);
}

/* "Cuaderno del Caminante" lo inyecta cuaderno-ui.js con clase propia y con
   estilos propios colgados del <head> en tiempo de ejecución. Se lo prefija
   con .sidebar para ganar por especificidad (0,2,0) sin abrir ese módulo.
   Mismo relieve y mismo filo dorado al posar el cursor que "+ Nuevo chat":
   si uno tiene cuerpo y el otro no, el segundo se lee como colgado. */
.sidebar .btn-cuaderno {
  width: 100%;
  margin-top: 9px;
  padding: 11px 14px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-weight: 500;
  font-size: 15px;
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 10px 18px -12px var(--cast);
  transition: box-shadow 0.25s ease, transform 0.18s ease;
}
.sidebar .btn-cuaderno:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 12px 22px -12px var(--cast),
              0 0 0 1px var(--nodal-hair);
  transform: translateY(-1px);
}

.user-avatar {
  background: var(--hollow-a);
  color: var(--muted);
  box-shadow: inset 0 1px 0 var(--edge);
}

/* ---------------------------------------------------------------------------
   EL PLANO DE LECTURA — elevado sobre el hundido
   --------------------------------------------------------------------------- */
.main-content {
  position: relative;
  background: linear-gradient(180deg, var(--bg) 30%, var(--bg-deep) 100%);
  box-shadow: -30px 0 60px -18px var(--cast);
}

/* Canto vertical de luz: el borde por donde el plano se separa del hundido.
   Se enciende arriba, donde nace el horizonte, y se apaga hacia abajo. */
.main-content::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 1px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(180deg,
      transparent 0%,
      var(--hair) 18%,
      var(--nodal-hair) 34%,
      var(--hair-soft) 64%,
      transparent 100%);
}
@media (max-width: 768px) {
  .main-content::before { display: none; }
  .main-content { box-shadow: none; }
}

/* ---------------------------------------------------------------------------
   LA CABECERA Y EL HORIZONTE
   La banda venía de 50px; la maqueta pedía 62 y quedaba pesada. 54 es el
   punto medio. El interletrado de la marca es lo que más la ensanchaba
   (la maqueta traía .14em, casi diez veces el actual): queda en .07em.
   --------------------------------------------------------------------------- */
.header {
  position: relative;
  height: 54px;
  padding: 0 34px;
  border-bottom: none;
  z-index: 2;
}

.header-title {
  font-size: 17.5px;
  letter-spacing: 0.07em;
}

/* EL HORIZONTE. Reemplaza el borde inferior de la cabecera: ya no es una
   regla gris que corta, sino la única fuente de luz de la pantalla. Los
   tres puntos posados encima van en la misma capa, como radiales. */
.header::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  pointer-events: none;
  z-index: 2;
  /* La línea sola, sin los tres puntos de luz que llevaba la maqueta: en
     pantalla real se leían como suciedad, no como detalle. */
  background: linear-gradient(90deg,
      transparent 0%, var(--hz-soft) 10%, var(--hz-core) 50%,
      var(--hz-soft) 90%, transparent 100%) 0 1px / 100% 1px no-repeat;
}
@media (max-width: 768px) {
  /* 29-ago: con viewport-fit=cover la página empieza en y=0, es decir DEBAJO
     de la muesca. La cabecera reserva ese alto como relleno superior y
     conserva sus 50px de contenido: por eso `min-height` suma el inset en vez
     de sustituirlo, y `height` pasa a auto (con box-sizing:border-box, una
     altura fija de 50px con relleno superior aplastaría el contenido a 3px).
     En un teléfono sin muesca el inset es 0 y la cabecera mide los 50px de
     siempre, byte por byte. */
  .header {
    padding: env(safe-area-inset-top) 14px 0;
    height: auto;
    min-height: calc(50px + env(safe-area-inset-top));
    flex-shrink: 0;
  }
  .header-title { font-size: 16.5px; }

  /* La barra lateral es `position: fixed` en móvil: no hereda el relleno de
     .app ni el de la cabecera, así que reserva sus propios márgenes o el
     logo se mete bajo la muesca y la fila de usuario bajo la barra de inicio. */
  .sidebar {
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
  }
}

/* ---------------------------------------------------------------------------
   LA LUZ QUE CUELGA DEL HORIZONTE
   Vive en .chat-area y NO dentro de .messages: si estuviera en el
   contenedor con scroll, la luz se iría hacia arriba al desplazarse. Es la
   fuente, no parte del contenido.
   --------------------------------------------------------------------------- */
.chat-area { position: relative; }

.chat-area::before,          /* caída dorada */
.chat-area::after {          /* halo azul loto */
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  pointer-events: none;
  z-index: 0;
}
.chat-area::before {
  height: 150px;
  background: linear-gradient(180deg, var(--fall) 0%, transparent 100%);
}
.chat-area::after {
  height: 460px;
  background: radial-gradient(120% 100% at 50% -6%, var(--sky) 0%, transparent 66%);
}
.messages { position: relative; z-index: 1; }
.input-area { position: relative; z-index: 2; }

/* En el teléfono el azul se abre más: el degradé de negro a azul oscuro es
   parte de la identidad de la pantalla chica. */
@media (max-width: 768px) {
  .main-content {
    background: linear-gradient(180deg, var(--phone-sky) 0%, var(--bg) 46%, var(--bg-deep) 100%);
  }
  .chat-area::after { height: 400px; }
}

/* ---------------------------------------------------------------------------
   MENSAJES
   El cuerpo del texto NO cambia: 15px / 1.75 / opacidad .92, como estaba.
   Lo que cambia es la luz, no la letra. Lo único que se toca del texto es
   la lista: sin viñetas, las líneas se leen como párrafos.
   --------------------------------------------------------------------------- */
.user-message .message-content {
  background: linear-gradient(180deg, var(--bubble-a), var(--bubble-b));
  border: none;
  border-radius: 16px 16px 5px 16px;
  padding: 14px 18px;
  box-shadow: inset 0 1px 0 var(--edge),
              0 1px 0 var(--filo),
              0 22px 34px -22px var(--cast);
}

.assistant-message-structured ul {
  padding-left: 0;
  list-style: none;
  gap: 12px;
}
.assistant-message-structured li::marker { content: none; }

/* La regla de cierre: ya no se desvanece a los dos lados sino hacia la
   derecha, con un punto de luz en el arranque. Un trazo que empieza. */
.assistant-message-structured hr.rule {
  opacity: 1;
  height: 1px;
  background:
    radial-gradient(1.6px 1.6px at 0 0, var(--hz-dot), transparent 100%),
    linear-gradient(90deg,
      var(--nodal-hair) 0%,
      rgba(224, 169, 43, 0.10) 54%,
      transparent 100%);
}

/* ---------------------------------------------------------------------------
   FILA DE ACCIONES
   En escritorio la acción se dice con palabras: la columna es de 720px y
   sobra sitio incluso con la fecha completa. Medido contra los idiomas que
   más estiran —alemán y francés son los peores— la fila llega a ~500px.
   Por debajo de 900px, y en el teléfono, vuelve a icono solo, y ahí sí
   aparece el tooltip, que junto a la palabra sería redundante.

   Las etiquetas (span.lbl) y el botón de plegado los emite app.js. Si app.js
   todavía no está portado, esta parte simplemente no hace nada: los iconos
   siguen viéndose como siempre.
   --------------------------------------------------------------------------- */
.message-actions { gap: 2px; flex-wrap: wrap; }
.message-time { margin-right: 12px; white-space: nowrap; }

/* ESPECIFICIDAD: los botones de escuchar y de guardar traen sus propios
   estilos, INYECTADOS EN TIEMPO DE EJECUCIÓN por tts-ui.js y cuaderno-ui.js.
   Al colgarse del <head> después de este archivo, ganaban por orden con la
   misma especificidad (0,1,0) y devolvían el botón a su caja fija de 21x19
   con padding 0: la palabra quedaba dentro, pero recortada e invisible.
   Prefijar con .message-actions sube a (0,2,0) y gana siempre, sin depender
   del orden de carga ni obligar a tocar esos dos módulos, que están cerrados.
   El alto y el ancho se declaran `auto` a propósito: son justo lo que aquella
   caja fija imponía. */
.message-actions .message-copy,
.message-actions .message-tts,
.message-actions .message-cu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: auto;
  height: 30px;
  flex: 0 0 auto;
  line-height: 1;
  padding: 0 10px;
  border-radius: 8px;
  font-size: 12px;
  white-space: nowrap;
  /* `gap` y `padding` entran en la transición por el arreglo del tirón
     (28-ago, ver el bloque de más abajo): son las dos medidas que cambian
     al plegar, y sin ellas la fila se estrecha de golpe. */
  transition: color 0.18s ease, background 0.22s ease, box-shadow 0.22s ease,
              gap 0.26s cubic-bezier(0.2, 0, 0.1, 1),
              padding 0.26s cubic-bezier(0.2, 0, 0.1, 1);
}
.message-actions .message-copy:hover,
.message-actions .message-tts:hover,
.message-actions .message-cu:hover {
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge);
}
.message-actions .lbl {
  display: inline-block;
  line-height: 1;
  /* Mismo tratamiento que el ::before: se estrecha, no se apaga. */
  overflow: hidden;
  max-width: 22ch;
  opacity: 1;
  transition: max-width 0.26s cubic-bezier(0.2, 0, 0.1, 1),
              opacity 0.16s ease;
}

/* LA PALABRA DE ESCUCHAR Y GUARDAR NO PUEDE SER UN <span>.
   Los dos módulos reescriben `btn.innerHTML` cada vez que el botón cambia de
   estado (reposo, cargando, sonando, guardado). Cualquier hijo que se les
   añada desaparece en el siguiente repintado: por eso "Copiar" se veía y
   estas dos no.

   Un atributo, en cambio, sobrevive a innerHTML. Y no hace falta inventarlo:
   los módulos ya ponen `data-tip` con el texto TRADUCIDO. Se dibuja desde
   ahí, sin JavaScript, y como regalo la palabra sigue el estado del botón
   —Escuchar, Pausar, Cargando— sin una línea más.

   `order: 2` lo coloca después del icono: en un contenedor flex el
   pseudo-elemento es un ítem más, y el SVG queda en 0. */
.message-actions .message-copy::before,
.message-actions .message-tts::before,
.message-actions .message-cu::before {
  content: attr(data-tip);
  order: 2;
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  /* ARREGLO DEL TIRÓN (28-ago). Ver el bloque «EL PLEGADO, SIN TIRÓN» más
     abajo: la palabra ya no desaparece, se estrecha. */
  overflow: hidden;
  max-width: 22ch;
  opacity: 1;
  transition: max-width 0.26s cubic-bezier(0.2, 0, 0.1, 1),
              opacity 0.16s ease;
}
.message-actions .message-tts svg,
.message-actions .message-cu svg { order: 0; }

/* Con la palabra a la vista, el tooltip sobra. */
.message-actions .message-copy::after,
.message-actions .message-tts::after,
.message-actions .message-cu::after { display: none; }

/* ORDEN DE LA FILA.
   El botón de copiar lo crea app.js al dibujar el mensaje; los de escuchar y
   guardar los añaden sus módulos DESPUÉS, cada uno cuando le toca. Por orden
   de llegada la fila quedaba: hora · copiar · flecha · escuchar · guardar,
   con la flecha de plegado en medio de los iconos.

   No se corrige moviendo nodos —eso obligaría a reabrir dos módulos cerrados
   y a depender de quién llega primero—, sino declarando el orden visual. Así
   la fila queda como se aprobó, llegue cada botón cuando llegue. */
.message-actions          { align-items: center; }
.message-actions .message-time { order: 0; }
.message-actions .message-tts  { order: 1; }   /* Escuchar */
.message-actions .message-cu   { order: 2; }   /* Guardar en el Cuaderno */
.message-actions .message-copy { order: 3; }   /* Copiar */
.message-actions .acts-fold    { order: 9; }   /* la flecha, siempre al final */

/* Plegado a "solo iconos". Es una preferencia global, no un estado por
   mensaje: media conversación con palabras y media sin ellas se leería
   como un error, no como una elección. */
.acts-fold {
  width: 26px; height: 26px;
  margin-left: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.22s ease, color 0.18s ease, background 0.22s ease;
}
.message:hover .acts-fold,
.acts-fold:focus-visible { opacity: 1; }
.acts-fold:hover { color: var(--text); background: var(--tool-hover); }
.acts-fold svg {
  width: 13px; height: 13px;
  fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}
body.acts-compact .acts-fold svg { transform: rotate(180deg); }

/* ---------------------------------------------------------------------------
   EL PLEGADO, SIN TIRÓN (28-ago)

   El síntoma: al pulsar la flecha, la fila de acciones se encogía de golpe y
   la propia flecha —que va al final por `order: 9`— salía disparada hacia la
   izquierda. En una interfaz que en todo lo demás se mueve despacio, ese
   salto se lee como un fallo.

   La causa NO era la duración de ninguna transición: era que no había
   ninguna. Las palabras se quitaban con `content: none` y `display: none`,
   y ni `content` ni `display` son animables. Pasaban de existir a no existir
   entre dos fotogramas, y el ancho de la fila con ellas.

   El arreglo, sin tocar app.js: en vez de quitar la palabra, se le lleva el
   ancho a cero. `max-width` sí es animable, y `overflow: hidden` la recorta
   mientras se cierra. Junto con `gap` y `padding` —declarados arriba— los
   tres cambios de medida ocurren a la vez y con la misma curva, así que la
   flecha se desliza en lugar de saltar.

   `22ch` es el techo, no la medida real: cubre la etiqueta más larga de los
   diez idiomas ("Guardar en el Cuaderno" y sus traducciones) con margen. La
   curva `cubic-bezier(0.2, 0, 0.1, 1)` arranca rápido a propósito: como el
   texto real es más corto que el techo, el primer tramo del recorrido no se
   ve, y una curva que empezara despacio lo convertiría en una espera.

   Las palabras siguen sin ocupar sitio cuando están plegadas: `max-width: 0`
   más `overflow: hidden` las deja en cero ancho real, no en texto invisible
   que empuje la fila.
   --------------------------------------------------------------------------- */
body.acts-compact .message-actions .lbl,
body.acts-compact .message-actions .message-copy::before,
body.acts-compact .message-actions .message-tts::before,
body.acts-compact .message-actions .message-cu::before {
  max-width: 0;
  opacity: 0;
}
body.acts-compact .message-actions .message-copy::after,
body.acts-compact .message-actions .message-tts::after,
body.acts-compact .message-actions .message-cu::after { display: block; }
body.acts-compact .message-actions .message-copy,
body.acts-compact .message-actions .message-tts,
body.acts-compact .message-actions .message-cu { padding: 0 7px; gap: 0; }
body.acts-compact .message-actions [data-tip]:has(.lbl)::after { display: block; }

@media (max-width: 900px) {
  .message-actions .lbl { display: none; }
  .message-actions .message-copy::before,
  .message-actions .message-tts::before,
  .message-actions .message-cu::before { content: none; }
  .message-actions .message-copy::after,
  .message-actions .message-tts::after,
  .message-actions .message-cu::after { display: block; }
  .message-actions .message-copy,
  .message-actions .message-tts,
  .message-actions .message-cu { padding: 0 7px; }
  .acts-fold { display: none; }
  .message-actions [data-tip]:has(.lbl)::after { display: block; }
}

/* ---------------------------------------------------------------------------
   RED NODAL — indicador de espera
   El dibujo no cambia: mismos doce nodos, mismo viewBox, mismas animaciones.
   Cambia solo el tamaño con que se pinta, y se fija DESDE AQUÍ para que el
   CSS gane sobre los atributos width/height del SVG. Así el teléfono puede
   tener otra medida sin duplicar el dibujo ni tocar app.js.
   --------------------------------------------------------------------------- */
.nodal-loader svg { width: 92px; height: 59px; }
@media (max-width: 768px) {
  .nodal-loader svg { width: 64px; height: 41px; }
}

/* ---------------------------------------------------------------------------
   CAMPO DE ESCRITURA
   --------------------------------------------------------------------------- */
.input-area { background: none; }

.input-wrapper {
  border: none;
  border-radius: 22px;
  padding: 11px 11px 11px 21px;
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  /* Cinco sombras, en el mismo orden que las de `:focus-within`. La quinta
     es la ranura del halo, vacía. Ver el bloque del halo más abajo: si las
     dos listas dejan de corresponderse, vuelve la mancha azul. */
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 -1px 0 var(--filo),
              0 30px 46px -24px var(--cast),
              0 0 0 0 transparent;
  transition: box-shadow 0.3s ease;
}
/* ---------------------------------------------------------------------------
   EL HALO DEL CAMPO DE ESCRITURA (28-ago)

   El síntoma: al entrar en la casilla aparecía un resplandor azul bonito
   bajo el campo que se iba solo en menos de un segundo, y en reposo no
   quedaba nada visible.

   La causa: ese resplandor no estaba diseñado, era un accidente de la
   transición. El navegador interpola las sombras de una lista POSICIÓN POR
   POSICIÓN, y las dos listas no se correspondían. La cuarta sombra pasaba
   de `0 30px 46px -24px var(--cast)` —una sombra grande, oscura y baja— a
   `0 0 0 1px var(--accent)` —un filo azul de un píxel—. Durante los 0,3s de
   la transición esa cuarta sombra era una mancha azul difusa por debajo de
   la caja, subiendo y cerrándose. Lo que se veía era el camino entre dos
   estados, no un estado.

   Y el estado final era casi invisible: un filo azul de 1px, del color
   `--accent` oscuro, contra un panel oscuro, y encima dibujado en la última
   capa —las sombras se pintan de la primera a la última, así que la última
   queda debajo de todas—.

   El arreglo tiene dos partes:
   1. Las dos listas se alinean posición por posición, de modo que cada
      sombra se transforme en su equivalente y ninguna cruce la caja.
   2. Se añade una quinta ranura para el halo, vacía en reposo
      (`0 0 0 0 transparent`) y con cuerpo al enfocar. Así el resplandor que
      antes aparecía por accidente ahora aparece a propósito **y se queda**
      mientras el campo tenga el foco.

   El halo se declara con `--accent-soft`, que es el mismo azul loto de la
   selección de texto y de los estados activos: no se inventa un color.
   --------------------------------------------------------------------------- */
.input-wrapper:focus-within {
  border-color: transparent;
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--accent),
              0 -1px 0 var(--filo),
              0 30px 46px -24px var(--cast),
              0 0 26px -4px var(--accent-soft);
}

.btn-send {
  width: 40px; height: 40px;
  flex: 0 0 40px;
  font-size: 22px;
  font-weight: 600;
  background: radial-gradient(130% 130% at 50% 10%, var(--send-a) 0%, var(--send-b) 100%);
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 8px 16px -8px var(--cast);
  transition: transform 0.18s ease, box-shadow 0.25s ease;
}
.btn-send:hover {
  background: radial-gradient(130% 130% at 50% 10%, var(--send-a) 0%, var(--send-b) 100%);
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 12px 22px -8px var(--cast),
              0 0 22px -4px var(--nodal-glow);
}
.btn-send:active { transform: scale(0.95); }

/* La cuota agotada manda sobre la estética: si el botón está apagado, no
   puede seguir brillando como si invitara a pulsarlo. */
.btn-send:disabled,
.btn-send.is-quota,
.btn-send:disabled:hover,
.btn-send.is-quota:hover {
  background: var(--soft);
  box-shadow: none;
  transform: none;
}

/* ---------------------------------------------------------------------------
   DIAL DE LUMINOSIDAD — el propio horizonte, en miniatura
   --------------------------------------------------------------------------- */
.lum-dial input[type="range"] {
  height: 1px;
  border-radius: 0;
  background: linear-gradient(90deg,
      var(--hollow-a), var(--soft) 42%, var(--nodal-hair) 74%, var(--nodal));
}
.lum-dial input[type="range"]::-webkit-slider-thumb {
  width: 9px; height: 9px;
  border: none;
  background: var(--nodal);
  box-shadow: 0 0 12px 2px var(--nodal-glow);
}
.lum-dial input[type="range"]::-moz-range-thumb {
  width: 9px; height: 9px;
  border: none;
  background: var(--nodal);
  box-shadow: 0 0 12px 2px var(--nodal-glow);
}

/* ---------------------------------------------------------------------------
   TOOLTIP — el mismo relieve que el resto
   Se conserva TODO el comportamiento anterior: el anclaje invertido en los
   mensajes de SHIVAIA, el del botón de enviar hacia arriba, el flotante del
   sidebar. Solo cambia la superficie.
   --------------------------------------------------------------------------- */
[data-tip]::after,
#chat-tip-float {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: none;
  box-shadow: inset 0 1px 0 var(--edge), 0 10px 22px -10px var(--cast);
}

/* ---------------------------------------------------------------------------
   MODALES — misma materia (cuenta, login, cuota, Cuaderno)
   --------------------------------------------------------------------------- */
.modal-panel {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: none;
  box-shadow: inset 0 1px 0 var(--edge),
              0 24px 64px -16px var(--cast),
              0 0 44px -12px var(--sky);
}
.modal-cancel {
  border: none;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge);
}
.modal-cancel:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--nodal-hair);
}
.login-field input {
  border: none;
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge);
}
.login-field input:focus {
  border-color: transparent;
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--accent);
}

/* ---------------------------------------------------------------------------
   MOVIMIENTO REDUCIDO
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .sidebar::after,
  .btn-new-chat,
  .btn-send,
  .acts-fold svg,
  .input-wrapper { transition: none; }
  /* El plegado suave del 28-ago también se apaga aquí: quien pidió menos
     movimiento prefiere el corte seco al deslizamiento. */
  .message-actions .lbl,
  .message-actions .message-copy,
  .message-actions .message-tts,
  .message-actions .message-cu,
  .message-actions .message-copy::before,
  .message-actions .message-tts::before,
  .message-actions .message-cu::before { transition: none; }
}

/* ====================== FIN DE LA CAPA "EL HORIZONTE" ====================== */

/* ---------------------------------------------------------------------------
   EFECTO DE ESCRITURA — reubicado
   Estaba en la cabecera, con un ✦ que no decía qué hacía y que casi nadie
   usaba. Ahora vive dentro del campo de escritura, junto al micrófono, con
   ESTADO VISIBLE: dorado encendido, gris apagado. El icono son tres líneas
   de texto de largo decreciente junto a un cursor: texto formándose.
   syncFxButton() en app.js ya envuelve el contenido en .fx-gly y le aplica
   la opacidad de estado; con un SVG dentro funciona igual, sin tocarlo.
   --------------------------------------------------------------------------- */
.input-wrapper .fx-btn {
  width: 32px;
  height: 32px;
  flex: 0 0 32px;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--muted);
  align-self: flex-end;
  margin-bottom: 4px;
  transition: color 0.2s ease, background 0.22s ease;
}
.input-wrapper .fx-btn:hover { background: var(--tool-hover); color: var(--text); }
.input-wrapper .fx-btn .fx-gly { display: flex; }
.input-wrapper .fx-btn svg {
  width: 17px; height: 17px;
  fill: none; stroke: currentColor; stroke-width: 1.25;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Encendido: dorado. La opacidad del glifo la sigue poniendo syncFxButton. */
.input-wrapper .fx-btn:has(.fx-gly[style*="opacity: 1"]) { color: var(--nodal); }

/* ---------------------------------------------------------------------------
   CHAT ACTIVO EN EL HISTORIAL
   La versión anterior era una pincelada de 3px con doble resplandor y un
   latido perpetuo (brushGlow). Con la capa nueva, esa luz compite con el
   horizonte, que ahora es la única fuente de la pantalla; y algo que late sin
   parar en un costado tira del ojo mientras se lee.

   Se cambia por un englobado: un plano tenue con el filo de acento a la
   izquierda, quieto. Se distingue igual y no reclama atención.
   --------------------------------------------------------------------------- */
.chat-item {
  border-radius: 9px;
  transition: background 0.22s ease, box-shadow 0.25s ease, color 0.18s ease;
}
.chat-item:hover { background: var(--tool-hover); }

.chat-item.active {
  background: linear-gradient(90deg, var(--accent-soft) 0%, transparent 92%);
  box-shadow: inset 2px 0 0 var(--accent);
}
/* La pincelada latiente se retira: la reemplaza el filo de arriba. */
.chat-item.active::before { display: none; }

/* ---------------------------------------------------------------------------
   TOOLTIPS DEL CAMPO DE ESCRITURA (efecto de escritura, dictado)
   Dos problemas, uno de sitio y otro de materia.

   SITIO: el globo genérico sale HACIA ABAJO, y estos botones viven pegados al
   borde inferior de la ventana: el texto quedaba cortado. Es el mismo caso
   que ya tenía resuelto el botón de enviar; se aplica el mismo remedio.

   MATERIA: el dictado inyecta sus propios estilos en tiempo de ejecución y
   volvía a dejar el globo plano, con borde en vez de canto. Prefijar con
   .input-wrapper sube la especificidad a (0,2,0) y gana sin abrir el módulo.
   --------------------------------------------------------------------------- */
.input-wrapper [data-tip]:not(.btn-send)::after {
  top: auto;
  bottom: calc(100% + 10px);
  left: auto;
  right: 0;
  max-width: 250px;
  transform: translateY(4px);
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: none;
  box-shadow: inset 0 1px 0 var(--edge), 0 14px 30px -12px var(--cast);
}
.input-wrapper [data-tip]:not(.btn-send):hover::after,
.input-wrapper [data-tip]:not(.btn-send):focus-visible::after { transform: translateY(0); }

/* Mismo peso de letra que el globo de enviar. */
.input-wrapper [data-tip]::after { font-weight: bold; }

/* ---------------------------------------------------------------------------
   LA LÍNEA BLANCA BAJO EL CHAT ACTIVO
   No era un borde: era un TOOLTIP. Los iconos de renombrar y eliminar llevan
   su propio `data-tip`, y el globo genérico se despliega HACIA ABAJO. Como
   .chat-item tiene `overflow: hidden` —lo necesita para recortar los títulos
   largos—, ese globo nunca se vio entero: sólo asomaba su borde superior,
   que es justo la línea fina que rompía el englobado.

   Existía desde antes; se notaba menos porque el globo era gris apagado.
   Con la capa nueva pasó a ser un plano claro con canto de luz, y saltó.

   No se arregla subiendo el globo: dentro de un contenedor recortado no cabe
   de ninguna manera. Se retira. La información no se pierde: los dos botones
   conservan su `aria-label`, y el nombre completo del chat sigue saliendo por
   #chat-tip-float, que vive colgado del <body> justamente para escapar de
   este recorte.
   --------------------------------------------------------------------------- */
.chat-item [data-tip]::after { display: none; }

.chat-item .chat-delete {
  border: none;
  outline: none;
  box-shadow: none;
  border-radius: 7px;
}
.chat-item .chat-delete:hover,
.chat-item .chat-delete:focus-visible {
  background: var(--tool-hover);
  box-shadow: none;
  color: var(--text);
}
/* El rojo se reserva para eliminar, que es lo único irreversible. Renombrar
   lleva las dos clases, así que se la excluye por la suya propia. */
.chat-item .chat-delete:not(.chat-rename):hover {
  background: rgba(220, 80, 80, 0.14);
  color: var(--peligro);
}

/* ---------------------------------------------------------------------------
   DIAL DE LUMINOSIDAD — más presente
   La barra arrancaba en un gris casi negro: en modo oscuro el tramo izquierdo
   desaparecía contra el fondo y el dial parecía empezar en la nada. Ahora
   arranca en el mismo hilo de luz que usa el canto de los planos —visible en
   oscuro y en claro, porque cambia de signo con el dial— y va ganando dorado
   hacia el sol. Dos píxeles en vez de uno: se lee sin pesar.
   --------------------------------------------------------------------------- */
.lum-dial input[type="range"] {
  width: 116px;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,
      var(--hair) 0%,
      var(--nodal-hair) 52%,
      var(--nodal) 100%);
}
.lum-dial input[type="range"]::-webkit-slider-thumb {
  width: 11px; height: 11px;
  border: none;
  background: var(--nodal);
  box-shadow: 0 0 14px 3px var(--nodal-glow);
}
.lum-dial input[type="range"]::-moz-range-thumb {
  width: 11px; height: 11px;
  border: none;
  background: var(--nodal);
  box-shadow: 0 0 14px 3px var(--nodal-glow);
}
.lum-gly { color: var(--muted); opacity: 0.85; }

@media (max-width: 768px) {
  .lum-dial input[type="range"] { width: 78px; }
}

/* ============================================================================
   CUADERNO DEL CAMINANTE Y PLANES — mismos materiales
   Los dos módulos inyectan sus estilos en tiempo de ejecución, así que se
   cuelgan del <head> DESPUÉS de este archivo y ganan por orden a igual
   especificidad. Por eso cada regla de abajo va prefijada (`body`, `.cu-panel`
   con su contexto): sube la especificidad y gana siempre, sin necesidad de
   abrir cuaderno-ui.js ni planes-ui.js, que están cerrados y en verde.

   Ningún cambio de estructura: mismas clases, mismo marcado, misma lógica.
   Sólo cambia de qué están hechas las superficies.
   ============================================================================ */

/* ---------------------------------------------------------------------------
   CUADERNO DEL CAMINANTE
   --------------------------------------------------------------------------- */
body .cu-overlay { background: var(--cast); }

body .cu-panel {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  border: none;
  border-radius: 18px;
  box-shadow: inset 0 1px 0 var(--edge),
              0 30px 80px -20px var(--cast),
              0 0 50px -14px var(--sky);
}

/* El horizonte también aquí: la cabecera del panel deja de estar cortada por
   una regla gris y pasa a estar iluminada por la misma fuente que la pantalla. */
body .cu-head {
  position: relative;
  border-bottom: none;
}
body .cu-head::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
      transparent 0%, var(--hz-core) 42%, var(--hz-soft) 82%, transparent 100%);
}
body .cu-title { letter-spacing: 0.07em; }

body .cu-foot { border-top: 1px solid var(--border); }

/* Fichas y perlas: planos elevados, no cajas con borde. */
body .cu-card,
body .cu-pearl {
  border: none;
  border-radius: 12px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 12px 22px -14px var(--cast);
  transition: box-shadow 0.25s ease, transform 0.18s ease;
}
body .cu-card:hover,
body .cu-pearl:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 16px 28px -14px var(--cast),
              0 0 0 1px var(--nodal-hair);
  transform: translateY(-1px);
}
body .cu-pearl-label { color: var(--nodal); }

body .cu-search {
  border: none;
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge);
  border-radius: 10px;
}
body .cu-search:focus {
  outline: none;
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--accent);
}

body .cu-act,
body .cu-back,
body .cu-close {
  border: none;
  border-radius: 8px;
  transition: color 0.18s ease, background 0.22s ease, box-shadow 0.22s ease;
}
body .cu-act:hover,
body .cu-back:hover,
body .cu-close:hover {
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge);
}

/* El reproductor del Cuaderno hereda el dial de la cabecera. */
body .cu-seek {
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--hair) 0%, var(--nodal-hair) 60%, var(--nodal) 100%);
}
body .cu-seek::-webkit-slider-thumb {
  width: 11px; height: 11px;
  background: var(--nodal);
  box-shadow: 0 0 14px 3px var(--nodal-glow);
}
body .cu-seek::-moz-range-thumb {
  width: 11px; height: 11px;
  background: var(--nodal);
  box-shadow: 0 0 14px 3px var(--nodal-glow);
}

/* ---------------------------------------------------------------------------
   PLANES  ·  reescrito el 9-sep-2026
   Aquí la estética se luce a propósito: es la pantalla donde se decide, y la
   profundidad tiene que estar más presente que en ningún otro sitio.

   ⚠️ POR QUÉ SE REESCRIBIÓ ENTERO. Este bloque hablaba de `#planes-overlay`,
   `.planes-panel`, `.plan-card` — el marcado de la pantalla informativa de
   2.0. La reescritura de planes-ui.js del 8-sep (catálogo real, «Mi pase»,
   aviso de baja) estrenó otro vocabulario: `.pln-ovl`, `.pln-dlg`,
   `.pln-card`. Ni un selector de los de antes existía ya en el DOM: la
   pantalla del dinero era la ÚNICA de SHIVAIA que se dibujaba con su capa
   base y nada más —caja plana, borde gris, rojo de sistema— mientras el
   resto de la casa tiene planos, cantos de luz y horizonte. No faltaba
   diseño: faltaba conectarlo.

   Se mantiene la doctrina de esta sección: planes-ui.js NO SE ABRE. Sus
   reglas viven en un <style> que entra al <head> después de este archivo,
   así que a igual especificidad ganan ellas; prefijar con `body` sube
   (0,1,0) a (0,1,1) y (1,0,0) a (1,0,1), y gana esto. Mismo marcado, misma
   lógica, misma batería: sólo cambia de qué están hechas las superficies.
   --------------------------------------------------------------------------- */
body .pln-ovl {
  z-index: 1000;                       /* la altura de los modales de la casa */
  background: var(--cast);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  /* El resguardo de la muesca, igual que `.app`: en horizontal el diálogo
     ya no se mete debajo del sensor del iPhone. */
  padding: 16px calc(16px + env(safe-area-inset-right))
           calc(16px + env(safe-area-inset-bottom))
           calc(16px + env(safe-area-inset-left));
}

/* EL PANEL. Mismo material que `.modal-panel` y `.cu-panel`, y encima el
   horizonte con su caída de luz.

   Las dos capas de luz van como IMÁGENES DE FONDO y no como ::before/::after
   a propósito: `.pln-dlg` es su propio contenedor de scroll (max-height:86vh,
   overflow:auto), y un pseudo-elemento posicionado ahí dentro se va con el
   contenido al hacer scroll — el horizonte se despegaría del canto del panel
   a la primera rueda del ratón. Un fondo con `background-attachment: scroll`
   (el de por defecto) queda anclado a la caja, no al contenido, y se queda
   donde debe. */
body .pln-dlg {
  border: none;
  border-radius: 20px;
  padding: 22px;
  background-color: transparent;
  background-image:
    linear-gradient(90deg, transparent 0%, var(--hz-soft) 12%, var(--hz-core) 50%,
                    var(--hz-soft) 88%, transparent 100%),
    linear-gradient(180deg, var(--fall) 0%, transparent 100%),
    linear-gradient(180deg, var(--panel-a) 0%, var(--panel-b) 100%);
  background-repeat: no-repeat, no-repeat, no-repeat;
  background-position: top center, top center, top center;
  background-size: 100% 1px, 100% 190px, 100% 100%;
  box-shadow: inset 0 1px 0 var(--edge),
              0 40px 90px -20px var(--cast),
              0 0 70px -14px var(--sky);
  transform: translateY(8px) scale(0.985);
  transition: transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1);
}
body .pln-ovl.visible .pln-dlg { transform: translateY(0) scale(1); }

/* El título, en la letra de la marca: es la misma voz que abre los demás
   modales (`.modal-title`), y esta pantalla no es una excepción. */
body .pln-dlg h3 {
  font-family: var(--font-brand);
  font-size: 19px;
  font-weight: 400;
  letter-spacing: 0.3px;
  color: var(--text);
  margin: 0 0 6px;
}
/* Los rótulos de sección, en versalitas y del color de lo secundario: dicen
   dónde empieza cada cosa sin competir con los precios. */
body .pln-dlg h4 {
  color: var(--muted);
  letter-spacing: 0.08em;
  opacity: 1;
  margin: 20px 0 9px;
}
body .pln-nota { color: var(--muted); opacity: 1; }

/* La cruz de cerrar: un plano pequeño, no un carácter suelto. Lleva su
   propio fondo porque queda pegada arriba (position:sticky) mientras el
   catálogo pasa por debajo. */
body .pln-cerrar {
  z-index: 2;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 50%;
  font-size: 15px;
  color: var(--muted);
  opacity: 1;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring);
  transition: color 0.18s ease, box-shadow 0.22s ease;
}
body .pln-cerrar:hover {
  color: var(--text);
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--nodal-hair);
}

/* «MI PASE». Es el plano destacado de la pantalla: lo distingue el filo
   dorado y un halo corto, la misma distinción que llevaba el plan destacado
   del catálogo anterior. No grita. */
body .pln-mipase {
  border: none;
  border-radius: 15px;
  padding: 14px 15px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge),
              inset 0 0 0 1px var(--nodal-hair),
              0 24px 44px -18px var(--cast),
              0 0 34px -10px var(--nodal-glow);
}
body .pln-mipase .pln-mp-plan { letter-spacing: 0.04em; }
body .pln-mipase .pln-mp-estado {
  border: none;
  color: var(--muted);
  opacity: 1;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring);
}
body .pln-mipase p { color: var(--muted); opacity: 1; }
body .pln-mipase p.pln-mp-extra { color: var(--muted); opacity: 0.78; }

/* LAS TARJETAS DE PASES: planos elevados que se acercan al pasar el cursor. */
body .pln-card {
  border: none;
  border-radius: 15px;
  padding: 14px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 18px 32px -18px var(--cast);
  transition: box-shadow 0.28s ease, transform 0.2s ease;
}
body .pln-card:hover {
  transform: translateY(-2px);
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 24px 40px -18px var(--cast),
              0 0 0 1px var(--nodal-hair);
}
body .pln-card .pln-nombre { letter-spacing: 0.08em; color: var(--muted); }
body .pln-card .pln-precio { font-size: 17px; color: var(--text); }
body .pln-card .pln-det { color: var(--muted); opacity: 1; }
body .pln-card .pln-extra { color: var(--nodal); }
/* El botón de la tarjeta cae al fondo aunque la de al lado tenga una línea
   más de texto: si no, las filas de botones quedan a distintas alturas. */
body .pln-card .pln-btn { margin-top: auto; }

/* LAS FILAS DE PACKS. Un pelo de luz por separación, no una regla gris. */
body .pln-fila {
  border-bottom: 1px solid var(--hair-soft);
  padding: 10px 4px;
}
body .pln-fila .pln-f-det { color: var(--muted); opacity: 1; }

/* LOS BOTONES. Relieve hueco en reposo; el filo dorado aparece al tocarlos.
   Es el mismo gesto de `.cu-act` y de `.modal-cancel`. */
body .pln-btn {
  border: none;
  border-radius: 9px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring),
              0 10px 18px -12px var(--cast);
  transition: box-shadow 0.25s ease, transform 0.18s ease,
              color 0.18s ease, filter 0.15s ease;
}
body .pln-btn:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--nodal-hair),
              0 14px 22px -12px var(--cast);
}
body .pln-btn:active { transform: scale(0.97); }
body .pln-btn:focus-visible {
  outline: none;
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 2px var(--accent);
}
/* Apagado: un plano hundido y sin relieve. «Próximamente» no invita a nada,
   y tiene que verse desde lejos que no invita. */
body .pln-btn[disabled] {
  opacity: 0.5;
  transform: none;
  color: var(--muted);
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge);
}
body .pln-btn[disabled]:hover {
  transform: none;
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge);
}

/* La acción que sigue adelante —elegir un pase, reactivarlo— lleva el azul
   loto de `.modal-confirm`: en toda SHIVAIA, confirmar es azul. El dorado
   se queda en su oficio de aquí, que es distinguir planos (el pase propio,
   el filo de las tarjetas), no empujar a pagar. */
body .pln-btn.pln-go {
  color: #fff;
  background: var(--accent);
  box-shadow: inset 0 1px 0 var(--edge), 0 10px 20px -12px var(--cast);
}
body .pln-btn.pln-go:hover {
  background: var(--accent);
  filter: brightness(1.08);
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 14px 24px -12px var(--cast);
}

/* Lo irreversible se ve distinto ANTES de tocarlo. Gris en reposo, rosa de
   `--peligro` al acercarse: idéntico a `.cu-act.cu-danger`. */
body .pln-btn.pln-peligro { color: var(--muted); }
body .pln-btn.pln-peligro:hover {
  color: var(--peligro);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--peligro-soft),
              0 14px 22px -12px var(--cast);
}
/* El único botón lleno de rojo de toda la interfaz: borrar la cuenta. */
body .pln-btn.pln-rojo {
  color: #fff;
  background: var(--peligro);
  box-shadow: inset 0 1px 0 var(--edge), 0 10px 20px -12px var(--cast);
}
body .pln-btn.pln-rojo:hover {
  background: var(--peligro);
  filter: brightness(1.06);
}
body .pln-btn.pln-rojo[disabled] {
  color: #fff;
  background: var(--peligro);
  filter: none;
}

/* Los mensajes de error, en el rosa de la casa y no en el rojo del sistema. */
body .pln-mp-msg { color: var(--muted); }
body .pln-mp-msg.pln-err { color: var(--peligro); }

/* EL AVISO PREVIO A LA BAJA. La salida amable es un plano propio con filo
   dorado: es una oferta, no una advertencia más de la lista. */
body .pln-baja .pln-amable {
  border: none;
  border-radius: 11px;
  padding: 12px 14px;
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge),
              inset 0 0 0 1px var(--nodal-hair),
              0 14px 26px -14px var(--cast);
}
body .pln-baja li { color: var(--muted); }
body .pln-baja label { color: var(--muted); opacity: 1; }
body .pln-baja input {
  border: none;
  border-radius: 10px;
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge);
}
body .pln-baja input:focus {
  outline: none;
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--accent);
}

/* EL BOTÓN DEL PIE DEL SIDEBAR. Sólo se ve sin sesión —con sesión, cuenta-ui
   lo esconde y la entrada vive en el menú—, así que tiene que convivir con
   los enlaces legales de ahí abajo: mismo plano hundido, misma discreción. */
body #planes-btn {
  border: none;
  border-radius: 10px;
  color: var(--muted);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring);
  transition: color 0.18s ease, box-shadow 0.22s ease;
}
body #planes-btn:hover {
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--nodal-hair);
}

/* TELÉFONO. Dos tarjetas por fila caben, pero el precio y el botón no: a
   partir de aquí el catálogo se lee en una sola columna, que es como se lee
   una lista de precios en la mano. */
@media (max-width: 480px) {
  body .pln-dlg { padding: 18px; border-radius: 18px; }
  body .pln-grid { grid-template-columns: 1fr; }
  body .pln-card { padding: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  body .pln-dlg,
  body .pln-card,
  body .pln-btn { transition: none; }
  body .pln-card:hover,
  body .pln-btn:hover { transform: none; }
}

/* ============================================================================
   PROFUNDIDAD EN MODO CLARO — el anillo
   En oscuro, la sombra basta para despegar un plano del fondo. En claro no:
   un panel casi blanco sobre un fondo casi blanco se disuelve por mucha
   sombra que lleve, porque no hay contraste que proyectar. Ahí hace falta un
   contorno de un píxel. `--ring` (applyLuminosity) es casi nulo en oscuro y
   gana cuerpo conforme sube la luz, así que la misma regla sirve para todo
   el recorrido del dial sin duplicar nada.
   ============================================================================ */
.user-message .message-content {
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 1px 0 var(--filo),
              0 22px 34px -20px var(--cast);
}
/* Esta regla pisa a la de la sección del campo de escritura por orden de
   archivo (misma especificidad, aparece después). Lleva por eso la MISMA
   lista de cinco sombras, ranura del halo incluida: si aquí se quedaran
   cuatro, el reposo tendría cuatro y el foco cinco, y volvería el accidente
   del 28-ago. Las dos listas se editan juntas o no se editan. */
.input-wrapper {
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 -1px 0 var(--filo),
              0 30px 46px -24px var(--cast),
              0 0 0 0 transparent;
}
.modal-panel,
body .cu-panel,
body .pln-dlg {
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 34px 84px -20px var(--cast),
              0 0 56px -14px var(--sky);
}
body .cu-card,
body .cu-pearl,
body .pln-card {
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 14px 26px -14px var(--cast);
}
body .cu-card:hover,
body .cu-pearl:hover,
body .pln-card:hover {
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--nodal-hair),
              0 20px 34px -14px var(--cast);
}

/* ============================================================================
   CUADERNO — LA PÁGINA ABIERTA
   Era el punto más plano de toda la interfaz: fondo blanco, la pregunta y la
   respuesta al mismo peso y una fecha suelta arriba. Es, además, la pantalla
   donde alguien vuelve a leer algo que guardó: merece leerse como una página,
   no como un cuadro de texto.
   ============================================================================ */
/* La fecha, arriba y en versalitas: dice cuándo sin robar la entrada. */
body .cu-det-date {
  font-size: 10.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--nodal);
  opacity: 0.85;
  margin-bottom: 12px;
}

/* La pregunta abre la página. Debajo, el horizonte: la misma línea que
   ilumina la pantalla, aquí separando lo que se preguntó de lo que se
   respondió. */
body .cu-det-q {
  position: relative;
  font-size: 21px;
  line-height: 1.45;
  letter-spacing: 0.01em;
  padding-bottom: 18px;
  margin-bottom: 20px;
  text-wrap: pretty;
}
body .cu-det-q::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
      var(--nodal-hair) 0%, var(--hz-soft) 46%, transparent 100%);
}

/* La respuesta, con el aire de lectura del chat. */
body .cu-det-a {
  font-size: 15px;
  line-height: 1.78;
  opacity: 0.92;
  text-wrap: pretty;
}

/* El cuerpo del panel recibe la caída de luz del horizonte de la cabecera,
   igual que la pantalla principal: así deja de ser un blanco plano. */
body .cu-body { position: relative; }
body .cu-body::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 120px;
  pointer-events: none;
  z-index: 0;
  background: linear-gradient(180deg, var(--fall) 0%, transparent 100%);
}
body .cu-body > * { position: relative; z-index: 1; }

/* ---------------------------------------------------------------------------
   BOTONES DE LA PÁGINA (escuchar, imagen, nota, quitar)
   --------------------------------------------------------------------------- */
body .cu-det-actions { margin-top: 26px; }

body .cu-act {
  border: none;
  border-radius: 9px;
  padding: 8px 13px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring),
              0 10px 18px -12px var(--cast);
  transition: box-shadow 0.25s ease, transform 0.18s ease, color 0.18s ease;
}
body .cu-act:hover {
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--nodal-hair),
              0 14px 22px -12px var(--cast);
}
body .cu-act.cu-danger { color: var(--muted); }
body .cu-act.cu-danger:hover {
  color: var(--peligro);
  box-shadow: inset 0 1px 0 var(--edge-strong),
              0 0 0 1px var(--peligro-soft),
              0 14px 22px -12px var(--cast);
}

/* ---------------------------------------------------------------------------
   CONFIRMACIÓN AL QUITAR UNA PÁGINA
   No es un modal: cuaderno-ui.js despliega una fila en línea. Se le da cuerpo
   de aviso —plano propio, filo rojo tenue— para que se lea como una decisión
   y no como dos botones más. Quitar una página del Cuaderno no se deshace.
   --------------------------------------------------------------------------- */
body .cu-confirm.show {
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: 11px;
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge),
              inset 0 0 0 1px var(--peligro-soft),
              0 14px 26px -14px var(--cast);
}
body .cu-confirm span { flex: 1; color: var(--text); }
body .cu-confirm button {
  border: none;
  border-radius: 8px;
  padding: 7px 13px;
  font-size: 12.5px;
  cursor: pointer;
  font-family: inherit;
  transition: box-shadow 0.22s ease, color 0.18s ease;
}

/* ---------------------------------------------------------------------------
   CUADERNO — EL PIE Y LA NOTA PERSONAL
   El panel se dibujaba sobre --bubble-user, que en claro es el caqui del
   globo de la pregunta: el pie entero y el cuadro de la nota salían verdosos,
   como si fueran otra cosa. Con el panel ya sobre su propio plano, sólo queda
   dar cuerpo a la nota y sustituir la regla gris del pie por el horizonte.
   --------------------------------------------------------------------------- */
body .cu-body { background: transparent; }

body .cu-foot {
  position: relative;
  border-top: none;
  background: transparent;
}
body .cu-foot::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1px;
  background: linear-gradient(90deg,
      transparent 0%, var(--hz-soft) 18%, var(--hz-core) 50%,
      var(--hz-soft) 82%, transparent 100%);
}

body .cu-note {
  border: none;
  border-radius: 11px;
  background: linear-gradient(180deg, var(--hollow-b), var(--hollow-a));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring);
  transition: box-shadow 0.25s ease;
}
body .cu-note:focus {
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--accent);
}
body .cu-note-row button {
  border: none;
  border-radius: 8px;
  padding: 7px 13px;
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text);
  background: linear-gradient(180deg, var(--hollow-a), var(--hollow-b));
  box-shadow: inset 0 1px 0 var(--edge), 0 0 0 1px var(--ring);
  transition: box-shadow 0.22s ease, transform 0.18s ease;
}
body .cu-note-row button:hover {
  transform: translateY(-1px);
  box-shadow: inset 0 1px 0 var(--edge-strong), 0 0 0 1px var(--nodal-hair);
}

/* La perla del día: es lo primero que se ve al abrir. Se la trata como tal. */
body .cu-pearl-label {
  color: var(--nodal);
  letter-spacing: 0.18em;
  font-size: 10.5px;
}
body .cu-pearl-q { font-size: 16px; line-height: 1.45; }

/* =========================================================
   PANTALLA TÁCTIL — REGLAS DE PLATAFORMA (29-ago)

   Este bloque va AL FINAL a propósito. Varios módulos del front
   (cuenta-ui.js, consentimiento-ui.js, cuaderno-ui.js) inyectan su propia
   hoja de estilos en tiempo de ejecución, y esa hoja entra en el documento
   DESPUÉS de style.css: con la misma especificidad, gana la última. Estar
   al final no alcanza para ganarle a una hoja inyectada, de ahí el
   `!important` de la primera regla —el único del archivo, y acotado a una
   sola propiedad dentro de una sola media query.

   `pointer: coarse` = el dedo, no el ratón. Cubre teléfono y tableta,
   incluido el iPad (que mide más de 768px y por lo tanto NUNCA entra en las
   media queries de ancho de este archivo). El Dell no entra aquí jamás.
   ========================================================= */

@media (pointer: coarse) {
  /* EL BUG DEL ZOOM PEGADO.
     Safari en iOS hace zoom automático al enfocar cualquier campo de texto
     cuya letra mida menos de 16px, y NO lo deshace al salir: la aplicación
     queda grande y corrida, y hay que achicarla a mano con dos dedos. Es
     exactamente lo que se vio en el iPhone y lo que nunca apareció en el
     A05, porque Chrome en Android no hace esto.

     La solución no es prohibir el zoom en el <meta> (eso rompe la
     accesibilidad para quien necesita agrandar): es que ningún campo baje de
     16px en un dispositivo táctil. Afecta al cuadro de mensaje (15px), a los
     campos de acceso (14px) y a los que inyectan los módulos (13px).

     Se excluyen los controles sin texto: en `range` la propiedad no hace
     nada y el dial de luminosidad tiene su ancho calculado a mano. */
  input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]),
  textarea,
  select {
    font-size: 16px !important;
  }

  /* ÁREAS TÁCTILES DE LOS BOTONES CHICOS.
     El botón de efecto de escritura mide 32x32 y vive pegado al de enviar
     (44x44): en el teléfono es fácil dar al vecino. En vez de agrandarlo
     —lo que descuadraría la fila de la caja de texto— se le cuelga una zona
     tocable invisible que lo lleva a 44x44 sin mover un pixel de lo que se
     ve. El pseudo-elemento no pinta nada: sólo recibe el dedo.

     OJO — va en ::before y no en ::after. El ::after de este botón YA está
     ocupado: app.js le escribe `data-tip` (línea 773) y la regla
     [data-tip]::after de más arriba lo usa para dibujar el globo de ayuda.
     Pisarlo con content:"" habría dejado el botón mudo. Y como .fx-btn es
     display:flex, el pseudo va en position:absolute para que no entre en el
     flujo y no corra el ícono ni un pixel. */
  .fx-btn { position: relative; }
  .fx-btn::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }
}

/* ============================================================================
   AJUSTES DEL 9-SEP (TARDE) — LA PANTALLA DEL DINERO EN EL TELÉFONO
   Mañana se compra de verdad, y casi todo el mundo va a comprar desde el
   móvil. Todo lo de aquí es superficie: ni una línea de planes-ui.js, ni una
   ruta, ni un número. Va al final del archivo a propósito —a igual
   especificidad gana el que aparece después— y sigue con el prefijo `body`
   para ganarle al <style> que el módulo inyecta en tiempo de ejecución.
   ============================================================================ */

/* EL ORDEN DE LECTURA. planes-ui.js inserta «Mi pase» con
   `insertBefore(caja, dlg.firstChild.nextSibling)` queriendo dejarlo DESPUÉS
   del título; pero el primer hijo del diálogo es la cruz de cerrar, así que
   `firstChild.nextSibling` ES el título y la caja acaba ENCIMA de él. De ahí
   que el globo pareciera pisar «Pases y consultas».

   Se corrige sin abrir el módulo: el diálogo pasa a ser una rejilla y el
   título lleva `order:-1`. En una rejilla el orden visual lo manda `order`,
   no el DOM — misma marca, misma lógica, lectura correcta. Y de paso la
   rejilla sirve para poner los packs de dos en dos en pantalla ancha. */
body .pln-dlg {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-content: start;
  column-gap: 12px;
}
body .pln-dlg > * { grid-column: 1 / -1; }
body .pln-dlg h3 { order: -1; padding-right: 40px; }
/* La cruz deja de flotar (en una rejilla `float` no existe) y se coloca en su
   esquina con margen negativo, para no ocupar una fila propia. Conserva su
   `position:sticky`: sigue a la vista mientras el catálogo pasa por debajo. */
body .pln-cerrar {
  order: -2;
  float: none;
  justify-self: end;
  margin: -4px -4px -34px 0;
}

/* ⚠️ EL AVISO QUE NADIE VEÍA (el caso del 409).
   Quien tiene un pase mensual y toca «Elegir» en otro plan recibe una
   explicación —hay que cancelar el actual primero— que planes-ui.js escribe
   al FINAL del diálogo, debajo de los packs. Sin scroll automático, el aviso
   caía fuera de la pantalla: la persona tocaba, no pasaba nada visible, y
   volvía a tocar. En una pantalla de pago eso no es un detalle.

   `position:sticky` lo ancla al borde inferior del panel: aparezca donde
   aparezca, se ve. `:not(:empty)` es la llave — el div existe siempre y está
   vacío casi todo el tiempo; sin esa condición dejaría una franja fantasma
   pegada abajo. */
body #pln-msg-compra:not(:empty) {
  position: sticky;
  bottom: 0;
  z-index: 3;
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: 11px;
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  box-shadow: inset 0 1px 0 var(--edge),
              inset 0 0 0 1px var(--peligro-soft),
              0 -12px 26px -14px var(--cast);
}

/* PANTALLA ANCHA — lo que se pedía con «¿no entraría todo de una vez?».
   El panel se ensancha y los packs se leen de dos en dos: en un portátil
   normal la pantalla entera cabe sin scroll. Debajo de este corte no se
   intenta: dos columnas de packs en un teléfono no se leen. */
@media (min-width: 940px) {
  body .pln-dlg { max-width: 900px; }
  body .pln-fila { grid-column: span 1; }
}

/* TELÉFONO — las tarjetas de pase, tumbadas.
   Cada tarjeta ocupaba ~245 px de alto (nombre, precio, detalle y un botón a
   todo lo ancho, uno debajo de otro): tres tarjetas eran casi dos pantallas
   de scroll antes de llegar a los packs. Tumbadas —el texto a la izquierda,
   «Elegir» a la derecha— la misma información ocupa un tercio, y el catálogo
   entero se abarca casi de un vistazo. Es la respuesta real a «no se nota que
   hay que hacer scroll»: la mejor señal de scroll es no necesitarlo. */
@media (max-width: 560px) {
  body .pln-dlg { padding: 18px; border-radius: 18px; }
  body .pln-grid { grid-template-columns: 1fr; gap: 8px; }
  body .pln-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 12px;
    padding: 11px 13px;
  }
  body .pln-card > * { grid-column: 1; }
  body .pln-card .pln-precio { font-size: 16px; }
  body .pln-card .pln-btn {
    grid-column: 2;
    grid-row: 1 / -1;
    align-self: center;
    margin-top: 0;
    padding: 9px 16px;
  }

  /* ⚠️ EL SOLAPE DE «ACOMPAÑAMIENTO».
     La fila de un pack es flex: nombre elástico y detalle con `nowrap`. Un
     nombre de una sola palabra larga no tiene por dónde partirse, así que en
     vez de encogerse se desbordaba y se montaba encima de «40 consultas».
     En el teléfono la fila pasa a dos renglones —el nombre arriba, el precio
     y el botón abajo— y además se permite partir la palabra si aun así no
     cupiera. Dos redes, porque esto se lee mal una sola vez y ya sembró la
     duda de si el producto existe. */
  body .pln-fila { flex-wrap: wrap; row-gap: 3px; padding: 11px 4px; }
  body .pln-fila .pln-f-nombre {
    flex: 1 0 100%;
    overflow-wrap: anywhere;
  }
  body .pln-fila .pln-f-det { white-space: normal; }
  body .pln-fila .pln-btn { margin-left: auto; }
}

/* El aire alrededor de «Mi pase»: era lo que se leía como un globo pegado. */
body .pln-mipase { margin: 14px 0 6px; }
body .pln-dlg h4 { margin-top: 22px; }

/* ============================================================================
   LOS GLOBOS DE AYUDA EN PANTALLA TÁCTIL
   En el escritorio un tooltip aparece al pasar el cursor y desaparece al
   retirarlo. En un teléfono no hay cursor que retirar: al tocar un botón el
   navegador deja ese elemento en estado :hover —«hover pegado»— hasta que se
   toca otra cosa. Por eso «Guardada en tu Cuaderno», «Copiar», «Detener la
   respuesta» o «Efecto de escritura» se quedaban clavados en la pantalla.

   No hace falta JavaScript: si el globo se muestra con una animación que
   termina en opacidad 0 y `forwards`, se desvanece solo aunque el :hover
   siga puesto. Aparece, se lee, se va — que es lo que hace en el escritorio.
   ============================================================================ */
@keyframes tip-esfumar {
  0%   { opacity: 0; }
  9%   { opacity: 1; }
  72%  { opacity: 1; }
  100% { opacity: 0; }
}
@media (hover: none) {
  [data-tip]:hover::after,
  [data-tip]:focus-visible::after {
    animation: tip-esfumar 2.8s ease forwards;
  }
  #chat-tip-float.visible { animation: tip-esfumar 2.8s ease forwards; }

  /* LA EXCEPCIÓN: el dictado. Ahí el globo NO debe irse — mientras se está
     grabando es lo único que dice que hay que volver a tocar para parar.
     Se exime por estado, no por texto (el texto está en diez idiomas). */
  [data-tip][aria-pressed="true"]:hover::after,
  [data-tip].grabando:hover::after,
  [data-tip].dictando:hover::after,
  [data-tip].rec:hover::after,
  body.dictando [data-tip]:hover::after,
  body.grabando [data-tip]:hover::after {
    animation: none;
    opacity: 1;
  }
}
@media (hover: none) and (prefers-reduced-motion: reduce) {
  [data-tip]:hover::after,
  [data-tip]:focus-visible::after,
  #chat-tip-float.visible { animation-duration: 2.8s; animation-timing-function: step-end; }
}

/* LEGIBILIDAD EN OSCURO. El globo se dibujaba con el degradado del panel y
   sin contorno: sobre el fondo oscuro del chat quedaba a un paso de fundirse
   con lo que hay detrás, y el texto de la respuesta se leía a través. Se le
   pone un color de fondo OPACO debajo del degradado, el anillo de un píxel
   que ya usan los planos en modo claro, y un desenfoque de respaldo por si
   algún navegador compone la capa con transparencia. */
[data-tip]::after,
#chat-tip-float {
  background-color: var(--panel-b);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 12px 26px -12px var(--cast);
}
.message-actions [data-tip]::after {
  background-color: var(--panel-b);
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 8px 18px -10px var(--cast);
}

/* ============================================================================
   LA FILA DE ACCIONES NO EXISTE HASTA QUE EL MENSAJE TERMINA DE ESCRIBIRSE
   Con el efecto de escritura encendido, los iconos (Escuchar, Copiar,
   Guardar) aparecían junto al mensaje desde el primer carácter. Tocar ▶ ahí
   pedía la voz de un texto a medio formar: el servidor comparaba palabra por
   palabra contra la respuesta firmada, no coincidía, y se veía un error del
   TTS que no era del TTS. La solución de fondo es la que se pidió: que los
   iconos se pinten cuando el mensaje ya está entero.

   `typewriterReveal` (app.js) marca el contenido con la clase `revealing`
   mientras escribe y se la quita al terminar —también si se toca el mensaje
   para saltar la animación—. La fila de acciones es HERMANA de ese contenido
   dentro de `.msg-col`, así que `:has()` la alcanza sin una línea de código.
   `:has()` ya se usa en este archivo desde agosto; no es una apuesta nueva.
   ============================================================================ */
.msg-col:has(> .revealing) .message-actions {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.28s ease;
}
.msg-col .message-actions { transition: opacity 0.28s ease; }
@media (prefers-reduced-motion: reduce) {
  .msg-col .message-actions { transition: none; }
}

/* ============================================================================
   «GUARDADA EN TU CUADERNO», IGUAL QUE «COPIAR»
   El marcador guardado se pinta con el azul del acento (`.message-cu.is-saved`
   en cuaderno-ui.js). Como el color va en el BOTÓN y la palabra de al lado es
   un pseudo-elemento suyo, se teñía también el texto: «Copiar» salía gris y
   «Guardada en tu Cuaderno» azul, con otro peso y otra presencia. De ahí que
   uno se comportara bien y el otro raro, en claro y en oscuro.

   El color se baja del botón al dibujo: el marcador sigue azul —que es lo que
   dice «esto ya está guardado»— y la letra vuelve a hablar con la misma voz
   que las demás.
   ============================================================================ */
body .message-actions .message-cu.is-saved { color: var(--muted); }
body .message-actions .message-cu.is-saved svg { color: var(--accent); }
body .message-actions .message-cu.is-saved:hover { color: var(--text); }
body .message-actions .message-cu.is-saved:hover svg { color: var(--accent); }
/* Y el globo, exactamente el mismo que el de «Copiar»: mismo color de letra,
   misma superficie, misma desaparición. */
body .message-actions .message-cu::after,
body .message-actions .message-copy::after {
  color: var(--text);
  background-color: var(--panel-b);
}

/* ============================================================================
   REMATES DE UI DEL 10-SEP (antes de las compras reales)
   Cuatro arreglos de hoja. Ninguno abre un módulo cerrado.
   ============================================================================ */

/* 1 · «DETENER LA RESPUESTA» SE QUEDABA PEGADO EN EL TELÉFONO.
   El botón de enviar es UNO solo que cambia de ↑ a ■ (app.js,
   setComposerWaiting): mismo nodo, otro texto dentro. El toque deja el
   :hover puesto, arranca `tip-esfumar`, y a mitad de sus 2,8 s el globo
   pasa a decir «Detener la respuesta» sin reiniciarse — o se reinicia, si
   el cambio de texto provoca un ciclo de salida y entrada del puntero.

   ⚠️ No basta con callar el modo ■. Si sólo se callara ése, al terminar la
   respuesta el globo volvería a NACER —«content» pasa de none a texto, y un
   pseudo-elemento nuevo estrena su animación— y «Enviar» saldría 2,8 s al
   final de cada respuesta, con el dedo ya lejos. En una pantalla táctil el
   globo de este botón llega siempre DESPUÉS de haberlo tocado: ya no ayuda
   a decidir nada. Se calla en los dos estados.

   La excepción es la cuota agotada (`.is-quota`): ahí el globo lleva el
   contador de cuánto falta, que es información y no ayuda. Ése se queda. */
@media (hover: none) {
  .btn-send[data-tip]:not(.is-quota):hover::after { content: none; }
}

/* 2 · «PREPARANDO LA VOZ…» SE IBA AL INSTANTE.
   No era el desvanecido. tts-ui.js reescribe `btn.innerHTML` en cada
   cambio de estado, y el dedo había tocado el <svg> de ▶ — que se destruye
   en ese mismo instante. En Safari de iOS el :hover pegado vive en el nodo
   tocado: al desaparecer el nodo, el botón deja de estar en :hover y el
   globo se apaga en el acto. Por eso «al instante» y no «a los 2,8 s».

   Cualquier arreglo que siga colgando de :hover hereda ese fallo. Mientras
   se prepara la voz, el globo NO es una ayuda pasajera: es el estado del
   botón —igual que el dictado grabando— y se ata al estado, no al puntero.
   Se ve desde que se pide la voz hasta que arranca el audio o se cancela.

   En pantalla ancha el globo de este botón está oculto (la palabra se lee
   al lado, «Preparando la voz…», también atada al estado): la regla sólo
   manda donde el globo existe — teléfono y fila plegada.

   Y en táctil, fuera de ese estado, el globo se calla por la misma razón
   que el de enviar: llega después del toque. Así, al sonar el audio el
   globo se va — que es exactamente cuándo tiene que irse. */
@keyframes tip-quedarse { from { opacity: 0; } to { opacity: 1; } }
.message-actions .message-tts.is-loading::after {
  opacity: 1;
  transform: translateY(0);
  animation: tip-quedarse 0.18s ease both;
}
@media (hover: none) {
  .message-actions .message-tts:not(.is-loading):hover::after { content: none; }
}

/* 3 · EL NOMBRE DEL CHAT, CON EL DEDO APOYADO.
   Mantener presionado un chat muestra ahora su nombre completo (app.js,
   armLongPress). Mientras el dedo sigue apoyado, el globo NO puede irse a
   los 2,8 s como los demás: se está leyendo. `.sostenido` lo exime del
   desvanecido; al soltar, app.js lo retira él mismo un momento después. */
#chat-tip-float.visible.sostenido {
  animation: none;
  opacity: 1;
}

/* 4 · PLANES: QUE SE NOTE QUE HAY MÁS.
   Una flecha abajo del diálogo mientras quede contenido por debajo del
   borde. La clase `.hay-mas` la pone navegacion-ui.js al medir el scroll,
   porque planes-ui.js está cerrado; se va al llegar al final.

   CÓMO ESTÁ HECHA. Dos pseudo-elementos del propio diálogo, superpuestos:
   ::before es la pastilla (la materia de los paneles) y ::after el trazo,
   recortado con una máscara para que tome `--nodal` y siga al dial — un
   color escrito dentro de un SVG no lo seguiría. El diálogo es una rejilla,
   así que los dos son ítems de ella: `order:99` los lleva al final,
   `margin-top` negativo los deja en CERO de alto (no empujan nada) y
   `position:sticky` los ancla al borde inferior visible mientras se
   desplaza.

   ⚠️ Sin `mask-image` en el panel para difuminar el borde: taparía también
   el aviso del 409, anclado justo ahí. Por la misma razón, con ese aviso a
   la vista la flecha no se pone (lo decide navegacion-ui.js).
   `pointer-events:none`: nunca se come el toque de un «Elegir». */
body .pln-dlg::before,
body .pln-dlg::after {
  content: "";
  grid-column: 1 / -1;
  justify-self: center;
  order: 99;
  position: sticky;
  bottom: 12px;
  z-index: 4;
  width: 36px;
  height: 36px;
  margin-top: -36px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, visibility 0s linear 0.25s;
}
body .pln-dlg::before {
  background: linear-gradient(180deg, var(--panel-a), var(--panel-b));
  box-shadow: inset 0 1px 0 var(--edge),
              0 0 0 1px var(--ring),
              0 10px 22px -8px var(--cast);
}
body .pln-dlg::after {
  background-color: var(--nodal);
  -webkit-mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6l4.5 4.5L12.5 6' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
          mask: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 6l4.5 4.5L12.5 6' fill='none' stroke='%23000' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
}
/* Un asomo doble al aparecer, y quieta. Movimiento que no pidió nadie,
   así que el justo para que el ojo la encuentre. */
@keyframes pln-mas-asomo {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(4px); }
}
body .pln-dlg.hay-mas::before,
body .pln-dlg.hay-mas::after {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.25s ease;
  animation: pln-mas-asomo 1.5s ease-in-out 0.3s 2;
}
@media (prefers-reduced-motion: reduce) {
  body .pln-dlg.hay-mas::before,
  body .pln-dlg.hay-mas::after { animation: none; transition: none; }
}

/* 5 · EL RECUADRO AZUL AL TOCAR ↑, ■ O «EFECTO DE ESCRITURA» (10-sep, noche).
   Es el resaltado nativo del toque de Android/Chrome (`tap-highlight`) y,
   si el dedo se demora, la selección del propio carácter del botón: ↑ y ■
   son TEXTO dentro del botón, no un dibujo, y se pueden seleccionar como
   cualquier letra. Se apagan los dos sólo en botones: el foco de teclado
   (:focus-visible) no se toca. */
button,
[data-tip] {
  -webkit-tap-highlight-color: transparent;
}
.btn-send,
.input-wrapper button,
.message-actions button,
.header button {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* 6 · GUARDAR EN EL CUADERNO, EN EL TELÉFONO: UNA SOLA VOZ.
   Al guardar hablaban dos cosas a la vez: la nota en línea de cuaderno-ui.js
   (`.cu-msg-note`) y el globo del :hover, que además sólo salía la primera
   vez. Queda la nota —aparece «Respuesta guardada» y se esfuma a los 3 s,
   y es la misma que avisa de los errores— y el globo de este botón se calla
   en táctil. Volver a tocar despinta el marcador sin decir nada: el dibujo
   vacío ya lo dice. */
@media (hover: none) {
  .message-actions .message-cu::after { content: none; }
}

/* 7 · LOS GLOBOS VUELVEN AL VOLVER A TOCAR (ver navegacion-ui.js,
   reinicioGlobos). Copia exacta de `tip-esfumar` con otro nombre: alternar
   el nombre es lo que reinicia la animación. Las exenciones de estado
   (dictado, voz preparándose) se excluyen: ahí el globo no se esfuma. */
@keyframes tip-esfumar-2 {
  0%   { opacity: 0; }
  9%   { opacity: 1; }
  72%  { opacity: 1; }
  100% { opacity: 0; }
}
@media (hover: none) {
  body:not(.dictando):not(.grabando) [data-tip].tip-otra:not([aria-pressed="true"]):not(.grabando):not(.dictando):not(.rec):not(.is-loading):hover::after {
    animation-name: tip-esfumar-2;
  }
}

/* 8 · TRAS EL LOGIN, SIN DESTELLO DE ENTRADA (ver navegacion-ui.js, «¿Venimos
   de un login?»). La entrada ahí es el velo retirándose; el destello dorado
   encima se leía como un segundo refresco. En cualquier otra carga, igual
   que siempre. */
html.nav-tras-login body.entering #enter-flash { animation: none; opacity: 0; }
html.nav-tras-login body.entering .app { animation: none; }
/* …y el destello vuelve cuando el velo se retira (navegacion-ui.js pone
   `.nav-destello` en ese instante). Misma animación de siempre; va después
   de la regla de arriba para ganarle a igual especificidad. */
html.nav-tras-login.nav-destello body #enter-flash { animation: enter-bloom 1200ms ease-out forwards; }
@media (prefers-reduced-motion: reduce) {
  html.nav-tras-login.nav-destello body #enter-flash { animation: none; opacity: 0; }
}
