/* =============================================================================
 * componentes-presentacion.css · Componentes comunes de presentación (T-0.15)
 *
 * Lo que la plantilla no trae y la guía de presentación pide en toda pantalla:
 *
 *   1. Estados de un contenedor: cargando, vacío y error (§6.5, §7).
 *   2. Indicadores (KPI) de un dashboard (§6.5).
 *   3. Validación de formularios: marca de obligatorio, resumen superior,
 *      campos de solo lectura (§6.4) y ojo para ver la contraseña.
 *   4. Contenedores de gráfico, con su hueco reservado y su estado (§6.5, §10.2).
 *   5. Datos de una ficha: etiqueta y valor, y valor ausente (§6.3).
 *   6. Capa de bloqueo por inactividad (RN-003).
 *   7. Editor de texto enriquecido (editor-texto.js).
 *
 * Reglas del fichero:
 *   - Sin nombres funcionales (§13.1): ningún concepto de la clínica. Cada
 *     pantalla pone sus propios identificadores; aquí solo hay clases.
 *   - Sin colores escritos: solo tokens --em-* del tema y variables de Bootstrap,
 *     así que todo vale igual en claro y en oscuro (TemaTest lo comprueba).
 *
 * Las plantillas de WEB-INF/jsp/_plantillas/ usan cada componente; el catálogo
 * está en docs/tema.md.
 * ============================================================================= */

/* -----------------------------------------------------------------------------
 * 1. Estados de un contenedor
 * -------------------------------------------------------------------------- */

/* Cargando dentro de un contenedor: tabla o gráfico. infinite-scroll.js lo muestra y
 * lo oculta; la página entera usa #preloader.
 *   <div id="xLoadingIndicator" class="data loading" role="status">
 *     <span class="spinner-border spinner-border-sm" aria-hidden="true"></span> Cargando…
 *   </div> */
.data.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1rem;
  color: var(--em-texto-secundario);
  font-size: 0.875rem;
}

/* Vacío y error. Explican qué pasa y, si existe, el siguiente paso (§8.3). El error
 * nunca se confunde con el vacío: otro icono y otro texto (§13.3).
 *   <div class="estado-contenedor estado-vacio">
 *     <i class="fas fa-inbox" aria-hidden="true"></i>
 *     <p class="estado-titulo">No hay resultados</p>
 *     <p class="estado-detalle">Prueba con otros filtros.</p>
 *   </div> */
.estado-contenedor {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  padding: 2rem 1rem;
  text-align: center;
  color: var(--em-texto);
}
.estado-contenedor > .fas,
.estado-contenedor > .far {
  margin-bottom: 0.5rem;
  font-size: 1.75rem;
  color: var(--em-texto-secundario);
}
.estado-contenedor.estado-error > .fas,
.estado-contenedor.estado-error > .far {
  color: var(--em-error-texto);
}
.estado-titulo {
  margin: 0;
  font-weight: 600;
  color: var(--em-titulo);
}
.estado-detalle {
  margin: 0;
  font-size: 0.875rem;
  color: var(--em-texto-secundario);
}
.estado-contenedor .btn {
  margin-top: 0.75rem;
}

/* -----------------------------------------------------------------------------
 * 2. Indicadores (KPI)
 *
 * Empiezan en «-»: el cero se reserva para un dato real recibido (§6.5).
 *   <section class="card kpi">
 *     <div class="card-body">
 *       <span class="kpi-icono"><i class="fas fa-…" aria-hidden="true"></i></span>
 *       <div>
 *         <p class="kpi-etiqueta">Concepto</p>
 *         <p class="kpi-valor" id="kpiConcepto">-</p>
 *         <p class="kpi-detalle">Periodo</p>
 *       </div>
 *     </div>
 *   </section>
 * -------------------------------------------------------------------------- */

