/**
 * DocFilling — Capa 3: Componentes
 * =============================================================================
 *
 * Esta es la UNICA capa que produce pixeles en codigo nuevo.
 *
 * Reglas (ver docs/DESIGN_SYSTEM.md):
 *   1. Se carga la ULTIMA en base.html. Gana la cascada por orden, nunca con
 *      !important. Este fichero debe mantenerse a CERO !important.
 *   2. Solo usa tokens de design-tokens.css. Cero hex, cero rgba crudos salvo
 *      overlays, cero px sueltos fuera de bordes de 1px.
 *   3. Prefijo `df-` para no colisionar con las ~32.000 lineas heredadas.
 *
 * Lenguaje visual: extraido de docfilling-editor/src/styles/theme.css.
 *   - chrome silencioso (superficies neutras, bordes de 1px, sin relleno de color)
 *   - el color significa, no decora (accent solo en foco y accion primaria)
 *   - densidad alta, tipografia pequena (14px base, 13px secundaria)
 *   - geometria discreta (radios 4/6/8px, sombras casi imperceptibles)
 *
 * Inventario actual: df-shell, df-toolbar, df-sidenav, df-statusbar,
 *                    df-dialog, df-toast, df-button.
 */

/* =============================================================================
   df-shell — esqueleto de la aplicacion
   Mismo reparto que el editor: toolbar arriba, barra de estado abajo, panel
   lateral opcional. El area central conserva `.app-container` / `.main-content`
   para que las ~32.000 lineas de CSS heredado sigan funcionando sin tocarlas.
   ============================================================================= */

:root {
    --df-toolbar-height: 40px;
    --df-statusbar-height: 24px;
    --df-sidenav-width: 216px;
    /* Consumido por .df-toast-region para colocarse bajo el chrome. */
    --df-chrome-top: var(--df-toolbar-height);
}

.df-shell {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    /* Filas `auto`: si una superficie no monta barra de estado (p.ej. la pagina
       publica), su fila colapsa a cero en vez de dejar un hueco. */
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
        "toolbar toolbar"
        "sidenav main"
        "statusbar statusbar";
    height: 100vh;
    overflow: hidden;
    background: var(--bg-primary);
}

/* Red de seguridad: las paginas publicas (index, login) sustituyen el bloque de
   navegacion por su propia <nav>. Se coloca en el area de toolbar igualmente. */
.df-shell > nav.navbar {
    grid-area: toolbar;
}

/* El contenedor heredado pasa a ocupar el area central del grid. Antes calculaba
   su altura contra la navbar de 64px; ahora la hereda de la celda. */
.df-shell > .app-container {
    grid-area: main;
    height: 100%;
    min-height: 0;
}

.df-shell .main-content {
    min-height: 0;
    padding: var(--spacing-4) var(--spacing-5);
}

/* =============================================================================
   df-toolbar — chrome superior silencioso (40px, como el editor)
   ============================================================================= */

.df-toolbar {
    grid-area: toolbar;
    display: flex;
    align-items: center;
    height: var(--df-toolbar-height);
    gap: var(--spacing-2);
    padding: 0 var(--spacing-2);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-primary);
    user-select: none;
}

.df-toolbar__brand {
    display: flex;
    align-items: center;
    flex-shrink: 0;
    padding: 0 var(--spacing-2) 0 var(--spacing-1);
    text-decoration: none;
}

.df-toolbar__logo {
    height: 24px;
    width: auto;
    object-fit: contain;
}

/* Aqui vivia un `content: url(...PNG)` que cambiaba el logo en tema oscuro.
   Al pasar el logotipo a SVG se sirven las dos variantes en el marcado y se
   elige con `display`, pero esta regla se quedo: `content` sustituye la imagen
   renderizada, asi que en oscuro seguia saliendo el PNG antiguo POR ENCIMA del
   SVG. Retirada. */

.df-toolbar__divider {
    width: 1px;
    height: 18px;
    background: var(--border-secondary);
    flex-shrink: 0;
}

.df-toolbar__nav {
    display: flex;
    align-items: center;
    gap: var(--spacing-1);
    min-width: 0;
}

.df-toolbar__spacer {
    flex: 1;
    min-width: var(--spacing-2);
}

.df-toolbar__actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-1);
    flex-shrink: 0;
}

/* Enlace de navegacion: texto apagado en reposo, sin relleno de color. */
.df-toolbar-link {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-1-5);
    height: 28px;
    padding: 0 var(--spacing-2-5);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-normal);
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.df-toolbar-link:hover {
    background: var(--interactive-hover);
    color: var(--text-primary);
}

.df-toolbar-link[aria-current="page"] {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font-weight: var(--font-weight-medium);
}

.df-toolbar-link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.df-toolbar-link .df-icon {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    flex-shrink: 0;
}

/* Boton de icono de la toolbar. */
.df-toolbar-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-1-5);
    height: 28px;
    min-width: 28px;
    padding: 0 var(--spacing-2);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    /* Se usa tanto en <button> como en <a> (cambio de modo): sin subrayado. */
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.df-toolbar-btn:hover {
    background: var(--interactive-hover);
    color: var(--text-primary);
}

.df-toolbar-btn[aria-pressed="true"] {
    background: var(--bg-tertiary);
    color: var(--text-primary);
}

.df-toolbar-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.df-toolbar-btn .df-icon {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    flex-shrink: 0;
}

/* Identidad del usuario: iniciales, sin relleno de color (adios al dorado). */
.df-toolbar__user {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-2);
    height: 28px;
    padding: 0 var(--spacing-1) 0 var(--spacing-2);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    cursor: pointer;
    transition: background-color var(--transition-fast);
}

.df-toolbar__user:hover {
    background: var(--interactive-hover);
}

.df-toolbar__user:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.df-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: var(--radius-full);
    background: var(--bg-tertiary);
    border: 1px solid var(--border-secondary);
    color: var(--text-secondary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    flex-shrink: 0;
}

/* Icono generico de la capa 3: hereda color, trazo de 1.5 como el editor. */
.df-icon {
    display: inline-block;
    stroke: currentColor;
    fill: none;
    vertical-align: middle;
}

/* GROSOR DEL TRAZO, PARA TODOS LOS ICONOS
   ---------------------------------------
   Va en CSS y no en los ficheros porque el atributo `stroke-width` del SVG es
   un atributo de PRESENTACION: cualquier regla CSS le gana. Asi el grosor se
   decide en un sitio y no en noventa y nueve.

   1,75 en los tamanos pequenos y 1,5 a partir de 24 px. A tamano grande el
   dibujo ya se lee y engrosarlo solo lo emborrona; a 16 y 20, en cambio, es lo
   que devuelve el peso que el trazo de 1,5 pierde al reducir un dibujo de 24.

   Los tres ficheros que traian el trazo cambiado —`check` a 2,5,
   `document-pdf` y `document-pdf-mixed` a 1— quedan igualados de paso: se veian
   uno mas gordo y dos mas flojos que el resto sin ninguna razon. */
.df-icon,
.icon svg,
.icon-2xs, .icon-xs, .icon-sm, .icon-md,
.icon-2xs svg, .icon-xs svg, .icon-sm svg, .icon-md svg,
.icon-tab svg,
svg.icon-2xs, svg.icon-xs, svg.icon-sm, svg.icon-md {
    stroke-width: 1.75;
}

.icon-lg, .icon-xl, .icon-2xl, .icon-3xl,
.icon-lg svg, .icon-xl svg, .icon-2xl svg, .icon-3xl svg,
svg.icon-lg, svg.icon-xl, svg.icon-2xl, svg.icon-3xl {
    stroke-width: 1.5;
}

/* =============================================================================
   df-sidenav — navegacion del modo Experto
   ============================================================================= */

.df-sidenav {
    grid-area: sidenav;
    display: flex;
    flex-direction: column;
    width: var(--df-sidenav-width);
    padding: var(--spacing-3) var(--spacing-2);
    gap: var(--spacing-1);
    background: var(--bg-secondary);
    border-right: 1px solid var(--border-primary);
    overflow-y: auto;
}

.df-sidenav__title {
    margin: 0 0 var(--spacing-1);
    padding: 0 var(--spacing-2);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wider);
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.df-sidenav__link {
    display: flex;
    align-items: center;
    gap: var(--spacing-2-5);
    height: 32px;
    padding: 0 var(--spacing-2-5);
    border-radius: var(--radius-sm);
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.df-sidenav__link:hover {
    background: var(--interactive-hover);
    color: var(--text-primary);
}

.df-sidenav__link[aria-current="page"] {
    background: var(--bg-tertiary);
    color: var(--text-primary);
    font-weight: var(--font-weight-medium);
    /* Barra de acento: la unica marca de color de la navegacion. */
    box-shadow: inset 2px 0 0 var(--accent);
}

.df-sidenav__link:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

.df-sidenav__link .df-icon {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    flex-shrink: 0;
}

.df-sidenav__separator {
    height: 1px;
    margin: var(--spacing-2) var(--spacing-2);
    background: var(--border-primary);
    border: none;
}

/* =============================================================================
   df-statusbar — barra inferior (24px, acento), como en el editor
   ============================================================================= */

.df-statusbar {
    grid-area: statusbar;
    display: flex;
    align-items: center;
    height: var(--df-statusbar-height);
    gap: var(--spacing-3);
    padding: 0 var(--spacing-3);
    background: var(--accent);
    color: var(--accent-text);
    font-size: var(--font-size-xs);
    line-height: 1;
    user-select: none;
}

.df-statusbar__spacer {
    flex: 1;
}

.df-statusbar__item {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-1-5);
    height: 100%;
    padding: 0 var(--spacing-1-5);
    border: none;
    background: transparent;
    color: inherit;
    font-family: var(--font-family-sans);
    font-size: var(--font-size-xs);
    white-space: nowrap;
}

button.df-statusbar__item {
    cursor: pointer;
}

button.df-statusbar__item:hover {
    background: rgba(0, 0, 0, 0.12);
}

button.df-statusbar__item:focus-visible {
    outline: 2px solid var(--accent-text);
    outline-offset: -2px;
}

/* Punto de estado. El color se hereda salvo estado anomalo. */
.df-statusbar__dot {
    width: 7px;
    height: 7px;
    border-radius: var(--radius-full);
    background: currentColor;
    opacity: 0.75;
    flex-shrink: 0;
}

.df-statusbar__dot.online,
.df-statusbar__dot.idle {
    opacity: 0.75;
}

