/* ==========================================================================
   Sistema de Gestión de Laboratorios · FSH · UNL
   Componentes del sistema visual institucional.
   ==========================================================================

   Complementa a Bootstrap 5.3: no lo sustituye. Cada bloque toma un componente
   de Bootstrap y le impone la identidad institucional mediante los tokens de
   `design-tokens.css`, en lugar de reescribirlo desde cero.

   Regla de trabajo: aquí viven los componentes **transversales** —los que
   aparecen en más de un módulo—. Lo que solo existe en una pantalla concreta va
   a `modules/`. Nada de valores sueltos: todo sale de un token.

   Homologación con Bienestar Universitario:
     · tarjetas con filete superior dividido rojo–azul e icono en recuadro azul
       claro;
     · tablas dentro de un contenedor blanco con radio y sombra discreta,
       cabecera clara y texto en azul institucional;
     · formularios con campo de fondo tenue y foco azul institucional;
     · avisos con borde lateral de 4 px, icono y texto (nunca solo color);
     · estado vacío con icono, título, explicación y acción.
   ========================================================================== */

/* ==========================================================================
   1. Botones
   ========================================================================== */
/* Control base a la altura homologada. El texto sube a semibold con tracking
   mínimo para que, al reducir la altura, la etiqueta conserve presencia. */
.btn {
    --bs-btn-font-weight: var(--font-weight-semibold);
    --bs-btn-font-size: var(--font-size-sm);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    border-radius: var(--radius-control);
    min-height: var(--control-height);
    letter-spacing: .005em;
    transition: background-color var(--fsh-transition), border-color var(--fsh-transition),
                box-shadow var(--fsh-transition), transform var(--fsh-transition);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(1px); }
.btn-sm { --bs-btn-font-size: .75rem; min-height: var(--control-height-sm); border-radius: var(--radius-sm); }
.btn-lg { --bs-btn-font-size: var(--font-size-base); min-height: var(--control-height-lg); }

/* Acción principal: azul institucional con un realce contenido dentro de su
   propia familia. Da relieve sin salirse de la paleta. */
.btn-primary {
    --bs-btn-bg: var(--fsh-blue-600);
    --bs-btn-border-color: var(--fsh-blue-600);
    --bs-btn-hover-bg: var(--fsh-blue-700);
    --bs-btn-hover-border-color: var(--fsh-blue-700);
    --bs-btn-active-bg: var(--fsh-blue-700);
    --bs-btn-active-border-color: var(--fsh-blue-700);
    --bs-btn-disabled-bg: var(--fsh-blue-600);
    --bs-btn-disabled-border-color: var(--fsh-blue-600);
    background-image: linear-gradient(180deg,
        rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, 0) 100%);
    box-shadow: 0 1px 2px rgba(0, 76, 123, .24), 0 4px 12px rgba(0, 76, 123, .16);
}
.btn-primary:hover { box-shadow: 0 2px 4px rgba(0, 76, 123, .26), 0 8px 18px rgba(0, 76, 123, .20); }
.btn-success, .btn-danger {
    background-image: linear-gradient(180deg,
        rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, 0) 100%);
}
.btn-outline-primary {
    --bs-btn-color: var(--fsh-blue-600);
    --bs-btn-border-color: var(--fsh-border-strong);
    --bs-btn-hover-bg: var(--fsh-blue-050);
    --bs-btn-hover-color: var(--fsh-blue-700);
    --bs-btn-hover-border-color: var(--fsh-blue-400);
    --bs-btn-active-bg: var(--fsh-blue-100);
    --bs-btn-active-color: var(--fsh-blue-700);
    --bs-btn-active-border-color: var(--fsh-blue-400);
}
/* Acción secundaria neutra: presente pero sin competir con la primaria. */
.btn-secondary {
    --bs-btn-bg: var(--fsh-surface);
    --bs-btn-border-color: var(--fsh-border-strong);
    --bs-btn-color: var(--fsh-text);
    --bs-btn-hover-bg: var(--fsh-surface-muted);
    --bs-btn-hover-border-color: var(--fsh-blue-400);
    --bs-btn-hover-color: var(--fsh-blue-700);
    --bs-btn-active-bg: var(--fsh-neutral-bg);
    --bs-btn-active-color: var(--fsh-text);
    --bs-btn-active-border-color: var(--fsh-border-strong);
}
.btn-danger {
    --bs-btn-bg: var(--fsh-danger);
    --bs-btn-border-color: var(--fsh-danger);
    --bs-btn-hover-bg: var(--unl-red-dark);
    --bs-btn-hover-border-color: var(--unl-red-dark);
    --bs-btn-active-bg: var(--unl-red-dark);
    --bs-btn-active-border-color: var(--unl-red-dark);
}
.btn-success {
    --bs-btn-bg: var(--fsh-success);
    --bs-btn-border-color: var(--fsh-success);
    --bs-btn-hover-bg: var(--unl-green-dark);
    --bs-btn-hover-border-color: var(--unl-green-dark);
}
/* Botón claro sobre fondo fotográfico (portada). */
.btn-light {
    --bs-btn-bg: #fff;
    --bs-btn-border-color: #fff;
    --bs-btn-color: var(--fsh-blue-700);
    --bs-btn-hover-bg: var(--fsh-blue-050);
    --bs-btn-hover-border-color: var(--fsh-blue-050);
    --bs-btn-hover-color: var(--fsh-blue-700);
    font-weight: var(--font-weight-bold);
}
/* Acción discreta: navegación y utilidades de baja jerarquía. */
.btn-link {
    --bs-btn-color: var(--fsh-blue-600);
    --bs-btn-hover-color: var(--fsh-blue-700);
    text-decoration: none;
}
.btn-link:hover { text-decoration: underline; transform: none; }
.btn:disabled, .btn.disabled { opacity: .55; cursor: not-allowed; transform: none; }

