/* Tabler basa casi todo en rem: subir este valor escala el proyecto entero. */
:root {
    font-size: 17px;
}

/*
 * Color de marca: Tabler deriva --tblr-primary-lt/-darken de --tblr-primary,
 * pero --tblr-primary-rgb es estático y se fija a mano con el mismo tono.
 * var(--tblr-yellow) para seguir el amarillo de .bg-yellow.
 */
:root {
    --tblr-primary: var(--tblr-yellow);
    --tblr-primary-rgb: 245, 159, 0;
}

/*
 * Texto sobre ámbar y naranja: en tinta. El blanco de Tabler se queda en 2:1
 * sobre #f59f00 y en 3:1 sobre #f76707, por debajo de 4.5:1. Cubre el botón
 * primario y los de aviso.
 */
:root {
    --tblr-primary-fg: var(--tblr-dark);
    --tblr-warning-fg: var(--tblr-dark);
    --tblr-yellow-fg: var(--tblr-dark);
    --tblr-orange-fg: var(--tblr-dark);
}

/*
 * Scoutie Sans (OFL) autoalojada, como Tabler: no está en Google Fonts. Son
 * fuentes variables, de ahí el peso en rango.
 */
@font-face {
    font-family: "Scoutie Sans";
    src: url("/assets/fonts/ScoutieSans-VariableFont_wght.ttf") format("truetype-variations");
    font-weight: 200 800;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Scoutie Sans";
    src: url("/assets/fonts/ScoutieSans-Italic-VariableFont_wght.ttf") format("truetype-variations");
    font-weight: 200 800;
    font-style: italic;
    font-display: swap;
}