.df-statusbar__dot.offline,
.df-statusbar__dot.ai_offline {
    background: var(--color-error-300);
    opacity: 1;
}

.df-statusbar__dot.checking,
.df-statusbar__dot.syncing,
.df-statusbar__dot.rebuilding_corpus,
.df-statusbar__dot.maintenance {
    background: var(--color-warning-300);
    opacity: 1;
    animation: df-pulse 1.4s ease-in-out infinite;
}

@keyframes df-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; }
}

/* =============================================================================
   Primitivas compartidas
   ============================================================================= */

.df-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-2);
    min-height: 2rem;
    padding: var(--spacing-1-5) var(--spacing-4);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    background: transparent;
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    line-height: 1.2;
    color: var(--text-primary);
    cursor: pointer;
    white-space: nowrap;
    transition: background-color var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
}

.df-button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.df-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Secundario: el reposo. Superficie neutra, borde de 1px. */
.df-button--secondary {
    background: var(--bg-primary);
    border-color: var(--border-secondary);
    color: var(--text-primary);
}

.df-button--secondary:hover:not(:disabled) {
    background: var(--interactive-hover);
}

/* Primario: la unica accion con relleno de color en la pantalla. */
.df-button--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-text);
}

.df-button--primary:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
}

/* Destructivo: rojo reservado a acciones irreversibles. */
.df-button--danger {
    background: var(--color-error-600);
    border-color: var(--color-error-600);
    color: var(--text-on-status);
}

.df-button--danger:hover:not(:disabled) {
    background: var(--color-error-700);
    border-color: var(--color-error-700);
}

.df-button--danger:focus-visible {
    outline-color: var(--color-error-600);
}

/* =============================================================================
   df-dialog — sustituye a confirm() / alert() nativos y a los 40 modales
   artesanales. Usa <dialog> nativo: foco atrapado, Escape e inerte de fondo
   los da el navegador, no hay que reimplementarlos.
   ============================================================================= */

.df-dialog {
    /* El reset heredado de style.css aplica `* { margin: 0 }`, que anula el
       `margin: auto` con el que el navegador centra los <dialog> modales.
       Se restituye aqui explicitamente. */
    margin: auto;
    width: min(30rem, calc(100vw - var(--spacing-8)));
    max-height: calc(100vh - var(--spacing-16));
    padding: 0;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    background: var(--bg-primary);
    color: var(--text-primary);
    box-shadow: var(--shadow-lg);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    overflow: hidden;
}

.df-dialog::backdrop {
    background: var(--overlay);
}

/* Entrada discreta. Se anula bajo prefers-reduced-motion (final del fichero). */
.df-dialog[open] {
    animation: df-dialog-in var(--transition-fast);
}

.df-dialog[open]::backdrop {
    animation: df-backdrop-in var(--transition-fast);
}

@keyframes df-dialog-in {
    from { opacity: 0; transform: translateY(-0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes df-backdrop-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

.df-dialog__body {
    display: flex;
    gap: var(--spacing-3);
    padding: var(--spacing-5) var(--spacing-5) var(--spacing-4);
}

/* Icono de variante. Es el unico color del dialogo junto al boton de accion. */
.df-dialog__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: var(--icon-size-lg);
    height: var(--icon-size-lg);
    border-radius: var(--radius-full);
}

.df-dialog__icon svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    stroke: currentColor;
    fill: none;
}

.df-dialog--danger .df-dialog__icon {
    background: var(--color-error-100);
    color: var(--color-error-600);
}

.df-dialog--warning .df-dialog__icon {
    background: var(--color-warning-100);
    color: var(--color-warning-700);
}

.df-dialog--info .df-dialog__icon,
.df-dialog--default .df-dialog__icon {
    background: var(--color-primary-100);
    color: var(--color-primary-600);
}

.df-dialog--success .df-dialog__icon {
    background: var(--color-success-100);
    color: var(--color-success-700);
}

.df-dialog__content {
    flex: 1;
    min-width: 0;
}

.df-dialog__title {
    margin: 0;
    font-size: var(--font-size-base);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    color: var(--text-primary);
}

.df-dialog__message {
    margin: var(--spacing-2) 0 0;
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
    color: var(--text-secondary);
    white-space: pre-line;
}

/* Detalle: lo que en los confirm() nativos iba tras el doble salto de linea. */
.df-dialog__detail {
    margin: var(--spacing-3) 0 0;
    font-size: var(--font-size-xs);
    line-height: var(--line-height-normal);
    color: var(--text-tertiary);
    white-space: pre-line;
}

.df-dialog__footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--spacing-2);
    padding: var(--spacing-3) var(--spacing-5);
    border-top: 1px solid var(--border-primary);
    background: var(--bg-secondary);
}

/* =============================================================================
   df-toast — implementacion unica. Sustituye a las 5 definiciones dispersas
   de showToast(). Region live para que los lectores de pantalla lo anuncien.
   ============================================================================= */

.df-toast-region {
    position: fixed;
    /* Por debajo del chrome superior, para no taparlo. --df-chrome-top lo
       redefine cada superficie (navbar 64px hoy, toolbar 40px en fase 2). */
    top: calc(var(--df-chrome-top, var(--navbar-height)) + var(--spacing-4));
    right: var(--spacing-4);
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2);
    pointer-events: none;
}

.df-toast {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-2-5);
    width: min(24rem, calc(100vw - var(--spacing-8)));
    padding: var(--spacing-3) var(--spacing-3-5);
    border: 1px solid var(--border-primary);
    border-left: 3px solid var(--text-tertiary);
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    box-shadow: var(--shadow-md);
    color: var(--text-primary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
    pointer-events: auto;
    cursor: pointer;
    opacity: 0;
    transform: translateX(0.75rem);
    transition: opacity var(--transition-normal), transform var(--transition-normal);
}

.df-toast--visible {
    opacity: 1;
    transform: translateX(0);
}

.df-toast--leaving {
    opacity: 0;
    transform: translateX(0.75rem);
}

/* La barra izquierda es lo unico coloreado: el estado se lee de un vistazo
   sin teñir todo el fondo (que es lo que hacia la implementacion antigua). */
.df-toast--success { border-left-color: var(--color-success-500); }
.df-toast--error   { border-left-color: var(--color-error-500); }
.df-toast--warning { border-left-color: var(--color-warning-500); }
.df-toast--info    { border-left-color: var(--accent); }

.df-toast__icon {
    flex-shrink: 0;
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    margin-top: 0.0625rem;
}

.df-toast__icon svg {
    width: 100%;
    height: 100%;
    stroke: currentColor;
    fill: none;
}

.df-toast--success .df-toast__icon { color: var(--color-success-600); }
.df-toast--error   .df-toast__icon { color: var(--color-error-600); }
.df-toast--warning .df-toast__icon { color: var(--color-warning-600); }
.df-toast--info    .df-toast__icon { color: var(--accent); }

.df-toast__message {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* =============================================================================
   df-field — campo de formulario
   ============================================================================= */

.df-field {
    margin-bottom: var(--spacing-4);
}

.df-field__label {
    display: block;
    margin-bottom: var(--spacing-1-5);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--text-primary);
}

.df-field__input {
    width: 100%;
    min-height: 2.5rem;
    padding: var(--spacing-2) var(--spacing-3);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.df-field__input::placeholder { color: var(--text-tertiary); }

.df-field__input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--interactive-focus);
}

.df-field__input[aria-invalid="true"] {
    border-color: var(--color-error-500);
}

.df-field__input[aria-invalid="true"]:focus {
    box-shadow: 0 0 0 3px var(--color-error-100);
}

.df-field__hint {
    margin: var(--spacing-1) 0 0;
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

.df-field__error {
    margin: var(--spacing-1) 0 0;
    font-size: var(--font-size-xs);
    color: var(--color-error-600);
    min-height: 1em;
}

.df-field__error:empty { display: none; }

/* =============================================================================
   df-signup — alta de organizaciones
   Unica pantalla de la aplicacion con registro de pagina de venta, no de
   herramienta: es la continuacion directa de la landing.
   ============================================================================= */

.df-signup {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: var(--spacing-10) var(--spacing-4);
    min-height: 100%;
    overflow-y: auto;
}

.df-signup__card {
    width: 100%;
    max-width: 26rem;
}

.df-signup__title {
    margin: 0 0 var(--spacing-2);
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-bold);
    letter-spacing: var(--letter-spacing-tight);
    color: var(--text-primary);
}

.df-signup__lead {
    margin: 0 0 var(--spacing-6);
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
}

.df-signup__lead strong { color: var(--text-primary); }

.df-signup__submit {
    width: 100%;
    min-height: 2.5rem;
    margin-top: var(--spacing-2);
}

.df-signup__error:not(:empty) {
    margin-bottom: var(--spacing-3);
    padding: var(--spacing-2) var(--spacing-3);
    border: 1px solid var(--color-error-200);
    border-radius: var(--radius-md);
    background: var(--color-error-50);
    font-size: var(--font-size-sm);
}

.df-signup__foot {
    margin: var(--spacing-5) 0 0;
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
    text-align: center;
}

.df-signup__foot a { color: var(--accent); }

/* =============================================================================
   Preferencias de accesibilidad
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
    .df-dialog[open],
    .df-dialog[open]::backdrop {
        animation: none;
    }

    .df-toast {
        transition: none;
        opacity: 1;
        transform: none;
    }
}

/* Indicador de consumo de la barra de estado. */
.df-statusbar__item--hidden { display: none; }

.df-statusbar__item--alert {
    font-weight: var(--font-weight-semibold);
    text-decoration: underline;
    text-underline-offset: 2px;
}

a.df-statusbar__item {
    text-decoration: none;
    cursor: pointer;
}

a.df-statusbar__item:hover { background: rgba(0, 0, 0, 0.12); }

/* =============================================================================
   df-badge — estado de una entidad
   Extraido de operator.css: era el patron mas solido de la aplicacion y no
   habia razon para que viviese en una sola pantalla.
   ============================================================================= */

.df-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-1);
    padding: 0.125rem var(--spacing-1-5);
    border-radius: var(--radius-full);
    font-size: 0.625rem;
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
    /* Neutro por defecto: un estado sin color declarado no debe gritar. */
    background: color-mix(in srgb, var(--text-tertiary) 15%, var(--bg-primary));
    color: var(--text-secondary);
}

/* Texto en tono 800, no 600/700: sobre el tinte al 15 % esos daban entre 3,97 y
   4,74, por debajo o al filo del minimo de 4,5. Con el 800 quedan sobre 6,3. */