/* Botones solo con icono: área táctil mínima de 38 px en cualquier tamaño. */
.btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 38px;
    min-height: 38px;
    padding: .35rem;
}
/* Grupo de acciones de fila o de cabecera. */
.action-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
}
.action-group--end { justify-content: flex-end; }

/* Botón en proceso: evita el doble envío y lo comunica visualmente. */
.btn.is-busy { pointer-events: none; opacity: .75; }
.btn.is-busy::before {
    content: "";
    display: inline-block;
    width: .85em;
    height: .85em;
    margin-right: .45em;
    vertical-align: -.1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: fsh-spin .7s linear infinite;
}
@keyframes fsh-spin { to { transform: rotate(360deg); } }

/* Barra de acciones de formulario. En móvil pasa a bloque completo. */
.form-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    padding-top: var(--space-md);
    margin-top: var(--space-md);
    border-top: 1px solid var(--fsh-border);
}

/* ==========================================================================
   2. Tarjetas y superficies
   ==========================================================================
   La tarjeta institucional: fondo blanco, borde gris claro, radio equilibrado,
   sombra mínima y —cuando encabeza un bloque— filete superior dividido
   rojo–azul. El filete es el sello de marca del sistema; se aplica a las
   tarjetas de módulo y a los paneles destacados, no a cada caja de la pantalla,
   para que no pierda valor por repetición. */
.card {
    border-radius: var(--fsh-radius);
    border: 1px solid var(--fsh-border);
    background: var(--fsh-surface);
    box-shadow: var(--fsh-shadow-sm);
}
.card.shadow-sm, .card.shadow { box-shadow: var(--fsh-shadow-sm) !important; }
.card-header {
    background: var(--fsh-surface);
    border-bottom: 1px solid var(--fsh-border);
    padding: .85rem 1.15rem;
    font-weight: var(--font-weight-semibold);
    color: var(--fsh-blue-700);
}
.card-header .card-title, .card-header h2, .card-header h3 {
    font-size: var(--font-size-lg);
    margin: 0;
    color: var(--fsh-blue-700);
}
.card-body { padding: 1.15rem; }
.card-footer {
    background: var(--fsh-surface-muted);
    border-top: 1px solid var(--fsh-border);
    padding: .8rem 1.15rem;
}

/* Panel de datos: agrupa información sin apilar tarjetas iguales. */
.data-panel {
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    box-shadow: var(--fsh-shadow-sm);
    overflow: hidden;
}
.data-panel__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: .8rem 1.15rem;
    border-bottom: 1px solid var(--fsh-border);
    background: var(--fsh-surface-muted);
}
.data-panel__title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    margin: 0;
    display: flex;
    align-items: center;
    gap: .5rem;
}
.data-panel__title i { color: var(--fsh-blue-600); }
.data-panel__body { padding: 1.15rem; }
.data-panel__body--flush { padding: 0; }

/* Formulario desplegado dentro de una sección, bajo su lista. Se distingue del
   contenido que lo rodea por un fondo apenas más apagado y un filete de acento
   arriba, no por otro marco blanco con borde gris: apilar tarjetas iguales
   confunde qué contiene a qué. */
.inline-form-block {
    position: relative;
    margin-top: var(--space-3);
    border: 1px solid var(--fsh-border);
    border-top: 3px solid var(--unl-red);
    border-radius: var(--fsh-radius);
    background: var(--fsh-surface-muted);
    padding: var(--space-3) 1.15rem;
}
.inline-form-block__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-bottom: var(--space-3);
}
.inline-form-block__title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    margin: 0;
    display: flex;
    align-items: center;
    gap: .5rem;
}
.inline-form-block__title i { color: var(--fsh-blue-600); }

/* Métricas: bloque uniforme para todos los módulos. Filete superior rojo–azul
   y el icono en recuadro azul claro, como en la referencia. */
.metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--space-3);
}
.metric-card {
    position: relative;
    overflow: hidden;
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    box-shadow: var(--fsh-shadow-sm);
    padding: 1.05rem 1.15rem;
    display: flex;
    flex-direction: column;
    gap: .3rem;
}
.metric-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--rule-brand-card);
}
.metric-card__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}
.metric-card__icon {
    width: 36px; height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--fsh-border);
    border-radius: var(--radius-control);
    background: var(--fsh-blue-050);
    color: var(--fsh-blue-600);
    font-size: 1.05rem;
    flex: 0 0 auto;
}
.metric-card--success .metric-card__icon { background: var(--fsh-success-bg); color: var(--fsh-success); border-color: var(--unl-green-40); }
.metric-card--warning .metric-card__icon { background: var(--fsh-warning-bg); color: var(--fsh-warning); border-color: #e8cf9c; }
.metric-card--danger  .metric-card__icon { background: var(--fsh-danger-bg);  color: var(--fsh-danger); border-color: var(--unl-red-40); }
.metric-card--neutral .metric-card__icon { background: var(--fsh-neutral-bg); color: var(--fsh-neutral); border-color: var(--fsh-border-strong); }
.metric-card__context { font-size: var(--font-size-sm); color: var(--fsh-text-muted); }

/* ==========================================================================
   3. Tarjetas de módulo (panel principal y portada)
   ==========================================================================
   El patrón de la referencia: filete superior dividido, icono en recuadro azul
   claro, título en azul oscuro, descripción secundaria y acción alineada abajo. */
.module-card {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--fsh-border);
    transition: border-color var(--fsh-transition), box-shadow var(--fsh-transition), transform var(--fsh-transition);
}
.module-card::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--rule-brand-card);
    z-index: 1;
}
.module-card:not(.module-card--soon):hover {
    border-color: var(--fsh-blue-200);
    box-shadow: var(--fsh-shadow) !important;
    transform: translateY(-2px);
}
.module-card .card-body { padding-top: 1.25rem; }
.module-card h3 { color: var(--fsh-blue-700); font-weight: var(--font-weight-bold); }
.module-icon {
    width: 40px; height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--fsh-border);
    border-radius: var(--radius-control);
    background-color: var(--fsh-blue-050);
    color: var(--fsh-blue-600);
    font-size: 1.2rem;
}
.module-card--soon { background: var(--fsh-surface-muted); }
.module-card--soon::before { background: var(--fsh-border); }
.module-card--soon .module-icon { background: var(--fsh-neutral-bg); color: var(--fsh-neutral); }