/* Caprasimo, la de los títulos de la portada: en la aplicación, solo la marca. */
@font-face {
    font-family: "Caprasimo";
    src: url("/assets/fonts/Caprasimo-Regular-latin.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

:root {
    --tblr-font-sans-serif: "Scoutie Sans", -apple-system, BlinkMacSystemFont, "San Francisco", "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

/*
 * Se anula el margin-left de compensación del scrollbar que Tabler pone desde
 * 992px: dejaba un margen de ~15px pegado al borde. Se acepta a cambio el
 * micro-salto entre páginas con y sin scroll.
 */
@media (min-width: 992px) {
    :root {
        margin-left: 0;
    }
}

.u-invalid {
    border-color: var(--tblr-form-invalid-border-color);
}

/* En un input-group-flat el borde lo comparten el campo y su adorno. */
.input-group-flat:has(.u-invalid) .input-group-text {
    border-color: var(--tblr-form-invalid-border-color);
}

/*
 * Barra de progreso de subida (ds-upload.js): solo el valor viaja inline como
 * variable CSS; el width vive en la clase.
 */
.u-progress-bar-dynamic {
    width: var(--u-progress, 0%);
}

/* Miniatura cuadrada de la galería de fotos/vídeo de un producto de carta
   (fragments/digitalmenu/menu-item-panel.html) -- mismo recorte para <img>
   y <video>, sin inline style. */
.u-media-thumb {
    aspect-ratio: 1;
    object-fit: cover;
}

/* Cualquier .dropzone (ver components/dropzone.html) bloqueado mientras se
   sube/reintenta -- Dropzone no tiene un atributo disabled nativo como
   <input>, así que el bloqueo es puramente visual/de interacción vía
   data-class, igual que se hacía con data-attr:disabled en el <input> que
   sustituye. */
.u-dropzone-disabled {
    opacity: 0.6;
    pointer-events: none;
}

/*
 * La barra propia de Dropzone solo tiene sentido con autoProcessQueue, que aquí
 * está apagado (sube uploadDocumentFiles): se quedaría fija sobre cada
 * miniatura.
 */
.dropzone .dz-progress {
    display: none;
}

/* Tabla a sangre dentro de un .modal-body: cancela el padding del modal
   (--tblr-modal-padding) para que la tabla llegue de borde a borde, igual
   que .card-table hace dentro de una .card -- el padding "interno" a los
   lados lo pone card-table en las celdas, no el contenedor. */
.u-modal-table-bleed {
    margin-left: calc(var(--tblr-modal-padding) * -1);
    margin-right: calc(var(--tblr-modal-padding) * -1);
}

/*
 * Iguala el espaciado vertical de .page-body al horizontal del container, para
 * que los cuatro lados midan lo mismo.
 */
.page-body {
    margin-top: var(--tblr-page-padding);
    margin-bottom: var(--tblr-page-padding);
}

/*
 * Sub-navbar de sección (fragments/inventory/section-nav): una .navbar de
 * Tabler a todo el ancho y sticky bajo la principal. El margin-top negativo la
 * sube contra la navbar principal; su propio .container-xl alinea los enlaces
 * con el contenido.
 */
.u-section-nav {
    position: sticky;
    top: 0;
    z-index: 1010;
    margin-top: calc(-1 * var(--tblr-page-padding));
    margin-bottom: var(--tblr-page-padding);
}

/*
 * Botón "Arriba" (scroll al inicio) del extremo derecho de la sub-navbar. Es
 * un <button> con pinta de .nav-link, así que se le quita el chrome propio del
 * botón (borde/fondo) para que iguale a los enlaces de la izquierda.
 */
.u-section-nav__top {
    border: 0;
    background: transparent;
    cursor: pointer;
}

/*
 * Separador del botón "Arriba": el borde va en el <li> para estirarlo a toda la
 * altura, compensando el padding de .navbar-nav.
 */
.u-section-nav__top-item {
    padding-left: 0.5rem;
    margin-top: calc(-1 * var(--tblr-navbar-padding-y, 0.25rem));
    margin-bottom: calc(-1 * var(--tblr-navbar-padding-y, 0.25rem));
    border-left: var(--tblr-border-width) solid var(--tblr-border-color);
}

/*
 * Enlaces de sección con scroll horizontal (barra oculta) en pantallas
 * estrechas; los items no encogen.
 */
.u-section-nav__links {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding-right: 0;
}

.u-section-nav__links::-webkit-scrollbar {
    display: none;
}

.u-section-nav__links .nav-item {
    flex-shrink: 0;
}

/*
 * Subrayado activo nativo de Tabler: cuelga por debajo del item y el overflow-x
 * lo recortaba. content-box + padding-bottom lo meten en el área visible sin
 * mover los items, y el margin-bottom negativo conserva el alto.
 */
.u-section-nav__links {
    box-sizing: content-box;
    padding-bottom: calc(var(--tblr-navbar-padding-y, 0.25rem) + 1px);
    margin-bottom: calc(-1 * (var(--tblr-navbar-padding-y, 0.25rem) + 1px));
}

.u-section-nav__links .nav-item.active .nav-link {
    font-weight: 600;
}

/*
 * Caducidad relativa ("1 mes y 3 días") de las tablas de stock: subrayado
 * punteado + cursor de ayuda para que se note que al pasar el ratón muestra
 * algo más (el popover con la fecha exacta).
 */
.u-date-hint {
    text-decoration: underline dotted;
    text-underline-offset: 0.2em;
    cursor: help;
}

/*
 * Árbol de familias con <div> anidados: una subfamilia es descendiente real de
 * su familia, así que se arrastra con ella y el padding indenta (en un <tr> no
 * lo haría).
 */

.u-family-header,
.u-family-row__main {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.50rem 1.25rem;
}

.u-family-header {
    background: var(--tblr-bg-surface-tertiary);
    font-size: 0.75rem;
    font-weight: var(--tblr-font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--tblr-secondary);
    border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
}

.u-family-row__main {
    border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
}

.u-family-col--handle,
.u-family-col--actions {
    flex: 0 0 1.5rem;
}

.u-family-col--name {
    flex: 1 1 auto;
    min-width: 0;
}

.u-family-list--nested {
    padding-left: 1rem;
}

.u-family-empty {
    padding: 1.5rem 1.25rem;
}

/* Fila de tabla clicable (p.ej. selección de receta en fragments/inventory/recipes-rows). */
.u-row-clickable {
    cursor: pointer;
}

/*
 * Cabecera abreviada con tooltip en CSS (tabindex para que funcione al tocar):
 * no depende de inicializar Bootstrap tras cada morph. Debajo, para no
 * recortarse contra .table-responsive.
 */
.u-abbr {
    position: relative;
    cursor: help;
    border-bottom: 1px dotted currentColor;
}

.u-abbr::after {
    content: attr(data-tip);
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 10;
    margin-top: 0.25rem;
    padding: 0.25rem 0.5rem;
    white-space: nowrap;
    font-size: 0.75rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    color: var(--tblr-bg-surface);
    background: var(--tblr-body-color);
    border-radius: var(--tblr-border-radius);
    box-shadow: var(--tblr-box-shadow-sm);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease;
    pointer-events: none;
}

.u-abbr:hover::after,
.u-abbr:focus::after {
    opacity: 1;
    visibility: visible;
}

/*
 * Variante para la columna del extremo derecho: ancla el tooltip a la derecha
 * para que no se salga (y se recorte) por el borde de .table-responsive.
 */
.u-abbr--end::after {
    left: auto;
    right: 0;
}

/*
 * Celda con truncado por ellipsis (fragments/inventory/recipes-rows, columna
 * Familia): los nombres largos (~10 caracteres o más) se cortan con "…". El
 * title nativo deja ver el nombre completo al pasar el ratón.
 */
.u-cell-truncate {
    display: inline-block;
    max-width: 10ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

/*
 * Aviso de trazabilidad junto al código de lote, reducido a la altura de una
 * línea.
 */
.u-lot-warning {
    display: inline-flex;
    align-items: center;
}

.u-lot-warning svg {
    width: 1rem;
    height: 1rem;
}

/*
 * Hueco fijo de icono, lo lleve o no la fila, para que el texto de la columna
 * empiece o acabe en la misma vertical. 1rem es el ancho de .u-lot-warning y
 * .u-icon-sm; --wide cabe dos iconos. flex-shrink:0 porque .icon no lo trae
 * fuera de un .btn. Ver wiki «Frontend».
 */
.u-icon-slot {
    display: inline-flex;
    align-items: center;
    width: 1rem;
    flex-shrink: 0;
}

.u-icon-slot--wide {
    width: 2.25rem;
    gap: 0.25rem;
}

/*
 * Icono suelto reducido a la altura de una línea (flechas de precio y de
 * orden).
 */
.u-icon-sm {
    display: inline-flex;
    align-items: center;
}

.u-icon-sm svg {
    width: 1rem;
    height: 1rem;
}

/*
 * Anchos de columna. Las tablas usan table-layout:auto, donde un width es solo
 * una preferencia: para fijar un ancho hace falta min-width, que Tabler no
 * trae. El nowrap global de .table es control de ancho; .u-col-wrap marca la
 * excepción y va en <th> y <td>. Reglas del <th>: .w-1 para acciones, .w-100
 * para la que crece (una por tabla), un token .u-col-* para el resto, y el
 * nombre largo con .u-col-wrap y <span class="u-cell-clamp-2">. Ver docs
 * arquitectura/frontend.md.
 */
:root {
    --u-col-date: 7rem;         /* dd/mm/aaaa                        */
    --u-col-datetime: 9.5rem;   /* dd/mm/aaaa HH:MM                  */
    --u-col-lot: 10rem;         /* codigo de lote                    */
    --u-col-code: 6rem;         /* codigo corto, CIF                 */
    --u-col-money: 7rem;        /* importe en euros                  */
    --u-col-qty: 8rem;          /* cantidad + unidad (.u-qty-cell)   */
    /* Suelo, no techo: un badge largo ensancha la columna igualmente. */
    --u-col-status: 6rem;
    --u-col-name: 16rem;        /* nombre que no puede ser .w-100    */
    --u-col-product: 18rem;     /* nombre de producto, CON .w-100    */
}

/*
 * Nowrap por defecto en toda celda de tabla (ver manual arriba). Fechas,
 * lotes, códigos, importes, cantidades y badges dejan de partirse en dos
 * líneas sin tocar una sola celda.
 */
.table {
    white-space: nowrap;

    /* Cifras de ancho fijo en toda tabla, para que los decimales alineen. */
    font-variant-numeric: tabular-nums;
}

/*
 * La excepción: la columna larga de cada tabla (nombre de producto,
 * observaciones, notas). Va en el <th> Y en el <td> -- white-space no se
 * hereda entre hermanos. Normalmente acompaña a .w-100.
 */
.table .u-col-wrap {
    white-space: normal;
}

/*
 * Techo de dos líneas para el nombre de una celda que envuelve, para que la
 * tabla siga leyéndose como rejilla. Va en un <span> que envuelve solo el
 * nombre, nunca en el <td>: el subtítulo de debajo quedaría fuera y -webkit-box
 * solo admite contenido en línea. white-space:normal propio para no heredar el
 * nowrap de .table. Sin text-overflow ni junto a .u-cell-truncate.
 */
.u-cell-clamp-2 {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    white-space: normal;
}

/*
 * Celda Origen/Destino del historial: flecha y nombre recortado como fila flex,
 * para que la flecha nunca caiga fuera del clamp. min-width:0 deja recortar el
 * nombre; flex-start alinea la flecha con la primera línea.
 */
.u-movement-direction {
    display: flex;
    align-items: flex-start;
    gap: 0.375rem;
    min-width: 0;
}

.u-movement-direction .u-icon-sm {
    flex-shrink: 0;
    margin-top: 0.125rem;
}

.u-movement-direction .u-cell-clamp-2 {
    min-width: 0;
}

/*
 * Clases de tipo de columna. Declaran width Y min-width con el mismo token
 * para que el ancho sea real y no una preferencia que cede (ver manual).
 */
.u-col-date {
    width: var(--u-col-date);
    min-width: var(--u-col-date);
}

.u-col-datetime {
    width: var(--u-col-datetime);
    min-width: var(--u-col-datetime);
}

.u-col-lot {
    width: var(--u-col-lot);
    min-width: var(--u-col-lot);
}

.u-col-code {
    width: var(--u-col-code);
    min-width: var(--u-col-code);
}

.u-col-money {
    width: var(--u-col-money);
    min-width: var(--u-col-money);
}

.u-col-qty {
    width: var(--u-col-qty);
    min-width: var(--u-col-qty);
}

.u-col-status {
    width: var(--u-col-status);
    min-width: var(--u-col-status);
}

/*
 * Nombre que no puede ser la .w-100 de su tabla pero tampoco debe encogerse:
 * con min-width la tabla desborda en vez de partirlo.
 */
.u-col-name {
    width: var(--u-col-name);
    min-width: var(--u-col-name);
}

/*
 * Suelo para el nombre de producto que lleva .w-100: .w-100 solo se lleva el
 * sobrante, y en un móvil sin sobrante la columna caía a la palabra más larga.
 * Solo min-width: el width ya lo pone .w-100.
 */
.u-col-product {
    min-width: var(--u-col-product);
}

/*
 * Cantidades con decimales alineados: número y unidad van en cajas de ancho
 * fijo, porque alinear a la derecha alinea la unidad y no la coma. El flex va
 * en un <div> dentro del <td>: en el <td> perdía el vertical-align de
 * .table-vcenter.
 */
.u-qty-cell {
    display: flex;
    justify-content: flex-end;
    align-items: baseline;
    font-variant-numeric: tabular-nums;
}

.u-qty-number {
    min-width: 4.5ch;
    text-align: right;
}

/*
 * Precio con tooltip de desglose: mismo subrayado de puntos que .u-abbr, aunque
 * aquí el tooltip lo pone data-popover.
 */
.u-unit-price {
    cursor: help;
    border-bottom: 1px dotted currentColor;
}

.u-qty-unit {
    min-width: 2.25ch;
    text-align: left;
    padding-left: 0.35em;
}

/*
 * La cola "/ unidad" del precio en una caja de ancho fijo, para que la coma
 * decimal alinee entre filas ("/ l" frente a "/ kg"). inline-block porque a
 * veces va en medio de una frase.
 */
.u-price-unit {
    display: inline-block;
    min-width: 4.5ch;
    text-align: left;
}

/*
 * Centinela de scroll infinito: con alto mínimo para que IntersectionObserver
 * lo detecte y quepa el spinner.
 */
.u-infinite-scroll-sentinel {
    min-height: 2.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
}

/*
 * Skeleton bajo una tabla paginada: barras con el alto de una fila, para que la
 * espera se lea como la tabla que viene.
 */
.u-table-skeleton {
    width: 100%;
}

.u-table-skeleton .placeholder {
    display: block;
    height: 1.25rem;
    /* 1,25rem a los lados: el mismo sangrado que .card-table da a la primera y
       la última celda, para que las barras caigan bajo el texto de la tabla. */
    margin: 0.85rem 1.25rem;
    border-radius: var(--tblr-border-radius);
}

/* Cabecera ordenable: el orden activo en negrita, sin icono aparte. */
.u-sortable-th {
    cursor: pointer;
    user-select: none;
}

.u-sortable-th--active {
    font-weight: var(--tblr-font-weight-bold, 700);
    color: var(--tblr-primary);
}

/*
 * Combobox de búsqueda: un input que filtra las opciones con data-show, sin un
 * <select> de cientos de elementos.
 */
.u-combobox {
    position: relative;
}

.u-combobox-menu {
    position: absolute;
    top: calc(100% + 2px);
    left: 0;
    right: 0;
    z-index: 1000;
    max-height: 16rem;
    overflow-y: auto;
    padding: 0.25rem 0;
    background: var(--tblr-bg-surface);
    border: var(--tblr-border-width) solid var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
    box-shadow: var(--tblr-box-shadow);
}

.u-combobox-option {
    display: block;
    width: 100%;
    padding: 0.375rem 0.75rem;
    text-align: left;
    background: transparent;
    border: 0;
    color: var(--tblr-body-color);
    cursor: pointer;
}

.u-combobox-option:hover,
.u-combobox-option:focus {
    background: var(--tblr-active-bg);
}

/*
 * "Crear producto nuevo" siempre visible al final, para no depender de detectar
 * cero resultados en el cliente.
 */
.u-combobox-option--create {
    color: var(--tblr-primary);
    border-top: var(--tblr-border-width) solid var(--tblr-border-color);
}

/*
 * Hueco del mensaje de validación del mini-form de ingredientes: .ds-fade la
 * añade el JS y el morph la borra, así que la altura se reserva en una clase
 * que sí viaja en el HTML.
 */
.u-msg-slot {
    display: block;
    min-height: 1.5em;
}

/*
 * Reparto 70/30 del resumen de IVA en tablet y escritorio: Bootstrap no trae
 * flex-basis en porcentaje.
 */
@media (min-width: 768px) {
    .u-vat-summary-table {
        flex: 0 1 70%;
    }

    .u-vat-summary-totals {
        flex: 0 1 30%;
    }
}

/*
 * Drawer lateral tipo cajón: panel y contenido son hermanos y se desplazan
 * juntos con transform. overflow-x:clip evita el scroll horizontal; overflow-y
 * explícito, o pasaría a auto.
 */
:root {
    --u-drawer-width: min(85vw, 280px);
    --u-drawer-width-mini: 4rem;
}

.u-shell {
    overflow-x: clip;
    overflow-y: visible;
}

.u-drawer {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 1030;
    width: var(--u-drawer-width);
    display: flex;
    flex-direction: column;
    background: var(--tblr-bg-surface);
    border-right: var(--tblr-border-width) solid var(--tblr-border-color);
    transform: translateX(-100%);
    transition: transform 0.3s ease, box-shadow 0.3s ease, width 0.3s ease;
    overflow-y: auto;
}

/*
 * La sombra solo abierto: cerrado sigue siendo fixed y la sombra se colaba en
 * el borde.
 */
.u-drawer--open {
    transform: translateX(0);
    box-shadow: var(--tblr-box-shadow-lg);
}

/*
 * .icon no lleva flex-shrink:0 fuera de un .btn y se encogía un instante al
 * expandir el drawer.
 */
.u-drawer .icon {
    flex-shrink: 0;
}

/*
 * nowrap y ellipsis para que una etiqueta larga no salte a dos líneas mientras
 * se anima el ancho.
 */
.u-drawer-label {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    min-width: 0;
}

.u-drawer-header {
    display: flex;
    align-items: center;
    flex: 0 0 auto;
    min-height: 3.5rem;
    padding: 0 1rem;
    border-bottom: var(--tblr-border-width) solid var(--tblr-border-color);
}

.u-drawer-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1029;
    background: rgba(0, 0, 0, 0.35);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
}

.u-drawer-backdrop--visible {
    opacity: 1;
    visibility: visible;
}

.u-shell-content {
    transition: transform 0.3s ease;
}

.u-shell-content--pushed {
    transform: translateX(var(--u-drawer-width));
}

/*
 * Desde md el drawer es una barra lateral fija en flujo (sticky), sin transform
 * ni backdrop.
 */
@media (min-width: 768px) {
    .u-shell {
        display: flex;
        align-items: flex-start;
    }

    .u-drawer {
        position: sticky;
        top: 0;
        bottom: auto;
        height: 100vh;
        flex: 0 0 auto;
        transform: none;
        box-shadow: none;
    }

    .u-drawer-backdrop {
        display: none;
    }

    .u-shell-content,
    .u-shell-content--pushed {
        flex: 1 1 auto;
        min-width: 0;
        transform: none;
    }
}

/*
 * De md a lg el drawer va en modo mini y $drawerOpen significa "expandido". Se
 * saca del flujo (fixed) para que al expandir no estreche el contenido: el
 * hueco mini se reserva con margin-left y el resto se empuja con translateX.
 */
@media (min-width: 768px) and (max-width: 1200px) {
    .u-drawer {
        position: fixed;
        width: var(--u-drawer-width-mini);
    }

    .u-drawer--open {
        width: var(--u-drawer-width);
    }

    /* Sin padding en modo mini: descentraba el icono. Expandido vuelve. */
    .u-drawer-header {
        padding: 0;
    }

    .u-drawer--open .u-drawer-header {
        padding: 0 1rem;
    }

    .u-shell-content {
        margin-left: var(--u-drawer-width-mini);
    }

    .u-shell-content--pushed {
        transform: translateX(calc(var(--u-drawer-width) - var(--u-drawer-width-mini)));
    }

    /*
     * Aquí $drawerOpen sí abre y cierra, así que vuelve el backdrop
     * (transparente) para cerrar con clic fuera.
     */
    .u-drawer-backdrop {
        display: block;
        background: transparent;
    }

    .u-drawer-label {
        display: none;
    }

    .u-drawer--open .u-drawer-label {
        display: inline;
    }

    /*
     * El icono no se centra: justify-content no es animable y saltaba a mitad
     * de la transición.
     */
}

/* lg (992px) en adelante: siempre completo, con etiquetas, ignorando $drawerOpen. */
@media (min-width: 1201px) {
    .u-drawer {
        width: var(--u-drawer-width);
    }

    .u-drawer-label {
        display: inline;
    }
}

/*
 * Dock inferior solo en móvil (d-md-none en la plantilla). Es hermano de
 * .u-shell-content y no hijo: un transform en el ancestro haría que bottom:0
 * anclara al final del contenido y no a la ventana.
 */
:root {
    --u-dock-height: 3.5rem;
}

.u-dock {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1020;
    display: flex;
    height: var(--u-dock-height);
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--tblr-bg-surface);
    border-top: var(--tblr-border-width) solid var(--tblr-border-color);
    transition: transform 0.3s ease;
}

.u-dock--pushed {
    transform: translateX(var(--u-drawer-width));
}

.u-dock-item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    color: var(--tblr-body-color);
    text-decoration: none;
}

.u-dock-item[aria-current="page"] {
    color: var(--tblr-primary);
}

.u-dock-item-dot {
    width: 1.125rem;
    height: 0.125rem;
    border-radius: 999px;
    background: transparent;
}

.u-dock-item[aria-current="page"] .u-dock-item-dot {
    background: var(--tblr-primary);
}

/*
 * Bounce del icono al pulsar un enlace del dock; la propia navegación destruye
 * la animación.
 */
@keyframes u-dock-bounce {
    0%, 100% {
        transform: translateY(0);
        animation-timing-function: cubic-bezier(0.5, 0, 1, 1);
    }
    50% {
        transform: translateY(-0.5rem);
        animation-timing-function: cubic-bezier(0, 0, 0.5, 1);
    }
}

.u-dock-item--loading .icon {
    animation: u-dock-bounce 0.6s infinite;
}

/*
 * El botón "turbo" es un <button> de dropdown: se resetea para que se vea como
 * los <a> del dock.
 */
.u-dock-item-wrap {
    flex: 1 1 0;
    display: flex;
}

.u-dock-item-wrap .u-dock-item {
    width: 100%;
    border: 0;
    background: transparent;
    padding: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

/* Hueco para que el dock fijo no tape el final de la página, solo en móvil. */
@media (max-width: 767.98px) {
    .u-shell-content {
        padding-bottom: calc(var(--u-dock-height) + env(safe-area-inset-bottom));
    }
}

/*
 * Estructura y espaciado de cards y tablas: un solo escalón de separación entre
 * cards, .card-table siempre, table-responsive u-table-scroll en tablas largas
 * y tres formas de cabecera. Ver wiki «Tablas y listados».
 */
/* Tabla con alto acotado y scroll interno; el thead se pega arriba de la caja. */
.u-table-scroll {
    /*
     * Alto fijo que no descuenta lo que hay encima, con un suelo de ocho filas.
     * Ver wiki «Tablas y listados».
     */
    max-height: max(26rem, 75vh);
    overflow-y: auto;
}

.u-table-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    /* fondo opaco: si no, las filas se ven por debajo de la cabecera al scroll.
       gris-100 en vez de surface para diferenciar la cabecera de las filas. */
    background: var(--tblr-gray-100);
}

/* .u-table-scroll dentro de un modal: los 75vh son para una página entera. */
.u-table-scroll--modal {
    max-height: 50vh;
}

/*
 * Como .u-table-scroll para una lista que no es tabla (el panel "Requiere
 * atención hoy"), con un alto fijo y modesto para no empujar el resto del
 * tablero.
 */
.u-list-scroll {
    max-height: 22rem;
    overflow-y: auto;
}

/*
 * Cajas donde corre el virtualizador: sin anclaje de scroll del navegador, que
 * borraría los nodos que usa de ancla, no se comporta igual en todos los
 * motores y Safari no lo implementa. El virtualizador ya hace esa cuenta.
 */
.u-table-scroll,
.u-list-scroll,
.u-order-catalog-scroll,
.u-merge-picker-scroll {
    overflow-anchor: none;
}

/*
 * Filas de relleno del virtualizador (el alto lo fija el JS). Borde hidden y no
 * de anchura 0, que con border-collapse pierde contra el de la fila vecina;
 * padding 0 para que puedan bajar del alto del padding de .table.
 */
.u-vlist-spacer > td {
    padding: 0;
    border-style: hidden;
}

/*
 * Maestro-detalle de recetas: en >=lg el detalle es sticky bajo la sub-navbar;
 * flex-start para que tenga recorrido.
 */
.u-recipe-detail {
    /* contexto de posicionamiento para el skeleton superpuesto (position:
       absolute). En <lg basta relative; en >=lg pasa a sticky (abajo), que
       también sirve de contenedor de bloque para el overlay. */
    position: relative;
}

@media (min-width: 992px) {
    .u-recipe-detail {
        position: sticky;
        top: calc(3.5rem + var(--tblr-page-padding));
        align-self: flex-start;
    }
}

/*
 * Skeleton del detalle de receta en dos capas: la opaca tapa al instante la
 * receta anterior, y los placeholders solo aparecen pasados 300 ms para no
 * parpadear en respuestas rápidas. visibility corta los clics mientras está
 * oculta.
 */
.u-recipe-skeleton {
    position: absolute;
    inset: 0;
    z-index: 2;
    overflow: hidden;
    background: var(--tblr-body-bg);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease, visibility 0.12s ease;
}

.u-recipe-skeleton.u-recipe-skeleton--on {
    opacity: 1;
    visibility: visible;
}

.u-recipe-skeleton-body {
    opacity: 0;
    transition: opacity 0.15s ease;
}

.u-recipe-skeleton--on .u-recipe-skeleton-body {
    opacity: 1;
    transition-delay: 0.3s;
}

/*
 * Mosaico de fichas con CSS columns, sin Masonry.js. El orden de lectura pasa a
 * ser por columna; las fichas son independientes.
 */
.u-quick-masonry {
    column-count: 1;
    column-gap: 1rem;
}

@media (min-width: 768px) {
    .u-quick-masonry {
        column-count: 2;
    }
}

@media (min-width: 1200px) {
    .u-quick-masonry {
        column-count: 3;
    }
}

.u-quick-masonry-item {
    break-inside: avoid;
    margin-bottom: 1rem;
}

/*
 * El estado vacío cruza las columnas (column-span) en vez de caer en la
 * primera.
 */
.u-quick-empty {
    column-span: all;
}

/*
 * Móvil: el detalle de receta se ve en modo drill-in; .u-pane-hidden solo
 * oculta por debajo de lg.
 */
@media (max-width: 991.98px) {
    .u-pane-hidden {
        display: none !important;
    }
}

/*
 * Fila "ningún resultado" de proveedores, la única tabla que filtra en el
 * cliente: :has detecta en vivo que no queda ninguna fila visible. Las que
 * filtran en servidor ya pintan su vacío.
 */
.u-no-results {
    display: none;
}

#suppliers-tbody:not(:has(.u-supplier-row:not(.d-none))) .u-no-results {
    display: table-row;
}

