/* ==========================================================================
   css/app.css
   --------------------------------------------------------------------------
   Sustituye al Tailwind CDN (cdn.tailwindcss.com).

   POR QUE SE SUSTITUYE
   --------------------
   El script de Tailwind en el navegador compila las utilidades JIT en tiempo
   de ejecucion: descarga, parsea el HTML, genera el CSS e inserta un
   MutationObserver que vuelve a compilar cada vez que el DOM cambia. En esta
   aplicacion el DOM cambia constantemente (se crean y destruyen nodos SVG al
   dibujar, se reconstruye la vista al mover un objeto), de modo que el
   compilador JIT se ejecutaba una y otra vez durante la interaccion.

   Coste real de ese enfoque:
     - ~120 KB de JavaScript de compilador que bloquea el primer pintado;
     - recompilacion continua mientras se dibuja;
     - stylesheets inyectados en <head>, que invalidan la cascada.

   Ademas, `w-84` (usada en el panel de herramientas) NO existe en la escala
   de Tailwind: el panel se renderizaba sin anchura en pantallas grandes.

   Aqui los estilos son estaticos y comprimidos, y se sirven desde la cache
   del service worker: sin coste de compilacion, arranque inmediato y
   funcionamiento offline. Ademas se resuelven los problemas responsive y de
   accesibilidad (areas tactiles, safe-area, foco visible) que el CDN no cubria.
   ========================================================================== */

/* ==========================================================================
   1. Tokens
   ========================================================================== */
:root {
  --bg: #050811;
  --panel: rgba(10, 15, 29, 0.92);
  --panel-inner: rgba(15, 23, 42, 0.8);
  --border: rgba(255, 255, 255, 0.12);

  --text: #e2e8f0;
  --text-dim: #94a3b8;
  --text-faint: #64748b;

  --sky: #38bdf8;
  --sky-600: #0284c7;
  --cyan: #22d3ee;
  --emerald: #34d399;
  --amber: #fbbf24;
  --rose: #fb7185;
  --violet: #c084fc;

  --cad-line: #38bdf8;
  --cad-curve: #c084fc;

  --ease-out: cubic-bezier(0, 0.2, 0.8, 1);
  --dur-fast: 0.15s;
  --dur: 0.25s;

  --z-canvas: 1;
  --z-svg: 3;
  --z-legend: 20;
  --z-panel: 30;
  --z-actions: 40;
  --z-fab: 50;

  /* Safe area: notch y barra de inicio en movil. */
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
}

/* ==========================================================================
   2. Reset y base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: var(--bg);
  color: var(--text);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

#stage {
  position: relative;
  width: 100%;
  /* dvh = altura REAL del viewport visible; vh incluye la barra de
     direcciones del navegador y deja un margen inferior fantasma. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  overscroll-behavior: none;
}

body { user-select: none; -webkit-user-select: none; }

/* Los valores numericos si deben poder copiarse al portapapeles. */
.readout, .mono {
  user-select: text;
  -webkit-user-select: text;
  font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
}

:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; border-radius: 6px; }
:focus:not(:focus-visible) { outline: none; }

/* ==========================================================================
   3. Capas de la escena
   ========================================================================== */
/* Envoltorio de las capas de lienzo. Es lo unico que se transforma al hacer
 * zoom o desplazamiento: el panel, la barra de acciones y los avisos quedan
 * fuera y no se escalan. `transform-origin: 0 0` hace que las coordenadas de
 * la transformacion sean las de la esquina superior izquierda del lienzo, que
 * es justo el sistema en el que trabaja el mapper de punteros. */
#viewport {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/* El plano de fondo va ENCIMA del canvas, no debajo.
 * El canvas se crea con `alpha: false` y pinta el fondo oscuro de forma opaca,
 * asi que una imagen en z-index 0 quedaba literalmente tapada: se cargaba
 * (el aviso de exito saltaba) pero no se veia nada. Va entre el canvas (1) y la
 * capa CAD (3), y con `pointer-events: none` para no interceptar los toques.
 *
 * `inset: 0` + `width/height: 100%` + `object-fit: contain` era un error: con eso
 * la imagen se estiraba al tamano del lienzo y se recortaba, ignorando el
 * tamano real que le fija `aplicarTamano`. Al insertar un plano aparecia un
 * reborde gris (la franja de la imagen que no contenia dibujo) y parte de la
 * sala quedaba fuera, sin poder calcar sobre ella.
 *
 * Ahora la imagen tiene SU tamano en pixeles de escena (`style.width/height`) y
 * se posiciona con `left/top` en coordenadas de escena, como cualquier objeto.
 * El contenedor no la estira ni la recorta: si el plano es mayor que el lienzo,
 * sale por el borde, que es lo correcto —se ve que sobra y se hace scroll. */
.layer-bg {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  pointer-events: none;
  opacity: 0.4;
  z-index: 2;
}

#sim-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: var(--z-canvas);
  display: block;
}

/* Capa vectorial de edicion (CAD).
 *
 * Se dimensiona en PIXELES DE ESCENA desde JS (`svg.style.width/height`), no
 * con `width:100%` del contenedor. Es el mismo motivo por el que `.layer-bg`
 * lleva su propio tamano y por el que el lienzo de simulacion fija
 * `canvas.style.width`: las tres capas comparten el sistema de coordenadas de
 * la ESCENA, que puede ser mucho mayor que la pantalla.
 *
 * Con `inset:0` + `width/height:100%` el SVG moria con el tamaño del stage. Al
 * insertar un plano de 50 m la escena crece a ~4120 px y `fitRect` baja la
 * escala a ~0,23: la caja del SVG se encogia a ~368 px en pantalla y el resto
 * del plano se quedaba SIN eventos de puntero — no se podia calcar encima, que
 * es justo para lo que se inserta un plano. */
#cad-layer {
  position: absolute;
  top: 0;
  left: 0;
  z-index: var(--z-svg);
  /* Critico en tactil: sin esto el dedo hace scroll en vez de dibujar. */
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/* ==========================================================================
   Escala fisica, modal y barra de escala
   ========================================================================== */

/* Lectura de magnitudes fisicas: una tabla compacta de valores y unidades. */
/* ==========================================================================
   Zona destructiva
   ==========================================================================
   «Borrar toda la geometria» deshace horas de trabajo de un clic. Se separa del
   resto con un filo rojo y un titulo propio para que se lea como lo que es
   ANTES de pulsarlo, y no se descubre que era otra cosa cuando ya se pulso. */
.danger-zone {
  border-top-color: rgba(239, 68, 68, 0.28) !important;
}

.danger-zone .section-title {
  color: #fca5a5;
}

/* Rojo apagado, no el rojosolido del boton «Borrar» de la seccion de edicion:
   ese actua sobre lo seleccionado y es reversible con el portapapeles; este
   borra todo y no lo es. */
.btn-danger-soft {
  background: rgba(239, 68, 68, 0.10);
  border-color: rgba(239, 68, 68, 0.35);
  color: #fca5a5;
}

.btn-danger-soft:hover {
  background: rgba(239, 68, 68, 0.20);
  border-color: rgba(239, 68, 68, 0.60);
  color: #fecaca;
}

/* Accion principal: se pega al borde superior del panel, sin aire de seccion.
   Es la accion que se repite en cada iteracion del trabajo y no deberia
   competir visualmente con una herramienta. */
.actions-primary {
  padding: 0 0 4px;
  margin: 0 0 6px;
}