/* Ficha destacada: la acción que hace el trabajo del módulo, no una consulta.
   Mantiene el fondo blanco y el filete de marca —es la misma familia— y se
   separa por peso: filete más grueso, icono mayor, título en escala de titular
   y acción en botón sólido en vez de contorno. A partir de 768 px el icono pasa
   al costado y la acción a la derecha, para que ocupar el ancho completo no
   deje una caja alta y vacía. */
.module-card--featured::before { height: 5px; }
.module-card--featured { border-color: var(--fsh-blue-200); }
.module-card--featured .module-icon {
    width: 52px;
    height: 52px;
    font-size: 1.55rem;
    background-color: var(--fsh-blue-100);
}
.module-card--featured h2 { font-size: var(--font-size-lg); }
@media (min-width: 768px) {
    .module-card--featured .card-body {
        display: grid;
        grid-template-columns: auto 1fr auto;
        align-items: center;
        column-gap: var(--space-md);
    }
    .module-card--featured .module-icon {
        grid-column: 1;
        grid-row: 1 / span 2;
        margin-bottom: 0;
    }
    .module-card--featured h2 { grid-column: 2; grid-row: 1; margin-bottom: .15rem; }
    .module-card--featured p { grid-column: 2; grid-row: 2; margin-bottom: 0; }
    .module-card--featured .btn {
        grid-column: 3;
        grid-row: 1 / span 2;
        margin-top: 0;
        align-self: center;
    }
}

/* ==========================================================================
   4. Formularios
   ==========================================================================
   Campo de fondo tenue que se aclara a blanco al recibir el foco, con anillo
   azul institucional. Etiqueta en semibold y azul oscuro: es lo que ordena un
   formulario largo sin necesidad de separadores. */
.form-label {
    font-weight: var(--font-weight-semibold);
    font-size: var(--font-size-sm);
    color: var(--fsh-blue-700);
    margin-bottom: .3rem;
}
.form-control, .form-select {
    border-color: var(--fsh-border-strong);
    border-radius: var(--radius-control);
    background-color: var(--fsh-surface-muted);
    font-size: var(--font-size-base);
    min-height: var(--control-height);
    padding: .48rem .75rem;
    color: var(--fsh-text);
    transition: border-color var(--fsh-transition), background-color var(--fsh-transition),
                box-shadow var(--fsh-transition);
}
.form-control:hover, .form-select:hover { border-color: var(--fsh-blue-400); }
.form-control:focus, .form-select:focus {
    border-color: var(--fsh-blue-600);
    background-color: #fff;
    box-shadow: 0 0 0 3px rgba(0, 76, 123, .15);
}
.form-control::placeholder { color: #98A1A8; }
.form-control:disabled, .form-select:disabled,
.form-control[readonly] {
    background-color: var(--fsh-neutral-bg);
    color: var(--fsh-text-muted);
    cursor: not-allowed;
}
textarea.form-control { min-height: 7rem; }
/* La ayuda va antes del error: primero se explica, después se corrige. */
.form-text {
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
    margin-top: .25rem;
}
.invalid-feedback {
    font-size: var(--font-size-sm);
    color: var(--fsh-danger);
    font-weight: var(--font-weight-medium);
}
.form-control.is-invalid, .form-select.is-invalid {
    border-color: var(--fsh-danger);
    /* El color no basta: el borde se refuerza para daltonismo. */
    border-width: 2px;
}
.form-check-input:checked {
    background-color: var(--fsh-blue-600);
    border-color: var(--fsh-blue-600);
}
.form-check-input:focus {
    border-color: var(--fsh-blue-400);
    box-shadow: 0 0 0 3px rgba(0, 76, 123, .15);
}
.form-check-label { font-size: var(--font-size-base); }
/* Marca de campo obligatorio, con texto alternativo para lectores. */
.required-mark { color: var(--fsh-danger); font-weight: var(--font-weight-bold); }

/* Secciones de formularios extensos. */
.form-section {
    padding-bottom: var(--space-md);
    margin-bottom: var(--space-md);
    border-bottom: 1px solid var(--fsh-border);
}
.form-section:last-of-type { border-bottom: 0; margin-bottom: 0; }
.form-section__title {
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    margin-bottom: .2rem;
}
.form-section__hint {
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
    margin-bottom: var(--space-3);
}

/* Rejilla de formularios extensos.
   ==========================================================================
   Un formulario de una sola columna desperdicia el ancho del escritorio y
   alarga la pantalla hasta que el usuario pierde de vista aquello sobre lo que
   está trabajando. En móvil no hay ancho que repartir, así que apila: la
   rejilla solo aparece a partir de 992 px.

   `min-width: 0` no es adorno: sin él, una celda de rejilla no baja del ancho
   de su contenido, y una tabla ancha dentro de una columna empujaría el scroll
   horizontal a la página entera en vez de quedarse dentro de su contenedor. */
.form-grid { display: grid; gap: 0 var(--space-md); }
.form-grid > * { min-width: 0; }
@media (min-width: 992px) {
    .form-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
    }
    /* Repartos desiguales: el campo que se escribe más largo se lleva más
       ancho. Un nombre de archivo cabe en dos quintos; unas notas, no. */
    .form-grid--40-60 { grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); }
    /* Los bloques de recursos sí necesitan aire entre filas: llevan encabezado
       propio y su propio marco, no son campos sueltos. */
    .form-grid--panels { gap: var(--space-md); }
}