.kpi .card-body {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.kpi-icono {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background-color: rgba(var(--em-primario-rgb), 0.1);
  color: var(--em-primario-texto);
  font-size: 1.25rem;
}
.kpi-etiqueta {
  margin: 0;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--em-texto-secundario);
}
/* Cabe un importe de seis cifras en la columna de un cuarto de ancho. */
.kpi-valor {
  margin: 0;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.2;
  color: var(--em-titulo);
  font-variant-numeric: tabular-nums;
}
.kpi-detalle {
  margin: 0;
  font-size: 0.75rem;
  color: var(--em-texto-secundario);
}

/* -----------------------------------------------------------------------------
 * 3. Formularios
 * -------------------------------------------------------------------------- */

/* Obligatorio: el asterisco lo pone la clase y se explica una sola vez con
 * .nota-obligatorios. El control lleva además `required`, que es lo que anuncia el
 * lector de pantalla; por eso el asterisco no se lee («/ ""»). */
.form-label.obligatorio::after {
  content: " *";
  content: " *" / "";
  color: var(--em-error-texto);
}
.nota-obligatorios {
  font-size: 0.8125rem;
  color: var(--em-texto-secundario);
}

/* Resumen de errores encima del formulario cuando falla más de un campo. Recibe el
 * foco para que se anuncie; cada error enlaza con su campo (formulario.js).
 *   <div class="alert alert-danger resumen-errores d-none" role="alert" tabindex="-1">
 *     <p class="resumen-errores-titulo">Revisa estos campos:</p>
 *     <ul></ul>
 *   </div> */
.resumen-errores:focus {
  outline: 2px solid var(--em-foco);
  outline-offset: 2px;
}
.resumen-errores-titulo {
  margin-bottom: 0.25rem;
  font-weight: 600;
}
.resumen-errores ul {
  margin: 0;
  padding-left: 1.25rem;
}
.resumen-errores a {
  color: inherit;
  text-decoration: underline;
}

/* Solo lectura frente a deshabilitado (§6.4). El de solo lectura se lee y se copia: sin
 * fondo y con borde discontinuo. El deshabilitado, con fondo gris y texto atenuado, y su
 * ayuda explica por qué. La plantilla les pone a los dos fondo blanco. */
.form-control[readonly]:not(.form-control-plaintext) {
  background-color: transparent;
  border-style: dashed;
}
.form-control:disabled,
.form-select:disabled {
  background-color: var(--em-deshabilitado-fondo);
  color: var(--em-texto-secundario);
  cursor: not-allowed;
}
/* En oscuro la plantilla fija el fondo de los campos con !important. */
[data-theme-version=dark] .form-control:disabled {
  background-color: var(--em-deshabilitado-fondo) !important;
}

/* Ojo de una contraseña (mostrar-contrasena.js). Ancho fijo: el ojo tachado es más ancho
 * y el campo no debe moverse al pulsarlo.
 *   <button type="button" class="btn btn-outline-primary boton-contrasena"
 *           data-mostrar-contrasena="password" aria-controls="password" aria-pressed="false"
 *           aria-label="Mostrar contraseña" title="Mostrar contraseña">
 *     <i class="fas fa-eye" aria-hidden="true"></i>
 *   </button> */
.boton-contrasena {
  width: 3.25rem;
  padding-left: 0;
  padding-right: 0;
}

/* -----------------------------------------------------------------------------
 * 4. Gráficos
 *
 * El hueco se reserva desde el primer pintado, para que la página no salte al llegar
 * los datos; el estado (cargando, vacío o error) se pinta dentro del propio hueco.
 *   <div class="grafico">
 *     <div class="grafico-lienzo" id="chartConcepto" role="img" aria-label="…"></div>
 *     <p class="grafico-estado" id="chartConceptoEstado">Cargando…</p>
 *   </div>
 * -------------------------------------------------------------------------- */