/* Boton oculto pero accesible: mantiene el «Simular» del modo ray tracing en el
 * DOM para los lectores de pantalla y para el teclado, sin duplicarlo en
 * pantalla. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   Fuente acustica
   ==========================================================================
   MAGENTA, deliberadamente distinto del CIAN del microfono.

   El color es lo unico que separa «quien emite» de «quien escucha» cuando hay
   varios microfonos en la sala, y antes ambos eran azules casi iguales. El
   magenta es el mismo del boton «Fuente» del panel, de modo que el color
   identifica la herramienta sin recordar dos paletas. */

.fuente-halo {
  fill: rgba(232, 121, 249, 0.16);
  stroke: rgba(232, 121, 249, 0.75);
  stroke-width: 1.5;
}

.fuente-cono {
  fill: #f0abfc;
  stroke: #701a75;
  stroke-width: 1.5;
  stroke-linejoin: round;
}

.fuente-onda {
  fill: none;
  stroke: #f0abfc;
  stroke-width: 2;
  stroke-linecap: round;
}

/* El pulso solo late mientras emite: distingue «fuente colocada» de «fuente
   sonando» de un vistazo, que es justo lo que hace falta para saber si lo que
   se ve en el ecograma es de esta simulacion o de la anterior. */
.fuente-pulso {
  fill: none;
  stroke: rgba(232, 121, 249, 0.55);
  stroke-width: 1.5;
  opacity: 0;
}

.fuente-pulso.is-on {
  opacity: 1;
  animation: fuente-latido 1.1s ease-out infinite;
}

@keyframes fuente-latido {
  0%   { r: 16px; opacity: 0.8; }
  100% { r: 34px; opacity: 0; }
}

/* Con `prefers-reduced-motion` el pulso se queda fijo: informa igual y no
   late. */
@media (prefers-reduced-motion: reduce) {
  .fuente-pulso.is-on { animation: none; opacity: 0.55; }
}

/* ==========================================================================
   Reloj de simulacion
   ==========================================================================
   Una sola lectura, en un solo sitio. Va en la columna de herramientas porque es
   una lectura de estado del estudio, no un mando. */

.sim-clock-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.sim-clock-k {
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-faint);
}

.sim-clock-v {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 15px;
  font-weight: 600;
  color: var(--cyan);
  font-variant-numeric: tabular-nums;
}

/* La barra se llena con el tiempo. Se mide en porcentaje del `T_sim` del
   recinto, que es el horizonte util: pasada esa duracion la cola ya no se
   oye y seguir llenando no informa de nada. */
.sim-clock-bar {
  height: 4px;
  margin-top: 6px;
  border-radius: 2px;
  background: rgba(148, 163, 184, 0.16);
  overflow: hidden;
}

.sim-clock-fill {
  height: 100%;
  width: 0;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--cyan), #a78bfa);
  transition: width 0.12s linear;
}

/* Cuando se alcanza el final de la cola, la barra se apaga: el estudio ha
   terminado y un reloj que sigue corriendo invites a pensar que aun queda. */
.sim-clock-bar.is-finished .sim-clock-fill {
  background: #34d399;
}

/* ==========================================================================
   Panel de diagnostico
   ==========================================================================
   Va pegado a la IZQUIERDA, que es donde esta el margen: el panel de
   herramientas ocupa la derecha y las dos hojas juntas se comen la pantalla en
   horizontal. En vertical, se abre desde abajo.

   El ancho es el de un visor, no el de un formulario: el eje temporal necesita
   que dos picos de 20 ms se vean separados, y a 320 px son un solo borron. */
.diag-panel {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  height: 100dvh;
  max-height: 100dvh;
  width: min(720px, 56vw);
  z-index: var(--z-panel);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: rgba(8, 12, 22, 0.96);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-right: 1px solid rgba(148, 163, 184, 0.18);
  box-shadow: 12px 0 40px rgba(0, 0, 0, 0.55);
  transform: translateX(0);
  transition: none !important;
  animation: none !important;
  touch-action: pan-y;
}

.diag-panel.is-off {
  display: none;
  pointer-events: none;
  visibility: hidden;
}

.diag-panel:not(.is-off) {
  pointer-events: auto;
  visibility: visible;
  transform: translateX(0);
}

.diag-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 8px 12px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.15);
  flex-shrink: 0;
}

.diag-title {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.02em;
}

.diag-title [data-icon] { color: var(--cyan); }

.diag-planta-chk {
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(34, 211, 238, 0.08);
  border: 1px solid rgba(34, 211, 238, 0.24);
  color: #cffafe;
  font-weight: 500;
}

.diag-receptor-box {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-left: auto;
}

.diag-receptor-box select {
  max-width: 170px;
  padding: 4px 8px;
  font-size: 11.5px;
}

.diag-close {
  background: transparent;
  border: 1px solid rgba(148, 163, 184, 0.22);
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
  padding: 5px;
  display: grid;
  place-items: center;
}

.diag-close:hover { background: rgba(148, 163, 184, 0.14); color: var(--text); }

/* --- Barra de captura y comparación de referencias (Antes vs. Después) --- */
.diag-compare-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 6px 12px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.14);
  background: rgba(15, 23, 42, 0.65);
  flex-shrink: 0;
}

.diag-btn-snap,
.btn-icon-sm.diag-btn-snap {
  width: auto;
  min-width: max-content;
  height: 26px;
  padding: 0 10px;
  gap: 5px;
  font-size: 11px;
  font-weight: 500;
  white-space: nowrap;
  flex-shrink: 0;
}

#diag-save-snap {
  border-color: rgba(52, 211, 153, 0.38);
  background: rgba(16, 185, 129, 0.14);
  color: #a7f3d0;
}

#diag-save-snap:hover {
  background: rgba(16, 185, 129, 0.24);
}

.diag-compare-select-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 180px;
  min-width: 0;
}

.diag-compare-select-wrap select {
  flex: 1;
  min-width: 120px;
  max-width: 260px;
  padding: 3px 7px;
  font-size: 11px;
}

/* --- Avisos de defecto ---------------------------------------------- */
.diag-alertas {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 0 12px;
  max-height: 22dvh;
  overflow-y: auto;
  flex-shrink: 0;
}

.diag-alertas:not(:empty) {
  padding-top: 8px;
  padding-bottom: 4px;
}

.diag-alerta {
  display: block;
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 11.5px;
  line-height: 1.4;
  border: 1px solid;
}

/* El eco flotante es el hallazgo principal: es el que arruina un auditorio, y
   por eso sale en rojo y no como una nota más. */
.diag-alerta.is-fatal {
  background: rgba(239, 68, 68, 0.13);
  border-color: rgba(239, 68, 68, 0.42);
  color: #fecaca;
}

.diag-alerta.is-warn {
  background: rgba(251, 191, 36, 0.12);
  border-color: rgba(251, 191, 36, 0.36);
  color: #fde68a;
}

.diag-alerta.is-info {
  background: rgba(56, 189, 248, 0.10);
  border-color: rgba(56, 189, 248, 0.30);
  color: #bae6fd;
}

.diag-alerta.is-compare {
  background: rgba(16, 185, 129, 0.12);
  border-color: rgba(52, 211, 153, 0.38);
  color: #d1fae5;
}

.diag-alerta b { font-weight: 600; }

/* --- Cuerpo: grafico + tabla ---------------------------------------- */
.diag-body {
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 268px;
  gap: 10px;
  padding: 10px 12px;
}

.diag-canvas-wrap {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

#diag-canvas {
  flex: 1 1 0;
  width: 100%;
  min-height: 150px;
  border-radius: 10px;
  background: rgba(3, 6, 14, 0.75);
  border: 1px solid rgba(148, 163, 184, 0.16);
  display: block;
  cursor: crosshair;
  touch-action: pan-y;
}