.df-badge--info {
    background: color-mix(in srgb, var(--color-info-500) 15%, var(--bg-primary));
    color: var(--color-info-800);
}

.df-badge--success {
    background: color-mix(in srgb, var(--color-success-500) 15%, var(--bg-primary));
    color: var(--color-success-800);
}

.df-badge--warning {
    background: color-mix(in srgb, var(--color-warning-500) 15%, var(--bg-primary));
    color: var(--color-warning-800);
}

.df-badge--danger {
    background: color-mix(in srgb, var(--color-error-500) 15%, var(--bg-primary));
    color: var(--color-error-800);
}

/* =============================================================================
   df-empty — estado vacio
   Un solo componente para las 7 variantes que habia repartidas por el CSS.
   ============================================================================= */

.df-empty {
    display: flex;
    flex: 1;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--spacing-8) var(--spacing-5);
    text-align: center;
}

.df-empty__icon {
    margin-bottom: var(--spacing-4);
    color: var(--text-tertiary);
}

.df-empty__icon svg,
.df-empty__icon .icon,
.df-empty__icon .df-icon {
    width: var(--icon-size-2xl);
    height: var(--icon-size-2xl);
}

.df-empty__title {
    margin: 0 0 var(--spacing-2);
    font-size: var(--font-size-lg);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

.df-empty__text {
    max-width: 42ch;
    margin: 0 0 var(--spacing-5);
    font-size: var(--font-size-sm);
    color: var(--text-secondary);
}

/* Variante compacta, para huecos dentro de un panel. */
.df-empty--compact {
    padding: var(--spacing-4);
}

.df-empty--compact .df-empty__title {
    font-size: var(--font-size-sm);
}

.df-empty--compact .df-empty__text {
    margin-bottom: 0;
    font-size: var(--font-size-xs);
}

/* =============================================================================
   df-progress — barra de progreso
   ============================================================================= */

.df-progress {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    min-width: 0;
}

.df-progress__track {
    flex: 1;
    height: 3px;
    min-width: 2rem;
    border-radius: var(--radius-full);
    background: var(--bg-tertiary);
    overflow: hidden;
}

.df-progress__fill {
    height: 100%;
    border-radius: var(--radius-full);
    background: var(--accent);
    transition: width var(--transition-slow);
}

.df-progress__label {
    flex-shrink: 0;
    font-size: var(--font-size-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.df-progress--complete .df-progress__fill {
    background: var(--color-success-500);
}

/* La accion principal de pagina vive en la toolbar y debe seguir su ritmo
   (28px), no el de un boton de formulario. Sin esto se ve encajada a la
   fuerza en un chrome de 40px. */
.df-toolbar .df-button {
    min-height: 28px;
    height: 28px;
    padding: 0 var(--spacing-3);
    font-size: var(--font-size-sm);
}

/* =============================================================================
   df-panel — bloque de contenido con cabecera
   Sustituye a los ~12 contenedores con la misma forma (borde, radio, cabecera
   con fondo) que cada pantalla de administracion se habia hecho por su cuenta.
   ============================================================================= */

.df-panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    background: var(--bg-primary);
    overflow: hidden;
}

.df-panel__header {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: var(--spacing-2) var(--spacing-3);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-primary);
    flex-shrink: 0;
}

.df-panel__title {
    margin: 0;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

.df-panel__spacer { flex: 1; }

.df-panel__body {
    flex: 1;
    min-height: 0;
    overflow: auto;
}

/* =============================================================================
   df-table — tabla densa
   Una sola implementacion para las 14 que habia. Densidad del editor: 13px,
   cabecera en versalitas pequenas, filas de 32px, sin cebra (el rayado compite
   con el contenido cuando las filas ya llevan badges y acciones).
   ============================================================================= */

.df-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-sm);
    color: var(--text-primary);
}

.df-table th,
.df-table td {
    padding: var(--spacing-2) var(--spacing-3);
    text-align: left;
    vertical-align: middle;
    border-bottom: 1px solid var(--border-primary);
}

.df-table thead th {
    /* Cabecera fija: en una tabla larga, perder los encabezados al bajar es
       perder el significado de las columnas. */
    position: sticky;
    top: 0;
    z-index: var(--z-raised);
    background: var(--bg-secondary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-semibold);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    color: var(--text-tertiary);
    white-space: nowrap;
}

.df-table tbody tr {
    transition: background-color var(--transition-fast);
}

.df-table tbody tr:hover {
    background: var(--interactive-hover);
}

.df-table tbody tr:last-child td {
    border-bottom: none;
}

/* Columna ordenable. El indicador se dibuja con CSS, no con glifos de texto
   como hacia la tabla heredada (⇅ ↑ ↓ se veian de tamanos distintos segun
   la fuente disponible). */
.df-table th[aria-sort] {
    cursor: pointer;
    user-select: none;
    padding-right: var(--spacing-6);
    position: sticky;
}

.df-table th[aria-sort]::after {
    content: '';
    position: absolute;
    right: var(--spacing-2-5);
    top: 50%;
    width: 0.5rem;
    height: 0.5rem;
    margin-top: -0.25rem;
    opacity: 0.35;
    background: currentColor;
    transition: opacity var(--transition-fast);
    /* Doble triangulo (sin ordenar) */
    clip-path: polygon(50% 0, 100% 40%, 0 40%, 0 60%, 100% 60%, 50% 100%);
}

.df-table th[aria-sort]:hover::after { opacity: 0.7; }

.df-table th[aria-sort="ascending"]::after {
    opacity: 1;
    color: var(--accent);
    clip-path: polygon(50% 10%, 100% 75%, 0 75%);
}

.df-table th[aria-sort="descending"]::after {
    opacity: 1;
    color: var(--accent);
    clip-path: polygon(50% 90%, 100% 25%, 0 25%);
}

/* Celdas numericas: alineadas a la derecha y con cifras de ancho fijo, para
   que las columnas de importes se puedan comparar de un vistazo. */
.df-table .df-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.df-table .df-mono {
    font-family: var(--font-family-mono);
    font-size: var(--font-size-xs);
}

.df-table .df-actions {
    display: flex;
    gap: var(--spacing-1);
    justify-content: flex-end;
}

.df-table__empty td {
    padding: var(--spacing-8) var(--spacing-3);
    text-align: center;
    color: var(--text-tertiary);
}

/* =============================================================================
   df-stat — cifra destacada
   Habia tres disenos distintos de "tarjeta de metrica" (dashboard, costos,
   superadmin). Este es el unico.
   ============================================================================= */

.df-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--spacing-3);
}

.df-stat {
    padding: var(--spacing-3) var(--spacing-4);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    background: var(--bg-primary);
}

.df-stat__label {
    display: block;
    margin-bottom: var(--spacing-1);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--letter-spacing-wide);
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* La cifra manda: es lo unico que se lee de lejos. */
.df-stat__value {
    display: block;
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    line-height: var(--line-height-tight);
    letter-spacing: var(--letter-spacing-tight);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.df-stat__hint {
    display: block;
    margin-top: var(--spacing-1);
    font-size: var(--font-size-xs);
    color: var(--text-tertiary);
}

/* =============================================================================
   df-docubot — DocuBot en el editor
   =============================================================================
   Requisito de producto: DocuBot disponible en TODOS los modos con la misma
   presentacion. En la aplicacion el panel lo estiliza chat-layout.css, pero ese
   fichero no se puede cargar aqui: define `.main-content` con un min-height
   contra la navbar, y en el editor `.main-content` es el area de edicion — la
   romperia.

   Por eso el panel se define aqui, con el MISMO marcado e IDs que en la
   aplicacion (el modulo de chat los busca por id), y TODO bajo
   body[data-surface="editor"] para que no pueda filtrarse a la aplicacion.
   ============================================================================= */

body[data-surface="editor"] .app-container {
    display: flex;
    flex: 1;
    min-height: 0;
    overflow: hidden;
}

body[data-surface="editor"] .app-container > .main-content {
    flex: 1;
    min-width: 0;
    /* Anula cualquier min-height heredado: aqui manda el flex del editor. */
    min-height: 0;
}

body[data-surface="editor"] .chat-panel {
    display: flex;
    flex-direction: column;
    width: 0;
    min-width: 0;
    overflow: hidden;
    background: var(--bg-primary);
    border-left: 1px solid var(--border-primary);
    transition: width var(--transition-slow);
}

body[data-surface="editor"] .app-container[data-chat-open="true"] .chat-panel {
    width: 360px;
    min-width: 280px;
}

body[data-surface="editor"] .chat-header {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    height: var(--df-toolbar-height);
    padding: 0 var(--spacing-2) 0 var(--spacing-3);
    background: var(--bg-secondary);
    border-bottom: 1px solid var(--border-primary);
    flex-shrink: 0;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

body[data-surface="editor"] .chat-header__title {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    min-width: 0;
}

body[data-surface="editor"] .btn-close-chat {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    margin-left: auto;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-secondary);
    cursor: pointer;
}

body[data-surface="editor"] .btn-close-chat:hover {
    background: var(--interactive-hover);
    color: var(--text-primary);
}

body[data-surface="editor"] .chat-messages {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--spacing-3);
    font-size: var(--font-size-sm);
    line-height: var(--line-height-normal);
    color: var(--text-primary);
}

body[data-surface="editor"] .chat-input {
    display: flex;
    align-items: flex-end;
    gap: var(--spacing-2);
    padding: var(--spacing-2) var(--spacing-3);
    border-top: 1px solid var(--border-primary);
    background: var(--bg-secondary);
    flex-shrink: 0;
}

body[data-surface="editor"] .chat-input-wrapper { flex: 1; min-width: 0; }

body[data-surface="editor"] .chat-input textarea {
    width: 100%;
    max-height: 8rem;
    padding: var(--spacing-2);
    border: 1px solid var(--border-secondary);
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    color: var(--text-primary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    resize: none;
}

body[data-surface="editor"] .chat-input textarea:focus {
    outline: none;
    border-color: var(--accent);
}

body[data-surface="editor"] .btn-send {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border: none;
    border-radius: var(--radius-md);
    background: var(--accent);
    color: var(--accent-text);
    cursor: pointer;
    flex-shrink: 0;
}

body[data-surface="editor"] .btn-send:hover { background: var(--accent-hover); }

/* Selector de modo de DocuBot. El modulo de chat lo inyecta en la cabecera del
   panel; en la aplicacion lo estiliza chat-layout.css, que aqui no se carga. */
body[data-surface="editor"] .mode-selector {
    position: relative;
    display: flex;
    align-items: center;
    min-width: 0;
}

body[data-surface="editor"] .mode-selector__trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-1-5);
    height: 26px;
    padding: 0 var(--spacing-2);
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-primary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-semibold);
    white-space: nowrap;
    cursor: pointer;
}