/* Número de los KPI; los de alarma le añaden .text-*. */
.kpi-value {
    font-size: 2rem;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums;
}

/* número + chip de variación en la misma línea base */
.kpi-figure {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
}

/*
 * Cifra abreviada de los KPI: la plantilla pinta las dos versiones y aquí se
 * elige por ancho (el servidor no lo conoce). El corte es lg, donde los KPI
 * pasan a dos por dos.
 */
.kpi-num-short {
    display: none;
}

@media (max-width: 991.98px) {
    .kpi-num-full {
        display: none;
    }

    .kpi-num-short {
        display: inline;
    }
}

/*
 * En móvil el número baja un punto y el chip de variación puede saltar de
 * línea.
 */
@media (max-width: 575.98px) {
    .kpi-value {
        font-size: 1.5rem;
    }

    .kpi-figure {
        flex-wrap: wrap;
    }
}

/*
 * Chip de variación "vs semana anterior". El color lo fija el Tone que calcula
 * el servicio (makeDelta): good = verde, bad = rojo, muted = gris — así "más
 * caducados" sale en rojo aunque el número suba. La flecha es un SVG inline.
 */
.kpi-delta {
    display: inline-flex;
    align-items: center;
    gap: 0.15rem;
    font-size: 0.8125rem;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.kpi-delta svg {
    width: 1em;
    height: 1em;
    /* los partials vienen con stroke-width 2; a 1em de alto la flecha se
       queda demasiado fina, asi que se engorda solo aquí. */
    stroke-width: 2.5;
}

.kpi-delta-good {
    color: var(--tblr-success);
}

.kpi-delta-bad {
    color: var(--tblr-danger);
}

.kpi-delta-muted {
    color: var(--tblr-secondary);
}

/* Embudo de filtros: outline plegado y sólido desplegado. */
.u-filter-toggle .icon-tabler-filter.icons-tabler-filled {
    display: none;
}

.u-filter-toggle--open .icon-tabler-filter.icons-tabler-outline {
    display: none;
}

.u-filter-toggle--open .icon-tabler-filter.icons-tabler-filled {
    display: inline-block;
}

/*
 * Filtros plegados por defecto en móvil desde el CSS, para que no haya salto
 * antes de que Datastar hidrate.
 */
.u-filters-collapse {
    display: none;
    flex-direction: column;
    gap: 0.5rem;
}

.u-filters-collapse--open {
    display: flex;
}

/*
 * El acordeón de filtros solo existe en móvil (<576px). A partir de sm se
 * oculta el botón toggle y los filtros quedan siempre visibles, pisando con
 * !important el display:none base aunque no esté la clase --open.
 */
@media (min-width: 576px) {
    .u-filter-toggle {
        display: none !important;
    }

    .u-filters-collapse {
        display: flex !important;
    }
}

/*
 * Input de edición inline de precio (fragments/inventory/item-suppliers-rows,
 * fragments/supplier/supplier-items-rows): angosto a propósito, ya que solo
 * cabe un número corto de precio.
 */
.u-price-input {
    width: 7.5rem;
}

/* Fila de sugerencia de la IA, con fondo distinto de las líneas reales. */
.u-ocr-suggestion-row {
    background-color: var(--tblr-azure-lt);
}

/*
 * Columna "Validar" fija al borde derecho (sticky) para no perderla al
 * desplazar en horizontal, con fondo blanco propio para leerse como franja de
 * acción. .card-table delante para ganar en especificidad a
 * ".table>:not(caption)>*>*" sin !important.
 */
.card-table .u-col-sticky-right {
    position: sticky;
    right: 0;
    z-index: 1;
    background-color: var(--tblr-white);
    /* borde fino gris (línea solida) + sombreado suave (blur) a la
       izquierda, para que se lea como una franja elevada sobre el
       contenido que se desplaza debajo, no solo como un borde plano. */
    box-shadow: -1px 0 0 var(--tblr-border-color), -4px 0 6px -4px rgba(0, 0, 0, 0.2);
}

/*
 * Tabla del catálogo de pedido que sangra hasta los bordes del modal, como
 * .card-table en una card.
 */
.u-order-catalog-bleed {
    margin-left: calc(var(--tblr-modal-padding) * -1);
    margin-right: calc(var(--tblr-modal-padding) * -1);
}

/* Scroll propio del catálogo de pedido, más bajo porque vive en un modal. */
.u-order-catalog-scroll {
    max-height: 45vh;
    overflow-y: auto;
}

.u-order-catalog-scroll thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--tblr-gray-100);
}