/* El lienzo coge el scroll con `pan-y`: el panel de diagnostico se desplaza
 * en vertical, pero la rueda sobre el grafico la gestiona el zoom. */
#diag-canvas.is-panning { cursor: grabbing; }

/* =========================================================================
 * Mandos del grafico: que capas se ven, como se amplia y como se recorren
 * los rebotes.
 * ========================================================================= */

.diag-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  padding: 4px 2px 6px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.12);
  margin-bottom: 2px;
  flex-shrink: 0;
}

/* Los checks son interruptores de CAPA, no preferences: cada uno lleva el
 * color de lo que enciende, para que el boton diga tambien QUE se va a ver. */
.diag-chk {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
}

.diag-chk input {
  accent-color: #22d3ee;
  width: 13px;
  height: 13px;
  margin: 0;
}

.diag-swatch {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: 0 0 auto;
}

/* Los colores son los de las capas: cian el directo del trazador,
 * magenta la IR del FDTD y verde esmeralda la referencia guardada. */
.diag-swatch.sw-rayos { background: #00f0ff; }
.diag-swatch.sw-ir { background: #f0abfc; }
.diag-swatch.sw-ref { background: #34d399; }

.diag-nav {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  margin-left: auto;
}

.diag-nav-label {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px;
  color: var(--text-dim);
  min-width: 112px;
  text-align: center;
  white-space: nowrap;
  user-select: text;
}

.diag-nav-label.is-sel { color: #fbbf24; }

/* Los botones del recorrido son pequenos a proposito: van a pulsarse en serie
 * mientras se repasa la lista de rebotes, y un boton grande obligaria a mover
 * el raton cada vez. */
.btn-icon-sm {
  width: 24px;
  height: 24px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  border: 1px solid rgba(148, 163, 184, 0.22);
  background: rgba(148, 163, 184, 0.08);
  color: var(--text);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}

.btn-icon-sm:hover { background: rgba(148, 163, 184, 0.18); }
.btn-icon-sm:disabled { opacity: 0.35; cursor: default; }

/* «Ajustar» es texto, no flecha: necesita un ancho propio. */
#diag-fit {
  width: auto;
  padding: 0 9px;
  font-size: 10.5px;
}

.diag-hint {
  margin: 0;
  font-size: 10px;
  color: var(--text-faint);
  flex-shrink: 0;
}

.diag-side {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
  height: 100%;
  overflow-y: auto;
  padding-right: 2px;
}

.diag-metrics {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 5px;
  flex-shrink: 0;
}

.diag-metric {
  padding: 6px 8px;
  border-radius: 7px;
  background: rgba(148, 163, 184, 0.07);
  border: 1px solid rgba(148, 163, 184, 0.12);
  min-width: 0;
}

.diag-metric.is-wide {
  grid-column: 1 / -1;
}

.diag-metric.is-highlight {
  background: rgba(34, 211, 238, 0.09);
  border-color: rgba(34, 211, 238, 0.28);
}

.diag-metric.is-fdtd {
  background: rgba(240, 171, 252, 0.08);
  border-color: rgba(240, 171, 252, 0.28);
}

.diag-metrics-group {
  grid-column: 1 / -1;
  font-size: 9.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  padding: 4px 2px 1px;
  border-bottom: 1px solid rgba(148, 163, 184, 0.14);
  margin-top: 2px;
}

.diag-metrics-group.is-fdtd {
  color: #f0abfc;
  border-bottom-color: rgba(240, 171, 252, 0.26);
}

.diag-metrics-group.is-rayos {
  color: #67e8f9;
  border-bottom-color: rgba(34, 211, 238, 0.24);
}

.diag-metric-k {
  display: block;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  margin-bottom: 2px;
}

.diag-metric-v {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px;
  color: var(--text);
  display: block;
}

.diag-metric-sub {
  display: block;
  font-size: 9.5px;
  color: var(--text-faint);
  margin-top: 2px;
  line-height: 1.3;
}

.diag-delta {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 5px;
  border-radius: 4px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  font-weight: 600;
}

.diag-delta.is-better {
  background: rgba(16, 185, 129, 0.18);
  color: #6ee7b7;
  border: 1px solid rgba(52, 211, 153, 0.35);
}

.diag-delta.is-worse {
  background: rgba(239, 68, 68, 0.18);
  color: #fca5a5;
  border: 1px solid rgba(248, 113, 113, 0.35);
}

.diag-delta.is-neutral {
  background: rgba(148, 163, 184, 0.15);
  color: #cbd5e1;
  border: 1px solid rgba(148, 163, 184, 0.25);
}

.diag-table-wrap {
  flex: 1 0 130px;
  min-height: 130px;
  max-height: 220px;
  overflow-y: auto;
  border-radius: 8px;
  border: 1px solid rgba(148, 163, 184, 0.12);
}

.diag-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 11px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
}

.diag-table th {
  position: sticky;
  top: 0;
  background: #0d1424;
  color: var(--text-faint);
  font-weight: 500;
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 6px 5px;
  text-align: right;
  border-bottom: 1px solid rgba(148, 163, 184, 0.14);
}

.diag-table td {
  padding: 4px 5px;
  text-align: right;
  color: var(--text-dim);
  border-bottom: 1px solid rgba(148, 163, 184, 0.06);
  white-space: nowrap;
}

.diag-table tr.is-directa td { color: var(--cyan); font-weight: 600; }
.diag-table tr.is-flutter td { color: #fca5a5; }
.diag-table tr.is-sel td { background: rgba(251, 191, 36, 0.14); }
.diag-table tbody tr { cursor: pointer; }
.diag-table tbody tr:hover { background: rgba(148, 163, 184, 0.09); }

.diag-vacio {
  padding: 22px 12px;
  text-align: center;
  font-size: 11.5px;
  color: var(--text-faint);
  font-family: system-ui, sans-serif;
}

/* --- Responsive ------------------------------------------------------ */
@media (max-width: 900px) {
  .diag-panel {
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: auto;
    max-height: 82dvh;
    overflow-y: auto;
    border-right: none;
    border-top: 1px solid rgba(148, 163, 184, 0.18);
    border-radius: 16px 16px 0 0;
    transform: translateY(0);
  }
  .diag-panel.is-off { display: none; }
  .diag-panel:not(.is-off) { transform: translateY(0); }
  .diag-body {
    grid-template-columns: 1fr;
    grid-template-rows: minmax(210px, 1fr) auto;
    overflow: visible;
  }
  .diag-side {
    height: auto;
    overflow: visible;
  }
  .diag-table-wrap { max-height: 160px; }
}

/* ==========================================================================
   Receptores
   ==========================================================================
   El microfono es una CAPA DE TRABAJO, no geometria: se dibuja siempre
   encima y con `pointer-events` activo para poder arrastrarlo, pero con trazo
   discontinuo para que no se confunda con un muro. Un receptor que parece un
   muro es un receptor que se coloca en sitio equivocado. */

.receptor-punto {
  fill: #22d3ee;
  stroke: #083344;
  stroke-width: 2;
}

.receptor-punto.is-activo {
  fill: #fbbf24;
  stroke: #78350f;
}

/* El radio de captura, a escala real. Se dibuja atenuado para no competir con
   el punto central, que es lo que hay que alcanzar. */
.receptor-captura {
  fill: rgba(34, 211, 238, 0.10);
  stroke: rgba(34, 211, 238, 0.55);
  stroke-width: 1;
  stroke-dasharray: 4 3;
  cursor: move;
}

.receptor-captura.is-activo {
  fill: rgba(251, 191, 36, 0.14);
  stroke: rgba(251, 191, 36, 0.8);
}

.receptor-radio-txt {
  font-size: 10px;
  fill: #67e8f9;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  pointer-events: none;
  user-select: none;
}

/* Rotulo R1, R2... del receptor. Es el nexo entre el punto del lienzo y la
   fila del selector del diagnostico: sin el, con tres microfonos no se sabe
   cual se esta midiendo. Debajo del punto, para no solaparse con la etiqueta
   de radio, que vive arriba. */
.receptor-etiqueta {
  font-size: 11px;
  font-weight: 600;
  fill: #22d3ee;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  pointer-events: none;
  user-select: none;
  paint-order: stroke;
  stroke: rgba(5, 8, 17, 0.85);
  stroke-width: 3px;
  stroke-linejoin: round;
}

.receptor-etiqueta.is-activo {
  fill: #fbbf24;
}

/* Etiqueta «Source» de la fuente. Mismo tratamiento que la de receptor —
   contorno oscuro para que se lea sobre el campo de ondas — y en magenta,
   el color de la fuente en todo lo demas. */
.fuente-etiqueta {
  font-size: 11px;
  font-weight: 600;
  fill: #f0abfc;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  pointer-events: none;
  user-select: none;
  paint-order: stroke;
  stroke: rgba(5, 8, 17, 0.85);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Linea de audiencia: el sitio donde se mide la uniformidad del campo. */
.receptor-linea {
  stroke: #a78bfa;
  stroke-width: 1.5;
  stroke-dasharray: 7 5;
  opacity: 0.75;
  pointer-events: none;
}

/* Cota de la herramienta «Medir». Es una capa de trabajo sobre el lienzo, no
   geometria acustica: no rebota el sonido ni entra en la simulacion. Sin estas
   reglas la cota salia con el trazo por defecto del navegador (negro sobre
   fondo casi negro) y el boton «Medir» parecia no hacer nada. */
line.cinta {
  stroke: #22d3ee;
  stroke-width: 1.5;
  stroke-linecap: round;
  /* Igual que el resto del CAD: el trazo no escala con el zoom, se lee igual
     de cerca o de lejos. */
  vector-effect: non-scaling-stroke;
}

/* Pivotes de la cota fijada. */
.cinta-punto {
  fill: #22d3ee;
  stroke: #083344;
  stroke-width: 1;
}

/* Etiqueta de la cota fijada: la longitud en la unidad que toca (mm/cm/m).
   Halo oscuro para que se lea tambien sobre un plano de fondo claro. */
.cinta-txt {
  font-size: 12px;
  font-weight: 600;
  fill: #e2e8f0;
  paint-order: stroke;
  stroke: rgba(5, 8, 17, 0.85);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* Borrador (en curso). Se distingue de la cota fijada —mas tenue y a trazos—
   para no leerse como dato confirmado mientras aun se esta arrastrando. */
line.cinta-borrador {
  stroke-dasharray: 7 5;
  opacity: 0.8;
}

.cinta-borrador-punto {
  fill: rgba(34, 211, 238, 0.25);
  stroke: #22d3ee;
  stroke-width: 1.5;
}

.cinta-borrador-txt {
  font-size: 12px;
  font-weight: 600;
  fill: #a5f3fc;
  paint-order: stroke;
  stroke: rgba(5, 8, 17, 0.85);
  stroke-width: 3px;
  stroke-linejoin: round;
  opacity: 0.9;
}

/* Trayectoria del pico seleccionado. Trazo continuo y grueso a proposito: es la
   unica capa que no se atenua, para distinguirse de una simulacion normal. */
.rayo-trazado {
  fill: none;
  stroke: #fbbf24;
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  filter: drop-shadow(0 0 5px rgba(251, 191, 36, 0.65));
}

.rayo-trazado-punto {
  fill: #fbbf24;
  stroke: #78350f;
  stroke-width: 2;
}

/* ==========================================================================
   Escena de fisica
   ========================================================================== */
.readout-box {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 10px;
  padding: 9px 10px;
  border-radius: 10px;
  background: var(--panel-inner);
  border: 1px solid rgba(51, 65, 85, 0.6);
  font-size: 10.5px;
}
.readout-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  color: var(--text-faint);
}
.readout-row b {
  color: var(--text-dim);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-weight: 500;
  text-align: right;
}

/* Barra de escala: indica cuantos metros mide el lienzo en pantalla. */
#scale-bar {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: var(--z-svg);
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 9px;
  border-radius: 8px;
  background: rgba(5, 8, 17, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.12);
  font-size: 10.5px;
  color: var(--text-dim);
  pointer-events: none;
  user-select: none;
}
#scale-bar .rule {
  display: block;
  height: 6px;
  border: 1px solid var(--sky);
  border-top: none;
  min-width: 12px;
}

/* Modal */
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-fab);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.modal.is-off { display: none; }
.modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 4, 10, 0.72);
  backdrop-filter: blur(3px);
}
.modal-card {
  position: relative;
  width: min(420px, 100%);
  max-height: 90vh;
  overflow-y: auto;
  /* Igual que el panel: dentro de #stage, necesita recuperar el scroll. */
  touch-action: pan-y;
  padding: 20px;
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.modal-card h2 {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.input-row {
  display: flex;
  gap: 8px;
}
.input-row input,
.input-row select,
#scale-known-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 9px;
  border-radius: 8px;
  background: rgba(2, 6, 23, 0.75);
  border: 1px solid rgba(51, 65, 85, 0.9);
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
}
.input-row select { flex: 0 0 110px; }
#scale-known-input { width: 100%; }
.input-row input:focus-visible,
.input-row select:focus-visible,
#scale-known-input:focus-visible {
  outline: 2px solid var(--sky);
  outline-offset: 1px;
}
.scale-preview {
  padding: 9px 10px;
  border-radius: 9px;
  background: rgba(34, 211, 238, 0.08);
  border: 1px solid rgba(34, 211, 238, 0.3);
  color: var(--cyan);
  font-size: 11.5px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  text-align: center;
}
.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
}
.modal-actions .btn { flex: 0 1 auto; }