body[data-surface="editor"] .mode-selector__trigger:hover {
    background: var(--interactive-hover);
}

/* Los iconos inyectados vienen sin dimensionar; sin esto un SVG sin width
   se despliega a su tamano intrinseco y tapa el panel. */
body[data-surface="editor"] .mode-selector__trigger svg,
body[data-surface="editor"] .mode-selector__option svg,
body[data-surface="editor"] .chat-header svg {
    width: var(--icon-size-sm);
    height: var(--icon-size-sm);
    flex-shrink: 0;
}

body[data-surface="editor"] .mode-selector__menu {
    position: absolute;
    top: calc(100% + var(--spacing-1));
    left: 0;
    z-index: var(--z-popover);
    min-width: 13rem;
    padding: var(--spacing-1);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    box-shadow: var(--shadow-lg);
}

body[data-surface="editor"] .mode-selector__option {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    width: 100%;
    padding: var(--spacing-1-5) var(--spacing-2);
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-primary);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    text-align: left;
    cursor: pointer;
}

body[data-surface="editor"] .mode-selector__option:hover {
    background: var(--interactive-hover);
}

/* =============================================================================
   Consolidacion de componentes heredados
   =============================================================================
   Estas clases NO llevan prefijo df- porque ya estan en el marcado de decenas
   de sitios. Se definen aqui, en la capa que se carga la ultima, y se BORRA su
   definicion de los ficheros de pagina. Una sola definicion por componente es
   lo que permite quitar los !important: dejan de existir peleas de cascada.
   ============================================================================= */

/* .spinner estaba definido en 9 ficheros con 5 tamanos distintos, y dos de
   ellos usaban var(--primary, #4f46e5) — un token que NO existe, asi que
   giraban en indigo y dorado en vez del color de marca. */
.spinner {
    width: 1.25rem;
    height: 1.25rem;
    border: 2px solid var(--border-primary);
    border-top-color: var(--accent);
    border-radius: var(--radius-full);
    animation: spin 1s linear infinite;
    flex-shrink: 0;
}

/* Dentro de un boton relleno el borde debe leerse sobre el color del boton,
   no sobre el fondo de la pagina. */
.df-button .spinner,
.btn-primary .spinner,
.btn .spinner {
    width: 1rem;
    height: 1rem;
    border-color: rgba(255, 255, 255, 0.35);
    border-top-color: currentColor;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* .loading-container estaba en 7 ficheros. */
.loading-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-3);
    padding: var(--spacing-8);
    color: var(--text-tertiary);
    font-size: var(--font-size-sm);
}

@media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 2s; }
}

/* -----------------------------------------------------------------------------
   Pestanas

   Estaban reimplementadas cuatro veces —style.css, settings.css, tasks.css y
   templates.css— con la misma idea y deriva cosmetica: el subrayado activo de
   2px o 3px, el padding en rem o en tokens, el tamano en 0.875rem / 0.82rem /
   --font-size-sm. Cada copia arrastraba ademas su bloque [data-theme="dark"]
   entero a base de !important.

   Ese bloque oscuro no hacia falta: --accent y --color-primary-500 YA cambian
   de valor en tema oscuro (#2563EB -> #89B4FA). Una definicion con tokens
   semanticos sirve para los dos temas, y por eso aqui no hay ni un !important
   ni un selector [data-theme].
   -------------------------------------------------------------------------- */

.tabs-header {
    display: flex;
    flex-shrink: 0;
    border-bottom: 2px solid var(--border-primary);
}

.tab-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-2);
    padding: var(--spacing-3) var(--spacing-6);
    background: transparent;
    border: none;
    border-bottom: 3px solid transparent;
    /* El subrayado activo tapa el borde del contenedor en vez de sumarse. */
    margin-bottom: -2px;
    /* Secundario, no terciario: una pestana inactiva sigue siendo texto que hay
       que poder leer. En terciario daba 3,15 de contraste sobre la barra
       rellena, por debajo del minimo de 4,5. */
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    cursor: pointer;
    transition: var(--transition-fast);
}

.tab-button:hover {
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.tab-button.active {
    background: var(--bg-primary);
    color: var(--accent);
    border-bottom-color: var(--accent);
}

.tab-button:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: -2px;
}

/* Barra rellena: en Configuracion las pestanas SON la navegacion del panel,
   reparten el ancho y se leen como una barra, no como un filtro. */
.tabs-header--fill {
    background: var(--bg-secondary);
}

.tabs-header--fill .tab-button {
    flex: 1;
    padding: var(--spacing-4) var(--spacing-6);
}

.tab-content {
    display: none;
}

.tab-content.active {
    display: block;
}

/* Contenido en columna que crece y hace scroll dentro del panel: lo necesita
   Configuracion, donde cada pestana es una pagina entera. */
.tab-content--flex.active {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: var(--spacing-4);
}

/* -----------------------------------------------------------------------------
   Controles de formulario

   La misma base (.form-group, su etiqueta, sus campos y el foco) estaba escrita
   cuatro veces: shared-components.css, settings.css, tasks.css y
   superadmin/invoices.css. Cuatro generaciones del mismo control, con el radio
   en 8px o 4px, el padding en tokens o en `0.5rem` y el borde en
   `var(--border-color)`.

   Dos de esas copias arrastraban ademas un bloque [data-theme="dark"] entero,
   diez !important incluidos. Existian por una sola linea: shared-components
   ponia `background: white` literal, que en tema oscuro deja el campo en blanco.
   Con `var(--bg-primary)` el problema desaparece y con el los overrides.

   Lo que sigue en los ficheros de pagina es lo que de verdad es de esa pagina:
   margenes propios, variantes con ambito (.user-form, .fields-container,
   #taskModal) y minimos de altura por numero de filas.
   -------------------------------------------------------------------------- */

.form-group {
    margin-bottom: var(--spacing-4);
}

.form-group.full-width {
    width: 100%;
}

.form-group label {
    display: block;
    margin-bottom: var(--spacing-2);
    font-family: var(--font-family-sans);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
    color: var(--text-primary);
}

.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group input[type="date"],
.form-group input[type="file"],
.form-group textarea,
.form-group select {
    width: 100%;
    padding: var(--spacing-3);
    box-sizing: border-box;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    font-family: inherit;
    font-size: var(--font-size-sm);
    background: var(--bg-primary);
    color: var(--text-primary);
    transition: var(--transition-fast);
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.form-group input::placeholder,
.form-group textarea::placeholder {
    color: var(--text-tertiary);
}

.form-group input[type="file"] {
    padding: var(--spacing-2);
}

.form-group textarea {
    resize: vertical;
    min-height: 4.5rem;
}

/* -----------------------------------------------------------------------------
   Panel maestro-detalle: lo comun a Tareas y Plantillas

   Las dos paginas son la misma estructura (lista a la izquierda, detalle a la
   derecha) y tenian copias literales de estas reglas. No comparten cascada
   —nunca se cargan juntas— asi que la duplicacion solo se quita moviendolas
   aqui. Se han movido unicamente las que eran IDENTICAS en ambas; lo que
   difiere de verdad entre una pagina y otra sigue en su fichero.
   -------------------------------------------------------------------------- */

.readonly-badge {
    display: inline-flex;
    align-items: center;
    color: var(--text-tertiary);
    margin-right: 0.35rem;
}

.readonly-row {
    opacity: 0.85;
}

.markdown-content ul {
    margin: 1rem 0;
    padding-left: 1.5rem;
}

.markdown-content li {
    margin: 0.5rem 0;
}

.markdown-content a {
    color: var(--color-primary-500);
    text-decoration: none;
}

.markdown-content a:hover {
    text-decoration: underline;
}

/* Con DocuBot abierto el detalle se estrecha: la cabecera pasa a columna y los
   botones se aprietan. Lo aplican por igual las dos paginas. */
[data-chat-open="true"] .detail-header {
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
}

[data-chat-open="true"] .detail-actions {
    justify-content: flex-start;
    flex-wrap: wrap;
}

[data-chat-open="true"] .detail-actions .btn {
    padding: 0.4rem 0.6rem;
    font-size: 0.8rem;
}

/* El SVG del distintivo de entorno se tine con filtros porque es una imagen,
   no un icono en linea. */
.environment-badge.development img {
    filter: brightness(0) saturate(100%) invert(38%) sepia(68%) saturate(490%) hue-rotate(26deg) brightness(95%) contrast(88%);
}

/* -----------------------------------------------------------------------------
   Animaciones de latido

   Habia CUATRO `@keyframes pulse` distintos con el mismo nombre —dos en
   tasks.css, uno en settings.css, uno en style.css— y en CSS gana el ultimo que
   se carga. Consecuencia: `.step.active .step-icon` escalaba en Configuracion y
   se desvanecia en Tareas, siendo el mismo indicador; y `.status-processing`,
   que quiere un desvanecido, escalaba en las paginas donde mandaba otro.

   Dos animaciones, dos nombres. Un nombre generico como `pulse` en una hoja
   compartida es una colision esperando a ocurrir.
   -------------------------------------------------------------------------- */

/* Indicador de paso activo: crece y vuelve. */
@keyframes df-pulse-scale {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.1); }
}

/* Estado en curso: respira sin moverse, para no arrastrar la vista. */
@keyframes df-pulse-fade {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.8; }
}

/* El logotipo se sirve en SVG (el juego PNG se veia blando en pantallas de alta
   densidad y no llevaba el degradado de marca). Hacen falta las dos variantes:
   la clara tiene el texto en gris pizarra, que se pierde sobre fondo oscuro. */
.df-toolbar__logo--oscuro { display: none; }

[data-theme="dark"] .df-toolbar__logo--claro { display: none; }
[data-theme="dark"] .df-toolbar__logo--oscuro { display: block; }

@media (prefers-color-scheme: dark) {
    html:not([data-theme="light"]) .df-toolbar__logo--claro { display: none; }
    html:not([data-theme="light"]) .df-toolbar__logo--oscuro { display: block; }
}