.grafico {
  position: relative;
}
.grafico-lienzo {
  min-height: 300px;
  border-radius: 4px;
}
.grafico-estado {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 1rem;
  text-align: center;
  color: var(--em-texto-secundario);
}
.grafico-estado[hidden] {
  display: none;
}
.grafico-estado.estado-error {
  color: var(--em-error-texto);
}
.grafico-subtitulo {
  font-size: 0.875rem;
  color: var(--em-texto-secundario);
}
@media (max-width: 575.98px) {
  .grafico-lienzo {
    min-height: 250px;
  }
}

/* Textos de ApexCharts: ejes, leyenda y tooltip, con la tipografía y los colores del
 * tema en claro y en oscuro (graficos.js pone el resto de opciones). */
.apexcharts-canvas,
.apexcharts-legend-text,
.apexcharts-tooltip,
.apexcharts-xaxistooltip {
  font-family: "Poppins", sans-serif !important;
}
.apexcharts-legend-text {
  color: var(--em-texto) !important;
}
.apexcharts-tooltip.apexcharts-theme-light {
  border-color: var(--bs-border-color) !important;
}

/* -----------------------------------------------------------------------------
 * 5. Ficha: etiqueta y valor
 *
 *   <dl class="datos-ficha">
 *     <dt>Etiqueta</dt><dd>Valor</dd>
 *     <dt>Otra</dt><dd><span class="valor-ausente">—</span></dd>
 *   </dl>
 * Dos columnas en escritorio y una en móvil, sin cambiar el orden de lectura.
 * -------------------------------------------------------------------------- */

.datos-ficha {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr;
  gap: 0.5rem 1.5rem;
  margin: 0;
}
.datos-ficha dt {
  font-weight: 500;
  color: var(--em-titulo);
}
.datos-ficha dd {
  margin: 0;
  overflow-wrap: anywhere;
}
/* Un campo extenso ocupa todo el ancho. */
.datos-ficha .dato-extenso {
  grid-column: 1 / -1;
}
@media (max-width: 575.98px) {
  .datos-ficha {
    grid-template-columns: 1fr;
    gap: 0.125rem;
  }
  .datos-ficha dd {
    margin-bottom: 0.5rem;
  }
}

/* Valor que legítimamente no existe: guion largo atenuado (§6.3). */
.valor-ausente {
  color: var(--em-texto-secundario);
}

/* Un texto largo escrito por alguien (un comentario, unas notas): respeta sus saltos de línea. */
.texto-con-saltos {
  white-space: pre-line;
}

/* Muestra de un color que elige la clínica en la configuración: el color llega como dato y se pone con
   background-color; aquí solo su forma. */
.muestra-de-color {
  display: inline-block;
  width: 1em;
  height: 1em;
  border: 1px solid var(--bs-border-color);
  border-radius: 0.2em;
  vertical-align: -0.125em;
}

/* -----------------------------------------------------------------------------
 * 6. Capa de bloqueo por inactividad (RN-003)
 *
 *   fragments/bloqueo.jspf, gobernada por js/common/inactividad.js.
 * Opaca y por encima de todo, también de los modales (1055) y de los avisos
 * globales (2000): en el puesto compartido no se ve nada de lo que hay debajo.
 * -------------------------------------------------------------------------- */
.capa-bloqueo {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background-color: var(--em-marino);
  overflow-y: auto;
}
/* La plantilla da a .card la altura de su fila: aquí, solo la de su contenido. */
.capa-bloqueo-tarjeta {
  width: 100%;
  max-width: 26rem;
  height: auto;
  margin: 0;
}
.marca-bloqueo {
  height: 2.5rem;
  width: auto;
  max-width: 100%;
}

/* -----------------------------------------------------------------------------
 * 7. Editor de texto enriquecido
 *
 *   js/common/editor-texto.js lo monta sobre un textarea, que queda oculto detrás:
 *   <div class="editor-texto">
 *     <div class="editor-texto-barra" role="toolbar" aria-label="Formato del texto">…</div>
 *     <div class="form-control editor-texto-area" contenteditable="true" role="textbox"
 *          aria-multiline="true" data-control-de="<name>">…</div>
 *   </div>
 * El área es un .form-control: borde, foco y error como los demás campos.
 * -------------------------------------------------------------------------- */