/* ==========================================================================
   4. Geometria CAD
   ========================================================================== */
/* Fila de casilla: el interruptor de "ignorar materiales" y, en su dia, el de
   cerrar el borde. Es una <label> entera para que el texto tambien active la
   casilla, que es lo que se espera al pulsar en el sitio. */
/* Lectura del recinto: area y estado, en el panel. */
.closure-readout {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 8px 0 6px;
  padding: 8px 12px;
  border-radius: 8px;
  background: rgba(148, 163, 184, 0.06);
}

.closure-k {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim, #94a3b8);
}

.closure-v {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 14px;
  font-weight: 600;
  color: var(--text, #e2e8f0);
}

/* Lista de fugas del modal. Cada fila lleva la medida destacada: es el dato
 * que el usuario tiene que decidir si acepta o no. */
.hueco-lista {
  list-style: none;
  margin: 12px 0;
  padding: 0;
  max-height: 34vh;
  overflow-y: auto;
}

.hueco-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  border-radius: 8px;
  background: rgba(249, 115, 22, 0.08);
  border: 1px solid rgba(249, 115, 22, 0.22);
  margin-bottom: 6px;
}

.hueco-m {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 14px;
  font-weight: 700;
  color: #fb923c;
  min-width: 68px;
}

.hueco-txt {
  font-size: 12.5px;
  color: var(--text-dim, #cbd5e1);
}

.hueco-mas {
  background: none;
  border-style: dashed;
  color: var(--text-dim, #94a3b8);
  font-size: 12px;
}

/* ======================================================================
 * Visor del plano
 * ----------------------------------------------------------------------
 * Es un visor de imagen con zoom y desplazamiento, dentro del dialogo de
 * escala. No es un `<dialog>` del navegador ni un canvas: es un `img` con
 * `transform`, porque el plano es lo que se tiene que VER con detalle, y
 * cualquier otra cosa obligaria a repintarlo a mano.
 * ==================================================================== */
.modal-wide {
  max-width: min(94vw, 900px);
}

.plan-viewer {
  margin: 12px 0;
}

.plan-stage {
  position: relative;
  height: 300px;
  overflow: hidden;
  border-radius: 10px;
  background:
    /* Rejilla de fondo: hace evidente que la imagen esta ampliada y
       desplazada, y sobre todo que NO esta recortada por el marco. */
    repeating-linear-gradient(45deg, #1e293b 0 8px, #172033 8px 16px);
  border: 1px solid rgba(148, 163, 184, 0.25);
  cursor: grab;
  touch-action: none;
}

.plan-stage.is-arrastrando { cursor: grabbing; }
.plan-stage.is-cotando { cursor: crosshair; }

.plan-stage img {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  /* Sin esto el navegador aplica su propio zoom con Ctrl+rueda y la
     previsualizacion deja de coincidir con lo que hay debajo. */
  user-select: none;
  -webkit-user-drag: none;
  image-rendering: auto;
}

/* Capa de la cota. Comparte el sistema de coordenadas que la imagen: ambas
 * se transforman con el mismo `translate`/`scale`, de modo que la linea que se
 * dibuja cae justo encima del plano aunque este este ampliado y desplazado.
 *
 * `transform-origin: 0 0` es OBLIGATORIO y va paired al de `.plan-stage img`.
 * Sin el, el SVG usa el `50% 50%` por defecto y escala desde su CENTRO,
 * mientras la imagen escala desde su ESQUINA: dos capas con dos origenes
 * distintos se separan en cuanto el zoom deja de ser 1, y la cota aparece
 * desplazada respecto al punto que el usuario acaba de marcar. Con el zoom al
 * 200 % la separacion era de mas de 400 px. */
.plan-cota {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.plan-cota-line {
  stroke: #fbbf24;
  stroke-width: 2;
  stroke-dasharray: 6 4;
}

/* La cota que se proyecta detras del raton, entre el primer clic y el segundo.
 * Se dibuja mas suave y mas fina: deja claro que aun no es la medida definitiva,
 * que el segundo clic confirma. Sin esta distincion no se sabe si lo que se ve
 * esta fijado o solo apuntado. */
.plan-cota-previa {
  opacity: 0.6;
  stroke-width: 1.5;
  stroke-dasharray: 3 3;
}

.plan-cota-tick {
  fill: #fbbf24;
  stroke: #0f172a;
  stroke-width: 1;
}

.plan-empty {
  position: absolute;
  inset: 0;
  margin: 0;
  display: grid;
  place-items: center;
  color: var(--text-dim, #94a3b8);
  font-size: 13px;
  text-align: center;
  padding: 0 24px;
}

.plan-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  flex-wrap: wrap;
}

.plan-zoom {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 11.5px;
  color: var(--text-dim, #cbd5e1);
  min-width: 52px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.plan-read {
  margin: 6px 0 0;
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 12px;
  color: #fbbf24;
  min-height: 16px;
}

.btn-icon-sm {
  padding: 5px 9px;
  font-size: 12px;
  line-height: 1;
}

/* ======================================================================
 * Lectura de unidades
 * ----------------------------------------------------------------------
 * Sobre el lienzo, fuera de #viewport: dentro se escalaria con el zoom, y la
 * escala es justo el dato que hay que leer.
 *
 * Es un panel de lectura, no un boton: sin `pointer-events: none` taparia el
 * lienzo justo en la esquina que se usa para dibujar.
 * ==================================================================== */
.units-hud {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 40;
  padding: 8px 11px;
  border-radius: 10px;
  display: grid;
  gap: 2px;
  pointer-events: none;
  font-family: var(--mono, ui-monospace, monospace);

  /* RETIRADO (v2.7.2). Esta caja caia ENCIMA del panel de diagnostico.
   *
   * Se oculta con `display: none` y NO con el atributo `hidden` a secas: el
   * atributo funciona mediante `display: none` de la hoja del navegador, y la
   * regla `display: grid` de arriba —de autor— gana. Por eso el `hidden` no
   * ocultaba nada y la caja seguia apareciendo encima del ecograma. */
  display: none !important;
}
  font-size: 11px;
  min-width: 168px;
}

.units-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.units-row.is-off { display: none; }

.units-k {
  font-family: var(--sans, system-ui, sans-serif);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-dim, #94a3b8);
}

/* Tabulares: los numeros deben ocupar el mismo ancho al cambiar de valor, o
 * el HUD vibra mientras se mueve el cursor. */
.units-v {
  font-variant-numeric: tabular-nums;
  color: var(--text, #e2e8f0);
  white-space: nowrap;
}

/* La longitud del segmento va destacada: es la que hay que leer de un vistazo
 * mientras se traza un muro. */
.units-v-hi {
  color: #22d3ee;
  font-weight: 600;
}

.units-row-sub {
  margin-top: 3px;
  padding-top: 4px;
  border-top: 1px solid rgba(148, 163, 184, 0.18);
}

.units-row-sub .units-v { color: var(--text-dim, #cbd5e1); }

/* ======================================================================
 * Diagnostico del recinto
 * ----------------------------------------------------------------------
 * Son ayudas de visualizacion, no geometria: no las simula ningun motor, solo
 * hacen visible si la sala esta cerrada y donde estan las fugas.
 *
 * `pointer-events: none` es lo que garantiza que no interceptan el raton. Si
 * no, un clic para mover un tirador cairia sobre el contorno y no haria nada:
 * el trazo discontinuo se dibuja justo encima de los muros, asi que la
 * superficie de captacion coincide justo con donde el usuario quiere pulsar.
 * ==================================================================== */
.recinto-contorno {
  fill: none;
  stroke: #22d3ee;
  stroke-width: 1.5;
  stroke-dasharray: 7 6;
  opacity: 0.55;
  pointer-events: none;
}

.hueco-linea {
  stroke: #f97316;
  stroke-width: 2.5;
  stroke-dasharray: 4 4;
  opacity: 0.9;
  pointer-events: none;
}

.hueco-punto {
  fill: rgba(249, 115, 22, 0.25);
  stroke: #f97316;
  stroke-width: 2;
  pointer-events: none;
}

.snap-punto {
  fill: rgba(34, 211, 238, 0.2);
  stroke: #22d3ee;
  stroke-width: 2;
  stroke-dasharray: 3 3;
  pointer-events: none;
}

/* -------------------------------------------------------------------
 * Guias de proyeccion (ayudas CAD al dibujar, v2.16.0)
 * -----------------------------------------------------------------
 * Ejes horizontales/verticales de un vertice de referencia y la recta que
 * pasa por dos referencias. Dibujadas en su propia capa (gGuias), encima de
 * todo y sin capturar puntero: son una lectura, no geometria.
 * ------------------------------------------------------------------ */
.guia-eje {
  stroke: #22d3ee;
  stroke-width: 1;
  stroke-dasharray: 2 4;
  opacity: 0.55;
  pointer-events: none;
}

.guia-linea {
  stroke: #c084fc;
  stroke-width: 1.2;
  stroke-dasharray: 4 5;
  opacity: 0.65;
  pointer-events: none;
}

.guia-ref {
  fill: #22d3ee;
  stroke: #050811;
  stroke-width: 1.5;
  pointer-events: none;
}

.guia-enganche {
  fill: #f0abfc;
  stroke: #050811;
  stroke-width: 1.5;
  pointer-events: none;
}

/* Receptor seleccionado (herramienta Mover): anillo distinto al resaltado
 * del diagnostico (is-activo) para no confundir «el que se mide» con «el que
 * editas». ------------------------------------------------------------------ */
.receptor-punto.is-seleccionado {
  fill: #f0abfc;
  stroke: #701a75;
}

.receptor-captura.is-seleccionado {
  fill: rgba(240, 171, 252, 0.18);
  stroke: rgba(240, 171, 252, 0.9);
}

.check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(148, 163, 184, 0.06);
  border: 1px solid var(--line, rgba(148, 163, 184, 0.18));
  cursor: pointer;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-dim, #cbd5e1);
}

.check-row input[type="checkbox"] {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  accent-color: var(--sky-400, #38bdf8);
  cursor: pointer;
}

/* Al pulsar la fila se nota que es interactiva. */
.check-row:hover { background: rgba(148, 163, 184, 0.1); }
.check-row:focus-within { outline: 2px solid var(--sky-400, #38bdf8); outline-offset: 2px; }

.cad-line, .cad-curve {
  fill: none;
  stroke-width: 3.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* El grosor no debe escalar al transformar: mantiene la legibilidad. */
  vector-effect: non-scaling-stroke;
}
.cad-line { stroke: var(--cad-line); filter: drop-shadow(0 0 6px rgba(56, 189, 248, 0.6)); }
.cad-curve { stroke: var(--cad-curve); filter: drop-shadow(0 0 6px rgba(192, 132, 252, 0.6)); }

.qrd-well { fill: #334155; stroke: var(--sky-600); stroke-width: 1.5; }

.selected-obj { filter: drop-shadow(0 0 14px rgba(56, 189, 248, 0.95)); }

.cad-handle {
  fill: #f43f5e;
  stroke: #fff;
  stroke-width: 2;
  cursor: grab;
  transition: fill var(--dur-fast) var(--ease-out);
}
.cad-handle:hover { fill: var(--rose); }
.cad-handle:active { cursor: grabbing; }

/* En tactil los tiradores se agrandan: el dedo necesita mas precision.
   `r` es una propiedad geometrica SVG y exige unidad de longitud. */
@media (pointer: coarse) {
  .cad-handle { r: 8px; stroke-width: 2.5; }
}

/* ==========================================================================
   5. Superficies de vidrio
   ========================================================================== */
.glass {
  background: var(--panel);
  backdrop-filter: blur(18px) saturate(140%);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  border: 1px solid var(--border);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7);
}

/* ==========================================================================
   6. Iconos SVG inline
   ========================================================================== */
.ic { fill: currentColor; display: block; flex: none; vertical-align: middle; }
[data-icon] { display: inline-flex; align-items: center; justify-content: center; }

/* ==========================================================================
   7. Botones
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: rgba(30, 41, 59, 0.8);
  color: var(--text);
  font-size: 12px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
  /* Sin esto, el navegador hace zoom de doble toque sobre la botonera. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { background: rgba(51, 65, 85, 0.9); }
.btn:active { transform: scale(0.95); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }

.btn-primary {
  background: linear-gradient(90deg, var(--sky) 0%, var(--cyan) 100%);
  border-color: transparent;
  color: #0b1220;
  font-weight: 800;
  box-shadow: 0 10px 24px rgba(56, 189, 248, 0.25);
}
.btn-primary:hover { background: linear-gradient(90deg, #7dd3fc, #67e8f9); }

.btn-ray {
  background: linear-gradient(90deg, var(--cyan) 0%, var(--emerald) 100%);
  border-color: transparent;
  color: #0b1220;
  font-weight: 800;
  box-shadow: 0 10px 24px rgba(34, 211, 238, 0.25);
}
.btn-ray:hover { background: linear-gradient(90deg, #67e8f9, #6ee7b7); }

.btn-danger { background: rgba(76, 5, 25, 0.8); border-color: rgba(159, 18, 57, 0.8); color: var(--rose); }
.btn-danger:hover { background: rgba(136, 19, 55, 0.9); }

/* Area tactil minima comoda (WCAG 2.5.5 / Material). */
@media (pointer: coarse) {
  .btn { min-height: 44px; padding: 12px 14px; }
}

.btn-icon { flex-direction: column; gap: 2px; padding: 6px; font-size: 10px; }
.btn-icon.is-active { border-color: var(--sky); background: rgba(51, 65, 85, 0.95); color: #e0f2fe; }
.btn-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.btn-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.btn-wide { width: 100%; }

/* ==========================================================================
   8. Controles de formulario
   ========================================================================== */
.field { display: flex; flex-direction: column; gap: 6px; }
.field-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 500;
}
.field-value { font-size: 11px; font-weight: 600; color: var(--sky); font-family: 'JetBrains Mono', ui-monospace, monospace; }
/* Ayuda de una línea bajo cada mando. Más pequeña que el valor: explica sin
   competir con la etiqueta. Sólo aparece en FDTD (el bloque se oculta en Ray). */
.field-help { font-size: 9px; color: var(--text-faint); line-height: 1.45; margin: 0; }

input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 5px;
  border-radius: 3px;
  background: rgba(51, 65, 85, 0.6);
  outline: none;
  cursor: pointer;
  margin: 0;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--sky);
  cursor: pointer;
  box-shadow: 0 0 10px rgba(56, 189, 248, 0.9);
  transition: transform var(--dur-fast) var(--ease-out);
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.2); background: #7dd3fc; }
input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px; border: none; border-radius: 50%;
  background: var(--sky); cursor: pointer;
  box-shadow: 0 0 10px rgba(56, 189, 248, 0.9);
}
input[type="range"]:disabled { cursor: not-allowed; }
input[type="range"]:disabled::-webkit-slider-thumb { background: var(--text-faint); box-shadow: none; }
input[type="range"]:disabled::-moz-range-thumb { background: var(--text-faint); box-shadow: none; }
input[type="range"]:focus-visible { outline: 2px solid var(--sky); outline-offset: 4px; }

/* En tactil los sliders necesitan mas altura para agarrarse bien. */
@media (pointer: coarse) {
  input[type="range"] { height: 8px; }
  input[type="range"]::-webkit-slider-thumb { width: 22px; height: 22px; }
  input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }
}

select, input[type="file"] {
  width: 100%;
  padding: 6px 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: rgba(30, 41, 59, 0.9);
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
}
select:focus-visible { outline: 2px solid var(--sky); outline-offset: 1px; }
input[type="file"] { color: var(--text-dim); }
input[type="file"]::file-selector-button {
  margin-right: 8px;
  padding: 4px 8px;
  border: none;
  border-radius: 6px;
  background: rgba(30, 41, 59, 0.9);
  color: var(--text);
  font-size: 11px;
  font-family: inherit;
  cursor: pointer;
}

/* Selector de plano de fondo.

   El <input type="file"> nativo dibuja su boton y el nombre del archivo con
   una maquina interna que no recorta: con un panel estrecho el nombre se
   desbordaba fuera de la caja y pisaba el boton (ver 07-tablet.png). Se oculta
   el input y se muestran un <label> + el nombre, que si admite `text-overflow`.

   El input oculto conserva su lugar en el flujo para que el foco por teclado
   siga funcionando; el anillo de foco se dibuja en el label mediante el
   selector de hermanos `+`. */
input[type="file"].sr-only { width: 1px; height: 1px; }
.file-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.file-btn { flex: none; }
.file-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11px;
  color: var(--text-dim);
}
#img-input:focus-visible + .file-row .file-btn { outline: 2px solid var(--sky); outline-offset: 2px; }
/* Interruptor (switch) */
.switch { position: relative; display: inline-flex; align-items: center; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track {
  width: 32px; height: 18px;
  background: rgba(51, 65, 85, 0.9);
  border-radius: 999px;
  transition: background var(--dur) var(--ease-out);
  position: relative;
}
.switch-track::after {
  content: '';
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid rgba(148, 163, 184, 0.7);
  transition: transform var(--dur) var(--ease-out);
}
.switch input:checked + .switch-track { background: var(--sky); }
.switch input:checked + .switch-track::after { transform: translateX(14px); border-color: #fff; }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--sky); outline-offset: 2px; }

@media (pointer: coarse) {
  .switch-track { width: 44px; height: 26px; }
  .switch-track::after { width: 20px; height: 20px; }
  .switch input:checked + .switch-track::after { transform: translateX(18px); }
}

/* ==========================================================================
   9. Secciones y conmutadores de modo
   ========================================================================== */
.section { display: flex; flex-direction: column; gap: 8px; }
.section-title {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}
.section-divider { border-top: 1px solid rgba(51, 65, 85, 0.8); padding-top: 10px; }

.segmented {
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: 12px;
  border: 1px solid rgba(30, 41, 59, 0.9);
  background: rgba(15, 23, 42, 0.9);
}
.segmented-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 6px 8px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  touch-action: manipulation;
}
.segmented-btn:hover { color: #fff; }
.segmented-btn[aria-pressed="true"] { background: var(--sky); color: #0b1220; }
.segmented-btn.ray[aria-pressed="true"] { background: var(--cyan); }

.badge {
  font-size: 10px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid;
  white-space: nowrap;
}
.badge-sky { background: rgba(8, 47, 73, 0.9); color: #7dd3fc; border-color: rgba(14, 116, 144, 0.8); }
.badge-cyan { background: rgba(8, 51, 68, 0.9); color: #67e8f9; border-color: rgba(14, 116, 144, 0.8); }
.badge-amber { background: rgba(66, 32, 6, 0.9); color: var(--amber); border-color: rgba(120, 53, 15, 0.8); }
.badge-emerald { background: rgba(6, 46, 34, 0.9); color: var(--emerald); border-color: rgba(6, 95, 70, 0.8); }

.dim { opacity: 0.45; }
.hidden { display: none !important; }

/* Deslizador de transformacion */
.transform-row { display: flex; align-items: center; gap: 8px; }
.transform-row label { font-size: 12px; color: var(--text-dim); width: 52px; flex: none; }
.transform-row .field-value { width: 42px; text-align: right; flex: none; }

/* ==========================================================================
   10. Leyenda de orden de reflexion
   ========================================================================== */
.legend {
  position: absolute;
  left: calc(16px + var(--safe-l));
  bottom: calc(16px + var(--safe-b));
  z-index: var(--z-legend);
  border-radius: 16px;
  padding: 14px;
  color: #fff;
  display: flex;
  flex-direction: column;
  gap: 8px;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.legend.is-off { opacity: 0; pointer-events: none; transform: translateY(8px); }
.legend-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(51, 65, 85, 0.8);
}
.legend-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #e2e8f0;
}
.legend-grid { display: grid; grid-template-columns: repeat(2, auto); gap: 6px 16px; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--text-dim); }
.legend-swatch { width: 12px; height: 12px; border-radius: 50%; flex: none; }

/* ==========================================================================
   11. Panel de herramientas
   ==========================================================================
   La anchura se fija de forma EXPLICITA. El monolito usaba `w-84`, clase que
   no existe en la escala de Tailwind, por lo que el panel se dimensionaba de
   forma automatica y quedaba estrecho o desbordado segun el contenido.
   ========================================================================== */
.panel {
  position: absolute;
  right: calc(16px + var(--safe-r));
  bottom: calc(96px + var(--safe-b));
  z-index: var(--z-panel);
  width: 336px;
  max-height: min(82vh, 82dvh);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* El panel esta dentro de #stage y hereda `touch-action: none` de la capa
   * de dibujo, que en tactil bloqueaba el desplazamiento vertical de la lista:
   * habia que arrastrar la barra de scroll a mano. `pan-y` devuelve el
   * desplazamiento vertical a este elemento sin tocar el dibujo. */
  touch-action: pan-y;
  border-radius: 16px;
  padding: 16px;
  color: #fff;
  display: flex;
  flex-direction: column;
  gap: 14px;
  transform-origin: bottom right;
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out),
              visibility var(--dur);
}
.panel.is-closed { opacity: 0; pointer-events: none; visibility: hidden; transform: scale(0.94); }

/* Barra de desplazamiento fina y discreta. */
.panel::-webkit-scrollbar { width: 6px; }
.panel::-webkit-scrollbar-track { background: rgba(15, 23, 42, 0.4); }
.panel::-webkit-scrollbar-thumb { background: rgba(51, 65, 85, 0.7); border-radius: 3px; }
.panel { scrollbar-width: thin; scrollbar-color: rgba(51, 65, 85, 0.7) transparent; }

.panel-header { display: flex; flex-direction: column; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid rgba(51, 65, 85, 0.8); }
.panel-title-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.panel-title { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--sky); }

.row-between { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.row-label { font-size: 12px; color: var(--text-dim); font-weight: 500; }

.actions-box { display: flex; flex-direction: column; gap: 8px; padding: 10px; border-radius: 12px; background: var(--panel-inner); border: 1px solid rgba(51, 65, 85, 0.6); }
.actions-box.ray { border-color: rgba(34, 211, 238, 0.3); }

.hint { font-size: 10px; color: var(--text-faint); line-height: 1.5; }

/* ==========================================================================
   12. Barra de acciones y FAB
   ========================================================================== */
.actionbar {
  position: absolute;
  right: calc(16px + var(--safe-r));
  bottom: calc(16px + var(--safe-b));
  z-index: var(--z-actions);
  display: flex;
  align-items: center;
  gap: 10px;
}
.action-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 14px;
  border-radius: 16px;
  font-size: 12px;
  font-weight: 700;
  color: var(--amber);
  cursor: pointer;
  touch-action: manipulation;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.action-btn:hover { background: rgba(30, 41, 59, 0.9); }
.action-btn:active { transform: scale(0.95); }
/* Gris hasta que «Simular» arranque la propagacion: ver `applySimulationStarted`
 * en panel.js. Sin esta regla el disabled nativo no se veia en este boton. */
.action-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
.action-btn:disabled:hover { background: var(--panel); } /* sin realce: no es clickeable */
.action-btn.is-paused { color: var(--emerald); }
.action-btn.sky { color: #7dd3fc; }

.fab-wrap { position: relative; display: flex; align-items: center; justify-content: center; }
.fab {
  position: relative;
  width: 56px; height: 56px;
  border-radius: 50%;
  border: none;
  background: linear-gradient(135deg, #0369a1, #38bdf8, #22d3ee);
  color: #0b1220;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 12px 32px rgba(56, 189, 248, 0.5);
  touch-action: manipulation;
  transition: transform var(--dur) var(--ease-out);
}
.fab:hover { transform: scale(1.05); }
.fab:active { transform: scale(0.95); }

/* Ondas expansivas del FAB: puramente decorativas. */
/* Ondas expansivas del FAB: puramente decorativas.

   `z-index: -1` las hunde detras de los botones hermanos. Sin esto los anillos
   escalan hasta 2.8x (unos 50 px por fuera del FAB) y tapaban el texto del
   boton contiguo ("Limpiar") en horizontal, donde la barra se queda sin holgura. */
.fab-ripple {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid rgba(56, 189, 248, 0.6);
  animation: ripple 2.5s infinite var(--ease-out);
  pointer-events: none;
  z-index: -1;
}
.fab-ripple:nth-of-type(2) { animation-delay: 0.8s; }
.fab-ripple:nth-of-type(3) { animation-delay: 1.6s; }
@keyframes ripple {
  0% { transform: scale(1); opacity: 0.85; }
  100% { transform: scale(2.8); opacity: 0; }
}

/* Anillo de pulso al emitir un pulso acustico. */
.pulse-ring {
  position: absolute;
  border-radius: 50%;
  border: 2px solid #7dd3fc;
  box-shadow: 0 0 20px rgba(56, 189, 248, 0.9);
  pointer-events: none;
  z-index: 4;
  transform: translate(-50%, -50%);
}

/* ==========================================================================
   13. Avisos
   ========================================================================== */
.toast-stack {
  position: absolute;
  top: calc(12px + env(safe-area-inset-top, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
  width: min(420px, calc(100vw - 24px));
}
.toast {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: 12px;
  background: rgba(10, 15, 29, 0.96);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 12px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6);
  pointer-events: auto;
  max-width: 100%;
}
.toast-warn { border-color: rgba(120, 53, 15, 0.9); color: var(--amber); }
.toast-ok { border-color: rgba(6, 95, 70, 0.9); color: var(--emerald); }

/* Aviso de app instalable: solo aparece si el navegador lo permite. */
.install-prompt {
  position: absolute;
  left: calc(16px + var(--safe-l));
  bottom: calc(16px + var(--safe-b));
  z-index: var(--z-legend);
  display: flex;
  align-items: center;
  gap: 8px;
}
.install-prompt.is-off { display: none; }

/* ==========================================================================
   14. Utilidades
   ========================================================================== */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.spread { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.stack { display: flex; flex-direction: column; gap: 8px; }

/* ==========================================================================
   15. Responsive
   ========================================================================== */

/* Pantallas bajas (portatiles de 768 px o menos): el panel no debe comerse
   la escena. */
@media (max-height: 700px) {
  .panel { max-height: min(68vh, 68dvh); }
  .legend { padding: 10px; }
  .legend-grid { gap: 4px 12px; }
}

/* Tablet / movil en horizontal */
@media (max-width: 900px) {
  .panel { width: 300px; }
}

/* Tablet en vertical y movil grande: el panel pasa a hoja inferior.
   En vertical el pulgar alcanza la parte baja, no un panel lateral. */
@media (max-width: 640px) {
  .panel {
    right: 0;
    left: 0;
    bottom: 0;
    width: 100%;
    max-height: min(72vh, 72dvh);
    border-radius: 20px 20px 0 0;
    padding: 16px 16px calc(16px + var(--safe-b));
    transform-origin: bottom center;
    border-left: none;
    border-right: none;
    border-bottom: none;
  }
  .panel.is-closed { transform: translateY(100%); opacity: 1; }

  /* La barra de acciones flota por encima de la hoja (z-index 40 > 30) y, con
     la hoja abierta, se comia sus ultimos controles: "Borrar" quedaba medio
     tapado y la seccion 4 era ilegible. No se puede apartar la barra (es el
     acceso a Pausar/Limpiar y el asidero del pulgar), asi que se le reserva su
     franja como padding inferior mientras la hoja esta abierta: el contenido se
     desplaza por encima de la barra en lugar de quedar debajo. 12 px de
     separacion + 52 px del FAB + 12 px de aire. */
  .panel:not(.is-closed) { padding-bottom: calc(76px + var(--safe-b)); }

  /* Asa visual: indica que la hoja se cierra deslizando. */
  .panel::before {
    content: '';
    display: block;
    width: 40px;
    height: 4px;
    margin: 0 auto 10px;
    flex: none;
    border-radius: 999px;
    background: rgba(148, 163, 184, 0.5);
  }

  .actionbar { right: calc(12px + var(--safe-r)); bottom: calc(12px + var(--safe-b)); gap: 8px; }
  .action-btn { padding: 10px; }
  .action-btn .btn-text { display: none; }  /* solo icono */
  .fab { width: 52px; height: 52px; }

  /* La leyenda se ancla arriba para no chocar con la hoja inferior. */
  .legend { bottom: auto; top: calc(12px + env(safe-area-inset-top, 0px)); left: calc(12px + var(--safe-l)); padding: 10px; }
  .legend-grid { grid-template-columns: auto; }
  .install-prompt { bottom: auto; top: calc(12px + env(safe-area-inset-top, 0px)); }
}

/* Movil pequeno: mas compactacion. */
@media (max-width: 400px) {
  .panel { max-height: min(78vh, 78dvh); }
  .btn-grid-4 { grid-template-columns: repeat(2, 1fr); }
}

/* Paisaje bajo (movil girado): el panel no puede ocupar media pantalla.

   Aqui se acota por arriba Y por abajo en lugar de solo por altura. El panel
   crece hacia arriba desde su borde inferior, de modo que con un unico
   `max-height` en una pantalla de 390 px se salia por el borde superior: la
   pestana de modos quedaba cortada por la mitad. El borde inferior deja hueco
   a la barra de acciones, que en horizontal ocupa la misma franja que el panel. */
@media (max-height: 480px) and (orientation: landscape) {
  .panel {
    top: calc(12px + env(safe-area-inset-top, 0px));
    bottom: calc(64px + var(--safe-b));
    max-height: none;
    width: 290px;
  }
  .legend { display: none; }
  /* Solo iconos: la barra se estrecha y deja de pisar el FAB. */
  .action-btn { padding: 10px; }
  .action-btn .btn-text { display: none; }
}

/* ==========================================================================
   16. Preferencias de movimiento y contraste
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .fab-ripple { display: none; }
}

@media (prefers-contrast: more) {
  :root {
    --text-dim: #cbd5e1;
    --text-faint: #94a3b8;
    --border: rgba(255, 255, 255, 0.4);
  }
}

/* Impresion: el vidrio y el desenfoque no existen en papel. */
@media print {
  .panel, .actionbar, .legend, .toast-stack { display: none !important; }
  body { background: #fff; }
}
/* Desplegable "Condiciones del aire y detalle". Se usa <details> nativo:
   plegado por defecto, accesible por teclado y sin JS que mantener. */
.disclosure {
  margin-top: 12px;
  border-top: 1px solid var(--line);
}
.disclosure-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 2px;
  cursor: pointer;
  list-style: none;
  color: var(--text-dim);
  font-size: 12px;
  user-select: none;
}
.disclosure-head::-webkit-details-marker { display: none; }
.disclosure-head::after {
  content: '▾';
  font-size: 11px;
  color: var(--text-dim);
  transition: transform .15s ease;
}
.disclosure[open] > .disclosure-head::after { transform: rotate(180deg); }
.disclosure-head:hover { color: var(--text); }
.disclosure-body { padding: 4px 0 2px; }

/* Boton secundario: mismo ancho, pero sin llenar. Para acciones que no son
   el camino principal del flujo (p.ej. teclear la escala en vez de trazarla). */
.btn-ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--text-dim);
}
.btn-ghost:hover {
  background: rgba(255, 255, 255, .04);
  color: var(--text);
  border-color: var(--accent);
}

/* Etiqueta de un grupo dentro de una seccion (p.ej. "Escala real"). */
.row-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
}