/* -----------------------------------------------------------------------------
   El texto de un boton es del boton

   style.css abre con una regla general —`p, span, div { color: var(--text-secondary) }`—
   que pinta de gris CUALQUIER span dentro de CUALQUIER boton, machacando el
   color del propio boton. El sintoma: el boton calcula `color: #fff` y en
   pantalla el texto sale gris pizarra sobre el acento, con un contraste de 1,5.
   Pasaba ya con el azul; el indigo solo lo hizo mas evidente.

   Aqui no se toca esa regla —afecta a media aplicacion y su retirada es una
   decision aparte—, se le devuelve al boton el mando sobre su contenido.
   Especificidad 0,0,2: cualquier regla de clase (un distintivo, un contador)
   sigue ganando, asi que esto solo alcanza a lo que no tenia color propio.
   -------------------------------------------------------------------------- */

button span,
button div,
.btn span,
.btn div,
.df-button span,
.df-button div,
a.btn span,
a.btn div,
/* Mismo problema en las barras: pintan sobre el acento y su texto debe salir
   de `--accent-text`, no del gris general. La regla oscura equivalente
   —`[data-theme="dark"] span`— tiene especificidad 0,1,1, asi que aqui hacen
   falta dos niveles para ganarle. */
.df-statusbar span,
.df-statusbar div,
.df-toolbar span,
.df-toolbar div {
    color: inherit;
}

/* =============================================================================
   df-md — maestro-detalle

   Las tres pantallas de este patron —Plantillas, Parrafos y Tareas— se
   comportaban de tres maneras distintas ante la MISMA interaccion. Al
   seleccionar un registro, Plantillas y Tareas plegaban la lista a 40 px y
   Parrafos no la plegaba. Es decir: en dos de las tres, para volver a la lista
   habia que DESHACER algo que la aplicacion habia hecho sola, y como no pasaba
   en la tercera nunca llegaba a convertirse en costumbre.

   Aqui la lista no desaparece jamas por su cuenta. Se pliega a mano y solo a
   mano, con el boton de la cabecera, y las tres pantallas comparten esta misma
   definicion en lugar de tener cada una la suya.

   La estructura sigue siendo FLEX, no grid, porque `initResizablePanels`
   (main.js) reparte el ancho escribiendo `style.flex` en los dos paneles. Con
   grid, arrastrar el divisor no haria nada.
   ============================================================================= */

:root {
    --df-md-lista-ancho: 340px;
    --df-md-meta-ancho: 280px;
}

/* El alto se descuenta del chrome real —barra superior, barra de estado y el
   relleno de `.main-content`— en vez de con el `100vh - 180px` heredado, que era
   un numero redondo sin origen: dejaba 116 px de hueco muerto bajo el panel. */
.df-md {
    display: flex;
    gap: 0;
    height: calc(100vh - var(--df-toolbar-height) - var(--df-statusbar-height) - 2rem);
    min-height: 500px;
}

/* -- Columna de lista ------------------------------------------------------ */

.df-md__lista {
    flex: 0 0 var(--df-md-lista-ancho);
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
    border-radius: var(--radius-lg) 0 0 var(--radius-lg);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.df-md__lista-cab {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: var(--spacing-3);
    border-bottom: 1px solid var(--border-secondary);
}

/* Segunda fila de cabecera, para los controles que no caben junto a la busqueda.
   En Parrafos son el conmutador lista/arbol y el filtro de entorno: los tres en
   una sola fila dejaban la caja de busqueda en 93 px, donde no cabe ni la
   palabra "parrafos" del marcador de posicion. */
.df-md__lista-cab--secundaria {
    padding-top: 0;
    padding-bottom: var(--spacing-2);
}

/* El separador lo pinta la ULTIMA fila de cabecera, no cada una: dos filas con
   borde dibujarian una raya en medio del propio encabezado. */
.df-md__lista-cab:has(+ .df-md__lista-cab--secundaria) {
    border-bottom: 0;
}

.df-md__lista-cuerpo {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-1);
    padding: var(--spacing-2);
}

.df-md__lista-pie {
    padding: var(--spacing-2) var(--spacing-3);
    border-top: 1px solid var(--border-secondary);
    font: var(--font-caption);
    color: var(--text-tertiary);
}

/* Plegada A MANO. El ancho se fija aqui y no con `style.flex` para que
   `initResizablePanels` no deje un ancho a medias tras replegar. */
.df-md__lista--plegada {
    flex: 0 0 44px;
    min-width: 44px;
}

.df-md__lista--plegada .df-md__lista-cab {
    justify-content: center;
    padding: var(--spacing-2);
}

.df-md__lista--plegada .df-md__lista-cab > :not(.df-md__plegar),
.df-md__lista--plegada .df-md__lista-cuerpo,
.df-md__lista--plegada .df-md__lista-pie {
    display: none;
}

/* 30 px, no 28, por dos razones que apuntan al mismo sitio:
   es la altura del buscador que tiene al lado —estaban 2 px desalineados— y
   deja al icono de 20 px una holgura de 5 px por lado. A 28 px el icono
   quedaba a 4 px del borde y, como este boton SI tiene borde visible, se veia
   lleno de lado a lado. */
.df-md__plegar {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    background: var(--bg-primary);
    color: var(--text-secondary);
    cursor: pointer;
}

.df-md__plegar:hover {
    background: var(--surface-tertiary);
    color: var(--text-primary);
}

/* El icono de este boton lo cambia el JS al plegar y desplegar (galon a la
   izquierda o a la derecha, segun hacia donde va el panel), asi que aqui no se
   rota nada: dos mecanismos para lo mismo acaban peleandose.
   Antes el estado desplegado mostraba una hamburguesa —que significa «menu», no
   «plegar»— y el plegado un galon, o sea que el boton se contradecia a si
   mismo segun el estado. */

/* -- Ficha de lista -------------------------------------------------------- */

/* Sustituye a la fila de tabla. Una tabla obliga a decidir un ancho por columna
   para un contenido que no lo tiene: el nombre de una plantilla mide lo que
   mide, y recortarlo con puntos suspensivos a 150 px era la unica salida. La
   ficha da dos lineas y deja de mentir sobre el ancho. */
.df-ficha {
    padding: var(--spacing-2) var(--spacing-3);
    border: 1px solid transparent;
    border-radius: var(--radius-md);
    cursor: pointer;
    text-align: left;
}

.df-ficha:hover {
    background: var(--surface-tertiary);
}

.df-ficha--sel {
    background: var(--color-primary-50);
    border-color: var(--color-primary-200);
    box-shadow: inset 3px 0 0 var(--accent);
}