/* Formulario compacto: el mismo lenguaje visual con menos alto.
   ==========================================================================
   El `min-height` general de los textarea impone siete líneas, que es lo que
   pide una descripción larga y lo que sobra para una frase. Dentro de un
   formulario compacto manda `rows`, que es donde el formulario declara cuánto
   espera que se escriba. La separación entre campos también baja: apretar el
   interlineado de un párrafo se nota; apretar el aire entre dos campos que ya
   tienen etiqueta propia, no. */
.form-compact textarea.form-control { min-height: 0; }
.form-compact .form-control, .form-compact .form-select { padding: .38rem .7rem; }
.form-compact .form-label { margin-bottom: .2rem; }
.form-compact .form-text { margin-top: .15rem; }
.form-compact .mb-3 { margin-bottom: var(--space-sm) !important; }

/* Casillero de recursos: insumos o equipos, elegidos a golpe de casilla.
   ==========================================================================
   Un marco, un encabezado y una lista. Nada de tarjeta por elemento: elegir
   veinte insumos es recorrer veinte líneas, no veinte fichas. El listado crece
   con el catálogo del laboratorio, así que se desplaza dentro de su propio
   marco —un laboratorio con cincuenta insumos no debe convertir el formulario
   en una página interminable— y el encabezado, con su contador de marcados, se
   queda fuera del desplazamiento, siempre a la vista.

   El color del encabezado lo pone quien lo usa (`--resource-accent`): insumos y
   equipos son dos listas distintas y conviene distinguirlas de un vistazo sin
   leer el rótulo. */
.resource-block {
    --resource-accent: var(--fsh-blue-600);
    min-width: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    background: var(--fsh-surface);
    overflow: hidden;
}
.resource-block__header {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .55rem .75rem;
    background: var(--fsh-surface-muted);
    border-bottom: 1px solid var(--fsh-border);
    /* Filete de color a la izquierda: el acento institucional entra por una
       línea fina, no por un fondo de color. */
    box-shadow: inset 3px 0 0 var(--resource-accent);
}
.resource-block__title {
    margin: 0;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    display: flex;
    align-items: center;
    gap: .4rem;
}
.resource-block__title i { color: var(--resource-accent); }
/* Insumos y equipos son dos listas distintas y se distinguen por el acento
   antes que por el rótulo. Los dos colores son institucionales: el rojo
   corporativo y el verde legible de la paleta UNL. */
.resource-block--materials { --resource-accent: var(--unl-red); }
.resource-block--equipment { --resource-accent: var(--unl-green-text); }
/* Contador de marcados: dice cuánto llevas elegido sin obligar a contar. */
.resource-block__count {
    margin-left: auto;
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    color: var(--fsh-text-muted);
    white-space: nowrap;
}
.resource-block__count[data-selected]:not([data-selected="0"]) {
    color: var(--resource-accent);
}
/* Buscador del casillero: filtra en el cliente lo que ya está en la página. No
   consulta nada, así que un catálogo largo se acota sin esperar al servidor. */
.resource-search { padding: .5rem .75rem 0; }
.resource-scroll { max-height: 15rem; overflow-y: auto; padding: .35rem .35rem .5rem; }

/* Una fila por elemento: casilla, nombre con su dato de existencia debajo y,
   cuando la lógica la pide, la cantidad predeterminada al final. */
.resource-row {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .35rem .4rem;
    border-radius: var(--fsh-radius-sm);
}
.resource-row + .resource-row { border-top: 1px solid var(--fsh-border); }
.resource-row:hover { background: var(--fsh-blue-050); }
.resource-row:focus-within { background: var(--fsh-blue-050); }
.resource-row__main { flex: 1 1 auto; min-width: 0; }
.resource-row__name {
    display: block;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--fsh-text);
    cursor: pointer;
    overflow-wrap: anywhere;
}
.resource-row__meta {
    font-size: var(--font-size-xs);
    color: var(--fsh-text-muted);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .3rem;
}
/* La casilla, algo mayor que la de un formulario corriente: aquí se pulsa
   muchas veces seguidas y con el dedo. */
.resource-row .form-check-input {
    flex: 0 0 auto;
    width: 1.15rem;
    height: 1.15rem;
    margin: 0;
}
.resource-row__qty { flex: 0 0 5.5rem; }
.resource-row__qty .form-control { text-align: right; }
.resource-empty {
    display: none;
    padding: .6rem .75rem;
    margin: 0;
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
}
/* Sin catálogo no hay nada que elegir: una línea, no un bloque alto. */
.resource-block__none {
    margin: 0;
    padding: .85rem .75rem;
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
    display: flex;
    align-items: center;
    gap: .45rem;
}
.resource-block__none i { color: var(--fsh-text-muted); }