.editor-texto-barra {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin-bottom: 0.375rem;
}
.editor-texto-barra .form-select {
  width: auto;
}
.editor-texto-area {
  min-height: 16rem;
  max-height: 60vh;
  overflow-y: auto;
  line-height: 1.6;
}
/* En un formulario con varios editores (la hoja de intervención), más bajos. */
.editores-compactos .editor-texto-area {
  min-height: 7rem;
}
.editor-texto-area h1,
.editor-texto-area h2,
.editor-texto-area h3 {
  margin: 0.75rem 0 0.5rem;
  color: var(--em-titulo);
}
.editor-texto-area h1 {
  font-size: 1.375rem;
}
.editor-texto-area h2 {
  font-size: 1.125rem;
}
.editor-texto-area h3 {
  font-size: 1rem;
}
.editor-texto-area p,
.editor-texto-area ul,
.editor-texto-area ol {
  margin-bottom: 0.5rem;
}
.editor-texto-area ul,
.editor-texto-area ol {
  padding-left: 1.5rem;
}
/* La plantilla quita las viñetas a todos los li. */
.editor-texto-area ul > li {
  list-style: disc;
}
.editor-texto-area ol > li {
  list-style: decimal;
}
.editor-texto-area > :first-child {
  margin-top: 0;
}

/* Sugerencias bajo un campo de búsqueda: un .dropdown-menu de Bootstrap que abre y cierra el script
   de la búsqueda. Ancho para que quepa cada resultado y scroll si son muchos. */
.lista-sugerencias {
  min-width: 22rem;
  max-width: calc(100vw - 2rem);
  max-height: 70vh;
  overflow-y: auto;
}
.lista-sugerencias .dropdown-item {
  white-space: normal;
}
.lista-sugerencias .sugerencia-detalle {
  font-size: 0.8125rem;
  opacity: 0.8;
}

/* Zona para soltar ficheros de un formulario de subida: borde discontinuo y, mientras se arrastra
   algo encima, el color del primario. Los botones de dentro son <label> de inputs de fichero ocultos. */
.zona-soltar-ficheros {
  border: 2px dashed var(--bs-border-color);
  border-radius: 0.75rem;
  padding: 1.25rem 1rem;
  text-align: center;
  color: var(--em-texto-secundario);
}
.zona-soltar-ficheros > .fas {
  font-size: 1.75rem;
  color: var(--em-marino);
  margin-bottom: 0.5rem;
}
.zona-soltar-ficheros.arrastrando {
  border-color: var(--em-marino);
  background-color: rgba(var(--em-primario-rgb), 0.06);
}

/* Miniatura de una imagen en una fila de tabla, o el icono de su tipo si no la tiene. */
.miniatura-tabla {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  object-fit: cover;
  border-radius: 0.5rem;
  border: 1px solid var(--bs-border-color);
}
.miniatura-icono {
  font-size: 1.5rem;
  color: var(--em-texto-secundario);
}

/* Vista previa de una imagen que sale impresa, entera y sobre blanco como en el papel, también en el
   tema oscuro: el logotipo de los documentos, por ejemplo. */
.vista-previa-imagen {
  max-width: 16rem;
  max-height: 4rem;
  object-fit: contain;
  padding: 0.5rem;
  background-color: var(--bs-white);
  border: 1px solid var(--bs-border-color);
  border-radius: 0.5rem;
}

/* -----------------------------------------------------------------------------
 * Firma en la tableta (D-015, T-4.06): a pantalla completa, sin nada más a la
 * vista. El lienzo usa el color del texto; al guardarla, la firma va casi negra.
 * -------------------------------------------------------------------------- */