.df-ficha__titulo {
    font: var(--font-body-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: var(--spacing-1);
}

.df-ficha__desc {
    font: var(--font-caption);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.df-ficha__meta {
    display: flex;
    align-items: center;
    gap: var(--spacing-1);
    margin-top: var(--spacing-1);
    min-height: 18px;
}

.df-ficha__fecha {
    margin-left: auto;
    font: var(--font-caption);
    color: var(--text-tertiary);
    flex-shrink: 0;
}

/* -- Ficha densa ----------------------------------------------------------- */

/* La ficha de tres lineas funciona con veinte registros y se convierte en un
   muro con doscientos: a 85 px caben SIETE en los 712 px de lista, o sea 27
   pantallas de scroll para un catalogo notarial corriente.
   La densa quita la linea de descripcion —que en la mayoria de los casos
   parafrasea el titulo: «Escritura de prestamo hipotecario» / «Plantilla de
   escritura de prestamo hipotecario»— y deja el titulo entero, sin recortar, y
   una segunda linea con lo que el titulo NO dice: cuanto se usa.
   85 px -> 57 px, y de 7 fichas visibles a 11.

   Es un modificador y no el comportamiento por defecto porque Tareas usa la
   misma ficha y ahi la descripcion si distingue una tarea de otra. */
.df-ficha--densa {
    padding: 6px var(--spacing-3);
}

.df-ficha--densa .df-ficha__titulo {
    /* El nombre completo, en dos lineas si hace falta. Recortarlo escondia
       justo el sufijo que distingue `..._CaixaBank_ESP` de `..._CaixaBank_CAT`. */
    white-space: normal;
    align-items: flex-start;
    line-height: 1.35;
}

.df-ficha--densa .df-ficha__meta {
    margin-top: 2px;
    min-height: 0;
    /* Una sola linea, pase lo que pase. Sin esto la muestra de contenido
       empujaba al distintivo de uso a un segundo renglon y la ficha crecia a
       tres lineas: justo lo que la densa venia a evitar. */
    flex-wrap: nowrap;
}

/* Lo que NO se recorta: dice cuanto se usa y es corto por definicion. */
.df-ficha__uso,
.df-ficha__borrador {
    white-space: nowrap;
    flex-shrink: 0;
}

/* Lo que SI se recorta: la muestra del contenido a la derecha. Es un extra —si
   no cabe entera no se pierde nada, esta el detalle al lado—, asi que es la
   pieza que cede el sitio. */
.df-ficha__muestra {
    margin-left: auto;
    /* Cuando no sobra sitio, `margin-left: auto` no separa nada y la muestra se
       pegaba al distintivo de uso hasta leerse como una sola frase
       («no lo incluye nadie Párrafo creado po…»). */
    padding-left: var(--spacing-3);
    min-width: 0;
    flex-shrink: 1;
    font: var(--font-caption);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* El nombre de un parrafo es un identificador: se escribe tal cual dentro de
   `{{INCLUDE nombre}}`. Se compone como codigo, no como titulo. */
.df-ficha__ident {
    font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.78rem;
    word-break: break-all;
}

/* Cuanto se usa: el dato que decide si una plantilla sobra. Vive donde antes
   estaba la descripcion. */
.df-ficha__uso {
    font: var(--font-caption);
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.df-ficha__uso--nulo {
    color: var(--text-tertiary);
}

/* Etiqueta, en neutro. Agrupar por materia YA dice de que va cada bloque, asi
   que el color aqui solo repetiria informacion. */
.df-ficha__etiqueta {
    font-size: 0.625rem;
    letter-spacing: 0.03em;
    color: var(--text-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    padding: 0 5px;
    line-height: 16px;
    white-space: nowrap;
}

/* Solo se marca la EXCEPCION. El distintivo de entorno salia en las 212 fichas
   y en 198 decia lo mismo: el color mas saturado de la lista marcaba justo lo
   que no distinguia nada. Y «Borrador» es una palabra del oficio; «DEV» es
   vocabulario nuestro. */
/* El color sale de `--color-warning-700`, no de `--warning-color`.
   `--warning-color` es #F59E0B en claro y da 2,15 sobre el panel: por debajo
   del 4,5 que exige AA, y repetido en cada ficha de la lista. El 700 es el
   escalon pensado para TEXTO y se invierte con el tema —#B45309 en claro,
   #FDE68A en oscuro—, asi que funciona en los dos. Medido con
   `docfilling-e2e/tools/contraste.mjs`. */
.df-ficha__borrador {
    font-size: 0.625rem;
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--color-warning-700);
    border: 1px solid var(--color-warning-700);
    border-radius: var(--radius-sm);
    padding: 0 4px;
    line-height: 15px;
    white-space: nowrap;
}

/* -- Cabecera de grupo ------------------------------------------------------ */

/* Se queda pegada arriba: con doscientas plantillas agrupadas por etiqueta, a
   los ochenta scrolls hay que poder saber en que materia estas sin subir. */
.df-md__grupo {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: 7px var(--spacing-3) 5px;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--border-primary);
}

.df-md__grupo-t {
    font: var(--font-overline);
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.df-md__grupo-n {
    margin-left: auto;
    font: var(--font-overline);
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

/* -- Control segmentado ----------------------------------------------------- */

/* Sustituye a los desplegables sueltos de las cabeceras. Un segmentado enseña
   las opciones y cual esta activa sin desplegarse, que es lo que hace falta
   cuando son tres y se cambian a menudo. */
.df-seg {
    display: flex;
    flex: 1;
    min-width: 0;
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.df-seg__b {
    flex: 1;
    min-width: 0;
    height: 26px;
    padding: 0 6px;
    font-size: 0.6875rem;
    line-height: 1;
    color: var(--text-secondary);
    background: var(--bg-primary);
    border: 0;
    border-right: 1px solid var(--border-primary);
    cursor: pointer;
    white-space: nowrap;
}

.df-seg__b:last-child {
    border-right: 0;
}

.df-seg__b:hover {
    background: var(--surface-tertiary);
}

.df-seg__b[aria-pressed="true"] {
    background: var(--surface-tertiary);
    color: var(--text-primary);
    font-weight: var(--font-weight-semibold);
}

/* Filtro suelto que acompaña al segmentado (Borradores / Sin incluir). */
.df-filtro {
    height: 26px;
    padding: 0 8px;
    font-size: 0.6875rem;
    white-space: nowrap;
    color: var(--text-secondary);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.df-filtro:hover {
    background: var(--surface-tertiary);
}

.df-filtro[aria-pressed="true"] {
    background: var(--surface-tertiary);
    color: var(--text-primary);
    border-color: var(--border-secondary);
    font-weight: var(--font-weight-semibold);
}

/* Cabecera de lista en dos filas de 28 px: buscador arriba, agrupacion y filtro
   debajo. Ocupa lo mismo que ocupaba UNA fila de Plantillas (57 px) y 40 px
   menos que las dos de Parrafos, y las dos pantallas se manejan igual. */
.df-md__lista-cab--doble {
    flex-direction: column;
    align-items: stretch;
    gap: 5px;
}

.df-md__lista-cab--doble .df-md__cab-fila {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    min-width: 0;
}

.df-md__lista-cab--doble .df-md__cab-fila > input[type="text"],
.df-md__lista-cab--doble .df-md__cab-fila > input[type="search"] {
    flex: 1;
    min-width: 0;
}

/* Plegada no debe enseñar la segunda fila.
   Y la PRIMERA tiene que seguir viéndose, porque dentro va el botón de
   desplegar. La regla general de plegado oculta los hijos directos de la
   cabecera que no sean el botón; al meter las filas, el botón dejó de ser hijo
   directo y lo que se ocultaba era la fila entera —con el botón dentro—. El
   panel se quedaba plegado sin manera de volver a abrirlo. */
.df-md__lista--plegada .df-md__lista-cab--doble > .df-md__cab-fila:first-child {
    display: flex;
    justify-content: center;
}

.df-md__lista--plegada .df-md__lista-cab--doble > .df-md__cab-fila:not(:first-child),
.df-md__lista--plegada .df-md__lista-cab--doble .df-md__cab-fila > :not(.df-md__plegar) {
    display: none;
}

/* -- Panel de detalle ------------------------------------------------------ */

.df-md__detalle {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--bg-primary);
    border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.df-md__cab {
    display: flex;
    align-items: center;
    gap: var(--spacing-3);
    flex-wrap: wrap;
    padding: var(--spacing-3) var(--spacing-4);
    border-bottom: 1px solid var(--border-primary);
}

.df-md__titulo {
    margin: 0;
    font: var(--font-heading-6);
    color: var(--text-primary);
    min-width: 0;
    word-break: break-word;
}

/* La jerarquia esta en el orden, no en el color: la accion principal va
   destacada y el resto plano. Eliminar sale de aqui y se va al menu de
   desbordamiento; tenia el mismo peso visual que Descargar. */
.df-md__acciones {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    flex-wrap: wrap;
}

/* Contenido a la izquierda, metadatos en la columna estrecha. `align-content`
   es lo que impide que los paneles se estiren hasta el fondo de la ventana:
   miden lo que mide su contenido. */
/* El relleno vive en CADA columna, no en el contenedor, para que la columna
   estrecha pueda llevar su propio fondo hasta el borde del panel en vez de
   flotar dentro de un margen. */
.df-md__cuerpo {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    display: grid;
    grid-template-columns: minmax(0, 1fr) var(--df-md-meta-ancho);
    align-content: start;
    gap: 0;
    padding: 0;
}

.df-md__cuerpo > :not(.df-md__meta) {
    min-width: 0;
    padding: var(--spacing-4);
}

/* Sin columna de metadatos (pestanas que ocupan todo el ancho). */
.df-md__cuerpo--completo {
    grid-template-columns: minmax(0, 1fr);
}

/* El cuerpo es a la vez el panel de una pestana. `.tab-content` lo oculta con
   `display: none` y lo muestra con `display: block`, que aqui destruiria la
   rejilla de dos columnas. Con las dos clases juntas la especificidad es 0,2,0
   y gana sobre ambas, asi que la pestana inactiva se oculta y la activa vuelve
   a ser rejilla. Mismo apano que `.tab-content--flex`, unas lineas mas arriba. */
.tab-content.df-md__cuerpo {
    display: none;
}

.tab-content.df-md__cuerpo.active {
    display: grid;
}

/* La columna de consulta lleva su propio fondo.
   Sin el, las dos columnas eran la misma superficie y la estrecha quedaba
   flotando: nada decia donde acaba lo que se lee y empieza lo que se consulta,
   y el peso visual se iba entero a la izquierda. Con superficie propia y su
   linea de separacion, el detalle queda equilibrado.

   Se estira hasta el alto de la FILA —lo que mida la columna ancha—, no hasta
   el fondo de la ventana: `align-content: start` en la rejilla sigue mandando,
   asi que un detalle corto no dibuja una banda vacia de 600 px. */
.df-md__meta {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-3);
    padding: var(--spacing-4);
    background: var(--surface-secondary);
    border-left: 1px solid var(--border-primary);
}

.df-md__dato {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-1);
    min-width: 0;
}

.df-md__dato dt,
.df-md__dato-etiqueta {
    font: var(--font-overline);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.df-md__dato dd,
.df-md__dato-valor {
    margin: 0;
    font: var(--font-body-sm);
    color: var(--text-primary);
    word-break: break-word;
}

.df-md__dato-valor--vacio {
    color: var(--text-tertiary);
    font-style: italic;
}

/* -- Piezas del detalle ----------------------------------------------------- */

/* La columna ancha deja de ser un parrafo suelto y pasa a tener secciones. Lo
   que define una plantilla —de que campos esta hecha, que parrafos incluye— no
   cabia en «Descripcion» y estaba escondido en otra pestaña o directamente en
   el editor. */
.df-seccion {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2);
    margin-bottom: var(--spacing-5);
}

.df-seccion:last-child {
    margin-bottom: 0;
}

.df-seccion__t {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-2);
    font: var(--font-overline);
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.df-seccion__n {
    font-variant-numeric: tabular-nums;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-tertiary);
}

.df-seccion__vacia {
    font: var(--font-body-sm);
    color: var(--text-tertiary);
}

/* Linea copiable. La directiva INCLUDE existe para pegarla en una plantilla:
   se partia en tres lineas dentro de la columna de 280 px y no habia forma de
   copiarla sin seleccionarla a mano. */
.df-copia {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: var(--spacing-2);
    background: var(--surface-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    min-width: 0;
}

.df-copia__txt {
    flex: 1;
    min-width: 0;
    font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.78rem;
    color: var(--text-primary);
    word-break: break-all;
    line-height: 1.45;
}

.df-copia__b {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 24px;
    padding: 0 8px;
    font-size: 0.6875rem;
    color: var(--text-secondary);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    cursor: pointer;
}

.df-copia__b:hover {
    color: var(--text-primary);
    background: var(--surface-tertiary);
}

/* Campos de la plantilla. Dos columnas: el nombre —tal y como se escribe— y
   quien lo rellena, que es la pregunta que se hace quien la mantiene. */
.df-campos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 1px;
    background: var(--border-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.df-campo {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-2);
    padding: 5px var(--spacing-2);
    background: var(--bg-primary);
    min-width: 0;
}

.df-campo__n {
    font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 0.75rem;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.df-campo__q {
    margin-left: auto;
    flex-shrink: 0;
    font-size: 0.625rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* Quien rellena: se distingue con una palabra, no con un color. El unico que
   lleva senal cromatica es el que necesita una persona, porque es el que puede
   dejar una tarea parada esperando. */
.df-campo__q--persona {
    color: var(--warning-color);
    font-weight: var(--font-weight-semibold);
}

/* Etiquetas del detalle: se ven y se editan en el mismo sitio. Ahora que la
   lista se agrupa por etiqueta, ponerlas es una accion frecuente y no puede
   seguir escondida en el menu de desbordamiento. */
.df-etiquetas {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--spacing-1);
}

.df-etiqueta {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.6875rem;
    color: var(--text-secondary);
    background: var(--surface-tertiary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    padding: 1px 6px;
}

.df-etiqueta__punto {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
}

.df-etiquetas__mas {
    font-size: 0.6875rem;
    color: var(--text-secondary);
    background: transparent;
    border: 1px dashed var(--border-secondary);
    border-radius: var(--radius-sm);
    padding: 1px 7px;
    cursor: pointer;
}

.df-etiquetas__mas:hover {
    color: var(--text-primary);
    border-color: var(--accent);
}

/* -- Panel vacio ------------------------------------------------------------ */

/* Deja de ser un anuncio. El texto que explica que es una plantilla ayuda la
   primera vez; a partir de la segunda ocupa media pantalla para no decir nada.
   En su lugar, lo que se ha tocado y lo que conviene mirar. */
.df-vacio {
    padding: var(--spacing-5);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-5);
    max-width: 640px;
}

.df-vacio__t {
    font: var(--font-body-sm);
    color: var(--text-tertiary);
    margin: 0;
}

.df-vacio__l {
    display: flex;
    flex-direction: column;
}

.df-vacio__i {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-3);
    padding: 7px 0;
    border-bottom: 1px solid var(--border-primary);
    font: var(--font-body-sm);
    color: var(--text-secondary);
    text-align: left;
    background: none;
    border-left: 0;
    border-right: 0;
    border-top: 0;
    width: 100%;
    cursor: pointer;
}

.df-vacio__i:last-child {
    border-bottom: 0;
}

.df-vacio__i:hover {
    color: var(--text-primary);
}

.df-vacio__i > span:last-child {
    margin-left: auto;
    color: var(--text-tertiary);
    flex-shrink: 0;
}

.df-vacio__pie {
    font: var(--font-body-sm);
    color: var(--text-secondary);
    margin: 0;
}

/* Sin esta regla el enlace se quedaba con el azul POR DEFECTO del navegador
   —#0000EE—, que sobre el panel oscuro da 1,89. No lo alcanzaba ninguna regla
   global de `a`, y a simple vista parecia un enlace normal. */
.df-vacio__pie a,
.df-vacio a {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.df-vacio__pie a:hover,
.df-vacio a:hover {
    color: var(--accent-hover);
}

/* Bajo 1100 px la columna estrecha deja de caber al lado y pasa debajo. Ahi la
   linea de separacion tiene que ser horizontal: una `border-left` en un bloque
   a todo el ancho no separa nada, solo pinta una raya suelta a la izquierda. */
@media (max-width: 1100px) {
    .df-md__cuerpo {
        grid-template-columns: minmax(0, 1fr);
    }

    .df-md__meta {
        border-left: 0;
        border-top: 1px solid var(--border-primary);
    }
}

/* En la columna estrecha los campos van en una sola columna: la rejilla de
   `minmax(240px, 1fr)` no cabe en 280 px y dejaba una fila por campo igual,
   pero con el nombre recortado a la mitad. */
.df-md__meta .df-campos {
    grid-template-columns: minmax(0, 1fr);
    /* Y con su propio desplazamiento. Una plantilla corriente declara treinta
       campos: sin tope, la lista empujaba el uso, las etiquetas y las fechas
       tan abajo que dejaban de existir. La lista es larga por naturaleza; el
       resto de la columna no puede pagarlo. */
    max-height: 42vh;
    overflow-y: auto;
}

/* El titulo y su recuento no caben en la misma linea a 280 px: se apilan. */
.df-md__meta .df-seccion__t {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
}

/* Campo pulsable: lleva a su sitio en la vista previa. */
.df-campo--enlace {
    cursor: pointer;
    width: 100%;
    text-align: left;
    border: 0;
    font: inherit;
}

.df-campo--enlace:hover {
    background: var(--surface-tertiary);
}

.df-campo--enlace:hover .df-campo__n {
    color: var(--accent);
}

/* Destello al saltar a un campo desde la lista. Sin esto el salto deja el campo
   en pantalla pero no dice CUAL de los treinta es. */
@keyframes df-destello {
    0%   { background: var(--accent); color: var(--accent-text); }
    100% { background: transparent; color: inherit; }
}

.df-campo-destacado {
    animation: df-destello 1.4s ease-out;
    border-radius: var(--radius-sm);
}

@media (prefers-reduced-motion: reduce) {
    .df-campo-destacado {
        animation: none;
        outline: 2px solid var(--accent);
        outline-offset: 2px;
    }
}

/* Bajo 900 px no hay sitio para dos paneles: la lista pasa a ocupar el ancho
   completo y el detalle debajo. */
@media (max-width: 900px) {
    .df-md {
        flex-direction: column;
        height: auto;
    }

    .df-md__lista,
    .df-md__detalle {
        flex: 1 1 auto;
        border-radius: var(--radius-lg);
    }

    .df-md__lista-cuerpo {
        max-height: 40vh;
    }
}

/* =============================================================================
   df-menu — menu de desbordamiento (···)

   Existe para dar sitio a las acciones que NO deben competir con la principal.
   Eliminar es el caso claro: es irreversible y estaba en la misma fila, con el
   mismo tamano, que Descargar.
   ============================================================================= */

.df-menu {
    position: relative;
    display: inline-flex;
}

.df-menu__lista {
    position: absolute;
    top: calc(100% + var(--spacing-1));
    right: 0;
    z-index: 50;
    min-width: 190px;
    display: none;
    flex-direction: column;
    padding: var(--spacing-1);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
}

.df-menu--abierto .df-menu__lista {
    display: flex;
}

.df-menu__opcion {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    width: 100%;
    padding: var(--spacing-2) var(--spacing-3);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-primary);
    font: var(--font-body-sm);
    text-align: left;
    cursor: pointer;
}

.df-menu__opcion:hover {
    background: var(--surface-tertiary);
}

.df-menu__opcion--peligro {
    /* 700, no 600. En claro `--color-error-600` (#DC2626) va bien sobre blanco,
       pero en oscuro no esta redefinido y se queda en el mismo rojo oscuro sobre
       un fondo casi negro: 3,3 de contraste. La escala 700 SI cambia de lado en
       cada tema (#B91C1C claro / #FECACA oscuro) y funciona en los dos. */
    color: var(--color-error-700);
}

.df-menu__opcion--peligro:hover {
    background: var(--color-error-50);
    color: var(--color-error-800);
}

.df-menu__separador {
    height: 1px;
    margin: var(--spacing-1) 0;
    background: var(--border-secondary);
}

/* Contador dentro de una pestana. Iba como `(<span>1</span>)` y los saltos de
   linea de la plantilla se colaban dentro de los parentesis: en pantalla se leia
   "Tareas asociadas ( 1 )". Sin parentesis no hay nada que separar. */
.tab-button__cuenta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    padding: 0 var(--spacing-1);
    border-radius: var(--radius-full);
    background: var(--surface-tertiary);
    color: var(--text-secondary);
    font: var(--font-caption);
}

/* Disparador del menu de desbordamiento: cuadrado, para que no compita en
   tamano con las acciones que llevan texto. */
.btn.df-menu__disparador {
    padding: 0.4rem 0.5rem;
}

/* Enlace dentro de un panel de detalle. Se escribia inline como
   `style="color: #007bff; text-decoration: underline"`: un azul literal que
   sobrevivio al cambio de paleta y que en tema oscuro quedaba a 2,9 de
   contraste. */
.df-enlace {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.df-enlace:hover {
    text-decoration-thickness: 2px;
}

/* Mensaje de carga o de lista vacia dentro de la columna de lista. Sustituye a
   los `<td colspan="4" style="text-align:center;padding:2rem;color:#666">`
   —con el gris literal— que cada pantalla repetia por su cuenta. */
.df-md__cargando {
    padding: var(--spacing-8) var(--spacing-4);
    text-align: center;
    color: var(--text-tertiary);
    font: var(--font-body-sm);
}

/* Un distintivo dentro de la columna de metadatos mide lo que mide su texto.
   `.df-md__dato` es una columna flex, asi que sus hijos se estiran a lo ancho
   por defecto: el estado de una tarea salia como una barra verde de 280 px. */
.df-md__dato > .environment-badge,
.df-md__dato > .task-status,
.df-md__dato > .df-badge {
    align-self: start;
}

/* Titulo de seccion dentro del cuerpo de detalle: versalitas grises, igual que
   los rotulos de la columna de metadatos. Cada pantalla lo componia a su
   manera —una en negrita de 0,9rem, otra de 0,85— para decir lo mismo. */
.df-md__cuerpo h4 {
    margin: 0 0 var(--spacing-2);
    font: var(--font-overline);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* =============================================================================
   df-inicio — pantalla de entrada del modo Operador

   Sustituye al aterrizaje en /dashboard, que eran cuatro contadores a cero y
   una caja vacia: ni decia que es DocFilling, ni que hacer, ni tenia nada que
   pulsar. El orden de la pantalla responde a las tres preguntas de quien entra
   a trabajar: ¿puedo empezar ya?, ¿que tengo a medias?, ¿que esta listo?

   Vive en la capa de componentes y no en un fichero de pagina porque reutiliza
   `.df-ficha` del maestro-detalle: la ficha de un documento es la misma cosa
   aqui y en la lista de Tareas.
   ============================================================================= */

.df-inicio {
    max-width: 1080px;
    margin: 0 auto;
    padding: var(--spacing-6) var(--spacing-4) var(--spacing-12);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-6);
}

.df-inicio__saludo {
    margin: 0;
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.df-inicio__resumen {
    margin: var(--spacing-1) 0 0;
    color: var(--text-secondary);
}

/* -- DocuBot como puerta de entrada ---------------------------------------- */

.df-bot {
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
    padding: var(--spacing-4);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-3);
}

.df-bot__cab {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
}

.df-bot__t {
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

.df-bot__ic {
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--radius-sm);
    background: var(--color-primary-50);
    color: var(--accent);
    display: grid;
    place-items: center;
}

.df-bot__caja {
    display: flex;
    gap: var(--spacing-2);
    align-items: center;
}

.df-bot__in {
    flex: 1;
    min-width: 0;
    padding: var(--spacing-3);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-secondary);
    color: var(--text-primary);
    font: var(--font-body);
}

.df-bot__in:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: var(--shadow-focus);
}

.df-bot__in:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.df-bot__ej {
    display: flex;
    gap: var(--spacing-2);
    flex-wrap: wrap;
    align-items: center;
}

.df-inicio__ej-t {
    font: var(--font-caption);
    color: var(--text-tertiary);
}

/* Los ejemplos salen de las plantillas reales de la organizacion: pulsar uno
   lleva a algo que existe, no a un texto de escaparate. */
.df-inicio__ej {
    padding: var(--spacing-1) var(--spacing-3);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-full);
    background: var(--bg-primary);
    color: var(--text-secondary);
    font: var(--font-caption);
    cursor: pointer;
}

.df-inicio__ej:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.df-inicio__aviso {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: var(--spacing-2) var(--spacing-3);
    border-radius: var(--radius-md);
    background: var(--color-warning-50);
    border: 1px solid var(--color-warning-200);
    color: var(--color-warning-800);
    font: var(--font-body-sm);
}

/* -- Bloques --------------------------------------------------------------- */

.df-inicio__bl {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-2);
}

/* `hidden` lo pinta el navegador con `display: none`, pero esa regla es de la
   hoja del agente de usuario y cualquier selector de clase le gana. Sin esto,
   `.df-inicio__bl { display: flex }` dejaba visibles los bloques ocultos: se
   veian los rotulos «Sigue donde lo dejaste» y «Como funciona» con nada
   debajo. */
.df-inicio__bl[hidden],
.df-inicio__aviso[hidden] {
    display: none;
}

.df-inicio__bl-cab {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-2);
}