/* Acciones alineadas al final: el botón que cierra la tarea queda donde acaba
   la lectura, no al principio de una línea vacía. */
.form-actions--end { justify-content: flex-end; }
@media (max-width: 575.98px) {
    .form-actions--end { justify-content: stretch; }
    .form-actions--end > .btn { flex: 1 1 auto; }
}

/* Barra de filtros y búsqueda de listados. */
.filter-bar {
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    padding: .9rem 1.05rem;
    margin-bottom: var(--space-md);
    box-shadow: var(--fsh-shadow-sm);
}
.filter-bar .form-label {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--fsh-text-muted);
    margin-bottom: .25rem;
}
.filter-bar .row { --bs-gutter-y: .75rem; }

/* ==========================================================================
   5. Tablas y listados
   ==========================================================================
   La tabla vive dentro de un contenedor blanco con radio y sombra discreta.
   Cabecera clara con texto en azul institucional, separadores suaves, filas
   compactas y realce leve al pasar. Nunca el aspecto rígido de una hoja de
   cálculo: sin rejilla completa de bordes. */
.table {
    --bs-table-color: var(--fsh-text);
    --bs-table-border-color: var(--fsh-border);
    --bs-table-hover-bg: var(--fsh-blue-050);
    margin-bottom: 0;
    font-size: var(--font-size-sm);
}
.table > thead > tr > th {
    background: var(--fsh-blue-050);
    color: var(--fsh-blue-700);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-bold);
    text-transform: uppercase;
    letter-spacing: .055em;
    border-bottom: 1px solid var(--fsh-border);
    padding: .7rem .8rem;
    white-space: nowrap;
    vertical-align: middle;
}
.table > tbody > tr > td {
    padding: .65rem .8rem;
    vertical-align: middle;
    border-bottom-color: var(--fsh-border);
}
.table > tbody > tr:last-child > td { border-bottom: 0; }
.table-hover > tbody > tr { transition: background-color var(--fsh-transition); }
.table-hover > tbody > tr:hover > * { background-color: var(--fsh-blue-050); }
.table strong { color: var(--fsh-blue-700); }

/* Cifras y fechas alineadas para comparación en columna. */
.table .col-num, .table .col-date {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.table .col-actions { text-align: right; white-space: nowrap; width: 1%; }
/* Contenido secundario largo: se trunca, pero conserva el texto completo. */
.cell-truncate {
    display: inline-block;
    max-width: 22rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}
/* Contenido importante: se ajusta, nunca se oculta. */
.cell-wrap { white-space: normal; overflow-wrap: anywhere; min-width: 12rem; }

/* Contenedor de tabla: bordes, radio y sombra coherentes con la tarjeta. */
.table-panel {
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    overflow: hidden;
    box-shadow: var(--fsh-shadow-sm);
}
.table-panel > .table-responsive {
    margin-bottom: 0;
    /* Desplazamiento horizontal controlado: la barra se ve fina y en el color
       del sistema, no la gruesa por omisión del navegador. */
    scrollbar-width: thin;
    scrollbar-color: var(--fsh-border-strong) transparent;
}

/* Cabecera fija: útil solo en listados realmente largos. */
.table-sticky > thead > tr > th {
    position: sticky;
    top: 0;
    z-index: 2;
}

.table-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    padding: .7rem 1rem;
    border-top: 1px solid var(--fsh-border);
    background: var(--fsh-surface-muted);
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
}
.pagination { --bs-pagination-color: var(--fsh-blue-600); margin-bottom: 0; gap: .25rem; }
/* Cada página es una pastilla suelta, no un bloque con los extremos redondeados:
   por eso el radio va en todas y no solo en la primera y la última. Bootstrap
   redondea los extremos con `.page-item:first-child .page-link`; el selector de
   abajo iguala esa especificidad y gana por orden de carga. Antes esto se
   resolvía con `!important`. */
.pagination .page-item .page-link { border-radius: var(--radius-sm); }
.pagination .page-link {
    border-color: var(--fsh-border);
    min-height: 36px;
    display: flex;
    align-items: center;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
}
.pagination .page-link:hover { border-color: var(--fsh-blue-400); color: var(--fsh-blue-700); }
.pagination .active > .page-link {
    background-color: var(--fsh-blue-600);
    border-color: var(--fsh-blue-600);
}

.dropdown-menu {
    border: 1px solid var(--fsh-border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-md);
    padding: .4rem;
}
.dropdown-item { border-radius: var(--radius-sm); padding: .52rem .7rem; font-size: var(--font-size-sm); }
.dropdown-item:focus, .dropdown-item:hover {
    background: var(--fsh-blue-050);
    color: var(--fsh-blue-700);
}

/* --- Tablas en móvil: se convierten en fichas legibles ------------------
   Cada celda muestra su etiqueta desde data-label. Se conservan todas las
   acciones; solo se oculta lo marcado explícitamente como secundario. */