/*
 * La esquina "Cantidad" necesita este selector más específico para quedar por
 * encima de las columnas que se desplazan.
 */
.u-order-catalog-scroll thead th.u-col-sticky-right {
    z-index: 3;
    /* mismo gris que el resto de la cabecera -- sin este override, el fondo
       blanco de .card-table .u-col-sticky-right (pensado para una celda de
       cuerpo) gana por especificidad y esta esquina desentona del resto de
       la fila. */
    background: var(--tblr-gray-100);
}

/*
 * Separador de la columna fija con un ::before: con border-collapse y sticky,
 * ni el borde ni el box-shadow se pintaban bien.
 */
.u-order-catalog-scroll .u-col-sticky-right {
    box-shadow: none;
}

.u-order-catalog-scroll .u-col-sticky-right::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: var(--tblr-border-width);
    background: var(--tblr-border-color);
}

/* Stepper de cantidad compacto, con sitio para dos dígitos. */
.u-order-qty-group {
    width: 8rem;
}

.u-order-qty-group .btn-icon {
    width: 1.75rem;
    padding: 0;
}

.u-order-qty-group .btn-icon .icon {
    width: 1rem;
    height: 1rem;
    margin: 0;
}

.u-order-qty-group .order-catalog-qty {
    font-size: 0.875rem;
}