.df-inicio__bl-t {
    margin: 0;
    font: var(--font-overline);
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.df-inicio__bl-n {
    font: var(--font-caption);
    color: var(--text-tertiary);
}

.df-inicio__mas {
    margin-left: auto;
    font: var(--font-body-sm);
    color: var(--accent);
    text-decoration: none;
}

.df-inicio__rej {
    display: grid;
    gap: var(--spacing-2);
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

.df-inicio__rej--pl {
    grid-template-columns: repeat(5, minmax(0, 1fr));
}

/* La ficha de documento reutiliza `.df-ficha`; aqui solo se le da el marco,
   que en la lista lo pone la columna y aqui no hay columna. */
.df-inicio__tar {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-primary);
    background: var(--bg-primary);
    text-decoration: none;
}

.df-inicio__tar:hover {
    border-color: var(--accent);
    background: var(--bg-primary);
}

.df-inicio__chip {
    font: var(--font-overline);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 0.1rem var(--spacing-2);
    border-radius: var(--radius-sm);
}

.df-inicio__chip--ok { background: var(--color-success-100); color: var(--color-success-700); }
.df-inicio__chip--curso { background: var(--color-info-100); color: var(--color-info-700); }
.df-inicio__chip--espera { background: var(--color-warning-100); color: var(--color-warning-700); }

.df-inicio__descargar {
    font: var(--font-caption);
    color: var(--accent);
}

/* -- Plantillas mas usadas ------------------------------------------------- */

.df-inicio__pl {
    display: flex;
    gap: var(--spacing-3);
    align-items: flex-start;
    padding: var(--spacing-3);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
}

.df-inicio__pl:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-sm);
}