@media (max-width: 767.98px) {
    .table-cards > thead { display: none; }
    .table-cards, .table-cards > tbody, .table-cards > tbody > tr,
    .table-cards > tbody > tr > td { display: block; width: 100%; }
    .table-cards > tbody > tr {
        border: 1px solid var(--fsh-border);
        border-radius: var(--fsh-radius);
        background: var(--fsh-surface);
        padding: .35rem .15rem;
        margin-bottom: .75rem;
    }
    .table-cards > tbody > tr > td {
        display: flex;
        align-items: baseline;
        justify-content: space-between;
        gap: var(--space-3);
        border: 0;
        padding: .4rem .85rem;
        text-align: left !important;
    }
    /* Solo las celdas que declaran etiqueta reservan espacio para ella: una
       celda a ancho completo (un estado vacío, por ejemplo) no debe quedar
       comprimida contra el 40 % de una etiqueta inexistente. */
    .table-cards > tbody > tr > td[data-label]::before {
        content: attr(data-label);
        flex: 0 0 40%;
        font-size: .75rem;
        font-weight: var(--font-weight-bold);
        text-transform: uppercase;
        letter-spacing: .03em;
        color: var(--fsh-text-muted);
    }
    .table-cards > tbody > tr > td:empty { display: none; }
    .table-cards .cell-truncate { max-width: 100%; }
    /* Separador sobre la fila de acciones: no necesita `!important` porque este
       selector ya pesa más que el `border: 0` de la celda genérica de arriba. */
    .table-cards .col-actions {
        justify-content: flex-end;
        border-top: 1px solid var(--fsh-border);
        margin-top: .3rem;
        padding-top: .6rem;
        width: auto;
    }
    .table-cards .col-actions::before { content: none; }
    /* Columnas puramente secundarias, ocultas solo si están marcadas. */
    .table-cards .col-optional { display: none; }
    /* Fila que continúa la anterior (una nota, un error): se une a su ficha en
       lugar de leerse como un registro más. */
    .table-cards > tbody > tr.row-note {
        margin-top: -.75rem;
        border-top: 0;
        border-top-left-radius: 0;
        border-top-right-radius: 0;
    }
}

/* ==========================================================================
   6. Distintivos de estado (badges)
   ==========================================================================
   Color + icono + texto. El color nunca porta el significado por sí solo. */
.badge {
    font-weight: var(--font-weight-bold);
    font-size: var(--font-size-xs);
    letter-spacing: .015em;
    line-height: 1.4;
    padding: .26em .62em;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    gap: .32em;
    vertical-align: middle;
    white-space: nowrap;
}
.badge i { font-size: .95em; }

/* Fondo tenue + texto oscuro: más sobrio y legible que el color saturado. */
.text-bg-success { background-color: var(--fsh-success-bg) !important; color: var(--fsh-success) !important; box-shadow: inset 0 0 0 1px rgba(65, 142, 58, .3); }
.text-bg-warning { background-color: var(--fsh-warning-bg) !important; color: var(--fsh-warning) !important; box-shadow: inset 0 0 0 1px rgba(138, 90, 0, .25); }
.text-bg-danger  { background-color: var(--fsh-danger-bg)  !important; color: var(--fsh-danger)  !important; box-shadow: inset 0 0 0 1px rgba(191, 8, 17, .28); }
.text-bg-info    { background-color: var(--fsh-info-bg)    !important; color: var(--fsh-info)    !important; box-shadow: inset 0 0 0 1px rgba(0, 76, 123, .25); }
.text-bg-primary { background-color: var(--fsh-blue-050)   !important; color: var(--fsh-blue-600) !important; box-shadow: inset 0 0 0 1px rgba(0, 76, 123, .25); }
.text-bg-secondary { background-color: var(--fsh-neutral-bg) !important; color: var(--fsh-neutral) !important; box-shadow: inset 0 0 0 1px rgba(91, 84, 80, .22); }
.text-bg-light   { background-color: var(--fsh-surface-alt) !important; color: var(--fsh-text-muted) !important; box-shadow: inset 0 0 0 1px var(--fsh-border-strong); }
.text-bg-dark    { background-color: var(--unl-blue-dark) !important; color: #fff !important; }

/* Término técnico con ayuda: la línea punteada anuncia que hay explicación. */
.badge[title], .has-hint[title] {
    cursor: help;
    text-decoration: underline dotted currentColor 1px;
    text-underline-offset: 2px;
}

/* ==========================================================================
   7. Mensajes, avisos y modales
   ==========================================================================
   Aviso institucional: fondo claro, borde lateral de 4 px, icono y texto. La
   información nunca depende solo del color. */
.messages-area { margin-bottom: var(--space-3); }
.alert {
    position: relative;
    border-radius: var(--radius-control);
    border: 1px solid transparent;
    border-left-width: 4px;
    padding: .8rem 1rem;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    box-shadow: var(--fsh-shadow-sm);
}
.alert-success { background: var(--fsh-success-bg); border-color: rgba(65, 142, 58, .3); border-left-color: var(--fsh-success); color: #1F4A1A; }
.alert-info    { background: var(--fsh-info-bg);    border-color: rgba(0, 76, 123, .3);  border-left-color: var(--fsh-blue-600); color: var(--fsh-blue-700); }
.alert-warning { background: var(--fsh-warning-bg); border-color: rgba(138, 90, 0, .3);  border-left-color: var(--fsh-warning); color: #6b4600; }
.alert-danger  { background: var(--fsh-danger-bg);  border-color: rgba(191, 8, 17, .3);  border-left-color: var(--fsh-danger);  color: #8c1e18; }
.alert-heading { font-size: var(--font-size-base); font-weight: var(--font-weight-bold); }

/* Aviso de baja intensidad: informa sin interrumpir la operación. */
.notice-soft {
    background: var(--fsh-surface-muted);
    border: 1px solid var(--fsh-border);
    border-left: 3px solid var(--fsh-blue-400);
    border-radius: var(--fsh-radius-sm);
    padding: .65rem .9rem;
    font-size: var(--font-size-sm);
    color: var(--fsh-text-muted);
}
.notice-soft i { color: var(--fsh-blue-600); }

/* Modal: cabecera blanca con un acento rojo–azul, cuerpo limpio, pie compacto. */
.modal-content {
    border: 0;
    border-radius: var(--fsh-radius-lg);
    box-shadow: var(--fsh-shadow-lg);
    overflow: hidden;
}
.modal-header {
    position: relative;
    background: var(--fsh-surface);
    border-bottom: 1px solid var(--fsh-border);
    padding: 1rem 1.2rem;
}
.modal-header::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--rule-brand-card);
}
.modal-title { font-size: var(--font-size-lg); font-weight: var(--font-weight-bold); color: var(--fsh-blue-700); }
.modal-body { padding: 1.2rem; }
.modal-footer {
    border-top: 1px solid var(--fsh-border);
    background: var(--fsh-surface-muted);
    padding: .8rem 1.2rem;
    gap: .5rem;
}
@media (max-width: 575.98px) {
    .modal-dialog { margin: .5rem; }
    .modal-footer { flex-direction: column-reverse; }
    .modal-footer > .btn { width: 100%; margin: 0; }
}

/* ==========================================================================
   8. Estados vacíos
   ==========================================================================
   Patrón común: icono, título, explicación y —cuando corresponde— acción.
   Nunca un hueco en blanco sin orientación. */
.empty-state {
    text-align: center;
    padding: 2.5rem 1.25rem;
    color: var(--fsh-text-muted);
    background: var(--fsh-surface);
}
.empty-state__icon {
    width: 54px; height: 54px;
    margin: 0 auto .9rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--fsh-border);
    border-radius: 50%;
    background: var(--fsh-blue-050);
    color: var(--fsh-blue-600);
    font-size: 1.4rem;
}
.empty-state__title {
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-bold);
    color: var(--fsh-blue-700);
    margin-bottom: .3rem;
}
.empty-state__text {
    font-size: var(--font-size-sm);
    max-width: 46ch;
    margin: 0 auto;
    line-height: 1.6;
}
.empty-state__action { margin-top: 1.1rem; }
/* Variante en línea, para el interior de una tabla. */
.empty-state--inline { padding: 1.75rem 1rem; background: transparent; }
/* Variante compacta: una fila, no media pantalla.
   Un estado vacío tiene que decir qué falta y cómo se arregla; cuando eso cabe
   en una línea, ocupar un tercio de la ventana no informa más, solo aleja el
   contenido siguiente. El icono se reduce y todo se alinea en horizontal, con
   la acción al final de la misma fila mientras haya ancho. */