/*
 * Equivalencia de un formato en la tarjeta "Formatos y equivalencias": el
 * número tabular alineado a la derecha para que la columna de equivalencias
 * se lea en vertical aunque los nombres de formato midan distinto.
 */
.u-format-equivalence {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * Paso del asistente de receta con puntos: una barra necesitaría un width
 * inline.
 */
.u-wizard-dots {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.u-wizard-dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    background: var(--tblr-border-color);
    transition: background 0.15s ease-in-out;
}

.u-wizard-dot--done {
    background: var(--tblr-primary);
}

/*
 * Gráfico de gasto: <svg> con la geometría calculada en Go; aquí solo colores,
 * grosores y tipografía.
 */
.u-chart-scroll {
    overflow-x: auto;
}

.u-chart {
    display: block;
    width: 100%;
    /* por debajo de este ancho las etiquetas del eje X se solapan; a partir
       de ahí manda el scroll horizontal del contenedor. */
    min-width: 34rem;
    height: auto;
}

/* líneas de referencia horizontales y eje X */
.u-chart__grid {
    stroke: var(--tblr-border-color);
    stroke-width: 1;
}

.u-chart__baseline {
    stroke: var(--tblr-border-color-translucent, var(--tblr-border-color));
    stroke-width: 1.5;
}

.u-chart__axis-label,
.u-chart__tick {
    fill: var(--tblr-secondary);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

/* el periodo en curso va en negrita: es el que leen los KPIs de arriba */
.u-chart__tick--current {
    fill: var(--tblr-body-color);
    font-weight: 600;
}

/* franja de fondo que resalta la columna del periodo en curso */
.u-chart__slot-current {
    fill: var(--tblr-border-color);
    opacity: 0.25;
}

/*
 * Barra apilada: primero el total (color del IVA) y encima el trozo de base
 * imponible, para que ambos rectángulos puedan llevar esquinas redondeadas
 * sin muesca en la junta (ver ChartBar en stats_chart.go).
 */
.u-chart__bar-total {
    fill: var(--tblr-primary);
    opacity: 0.35;
}

.u-chart__bar-base {
    fill: var(--tblr-primary);
}

.u-chart__bar {
    transition: opacity 0.15s ease-in-out;
}

.u-chart:hover .u-chart__bar:not(:hover) {
    opacity: 0.55;
}

/*
 * Periodo en curso: más opacidad en el tramo de IVA. Va después del hover
 * porque comparten especificidad.
 */
.u-chart__bar--current .u-chart__bar-total {
    opacity: 0.55;
}

.u-chart:hover .u-chart__bar--current:not(:hover) {
    opacity: 0.8;
}

.u-chart__bar-value {
    fill: var(--tblr-body-color);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* media del periodo: discontinua para que no se confunda con una barra */
.u-chart__average {
    stroke: var(--tblr-orange, #f76707);
    stroke-width: 1.5;
    stroke-dasharray: 5 4;
}

.u-chart__average-label {
    fill: var(--tblr-orange, #f76707);
    font-size: 10px;
    font-weight: 600;
}

/* leyenda base/IVA de la cabecera de la tarjeta */
.u-chart-legend {
    display: flex;
    align-items: center;
    gap: 1rem;
    font-size: 0.8125rem;
    color: var(--tblr-secondary);
}

.u-chart-legend__item {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}

.u-chart-legend__swatch {
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 0.25rem;
    background: var(--tblr-primary);
}

.u-chart-legend__swatch--vat {
    opacity: 0.35;
}

/* pie de la tarjeta del gráfico: totales del rango completo */
.u-summary-footer {
    font-variant-numeric: tabular-nums;
}

/*
 * Rankings con <progress>: el porcentaje va en value, sin style inline, y los
 * extremos se redondean bien.
 */
.u-rank-row {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.u-rank-amount {
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/*
 * appearance:none para poder dar color y radio (WebKit), border:0 por Firefox.
 * Radio 999px para semicírculos con cualquier alto.
 */
.u-rank-bar {
    display: block;
    width: 100%;
    height: 0.375rem;
    appearance: none;
    -webkit-appearance: none;
    border: 0;
    border-radius: 999px;
    overflow: hidden;
    background-color: var(--tblr-border-color);
}

/* Color del relleno por variante. Va en una variable porque hay que
   repetirlo en los pseudo-elementos de cada motor, que no se pueden agrupar
   en un solo selector (ver abajo). */
.u-rank-bar--supplier {
    --u-rank-fill: var(--tblr-primary);
}

.u-rank-bar--item {
    --u-rank-fill: var(--tblr-teal, #0ca678);
}

/*
 * Carril y relleno son pseudo-elementos distintos en cada motor, y NO se
 * pueden juntar en una lista de selectores: un navegador que no entienda uno
 * de los dos descarta la regla entera. De ahí que estén repetidos.
 */
.u-rank-bar::-webkit-progress-bar {
    background-color: var(--tblr-border-color);
    border-radius: 999px;
}

.u-rank-bar::-webkit-progress-value {
    background-color: var(--u-rank-fill);
    border-radius: 999px;
}

.u-rank-bar::-moz-progress-bar {
    background-color: var(--u-rank-fill);
    border-radius: 999px;
}

/*
 * Calendario de actividad: FullCalendar inyecta su CSS; aquí va la piel de
 * Tabler copiada de tabler-vendors.css (solo estas reglas) y lo propio del
 * tema.
 */

:root {
    --fc-border-color: var(--tblr-border-color);
    --fc-daygrid-event-dot-width: 5px;
    --fc-event-bg-color: var(--tblr-primary-lt);
    --fc-event-border-color: var(--tblr-primary-200);
    --fc-event-text-color: var(--tblr-body-color);
}

.fc-toolbar-title {
    font-size: var(--tblr-font-size-h3) !important;
    font-weight: var(--tblr-font-weight-medium);
}

.fc-daygrid-dot-event {
    background-color: var(--fc-event-bg-color);
    border: 1px solid var(--fc-event-border-color);
    color: var(--fc-event-text-color);
    padding-top: 1px;
    padding-bottom: 1px;
}

.fc-daygrid-dot-event .fc-event-title {
    font-weight: inherit;
}

.fc-col-header-cell {
    background-color: var(--tblr-bg-surface-secondary);
    color: var(--tblr-secondary);
    text-transform: uppercase;
    font-weight: var(--tblr-font-weight-medium);
    font-size: var(--tblr-font-size-h5);
    padding-top: 0.5rem !important;
    padding-bottom: 0.5rem !important;
    line-height: 1;
}

.fc-event-time {
    font-weight: var(--tblr-font-weight-bold) !important;
}

.fc-col-header-cell-cushion {
    color: inherit;
}

.fc-daygrid-day-number {
    color: var(--tblr-secondary);
}

.fc-button {
    color: var(--tblr-body-color) !important;
    background-color: transparent !important;
    border-color: var(--tblr-border-color) !important;
    font-weight: var(--tblr-font-weight-medium) !important;
}

/*
 * Color por tipo de evento, la misma clase que el punto de su chip de filtro.
 * Se redefinen las --fc-* para que valgan en todas las vistas; siempre tokens
 * --tblr-*, que siguen el color de marca.
 */
.u-cal-expiry {
    --fc-event-bg-color: var(--tblr-orange-lt);
    --fc-event-border-color: var(--tblr-orange-200);
}

.u-cal-entry {
    --fc-event-bg-color: var(--tblr-green-lt);
    --fc-event-border-color: var(--tblr-green-200);
}

.u-cal-production {
    --fc-event-bg-color: var(--tblr-purple-lt);
    --fc-event-border-color: var(--tblr-purple-200);
}

.u-cal-delivery-note {
    --fc-event-bg-color: var(--tblr-blue-lt);
    --fc-event-border-color: var(--tblr-blue-200);
}

.u-cal-invoice {
    --fc-event-bg-color: var(--tblr-indigo-lt);
    --fc-event-border-color: var(--tblr-indigo-200);
}

.u-cal-other {
    --fc-event-bg-color: var(--tblr-bg-surface-secondary);
    --fc-event-border-color: var(--tblr-border-color);
}

.u-cal-order {
    --fc-event-bg-color: var(--tblr-cyan-lt);
    --fc-event-border-color: var(--tblr-cyan-200);
}

.u-cal-temperature {
    --fc-event-bg-color: var(--tblr-teal-lt);
    --fc-event-border-color: var(--tblr-teal-200);
}

.u-cal-cleaning {
    --fc-event-bg-color: var(--tblr-lime-lt);
    --fc-event-border-color: var(--tblr-lime-200);
}

.u-cal-tpm {
    --fc-event-bg-color: var(--tblr-yellow-lt);
    --fc-event-border-color: var(--tblr-yellow-200);
}

.u-cal-oil-change {
    --fc-event-bg-color: var(--tblr-pink-lt);
    --fc-event-border-color: var(--tblr-pink-200);
}

.u-cal-oil-pickup {
    --fc-event-bg-color: var(--tblr-gray-200);
    --fc-event-border-color: var(--tblr-gray-300);
}

/* Tono de gravedad, solo en caducidades y temperaturas fuera de rango. */
.u-cal-tone-warning {
    --fc-event-bg-color: var(--tblr-warning-lt);
    --fc-event-border-color: var(--tblr-warning);
}

.u-cal-tone-danger {
    --fc-event-bg-color: var(--tblr-danger-lt);
    --fc-event-border-color: var(--tblr-danger);
    font-weight: var(--tblr-font-weight-medium);
}

/*
 * Chip de filtro: flex en vez del float de .form-check, para que una etiqueta
 * de dos líneas no se meta bajo la casilla. flex-start alinea las casillas de
 * toda la fila.
 */
.u-cal-filter-label {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    padding-left: 0;
    cursor: pointer;
}

.u-cal-filter-label .form-check-input {
    float: none;
    flex-shrink: 0;
    margin: 0.15rem 0 0;
}

.u-cal-filter-label .form-check-label {
    /* el gap del flex padre ya separa, y el dot no debe encogerse nunca */
    line-height: 1.3;
}

.u-cal-filter-text {
    /* min-width:0 deja que el texto se parta dentro de la columna estrecha
       en vez de forzar el ensanchado de la celda de la rejilla. */
    min-width: 0;
}

/*
 * Punto de color del chip de filtro. Toma el color del borde de su clase de
 * tipo (que va en el mismo elemento), así no hay una segunda tabla de
 * colores que mantener en paralelo.
 */
.u-cal-dot {
    width: 0.6rem;
    height: 0.6rem;
    margin-top: 0.3rem;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--fc-event-bg-color);
    border: 1px solid var(--fc-event-border-color);
}

/* Scroll horizontal propio para la rejilla del mes en móvil. */
.fc .fc-daygrid-body,
.fc .fc-scrollgrid-sync-table {
    min-width: 100%;
}

/*
 * Semana: los eventos fluyen a varias líneas en vez de cortarse; la rejilla del
 * mes conserva las píldoras de una línea.
 */
.fc-dayGridWeek-view .fc-daygrid-day-events {
    min-height: 0;
}

.fc-dayGridWeek-view .fc-daygrid-event,
.fc-dayGridWeek-view .fc-daygrid-event .fc-event-title,
.fc-dayGridWeek-view .fc-daygrid-event .fc-event-title-container {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

.fc-dayGridWeek-view .fc-daygrid-event {
    line-height: 1.25;
    align-items: flex-start;
}

@media (max-width: 575.98px) {
    .fc .fc-toolbar {
        flex-direction: column;
        gap: 0.5rem;
    }
}

/*
 * Etiqueta de producto (80 mm), compartida por la vista previa y la impresión
 * (fragments/haccp/label-body.html). En mm y pt, no en rem, para que no la
 * escale el tamaño de fuente del panel. Par sincronizado con
 * core/printing/label_raster.go.
 */
.u-label-sheet {
    width: 80mm;
    box-sizing: border-box;
    padding: 3mm 4mm;
    background: #fff;
    color: #000;
    /* Familia de sistema y no la de marca: una impresora termica rasteriza
       el texto a 203 dpi, y una fuente con hinting fino sale peor. */
    font-family: Arial, Helvetica, sans-serif;
    line-height: 1.25;
}

.u-label-reason {
    font-size: 9pt;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    border-bottom: 0.4mm solid #000;
    padding-bottom: 1mm;
    margin-bottom: 2mm;
}

.u-label-title {
    font-size: 15pt;
    font-weight: 700;
    overflow-wrap: anywhere;
}

.u-label-qty {
    font-size: 10pt;
    margin-top: 0.5mm;
}

/* La caducidad es lo único que se lee de reojo desde el otro lado de la
   cocina: recuadro y cuerpo grande, no una fila más de la lista. */
.u-label-expiry {
    display: flex;
    flex-direction: column;
    border: 0.5mm solid #000;
    padding: 1.5mm 2mm;
    margin: 2.5mm 0;
}

.u-label-expiry-key {
    font-size: 8pt;
    text-transform: uppercase;
}

.u-label-expiry-value {
    font-size: 18pt;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.u-label-rows {
    display: flex;
    flex-direction: column;
    gap: 0.8mm;
}

.u-label-row {
    display: flex;
    justify-content: space-between;
    gap: 3mm;
    font-size: 9pt;
}

.u-label-key {
    text-transform: uppercase;
}

.u-label-value {
    font-weight: 700;
    text-align: right;
    overflow-wrap: anywhere;
}

.u-label-notes {
    margin-top: 2mm;
    padding-top: 1mm;
    border-top: 0.2mm dashed #000;
    font-size: 8pt;
    overflow-wrap: anywhere;
}

/*
 * QR de trazabilidad: 22 mm de lado, con el tamaño decidido aquí (el SVG llega
 * sin width ni height). Un QR versión 2-3 deja módulos de 0,59-0,67 mm, unos 5
 * puntos a 203 dpi; por debajo de 20 mm empiezan los fallos de lectura.
 */
.u-label-qr {
    display: flex;
    align-items: center;
    gap: 2mm;
    margin-top: 2mm;
    padding-top: 1.5mm;
    border-top: 0.2mm dashed #000;
}

.u-label-qr-svg {
    flex: 0 0 auto;
    width: 22mm;
    height: 22mm;
}

.u-label-qr-text {
    display: flex;
    flex-direction: column;
    /* Sin esto, el texto largo de la URL desborda el flex en vez de partirse */
    min-width: 0;
}

.u-label-qr-title {
    font-size: 8pt;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

/* La URL en texto, para quien no tenga una cámara a mano. 7pt es lo mínimo
   que una termica de 203 dpi deja legible. */
.u-label-qr-url {
    font-size: 7pt;
    overflow-wrap: anywhere;
}

/* Marco de la vista previa: solo en el asistente. */
.u-label-preview-frame {
    display: flex;
    justify-content: center;
    padding: 1rem;
    overflow-x: auto;
    background: var(--tblr-gray-100);
    border: 1px dashed var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
}

.u-label-preview-frame .u-label-sheet {
    flex: 0 0 auto;
    box-shadow: 0 0 0 1px var(--tblr-border-color);
}

/*
 * Impresoras: el token en monoespaciada, porque hay que copiarlo y en
 * proporcional la l y el 1 no se distinguen.
 */
.u-token-field {
    font-family: var(--tblr-font-monospace);
    font-size: 0.875rem;
}

/* ==========================================================================
   Toasts
   ========================================================================== */

/*
 * Separación entre toasts (la variable la declara cada .toast, así que se pisa
 * aquí) y duración de la barra, que es el temporizador real del toast
 * (toast.js).
 */
.toast {
    --tblr-toast-spacing: var(--tblr-spacer-2);
    --u-toast-duration: 5s;

    position: relative;
    overflow: hidden;
}

.u-toast-progress {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 3px;
    background: var(--tblr-border-color);
}

/* scaleX porque se anima en el compositor; linear porque es un reloj. */
.u-toast-progress-bar {
    display: block;
    height: 100%;
    background: var(--tblr-primary);
    transform-origin: left center;
    animation: u-toast-fill var(--u-toast-duration) linear forwards;
}

/*
 * Pausa al pasar el ratón (o al entrar el foco). No hay regla que reanude:
 * una vez pausado, el toast solo se cierra con la X.
 */
.toast.u-toast--paused .u-toast-progress-bar {
    animation-play-state: paused;
    background: var(--tblr-secondary);
}

@keyframes u-toast-fill {
    from {
        transform: scaleX(0);
    }

    to {
        transform: scaleX(1);
    }
}

/*
 * Árbol del catálogo con <div> anidados y <details> nativos: sin JS, sin ids
 * por nodo y sin style inline. Solo se sustituye el triángulo del navegador.
 */

.u-tree-node > .u-tree-summary {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.5rem;
    border-radius: var(--tblr-border-radius);
    cursor: pointer;
    list-style: none;
}

/* Safari sigue pintando el marcador por su cuenta si solo se usa list-style. */
.u-tree-node > .u-tree-summary::-webkit-details-marker {
    display: none;
}

.u-tree-node > .u-tree-summary::before {
    content: "";
    flex: 0 0 auto;
    width: 0.4rem;
    height: 0.4rem;
    border-right: 1.5px solid var(--tblr-secondary);
    border-bottom: 1.5px solid var(--tblr-secondary);
    transform: rotate(-45deg);
    transition: transform 0.15s ease-in-out;
}

.u-tree-node[open] > .u-tree-summary::before {
    transform: rotate(45deg);
}

.u-tree-node > .u-tree-summary:hover {
    background: var(--tblr-bg-surface-tertiary);
}

/* La rama lleva una guía vertical: con cuatro niveles, la indentación sola
   no basta para saber de qué cuelga cada hoja. */
.u-tree-branch {
    padding-left: 0.75rem;
    margin-left: 0.45rem;
    border-left: var(--tblr-border-width) solid var(--tblr-border-color);
}

.u-tree-leaf {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    /* El padding-left alinea la hoja con el texto de un summary hermano, que
       lleva delante el triangulito de 0.4rem más su gap. */
    padding: 0.375rem 0.5rem 0.375rem 0.9rem;
    border-radius: var(--tblr-border-radius);
}

.u-tree-leaf--link {
    color: inherit;
    text-decoration: none;
}

.u-tree-leaf--link:hover {
    background: var(--tblr-bg-surface-tertiary);
    color: inherit;
}

.u-tree-label {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
}

.u-tree-label .icon {
    flex: 0 0 auto;
    width: 1rem;
    height: 1rem;
    color: var(--tblr-secondary);
}

.u-tree-format + .u-tree-format {
    margin-top: 0.25rem;
}

/*
 * Buscador de la unificación con alto fijo, para no empujar los botones del
 * modal fuera de la pantalla.
 */
.u-merge-picker-scroll {
    max-height: 22rem;
    overflow-y: auto;
}

/*
 * Arranque del asistente: por defecto skeleton visible y asistente oculto,
 * porque data-show no existe hasta que arranca Datastar y sin esto se veían los
 * doce pasos apilados. Datastar da la vuelta con la primera expresión. display
 * y no opacity para que solo uno ocupe sitio.
 */
.u-onb-wizard {
    display: none;
}

.u-onb-wizard--ready {
    display: block;
}

/*
 * Condición propia y no un selector de hermano: el skeleton va antes en el DOM
 * y CSS no selecciona hacia atrás.
 */
.u-onb-skeleton--done {
    display: none;
}

/*
 * Placeholders con 300 ms de retraso para no parpadear cuando Datastar arranca
 * rápido.
 */
.u-onb-skeleton-body {
    opacity: 0;
    animation: u-onb-skeleton-in 0.15s ease 0.3s forwards;
}

@keyframes u-onb-skeleton-in {
    to {
        opacity: 1;
    }
}

/* Alto de barra de los placeholders que hacen de campo de formulario: un
   .placeholder vacío hereda el alto de la línea y queda como un filete. */
.u-onb-skeleton-field {
    height: 2.25rem;
}

.u-onb-skeleton-foot {
    margin-top: 1.5rem;
}

/*
 * Móvil: el skeleton ocupa la pantalla (una tarjeta a media altura parece
 * contenido terminado). Resta el py-4 del layout para no forzar scroll.
 * min-height en vh y luego dvh, por los navegadores que no entienden dvh.
 */
@media (max-width: 767.98px) {
    .u-onb-skeleton {
        min-height: calc(100vh - 3rem);
        min-height: calc(100dvh - 3rem);
    }

    .u-onb-skeleton-body {
        display: flex;
        flex-direction: column;
    }

    .u-onb-skeleton-foot {
        margin-top: auto;
    }
}

@media (prefers-reduced-motion: reduce) {
    .u-onb-skeleton-body {
        animation-duration: 0.01ms;
    }
}

/*
 * Barra de progreso de navegación (progress.js), por encima de los modales
 * (z-index 1100) y sin capturar clics.
 */
.u-progress {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    z-index: 1100;
    height: 3px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
}

.u-progress--on {
    opacity: 1;
}

/*
 * scaleX y no width para no recalcular el layout en cada paso.
 * --u-progress-value (0-1) es lo único que escribe el JS.
 */
.u-progress-bar {
    width: 100%;
    height: 100%;
    background: var(--tblr-primary);
    transform: scaleX(var(--u-progress-value, 0));
    transform-origin: 0 50%;
    transition: transform 0.25s ease-out;
}

/*
 * Estado de rearme: sin transición, para plantar la barra en el origen sin
 * que se vea retroceder desde donde acabó la carga anterior. progress.js lo
 * pone y lo quita en el mismo tick, con un reflow forzado en medio.
 */
.u-progress--reset .u-progress-bar {
    transition: none;
}

@media (prefers-reduced-motion: reduce) {
    /* La barra se queda -- es el acuse de recibo, no un adorno -- pero deja
       de deslizarse: salta directamente a cada valor. */
    .u-progress-bar {
        transition: none;
    }
}

/* Página de recomendaciones. */

/*
 * Índice sticky con techo y scroll propio para cuando no quepa. El margen
 * negativo y el padding evitan la barra horizontal que provoca el desborde
 * intencionado de la pastilla activa de Tabler al tener overflow-y.
 */
.u-recommendations-nav {
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    padding-block: 1rem;
    padding-inline: 0.75rem;
    margin-inline: -0.75rem;
}

/*
 * scroll-margin-top para que el título no quede tapado al saltar desde el
 * índice.
 */
.u-rec-section {
    margin-top: 2.5rem;
    padding-top: 2rem;
    border-top: 1px solid var(--tblr-border-color);
    scroll-margin-top: 1.5rem;
}

.u-rec-section--last {
    margin-bottom: 0;
}

/* ==========================================================================
   Backoffice (core/backoffice)
   Consola de desarrollo, no pantalla de producto: lo justo para que un JSON
   largo y un editor de schema se puedan leer. Ver templates/layouts/backoffice.html.
   ========================================================================== */

.u-backoffice-json {
    max-height: 32rem;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 0.75rem;
}

.u-backoffice-editor {
    font-family: var(--tblr-font-monospace);
    font-size: 0.75rem;
}

/* Visor del documento original junto a su transcripción. Es donde el equipo
   completa a mano lo que la IA no supo leer, así que las dos columnas tienen
   que verse a la vez y a la misma altura: mirar el papel y teclear el dato
   son el mismo gesto. */
/* El <summary> de un bloque plegable con cabecera de card: sin esto el
   triángulo se pinta fuera del padding y la cabecera deja de parecer
   pulsable. */
.u-backoffice-summary {
    cursor: pointer;
    list-style-position: inside;
}

.u-backoffice-doc {
    width: 100%;
    height: 38rem;
    border: 0;
    background: var(--tblr-bg-surface-tertiary);
}

.u-backoffice-doc--image {
    height: auto;
    max-height: 38rem;
    object-fit: contain;
}

.u-backoffice-markdown {
    max-height: 38rem;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
    font-size: 0.75rem;
}

/*
 * Chips de día del editor de horarios: un checkbox real escondido sin
 * display:none (sigue recibiendo foco y lo anuncia el lector de pantalla) y la
 * pinta en el <span> hermano.
 */
.u-sched-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.u-sched-chip {
    position: relative;
    cursor: pointer;
}

.u-sched-chip__input {
    position: absolute;
    inset: 0;
    opacity: 0;
    margin: 0;
    cursor: pointer;
}

.u-sched-chip__face {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border: var(--tblr-border-width) solid var(--tblr-border-color);
    border-radius: 50%;
    font-weight: var(--tblr-font-weight-medium);
    color: var(--tblr-secondary);
    background: var(--tblr-bg-surface);
    transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.u-sched-chip__input:checked + .u-sched-chip__face {
    border-color: var(--tblr-primary);
    background: var(--tblr-primary);
    color: #fff;
}

/* El foco se pinta en la cara porque el input real es invisible. */
.u-sched-chip__input:focus-visible + .u-sched-chip__face {
    outline: 2px solid var(--tblr-primary);
    outline-offset: 2px;
}

/*
 * Anchos de los campos del editor: sin tope, el select de veces y los dos
 * inputs de hora se estiran a toda la fila en cuanto el día cabe holgado, y
 * la fila se lee como un formulario suelto en vez de como una línea.
 */
.u-sched-times {
    width: auto;
    min-width: 7rem;
}

.u-sched-time {
    width: auto;
    min-width: 8rem;
}

.u-sched-every {
    width: 6rem;
}

/* Ilustración del login (Tabler): los colores salen del tema. */
.u-illus-primary {
    fill: var(--tblr-illustrations-primary, var(--tblr-primary));
}

.u-illus-skin {
    fill: var(--tblr-illustrations-skin, #ffcb9d);
}

/* Marca: la de la portada (.lp-brand), punto ámbar y Caprasimo. */
.u-brand {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    font-family: "Caprasimo", Georgia, serif;
    font-weight: 400;
    font-size: 1.6rem;
    line-height: 1;
    color: var(--tblr-body-color);
}

.u-brand:hover {
    text-decoration: none;
}

.u-brand-dot {
    width: 1.1em;
    height: 1.1em;
    border-radius: 50%;
    background: var(--tblr-primary);
}

/*
 * Páginas de cuenta y asistente de alta: el ámbar sobre blanco no llega a
 * 4.5:1, así que, como en la portada, los enlaces van en tinta.
 */
.u-auth {
    --tblr-link-color: var(--tblr-body-color);
    --tblr-link-hover-color: var(--tblr-body-color);
}

.u-auth a:not(.btn, .u-brand) {
    font-weight: var(--tblr-font-weight-medium);
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 0.22em;
    transition: text-decoration-color 180ms ease;
}

.u-auth a:not(.btn, .u-brand):hover,
.u-auth a:not(.btn, .u-brand):focus-visible {
    text-decoration-color: currentColor;
}

/*
 * Lado derecho de la dirección del local (paso 3 del asistente): los hijos del
 * crossfade alineados a la izquierda y no centrados. Selector doble para ganar
 * al CSS que ds-anim.js inyecta al final del <head>.
 */
.u-slug-aside.ds-crossfade {
    display: flex;
    width: 100%;
    min-height: 2.25rem;
}

.u-slug-aside.ds-crossfade > *:not(:first-child) {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
}