.df-inicio__pl--otras {
    border-style: dashed;
}

.df-inicio__pl-ic {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background: var(--color-primary-50);
    color: var(--accent);
    display: grid;
    place-items: center;
}

.df-inicio__pl--otras .df-inicio__pl-ic {
    background: var(--surface-tertiary);
    color: var(--text-secondary);
}

.df-inicio__pl-t {
    display: block;
    font: var(--font-body-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
    line-height: 1.3;
}

.df-inicio__pl-uso {
    display: block;
    font: var(--font-caption);
    color: var(--text-tertiary);
    margin-top: var(--spacing-1);
}

/* -- Como funciona (solo el primer dia) ------------------------------------ */

.df-inicio__como {
    display: grid;
    gap: var(--spacing-4);
    grid-template-columns: repeat(3, 1fr);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    padding: var(--spacing-4);
}

.df-inicio__paso {
    display: flex;
    gap: var(--spacing-3);
    align-items: flex-start;
}

.df-inicio__paso-n {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: var(--radius-full);
    background: var(--color-primary-50);
    color: var(--accent);
    font: var(--font-caption);
    font-weight: var(--font-weight-bold);
    display: grid;
    place-items: center;
}

.df-inicio__paso-t {
    display: block;
    font: var(--font-body-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

.df-inicio__paso-d {
    display: block;
    font: var(--font-caption);
    color: var(--text-secondary);
    margin-top: var(--spacing-1);
}

@media (max-width: 900px) {
    .df-inicio__rej--pl { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .df-inicio__como { grid-template-columns: 1fr; }
}

/* Boton principal deshabilitado.

   El estilo heredado lo resuelve con `opacity: .5`, que sobre el acento deja el
   texto a 1,47 de contraste: ilegible. Y es justo el momento en que hay que
   poder leerlo —«Empezar» desactivado porque la IA no responde—: si no se lee,
   no se entiende que es lo que no esta disponible.

   Un control desactivado queda fuera de la exigencia de AA, pero eso permite
   que sea gris, no que sea invisible. Se le da superficie propia en vez de
   translucidez: se ve apagado y se lee. */
.btn.btn-primary:disabled,
.btn.btn-primary[disabled],
.df-button--primary:disabled {
    background: var(--surface-tertiary);
    border-color: var(--border-primary);
    /* --text-secondary, no --text-tertiary: el terciario sobre esta superficie
       da 4,08 en claro y 4,06 en oscuro, ambos por debajo de AA. El secundario
       da 6,90 y 7,00. Sigue leyendose apagado y ademas se lee. */
    color: var(--text-secondary);
    box-shadow: none;
    opacity: 1;
}

/* =============================================================================
   df-catalogo — catalogo navegable de plantillas

   Lo que abre «Otras plantillas» del inicio. Alli se enseñan cuatro; aqui estan
   todas, con buscador siempre y filtros por etiqueta cuando las hay.
   ============================================================================= */

.df-catalogo {
    max-width: 1080px;
    margin: 0 auto;
    padding: var(--spacing-6) var(--spacing-4) var(--spacing-12);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-4);
}

.df-catalogo__cab {
    display: flex;
    align-items: flex-start;
    gap: var(--spacing-4);
    flex-wrap: wrap;
}

.df-catalogo__cab > :last-child {
    margin-left: auto;
}

.df-catalogo__t {
    margin: 0;
    font-size: var(--font-size-2xl);
    font-weight: var(--font-weight-semibold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.df-catalogo__sub {
    margin: var(--spacing-1) 0 0;
    color: var(--text-secondary);
}

.df-catalogo__busca {
    display: flex;
    align-items: center;
    gap: var(--spacing-2);
    padding: var(--spacing-2) var(--spacing-3);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    background: var(--bg-primary);
    color: var(--text-tertiary);
    max-width: 420px;
}

.df-catalogo__busca:focus-within {
    border-color: var(--accent);
    box-shadow: var(--shadow-focus);
}

.df-catalogo__busca input {
    flex: 1;
    min-width: 0;
    border: 0;
    background: transparent;
    color: var(--text-primary);
    font: var(--font-body-sm);
}

.df-catalogo__busca input:focus {
    outline: none;
}

.df-catalogo__filtros {
    display: flex;
    gap: var(--spacing-2);
    flex-wrap: wrap;
}

.df-catalogo__filtros[hidden] {
    display: none;
}

.df-catalogo__filtro {
    padding: var(--spacing-1) var(--spacing-3);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-full);
    background: var(--bg-primary);
    color: var(--text-secondary);
    font: var(--font-body-sm);
    cursor: pointer;
}

.df-catalogo__filtro span {
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

.df-catalogo__filtro:hover {
    border-color: var(--accent);
}

.df-catalogo__filtro--on {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-text);
    font-weight: var(--font-weight-semibold);
}

.df-catalogo__filtro--on span {
    color: var(--accent-text);
}

.df-catalogo__cuerpo {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-6);
}

.df-catalogo__grupo-t {
    margin: 0 0 var(--spacing-2);
    font: var(--font-overline);
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    display: flex;
    align-items: baseline;
    gap: var(--spacing-2);
}

.df-catalogo__grupo-n {
    font-variant-numeric: tabular-nums;
}

.df-catalogo__rej {
    display: grid;
    gap: var(--spacing-2);
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

.df-catalogo__pl {
    display: flex;
    gap: var(--spacing-3);
    align-items: flex-start;
    padding: var(--spacing-3);
    background: var(--bg-primary);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-md);
    text-decoration: none;
    color: inherit;
}

.df-catalogo__pl:hover {
    border-color: var(--accent);
    box-shadow: var(--shadow-sm);
}

/* Lo que ya usas se reconoce sin leer: un filo del acento, no un color de
   fondo, que competiria con las etiquetas. */
.df-catalogo__pl--usada {
    box-shadow: inset 3px 0 0 var(--accent);
}

.df-catalogo__pl-ic {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--radius-md);
    background: var(--color-primary-50);
    color: var(--accent);
    display: grid;
    place-items: center;
}

.df-catalogo__pl-txt {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-1);
}

.df-catalogo__pl-t {
    font: var(--font-body-sm);
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
    line-height: 1.3;
}

.df-catalogo__pl-d {
    font: var(--font-caption);
    color: var(--text-secondary);
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.df-catalogo__pl-uso {
    font: var(--font-caption);
    color: var(--text-tertiary);
}

.df-catalogo__etis {
    display: flex;
    gap: var(--spacing-1);
    flex-wrap: wrap;
    margin-top: var(--spacing-1);
}

/* Etiqueta del catalogo: neutra, con el color de la etiqueta reducido a un
   punto.
   Antes el color elegido por el usuario iba de FONDO y el texto lo fijaba el
   tema (`--text-inverse`). Esa pareja no se puede garantizar: el fondo es
   arbitrario y el texto no, asi que el contraste depende de que nadie elija un
   color desafortunado. Medido daba 3,67 en oscuro con el gris por defecto —por
   debajo de AA— y fallaria igual en claro con cualquier etiqueta clara.
   Con el punto, el color sigue distinguiendo y el texto siempre se lee. Es
   ademas como ya se pintan las etiquetas en el detalle (`.df-etiqueta`) y en
   las listas (`.df-ficha__etiqueta`): tres sitios, un solo aspecto. */
.df-catalogo__eti {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font: var(--font-overline);
    padding: 0.1rem var(--spacing-2);
    border: 1px solid var(--border-primary);
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    background: var(--surface-tertiary);
}

.df-catalogo__eti::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--eti-color, var(--text-tertiary));
    flex-shrink: 0;
}

.df-catalogo__vacio {
    padding: var(--spacing-8);
    text-align: center;
    border: 1px dashed var(--border-primary);
    border-radius: var(--radius-md);
}

.df-catalogo__vacio[hidden] {
    display: none;
}

.df-catalogo__vacio-t {
    margin: 0;
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
}

.df-catalogo__vacio-d {
    margin: var(--spacing-1) 0 0;
    color: var(--text-secondary);
    font: var(--font-body-sm);
}