.empty-state--compact {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm) var(--space-md);
    text-align: left;
    padding: .85rem 1rem;
}
.empty-state--compact .empty-state__icon {
    width: 34px; height: 34px;
    margin: 0;
    flex: 0 0 auto;
    font-size: 1rem;
}
.empty-state--compact .empty-state__body { flex: 1 1 16rem; min-width: 0; }
.empty-state--compact .empty-state__title {
    font-size: var(--font-size-base);
    margin-bottom: .1rem;
}
.empty-state--compact .empty-state__text { max-width: none; margin: 0; line-height: 1.45; }
.empty-state--compact .empty-state__action { margin: 0; flex: 0 0 auto; }

/* ==========================================================================
   9. Fichas de detalle
   ========================================================================== */
/* Lista de pares etiqueta/valor. En móvil se apila. */
.detail-list {
    display: grid;
    grid-template-columns: minmax(9rem, 14rem) 1fr;
    gap: .55rem var(--space-md);
    margin: 0;
    font-size: var(--font-size-sm);
}
.detail-list dt {
    font-weight: var(--font-weight-semibold);
    color: var(--fsh-text-muted);
    font-size: var(--font-size-sm);
}
.detail-list dd {
    margin: 0;
    color: var(--fsh-text);
    overflow-wrap: anywhere;
}
@media (max-width: 575.98px) {
    .detail-list { grid-template-columns: 1fr; gap: .1rem .5rem; }
    .detail-list dd { margin-bottom: .6rem; }
}

/* Ficha compacta: los mismos pares etiqueta/valor, repartidos a lo ancho.
   ==========================================================================
   `detail-list` pone cada dato en su propia fila alta, y con ocho datos eso son
   ocho filas que empujan fuera de la pantalla lo que el usuario venía a hacer.
   Aquí el dato es una celda: etiqueta encima, valor debajo, y tantas columnas
   como quepan. El número no se fija; lo decide el ancho disponible, así que la
   misma ficha da cuatro columnas en un monitor, dos en una tableta y una en un
   teléfono sin necesidad de puntos de corte propios. */
.fact-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: .7rem var(--space-md);
    margin: 0;
}
.fact-grid > div { min-width: 0; }
.fact-grid dt {
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--fsh-text-muted);
    margin-bottom: .1rem;
}
.fact-grid dd {
    margin: 0;
    font-size: var(--font-size-sm);
    color: var(--fsh-text);
    overflow-wrap: anywhere;
}

/* Recuento en línea: «4 insumos», «2 equipos». Un número y su unidad dicen lo
   que hace falta en la tabla; el detalle vive donde se configura. */