.firma-tableta {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
  background: var(--em-campo-fondo);
}
.firma-tableta-lienzo {
  flex: 1;
  width: 100%;
  min-height: 12rem;
  border: 2px dashed var(--em-campo-borde);
  border-radius: 0.5rem;
  color: var(--em-texto);
  background: var(--em-campo-fondo);
  touch-action: none;
  cursor: crosshair;
}
.firma-tableta-acciones {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}
body.con-firma-tableta {
  overflow: hidden;
}

/* -----------------------------------------------------------------------------
 * Galería de fotografías clínicas y comparador (F-INT-06, T-4.07).
 * -------------------------------------------------------------------------- */
.galeria-fotos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 1rem;
}
.galeria-fotos-item {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.galeria-fotos-item img,
.galeria-fotos-sin-miniatura {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 1px solid var(--em-campo-borde);
  border-radius: 0.5rem;
  background: var(--em-deshabilitado-fondo);
  color: var(--em-texto-secundario);
  font-size: 2rem;
}
.galeria-fotos-pie {
  font-size: 0.8125rem;
  color: var(--em-texto-secundario);
}
.comparador-foto img {
  display: block;
  width: 100%;
  max-height: 65vh;
  object-fit: contain;
  border-radius: 0.5rem;
  background: var(--em-deshabilitado-fondo);
}

/* Adendas de una intervención cerrada (F-INT-09, T-4.08): el texto tal como se escribió, con sus saltos. */
.adenda-texto {
  display: block;
  white-space: pre-wrap;
}

/* -----------------------------------------------------------------------------
 * Línea de tiempo (F-PAC-07, T-4.09): de la entrada más reciente a la más antigua. Una anulada sigue a la vista,
 * tachada.
 * -------------------------------------------------------------------------- */
.historial-linea {
  position: relative;
  padding-left: 1.75rem;
  border-left: 3px solid var(--em-campo-borde);
  margin-left: 0.5rem;
}
.historial-entrada {
  position: relative;
  margin-bottom: 1rem;
}
.historial-entrada::before {
  content: "";
  position: absolute;
  left: -2.3rem;
  top: 0.9rem;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  border: 3px solid var(--em-campo-fondo);
  background: var(--em-primario);
}
.historial-entrada.historial-anulada::before {
  background: var(--em-texto-secundario);
}
.historial-tarjeta {
  padding: 1rem 1.25rem;
  border: 1px solid var(--em-campo-borde);
  border-radius: 0.625rem;
  background: var(--em-campo-fondo);
}
.historial-anulada .historial-titulo,
tr.historial-anulada td {
  text-decoration: line-through;
  color: var(--em-texto-secundario);
}
.historial-detalle + .historial-detalle {
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px dashed var(--em-campo-borde);
}
.historial-efecto-adverso {
  color: var(--em-error-texto);
}
.historial-fotos {
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: 0.75rem;
}

/* -----------------------------------------------------------------------------
 * Fecha que se teclea: la plantilla estira el icono del calendario sobre toda la casilla, así que cualquier clic abre
 * el calendario. Con .fecha-tecleable el icono vuelve a su sitio, a la derecha: al pulsar en el texto se escriben el
 * día, el mes y el año, y el calendario solo se abre desde el icono. Para fechas lejanas, como una de nacimiento.
 * -------------------------------------------------------------------------- */
input[type=date].fecha-tecleable::-webkit-calendar-picker-indicator {
  position: static;
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  padding: 0;
  margin-left: 0.5rem;
  background: var(--em-texto-secundario);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7zM5 9h14v11H5V9z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 2v2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2V2h-2v2H9V2H7zM5 9h14v11H5V9z'/%3E%3C/svg%3E") center / contain no-repeat;
  cursor: pointer;
}
input[type=date].fecha-tecleable::-webkit-calendar-picker-indicator:hover {
  background: var(--em-primario);
}