.count-chip {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    font-size: var(--font-size-sm);
    color: var(--fsh-text);
    white-space: nowrap;
}
.count-chip i { color: var(--fsh-blue-600); }
.count-chip--none { color: var(--fsh-text-muted); }

/* Información técnica: plegada por omisión, nunca eliminada (auditoría). */
.tech-details {
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius-sm);
    background: var(--fsh-surface-muted);
    margin-top: var(--space-md);
}
.tech-details > summary {
    cursor: pointer;
    padding: .6rem .9rem;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--fsh-text-muted);
    list-style: none;
    display: flex;
    align-items: center;
    gap: .45rem;
}
.tech-details > summary::-webkit-details-marker { display: none; }
.tech-details > summary::before {
    content: "\F285";              /* bi-chevron-right */
    font-family: "bootstrap-icons";
    font-size: .8rem;
    transition: transform var(--fsh-transition);
}
.tech-details[open] > summary::before { transform: rotate(90deg); }
.tech-details__body {
    padding: .3rem .9rem .9rem;
    font-size: var(--font-size-sm);
    border-top: 1px solid var(--fsh-border);
}
.tech-details__body code, .code-value {
    font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    font-size: var(--font-size-sm);
    background: var(--fsh-neutral-bg);
    border-radius: var(--fsh-radius-sm);
    padding: .1rem .35rem;
    overflow-wrap: anywhere;
    color: var(--fsh-text);
}

/* Cronología de historial e incidencias. */
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline__item {
    position: relative;
    padding: 0 0 1rem 1.4rem;
    border-left: 2px solid var(--fsh-border);
}
.timeline__item:last-child { border-left-color: transparent; padding-bottom: 0; }
.timeline__item::before {
    content: "";
    position: absolute;
    left: -5px; top: .35rem;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--fsh-blue-400);
}
.timeline__time { font-size: var(--font-size-xs); color: var(--fsh-text-muted); }

/* ==========================================================================
   10. Carga y realimentación
   ========================================================================== */
.ajax-loader {
    position: fixed;
    inset: 0;
    background: rgba(244, 246, 248, .72);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 1080;
}
.ajax-loader.is-active { display: flex; }
.ajax-loader-box {
    background: var(--fsh-surface);
    border: 1px solid var(--fsh-border);
    border-radius: var(--fsh-radius);
    padding: 1.1rem 1.6rem;
    box-shadow: var(--fsh-shadow-lg);
    display: flex;
    align-items: center;
    gap: .85rem;
}
.ajax-loader-text { font-weight: var(--font-weight-medium); color: var(--fsh-text); }
.spinner-border { color: var(--fsh-blue-600); }

/* Esqueleto de carga para bloques que se rellenan por sondeo. */
.skeleton {
    background: linear-gradient(90deg, var(--fsh-neutral-bg) 25%, #e4e9f1 37%, var(--fsh-neutral-bg) 63%);
    background-size: 400% 100%;
    animation: fsh-skeleton 1.4s ease infinite;
    border-radius: var(--fsh-radius-sm);
    color: transparent !important;
    min-height: 1em;
}
@keyframes fsh-skeleton {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

/* Valor que acaba de actualizarse por sondeo: destello breve y discreto. */
.value-updated { animation: fsh-flash 1.1s ease; }
@keyframes fsh-flash {
    0% { background-color: var(--unl-blue-15); }
    100% { background-color: transparent; }
}

/* Bloque en actualización: se atenúa sin desaparecer ni saltar. */
.is-refreshing { opacity: .6; transition: opacity var(--fsh-transition); }

/* ==========================================================================
   11. Utilidades de medida
   ==========================================================================
   Anchos y ajustes puntuales que antes viajaban en el atributo `style` de la
   plantilla. Sacarlos a clase es lo que permite que el sistema visual controle
   la densidad de verdad: un `min-width` suelto en el HTML no responde a ningún
   token y no se puede ajustar por punto de corte. */
.cell-min-sm { min-width: 8rem; }
.cell-min-md { min-width: 9rem; }
.cell-min-lg { min-width: 12rem; }
.col-index { width: 5rem; }
/* Imagen de código QR: legible al escanear, sin llenar la pantalla. El `min()`
   conserva el tope de 320 px en pantalla ancha y respeta el ancho disponible en
   un móvil estrecho, donde un valor fijo desbordaría la tarjeta. */
.qr-figure { max-width: min(320px, 100%); }
/* Vídeo del escáner QR: llena su marco sin deformar la imagen de la cámara.
   Lo inyecta html5-qrcode dentro de la región, de ahí la regla por descendencia;
   el `!important` gana a los tamaños en línea que la propia librería le fija y
   que, dentro del marco cuadrado, dejarían franjas negras a los lados. */
[data-att-region] video {
    width: 100% !important;
    height: 100% !important;
    object-fit: cover;
}

@media (max-width: 767.98px) {
    /* En la vista de ficha, el ancho mínimo estorba: la celda ya ocupa el ancho
       completo de la tarjeta. */
    .table-cards .cell-min-sm,
    .table-cards .cell-min-md,
    .table-cards .cell-min-lg { min-width: 0; }
}

/* ==========================================================================
   12. Área táctil
   ==========================================================================
   En dispositivos sin puntero fino los controles suben a 40 px. El botón usa
   inline-flex: con `flex` pasaría a bloque y se estiraría a todo el ancho de su
   celda o de su párrafo. */
@media (pointer: coarse) {
    .btn {
        min-height: 40px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    .sidebar-link, .dropdown-item, .page-link {
        min-height: 40px;
        display: flex;
        align-items: center;
    }
}
