/* Paleta e identidad visual alineadas con websdirect.es */
:root {
    --wd-navy: #0d1b2a;
    --wd-navy-2: #162436;
    --wd-teal: #00a896;
    --wd-teal-dark: #007a6c;
    --wd-teal-light: #e0f5f3;
    --wd-coral: #ff6b35;
    /* El coral de la marca no sirve tal cual para un botón con texto
       blanco: da 2,8:1 de contraste y la WCAG pide 4,5:1 para texto
       normal (1.4.3). Estos dos son el mismo color bajado de luminosidad
       —el tono es el de --wd-coral, 16°— hasta que el blanco encima se
       lee: 4,6:1 el de fondo y 6,3:1 el del hover, que además tiene que
       ser más oscuro que el primero para que se note el ratón encima.
       Misma historia que --wd-amber-dark con el botón de avisos.

       --wd-coral se queda como está y sigue siendo el de la marca: lo
       usa el globo del contador de avisos, que no se toca aquí. */
    --wd-coral-dark: #cc4a17;
    --wd-coral-darker: #a83c12;
    --wd-gray-900: #1c2128;
    --wd-gray-700: #374151;
    --wd-gray-600: #5c6370;
    --wd-gray-400: #9ba3ae;
    --wd-gray-200: #eaecef;
    --wd-gray-50: #f8f9fa;
    --wd-amber-dark: #a67c00;

    --wd-sidebar-width: 260px;
    --wd-sidebar-width-collapsed: 64px;

    /* El azul del sidebar, que es SOLO suyo: --wd-navy sigue siendo el
       de la marca (el degradado y el logotipo de la pantalla de login) y
       no se toca. Antes el sidebar usaba ese mismo --wd-navy con dos
       tonos más para el hover y el activo (--wd-navy-mid y --wd-navy-3),
       que no se usaban en ningún otro sitio; se han quitado de la paleta
       y viven aquí, porque un tono que solo vale para un componente en
       la paleta general es un tono que alguien acaba usando en otro
       lado.

       Los otros dos no son decorativos: sobre este fondo, el hover de
       antes (#1a2e45) es prácticamente el mismo color y el menú se
       quedaría sin señal de "estoy encima de esto". Guardan con el fondo
       la misma distancia que guardaban con el azul anterior. */
    --wd-sidebar-bg: #182e46;
    --wd-sidebar-hover: #254161;
    --wd-sidebar-active: #294364;
    /* La serif de la casa: los títulos del contenido (h1, h2, h3), la
       marca del sidebar y la de la pantalla de login. Georgia queda
       detrás como respaldo y no como la de antes: Cambria viene con
       Windows y con Office, así que en un equipo sin ninguno de los dos
       —un Mac sin Office, un Linux— haría falta algo con la misma pinta
       antes de caer en la serif del sistema. */
    --wd-font-serif: Cambria, Georgia, "Times New Roman", serif;
    --wd-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
}

/* Tamaño de la letra de todo el CRM: un 20% menos que el del navegador
   (12,8px en vez de los 16 de serie), para que quepa más en pantalla.
   Se toca la raíz y no el body porque los títulos, las chapas, los
   botones y las utilidades tipo .small llevan su tamaño en rem y no
   heredan del body: se quedarían como estaban. Bajando la raíz baja todo
   a la vez y en proporción, incluidos los espacios interiores de
   Bootstrap, que también van en rem.

   Los breakpoints van en px, así que el responsive no cambia. Lo que sí
   se queda con su medida son las cuatro cosas fijadas en px de esta hoja
   (el ancho del sidebar, el alto de la barra de arriba, las columnas del
   tablero y los avatares): se ven algo más holgadas respecto al texto, y
   ahí es donde toca ajustar si hace falta.

   En :root y no en html —que es el mismo elemento— para ganarle a la
   regla que Bootstrap puede traer ahí: con la misma especificidad manda
   esta hoja, que se carga después, pero un `html` a secas pierde contra
   un `:root`. */
:root {
    font-size: 80%;
}

body {
    background-color: var(--wd-gray-50);
    color: var(--wd-gray-900);
    font-family: var(--wd-font-sans);
}

#wrapper {
    min-height: 100vh;
}

/* Sidebar
   NOTA: Bootstrap fuerza `background-color: transparent !important` en
   `.offcanvas-lg` a partir del breakpoint lg (para que el offcanvas se
   comporte como un sidebar estático empotrado). Por eso el color de fondo
   aquí también necesita !important: son ambas reglas de igual
   especificidad y esta hoja se carga después de la de Bootstrap, así que
   gana la cascada, pero sin !important perdería directamente frente a la
   regla de Bootstrap. */
.wd-sidebar {
    width: var(--wd-sidebar-width);
    /* Sin flex-shrink:0, en pantallas donde el contenido central pide más
       ancho que el hueco disponible (el tablero del funnel, con sus
       columnas de 400px cada una) flexbox reparte el hueco que falta
       encogiendo TAMBIÉN al sidebar —flex-shrink es 1 de serie—, y se
       queda más estrecho de lo que dice su `width`. Es la razón de que
       el sidebar se viera de un ancho en el funnel y de otro en
       Usuarios, cuya tabla no llega a pedir tanto. */
    flex-shrink: 0;
    background-color: var(--wd-sidebar-bg) !important;
    color: rgba(255, 255, 255, 0.75);
    transition: width 0.15s ease;
}

.wd-sidebar .wd-brand {
    color: #fff;
    font-family: var(--wd-font-serif);
    font-weight: 700;
    font-size: 1.15rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

/* El logotipo (280×59: la W de la marca y a su derecha el texto). El
   envoltorio recorta por ancho, que es de lo que se vale el modo plegado
   más abajo para dejar a la vista solo la W. */
.wd-sidebar .wd-brand-logo {
    display: block;
    overflow: hidden;
    line-height: 0;
}

.wd-sidebar .wd-brand-logo img {
    display: block;
    height: 34px;
    width: auto;
}

.wd-sidebar .wd-section-title {
    text-transform: uppercase;
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    color: var(--wd-gray-400);
    padding: 1rem 1.25rem 0.35rem;
}

.wd-sidebar .nav-link {
    color: rgba(255, 255, 255, 0.75);
    padding: 0.55rem 1.25rem;
    border-radius: 0;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 0.925rem;
}

.wd-sidebar .nav-link i {
    font-size: 1.05rem;
    width: 1.25rem;
    text-align: center;
    flex-shrink: 0;
}

.wd-sidebar .nav-link:hover {
    background-color: var(--wd-sidebar-hover);
    color: #fff;
}

.wd-sidebar .nav-link.active {
    background-color: var(--wd-sidebar-active);
    color: #fff;
    border-left: 3px solid var(--wd-teal);
    padding-left: calc(1.25rem - 3px);
}

/* El botón que pliega y despliega el sidebar (ver assets/js/sidebar.js):
   se pinta como un enlace más del menú, fijo al fondo, para que plegado
   quede exactamente igual de ancho que los iconos de encima. */
.wd-sidebar-toggle {
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    flex-shrink: 0;
    background: transparent;
    border: 0;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    color: rgba(255, 255, 255, 0.75);
    padding: 0.55rem 1.25rem;
    font-size: 0.925rem;
    text-align: left;
}

.wd-sidebar-toggle:hover {
    background-color: var(--wd-sidebar-hover);
    color: #fff;
}

.wd-sidebar-toggle i {
    font-size: 1.05rem;
    width: 1.25rem;
    text-align: center;
    flex-shrink: 0;
}

/* Plegado a solo iconos (escritorio; ver el toggle d-lg-flex del botón:
   en móvil el sidebar es un offcanvas completo y esto no aplica). La
   clase la pone el servidor de entrada, según la preferencia guardada
   (layouts/main.php), y assets/js/sidebar.js la alterna al vuelo. */
@media (min-width: 992px) {
    #wrapper.wd-sidebar-collapsed .wd-sidebar {
        width: var(--wd-sidebar-width-collapsed);
    }

    #wrapper.wd-sidebar-collapsed .wd-sidebar .wd-section-title,
    #wrapper.wd-sidebar-collapsed .wd-sidebar .wd-nav-label {
        display: none;
    }

    #wrapper.wd-sidebar-collapsed .wd-sidebar .wd-brand {
        justify-content: center;
        padding-left: 0;
        padding-right: 0;
    }

    /* Plegado solo cabe la W, y se recorta justo ella para que quede en el
       mismo eje que los iconos del menú: en el original va del píxel 9 al
       80 de los 280 (a su derecha empieza el texto de la marca), que a los
       34 px de alto que se pinta son 41,5 de ancho a partir del 5,2. El
       envoltorio recorta esa franja y el margen negativo arrastra la imagen
       hasta su borde; de centrarla ya se encarga el .wd-brand de arriba. */
    #wrapper.wd-sidebar-collapsed .wd-sidebar .wd-brand-logo {
        width: 41.5px;
    }

    #wrapper.wd-sidebar-collapsed .wd-sidebar .wd-brand-logo img {
        margin-left: -5.2px;
    }

    #wrapper.wd-sidebar-collapsed .wd-sidebar .nav-link,
    #wrapper.wd-sidebar-collapsed .wd-sidebar-toggle {
        justify-content: center;
        padding-left: 0;
        padding-right: 0;
    }

    #wrapper.wd-sidebar-collapsed .wd-sidebar .nav-link.active {
        padding-left: 0;
        border-left: 0;
        border-top: 3px solid var(--wd-teal);
    }

    /* Las zonas de cierre del funnel empiezan justo donde acaba el
       sidebar (ver más abajo): plegado, ese punto se mueve. */
    #wrapper.wd-sidebar-collapsed .wd-funnel-dropzones {
        left: var(--wd-sidebar-width-collapsed);
    }
}

/* Topbar */
.wd-topbar {
    height: 60px;
    /* El contenedor de referencia del buscador desplegado en móvil, que
       se coloca en absoluto para tapar la barra entera (ver
       .wd-buscador--abierto al final del fichero). */
    position: relative;
}

/* Las dos zonas de los lados de la barra (la del buscador y la de las
   acciones). Reparten a partes iguales todo el hueco que no ocupa lo
   que va en medio, y eso es lo que deja el botón del buzón de
   sugerencias centrado EN LA BARRA y no en el hueco que sobre: sin
   esto, el centro se correría hacia el lado que menos ocupara.

   `flex-basis: 0` y no `auto` a propósito: con `auto` el reparto
   partiría del ancho del contenido de cada zona, que es justo lo que no
   queremos que cuente. */
.wd-topbar-lado {
    flex: 1 1 0;
}

/* La zona de la izquierda, y SOLO ella, puede encogerse por debajo de
   su contenido: dentro va el buscador, que ya sabe hacerse pequeño (su
   `max-width` es un tope, no un ancho). Es lo que evita que la barra se
   desborde en una pantalla estrecha, donde el bloque de la derecha ya
   no cabe en su mitad.

   La de la derecha no lo lleva a propósito: ahí el que cedería sería el
   nombre del usuario, cortado por la mitad. Cuando pasa eso, el botón
   del centro se descentra un poco — que es un defecto mucho menor. */
.wd-topbar-lado--buscador {
    min-width: 0;
}

.wd-avatar {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background-color: var(--wd-teal);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.85rem;
    /* Para la foto, que se coloca encima de las iniciales */
    position: relative;
    overflow: hidden;
}

.wd-avatar-iniciales {
    line-height: 1;
}

/* La foto de perfil de Google, tapando el círculo con las iniciales.
   Va encima y no en su lugar a propósito: la URL que guardó el login
   deja de responder cuando el usuario cambia su foto en Google, y si la
   imagen no carga el `alt=""` no pinta nada y vuelven a verse las
   iniciales sin necesidad de JavaScript. */
.wd-avatar-img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
}

/* Variante para las tarjetas del funnel, donde el avatar del propietario
   comparte una línea estrecha con el importe. */
.wd-avatar-sm {
    width: 24px;
    height: 24px;
    font-size: 0.7rem;
}

.wd-content-area {
    min-height: calc(100vh - 60px);
}

.wd-content-area h1,
.wd-content-area h2,
.wd-content-area h3 {
    font-family: var(--wd-font-serif);
    color: var(--wd-gray-900);
}

.wd-content-area .text-muted {
    color: var(--wd-gray-600) !important;
}

/* Botones y enlaces con el acento de marca (teal) en vez del azul de Bootstrap */
.btn-primary {
    --bs-btn-bg: var(--wd-teal);
    --bs-btn-border-color: var(--wd-teal);
    --bs-btn-hover-bg: var(--wd-teal-dark);
    --bs-btn-hover-border-color: var(--wd-teal-dark);
    --bs-btn-active-bg: var(--wd-teal-dark);
    --bs-btn-active-border-color: var(--wd-teal-dark);
    --bs-btn-disabled-bg: var(--wd-teal);
    --bs-btn-disabled-border-color: var(--wd-teal);
}

.btn-outline-primary {
    --bs-btn-color: var(--wd-teal-dark);
    --bs-btn-border-color: var(--wd-teal);
    --bs-btn-hover-bg: var(--wd-teal);
    --bs-btn-hover-border-color: var(--wd-teal);
    --bs-btn-active-bg: var(--wd-teal);
    --bs-btn-active-border-color: var(--wd-teal);
}

a {
    color: var(--wd-teal-dark);
}

/* Botón outline "neutro" propio, en vez de reutilizar btn-outline-* de
   Bootstrap: los botones que genera la extensión Buttons de DataTables
   (p. ej. colvis) añaden su propia clase de color por defecto además de la
   que le pasemos, y mezclar dos variantes de color de Bootstrap en el mismo
   botón hace que el texto quede invisible hasta pasar el ratón por encima. */
.wd-btn-outline {
    background-color: #fff;
    color: var(--wd-gray-700);
    border: 1px solid var(--wd-gray-200);
}

.wd-btn-outline:hover {
    color: var(--wd-teal-dark);
    border-color: var(--wd-teal);
    background-color: #fff;
}

/* Botón de "añadir": el que abre el formulario de alta de algo (un lead,
   una oportunidad, una tarea, un documento). En coral y no en el teal de
   .btn-primary porque son dos acciones distintas que antes se veían
   iguales: en una pantalla con el listado detrás y el modal delante, el
   botón que ABRE el formulario y el que lo GUARDA tenían el mismo color,
   y el ojo no sabía cuál de los dos era el que cerraba el gesto.

   El teal se queda para guardar y confirmar, que es lo que termina una
   acción; este color es el que la empieza.

   Se hace con las variables --bs-btn-* y no con background-color a pelo
   para que herede del .btn de Bootstrap todo lo demás (el foco, el
   :active, el disabled, los tamaños) igual que lo hereda .btn-primary. */
.wd-btn-anadir {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--wd-coral-dark);
    --bs-btn-border-color: var(--wd-coral-dark);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--wd-coral-darker);
    --bs-btn-hover-border-color: var(--wd-coral-darker);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--wd-coral-darker);
    --bs-btn-active-border-color: var(--wd-coral-darker);
    --bs-btn-disabled-color: #fff;
    --bs-btn-disabled-bg: var(--wd-coral-dark);
    --bs-btn-disabled-border-color: var(--wd-coral-dark);
    --bs-btn-focus-shadow-rgb: 204, 74, 23;
}

/* DataTables (listados): pagination y estado "activo" con el acento de marca */
.page-item.active .page-link {
    background-color: var(--wd-teal);
    border-color: var(--wd-teal);
}

.page-link {
    color: var(--wd-teal-dark);
}

.page-link:hover {
    color: var(--wd-teal-dark);
}

/* Panel de filtros del funnel: un cuarto del ancho de la ventana, no los
   400px fijos de Bootstrap. El mínimo es para que en una pantalla
   pequeña siga cabiendo un campo de fecha, y el máximo para que en un
   móvil no se salga.

   Doble clase para ganarle en especificidad a `.offcanvas-end`, que es de
   Bootstrap y también trae `width`. La sombra es lo que lo hace parecer
   flotando por encima del tablero, ya que no hay fondo oscuro detrás. */
.offcanvas.wd-funnel-filtros {
    width: 25%;
    min-width: 320px;
    max-width: 100%;
    box-shadow: -8px 0 24px rgba(13, 27, 42, 0.12);
}

/* Funnel (Kanban) */
.wd-funnel-board {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    overflow-x: auto;
    padding-bottom: 0.5rem;
}

.wd-funnel-column {
    flex: 0 0 400px;
    /* Y 400 de verdad, pase lo que pase dentro. Un ítem flex trae de serie
       min-width: auto, que es "no te encojas por debajo de tu contenido", y
       el contenido de una tarjeta mide lo que mide el nombre de la
       oportunidad en una sola línea: el .text-truncate del nombre (ver
       opportunities/_card.php) es white-space: nowrap, así que su ancho
       mínimo es el título entero, por largo que sea. Ese mínimo le ganaba
       al flex-basis y la columna se estiraba —una oportunidad con un nombre
       de diez palabras dejaba su columna al triple de ancha que las demás—.
       Con min-width: 0 el mínimo automático desaparece, la columna se queda
       en sus 400px y el nombre ya se corta con "..." dentro de la tarjeta
       (que es lo que el text-truncate quería hacer desde el principio; el
       título completo sigue saliendo al pasar el ratón por el title). */
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* La altura la mide el JS y la deja en --wd-funnel-alto: es la
       distancia real entre el borde superior del tablero y el final de
       la ventana. El calc() de aquí es solo el respaldo por si el script
       no llega a ejecutarse.

       Antes ese calc() era el único valor, y el 180px era una estimación
       de todo lo que hay encima (barra, título, fila de filtros,
       márgenes). Como no coincidía con la altura real, las columnas se
       pasaban del alto de la ventana y aparecía un scroll en la página
       entera. Y ese número no puede acertar siempre: el panel de filtros
       se despliega y desplaza el tablero hacia abajo. */
    max-height: var(--wd-funnel-alto, calc(100vh - 180px));
    /* Y el MISMO valor como mínimo, o sea alto fijo: una columna llega
       hasta abajo tenga las oportunidades que tenga y esté plegada o
       desplegada.

       Antes solo era max-height, así que una columna sin tarjetas medía lo
       que su cabecera. Se notaba sobre todo con TODAS las columnas
       plegadas, que es cuando el tablero se quedaba en una tira de
       pastillas arriba de la pantalla: una columna plegada se estiraba
       hasta la más alta de la fila, y si no hay ninguna alta no hay hasta
       dónde estirarse.

       Y de paso arregla el arrastrar: la zona donde se suelta una tarjeta
       es el cuerpo de la columna, y en una columna con dos tarjetas
       terminaba donde terminaba la segunda. */
    min-height: var(--wd-funnel-alto, calc(100vh - 180px));
    background-color: var(--wd-gray-50);
    border: 1px solid var(--wd-gray-200);
    border-radius: 0.5rem;
}

.wd-funnel-column-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 0.9rem;
    border-bottom: 1px solid var(--wd-gray-200);
    /* Color de la etapa, elegido en Administración > Etapas de funnel. Lo
       pone la vista en la columna como --wd-etapa-fondo; el respaldo es el
       gris de la columna, por si una etapa se quedara sin color.

       Redondeado arriba para acompañar al borde de la columna: sin esto el
       color cuadra las dos esquinas de arriba. El píxel que se descuenta
       es el grosor del borde. */
    background-color: var(--wd-etapa-fondo, var(--wd-gray-50));
    border-radius: calc(0.5rem - 1px) calc(0.5rem - 1px) 0 0;
    /* El total del valor va en un segundo renglón de la cabecera: al lado
       del contador no cabe, un importe largo se comería el nombre de la
       etapa (que es lo que cede el ancho al resto). */
    flex-wrap: wrap;
}

/* Total del valor de las oportunidades de la columna. Bajo el contador y
   alineado con él, a la derecha: los dos números que resumen la columna
   se leen juntos.

   Se queda vacío cuando ninguna oportunidad de la etapa tiene valor, pero
   el renglón se reserva igual (min-height): si unas cabeceras tuvieran
   una línea y otras dos, las tarjetas de cada columna empezarían a
   distinta altura y el tablero se leería torcido. */
.wd-funnel-column-total {
    flex: 0 0 100%;
    text-align: right;
    font-size: 0.8rem;
    line-height: 1.2;
    min-height: 1.2em;
    font-weight: 600;
    color: var(--wd-gray-600);
    /* El gap de la cabecera separaría el total del renglón de arriba más
       de lo que hace falta. */
    margin-top: -0.25rem;
}

.wd-funnel-column-icon {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
    flex-shrink: 0;
}

.wd-funnel-column-title {
    font-weight: 600;
    font-size: 0.9rem;
    /* Ocupa el hueco que queda entre el icono y el contador, que es lo
       que empuja contador y botón contra el borde derecho. */
    flex: 1 1 auto;
    min-width: 0;
}

.wd-funnel-column-body {
    /* Ocupa todo lo que le deje la cabecera. Desde que la columna tiene
       alto fijo, esto es lo que hace que el hueco de debajo de la última
       tarjeta siga siendo zona donde soltar: sin el flex-grow, el cuerpo
       terminaría donde termina la última tarjeta y el resto de la columna
       sería decorado.

       El min-height: 0 es lo que deja funcionar al overflow-y: un hijo
       flex no baja de la altura de su contenido si no se le dice, y sin él
       una columna llena se pasaría de largo en vez de hacer scroll por
       dentro. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.wd-funnel-column-toggle {
    flex-shrink: 0;
    color: var(--wd-gray-600);
    background: transparent;
    border: none;
    border-radius: 0.25rem;
    padding: 0.1rem 0.3rem;
    line-height: 1;
}

.wd-funnel-column-toggle:hover {
    color: var(--wd-teal-dark);
    background-color: var(--wd-gray-200);
}

/* Columna plegada.
   El ancho es fijo y el mismo para todas (no depende de lo largo que sea
   el nombre de la etapa), así que el tablero no baila al plegar unas
   columnas u otras. El cuerpo se oculta: mientras está plegada no es zona
   de destino para el drag-and-drop, y por eso el JS la despliega en
   cuanto se arrastra una tarjeta por encima. */
.wd-funnel-column.wd-funnel-collapsed {
    flex: 0 0 44px;
    /* Aquí había un align-self: stretch, para que la tira fuera tan alta
       como la columna más alta de la fila. Ya no hace falta —y no
       bastaba— desde que .wd-funnel-column tiene alto fijo: estirarse
       hasta la más alta no sirve de nada cuando están TODAS plegadas, que
       era justo el caso en el que el tablero se quedaba en una tira de
       pastillas arriba de la pantalla. */
}

.wd-funnel-collapsed .wd-funnel-column-header {
    flex-direction: column;
    /* Crece hasta llenar la columna. Era un height: 100%, y dejó de
       funcionar en cuanto la columna pasó a tener alto fijo por min/max
       y no por su contenido: un porcentaje se mide contra la propiedad
       `height` del padre, que sigue siendo `auto`, así que el navegador
       lo ignoraba y la cabecera volvía a medir lo que ocupan sus cuatro
       cosas. Se veía: el color de la etapa cubría el primer palmo de la
       tira y el resto quedaba en gris.

       Con flex-grow no hay porcentaje que resolver —la cabecera es el
       único hijo visible cuando la columna está plegada, porque el
       cuerpo se oculta— y llena lo que haya. */
    flex: 1 1 auto;
    min-height: 0;
    padding: 0.75rem 0.3rem;
    border-bottom: 0;
    /* Plegada, la cabecera ES la columna: el color de la etapa ocupa toda
       la tira y se redondea por los cuatro lados. Es lo que hace que una
       columna plegada se reconozca, porque el nombre en vertical se lee
       mal y el icono es pequeño. */
    border-radius: calc(0.5rem - 1px);
    /* En vertical no hay segundo renglón que envolver, y con wrap la
       cabecera se partiría en dos columnas si el contenido pasara del
       alto. */
    flex-wrap: nowrap;
}

.wd-funnel-collapsed .wd-funnel-column-body {
    display: none;
}

/* En 44px de ancho no cabe un importe, así que la columna plegada se
   queda con el contador. */
.wd-funnel-collapsed .wd-funnel-column-total {
    display: none;
}

/* En vertical el orden natural del DOM no sirve: se recoloca con `order`
   para dejar el botón arriba, el contador debajo, el nombre girado en el
   centro y el icono de la etapa al fondo. */
.wd-funnel-collapsed .wd-funnel-column-toggle {
    order: 1;
}

/* El chevron apunta hacia donde se va la columna: a la izquierda para
   plegarla, a la derecha para devolverla. Sin `inline-block` el giro no se
   aplicaría: los iconos son <i> en línea, y transform no afecta a las
   cajas en línea. */
.wd-funnel-collapsed .wd-funnel-column-toggle .bi {
    display: inline-block;
    transform: rotate(180deg);
}

.wd-funnel-collapsed .wd-funnel-count {
    order: 2;
}

.wd-funnel-collapsed .wd-funnel-column-title {
    order: 3;
    writing-mode: vertical-rl;
    /* Se queda con todo el hueco que sobra de la tira, y centra el nombre
       dentro: es lo que deja el chevron y el contador arriba, el icono de
       la etapa abajo del todo y el nombre en medio. Con el texto en
       vertical el eje del renglón es el vertical, así que `text-align`
       centra a lo alto.

       Antes no hacía falta porque la tira medía lo que sus cuatro cosas y
       no sobraba hueco; ahora llega hasta el final de la pantalla. */
    flex: 1 1 auto;
    text-align: center;
    /* Con el texto en vertical el recorte y los puntos suspensivos actúan
       sobre el alto, que es el eje del renglón. */
    min-height: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wd-funnel-collapsed .wd-funnel-column-icon {
    order: 4;
}

.wd-funnel-card {
    background-color: #fff;
    border: 1px solid var(--wd-gray-200);
    border-radius: 0.5rem;
    padding: 0.75rem;
    box-shadow: 0 1px 2px rgba(13, 27, 42, 0.05);
    cursor: grab;
}

/* Los tres tipos de reunión del popup de cualificación. Son tres caminos
   distintos —cada uno pide otras cosas y dura otro rato—, así que se
   pintan como tres tarjetas que se pulsan enteras y la elegida se marca
   con el color de la casa: con tres radios a pelo hay que leer los tres
   para ver cuál está puesto. */
.wd-reunion-tipo {
    display: block;
    cursor: pointer;
    padding: 0.6rem 0.75rem;
    border: 1px solid var(--wd-gray-200);
    border-radius: 0.5rem;
    background-color: #fff;
}

.wd-reunion-tipo:hover {
    border-color: var(--wd-teal);
}

/* :has() es lo que permite resaltar la tarjeta desde el radio que lleva
   dentro sin una línea de JS. Donde no esté soportado, la tarjeta se
   queda sin resaltar y el radio marcado sigue viéndose: se degrada a lo
   que había, no se rompe. */
.wd-reunion-tipo:has(input:checked) {
    border-color: var(--wd-teal);
    background-color: var(--wd-teal-light);
}

.wd-funnel-card-ghost {
    opacity: 0.5;
    border: 1px dashed var(--wd-teal);
    background-color: var(--wd-gray-50);
}

.wd-funnel-card.sortable-chosen {
    cursor: grabbing;
}

/* El nombre de la tarjeta abre el popup de edición (ver
   opportunities-form.js), así que se lee como algo que se pulsa: el cursor
   normal de la tarjeta es "grab" (se arrastra), y aquí hace falta el de
   siempre para no decir "arrástrame" sobre lo único que no se arrastra
   dentro de ella (está en el filter de Sortable). El subrayado solo al
   pasar el ratón, para no ensuciar el tablero con texto subrayado de
   fábrica. */
.wd-opp-titulo {
    cursor: pointer;
}

.wd-opp-titulo:hover {
    text-decoration: underline;
}

/* El vínculo lleva a la ficha del lead o del cliente. Con `!important` de
   Bootstrap en .text-muted no hace falta pisar el color aquí: solo el
   cursor (hereda el "grab" de la tarjeta) y el subrayado, igual que el
   nombre. */
.wd-opp-vinculo-link {
    cursor: pointer;
    text-decoration: none;
}

.wd-opp-vinculo-link:hover {
    text-decoration: underline;
}

/* Modal de oportunidad (ver opportunities/_opp_modal.php): un menú a la
   izquierda y a la derecha, en todo lo que queda, el contenido de la
   pestaña elegida. El color de la pestaña activa es el teal de la casa y
   no el azul por defecto de Bootstrap, que aquí desentonaría con el resto
   del CRM.

   Más ancho que un modal-xl: casi todo el de la ventana, hasta 1320px (el
   ancho del .container de Bootstrap en xxl). Lo pide "Actividad", que
   lleva su ficha y la línea de tiempo lado a lado, y con el menú al lado
   en 1140px no cabían. Por debajo de md se queda como estaba: ahí el menú
   va encima del contenido y el ancho no es el problema. */
@media (min-width: 768px) {
    .wd-opp-modal-dialog {
        max-width: min(1320px, calc(100vw - 3rem));
    }

    /* El menú, en una columna fija y estrecha en vez de 1/4 del popup:
       con el popup más ancho, 1/4 eran más de 300px para ocho palabras. */
    .wd-opp-modal-menu-col {
        width: 185px;
    }

    /* Pegado arriba mientras se baja por una pestaña larga (la línea de
       tiempo de "Actividad"): si no, para cambiar de sección habría que
       volver a subir hasta arriba del todo. */
    .wd-opp-modal-menu {
        position: sticky;
        top: 0;
    }
}

/* El contenido: min-width 0 para que una tabla ancha dentro no empuje la
   columna más allá del popup (un item flex no encoge por debajo de su
   contenido si no se le dice). "Actividad" pinta sus tarjetas sobre gris,
   como en el popup propio que tuvo, y ese gris tiene que llegar hasta
   abajo aunque la pestaña sea más corta que el menú. */
.wd-opp-modal-contenido {
    min-width: 0;
}

.wd-opp-modal-contenido:has(.wd-actividad) {
    background: #f3f5f8;
}

/* En flex para que una etiqueta que parte en dos líneas ("Anotaciones /
   Próx. pasos", en la columna estrecha) siga alineada con el texto de
   arriba y no se meta debajo del icono. */
.wd-opp-modal-menu .list-group-item {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    border: none;
    border-radius: 0;
    border-left: 3px solid transparent;
    padding: 0.65rem 1rem;
    color: var(--wd-gray-700);
}

.wd-opp-modal-menu .list-group-item:hover {
    background-color: var(--wd-gray-50);
}

.wd-opp-modal-menu .list-group-item.active {
    background-color: var(--wd-teal-light);
    border-left-color: var(--wd-teal);
    color: var(--wd-teal-dark);
    font-weight: 600;
}

/* Alto mínimo para que la pestaña "Notas" —bastante más corta que "Datos
   generales" cuando hay pocas o ninguna— no deje el modal dando un salto
   grande de tamaño al cambiar de pestaña. */
.wd-opp-modal-panel {
    min-height: 320px;
    /* Para que "Actividad" decida si pone la ficha al lado de la línea o
       encima por lo que mide el panel, no por lo que mide la ventana. */
    container: wd-opp-panel / inline-size;
}

/* Atajos de cierre: la fila de zonas donde se sueltan las tarjetas para
   marcarlas como perdidas, abandonadas o ganadas.

   Van fijas al fondo de la ventana y solo se ven mientras se arrastra una
   tarjeta (el JS pone .wd-funnel-dragging en el body). Se quedan por
   encima del tablero en vez de empujarlo: si al aparecer movieran las
   columnas, la tarjeta que se está arrastrando cambiaría de sitio a mitad
   del gesto. Por eso tapan la parte de abajo de las columnas.

   El left del breakpoint lg es el ancho del sidebar, que a partir de ahí
   deja de ser un offcanvas y ocupa su hueco en la página. */
.wd-funnel-dropzones {
    position: fixed;
    z-index: 1030;
    bottom: 0;
    left: 0;
    right: 0;
    display: flex;
    gap: 0.75rem;
    padding: 0.75rem 1.5rem;
    /* Fondo opaco: la fila tapa la parte de abajo de las columnas, y con
       las tarjetas transparentándose por detrás no se leería nada. */
    background-color: #fff;
    border-top: 1px solid var(--wd-gray-200);
    box-shadow: 0 -6px 16px rgba(13, 27, 42, 0.08);
    /* Ni se ve ni se puede soltar nada en ella mientras no se arrastra. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(0.5rem);
    transition: opacity 0.12s ease-out, transform 0.12s ease-out, visibility 0.12s;
}

.wd-funnel-dragging .wd-funnel-dropzones {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

@media (min-width: 992px) {
    .wd-funnel-dropzones {
        left: var(--wd-sidebar-width);
    }
}

/* El color de cada zona lo pone la vista en --wd-dropzone-rgb, tomado del
   color de badge que ya tiene ese estado, para que no haya dos paletas
   que mantener sincronizadas. */
.wd-funnel-dropzone {
    flex: 1 1 0;
    min-width: 0;
    padding: 1.1rem 0.75rem;
    border: 2px dashed rgba(var(--wd-dropzone-rgb), 0.5);
    border-radius: 0.5rem;
    background-color: rgba(var(--wd-dropzone-rgb), 0.08);
    color: rgb(var(--wd-dropzone-rgb));
    text-align: center;
}

.wd-funnel-dropzone-label {
    display: block;
    font-weight: 600;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.wd-funnel-dropzone-aviso {
    display: block;
    font-size: 0.75rem;
    margin-top: 0.15rem;
}

/* Mientras la tarjeta está encima, Sortable la mete dentro de la zona.
   Se oculta (si no, la zona crecería y se descolocaría la fila) y se usa
   su presencia para resaltar la zona, que es justo el aviso de "suelta
   aquí" sin tener que llevar ese estado desde el JS. */
.wd-funnel-dropzone .wd-funnel-card {
    display: none;
}

.wd-funnel-dropzone:has(.wd-funnel-card) {
    border-style: solid;
    background-color: rgba(var(--wd-dropzone-rgb), 0.2);
    box-shadow: inset 0 0 0 1px rgba(var(--wd-dropzone-rgb), 0.35);
}

/* Sin etapa de destino en este funnel: se ve, dice por qué en el title y
   no acepta tarjetas (de eso se encarga el JS). */
.wd-funnel-dropzone-sin-etapa {
    border-color: var(--wd-gray-200);
    background-color: var(--wd-gray-50);
    color: var(--wd-gray-400);
    cursor: not-allowed;
}

/* Modales con scroll cuando el <form> envuelve header/body/footer.
   Bootstrap espera que .modal-body sea hijo directo de .modal-content para
   que modal-dialog-scrollable funcione; con el form en medio se rompe la
   cadena flex y el body no hace scroll (los campos de abajo, como el bloque
   de "lead nuevo" de una oportunidad, quedan inaccesibles). */
.modal-dialog-scrollable .modal-content > form {
    display: flex;
    flex-direction: column;
    max-height: 100%;
    min-height: 0;
    overflow: hidden;
}

.modal-dialog-scrollable .modal-content > form > .modal-body {
    min-height: 0;
    overflow-y: auto;
}

/* Ficha de contactos (usada en modales y fichas) */
.wd-modal-contact {
    border: 1px solid var(--wd-gray-200);
    border-radius: 0.5rem;
    padding: 0.6rem 0.75rem;
    margin-bottom: 0.6rem;
}

/* El contacto que ha dado de alta el CRM solo, sacandolo de la
   correspondencia del cliente: fondo gris para distinguirlo de un
   vistazo del que ha metido una persona.

   Va ANTES de .wd-modal-contact-principal a proposito, y no es
   cosmetico: un contacto puede ser las dos cosas -alguien marca como
   principal a uno que se dio de alta solo-, las dos reglas tienen la
   misma especificidad, y en CSS gana la ultima. Puesto despues, el
   gris le comeria el verde al principal y la ficha dejaria de decir
   cual es. Asi manda el principal, que es el dato mas importante de
   los dos; el de alta automatica sigue teniendo su icono. */
.wd-modal-contact-automatica {
    background-color: var(--wd-gray-200);
}

.wd-modal-contact-principal {
    border-color: var(--wd-teal);
    background-color: var(--wd-teal-light);
}

/* Utilidad genérica, no solo del funnel: la usa cualquier fila con
   flexbox que tenga que truncar un texto largo en vez de desbordar la
   card (text-truncate no basta por sí solo — un hijo flex no se encoge
   por debajo de su contenido si no se le dice min-width:0 explícito).
   Empezó viviendo solo bajo .wd-funnel-card, pero las filas de tareas,
   reuniones y visitas del dashboard usan la misma clase esperando que
   sea global, así que sin este scope el texto se les salía de la card
   en vez de truncarse. */
.min-width-0 {
    min-width: 0;
}

.wd-funnel-card-actions {
    display: flex;
    justify-content: space-between;
    border-top: 1px solid var(--wd-gray-200);
    padding-top: 0.4rem;
    margin-top: 0.25rem;
}

/* El globo del ojo de la tarjeta, con la última nota
   (opportunity-ultima-nota.js). Negro y con letra más grande que el
   popover de serie, como el tooltip del nombre de la oportunidad (el de
   Bootstrap, que es el color de énfasis sobre fondo claro). Más ancho
   porque lo que lleva es un párrafo, y con los saltos de línea de la
   nota. El servidor ya la manda recortada; el tope de alto es por si
   llega con muchas líneas cortas, y entonces se hace scroll dentro. */
.wd-ultima-nota-popover {
    --bs-popover-max-width: 400px;
    --bs-popover-font-size: 1rem;
    --bs-popover-bg: var(--bs-emphasis-color);
    --bs-popover-border-color: var(--bs-emphasis-color);
    --bs-popover-header-bg: var(--bs-emphasis-color);
    --bs-popover-header-color: var(--bs-body-bg);
    --bs-popover-header-font-size: 1rem;
    --bs-popover-body-color: var(--bs-body-bg);
    --bs-popover-arrow-border: var(--bs-emphasis-color);
}

.wd-ultima-nota-popover .popover-header {
    border-bottom-color: rgba(255, 255, 255, 0.15);
}

.wd-ultima-nota-popover .wd-ultima-nota-meta {
    color: rgba(255, 255, 255, 0.65);
    font-size: 0.85rem;
}

.wd-ultima-nota-popover .wd-ultima-nota-cuerpo {
    white-space: pre-wrap;
    max-height: 18rem;
    overflow-y: auto;
}

.wd-ultima-nota-popover .wd-ultima-nota-abrir {
    color: #fff;
    font-size: 0.85rem;
}

/* El ojo cuando la última nota está sin ver para quien mira. */
.wd-icon-btn.wd-opp-ultima-nota.wd-sin-ver {
    color: var(--bs-warning);
}

/* Un motivo de pérdida desactivado (Administración > Motivos de pérdida):
   sigue en la lista, pero se lee como apagado. */
.wd-motivo-inactivo .wd-motivo-nombre {
    color: var(--wd-gray-600);
    text-decoration: line-through;
}

.wd-icon-btn {
    color: var(--wd-gray-600);
    background: transparent;
    border: none;
    padding: 0.2rem 0.35rem;
    position: relative;
}

.wd-icon-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    background-color: var(--wd-coral);
    color: #fff;
    border-radius: 999px;
    font-size: 0.65rem;
    line-height: 1;
    padding: 2px 4px;
    min-width: 14px;
    text-align: center;
}

table.dataTable > thead > tr > th.dt-orderable-asc:hover,
table.dataTable > thead > tr > th.dt-orderable-desc:hover {
    outline-color: var(--wd-teal);
}

/* El icono de ordenar de DataTables sale a la derecha del título de la
   columna, EXCEPTO en las que detecta como fecha o número ("F. alta",
   "Oportunidades", "Valor total", "Ref." o "Valor" según el listado:
   unas son fechas y otras llevan un data-order numérico) — ahí su
   propio CSS pone `flex-direction: row-reverse` en el contenedor de la
   cabecera y el icono se cuela delante, a la izquierda. Se fuerza el
   mismo orden que el resto para que todas las columnas lo lleven en el
   mismo sitio.
   Selector con el ID de cada tabla y no con una clase que valga para
   todas: la hoja de DataTables se carga DESPUÉS de esta (en el <head>
   de cada listado) y con la misma especificidad de clases ganaría ella,
   entre en el orden que entre (mismo motivo que la nota del calendario,
   más arriba) — así que hace falta una lista, no una regla genérica.
   Si aparece en otro listado, se añade aquí su id.
   Las dos de proyectos van juntas porque son LA MISMA tabla
   (projects/_tabla.php) pintada en dos sitios, el listado del módulo y
   la ficha del cliente: arreglar una y dejar la otra torcida sería
   peor que no arreglar ninguna. */
#wdLeadsTable th.dt-type-numeric div.dt-column-header,
#wdLeadsTable th.dt-type-date div.dt-column-header,
#wdClientesTable th.dt-type-numeric div.dt-column-header,
#wdClientesTable th.dt-type-date div.dt-column-header,
#wdProyectosTable th.dt-type-numeric div.dt-column-header,
#wdProyectosTable th.dt-type-date div.dt-column-header,
#wdClienteProyectosTable th.dt-type-numeric div.dt-column-header,
#wdClienteProyectosTable th.dt-type-date div.dt-column-header {
    flex-direction: row;
}

/* Listados con el desplegable de filas por página abajo, junto al
   "Mostrando 1 a 25 de N registros" (selectorFilas en
   wdCrearTablaEmpresas): los dos en la misma línea y centrados en
   vertical. El padding-top que DataTables le pone al contador lo baja
   respecto al desplegable, por eso se quita. */
.dt-container .dt-layout-cell:has(> .dt-info):has(> .dt-length) {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.5rem;
}

.dt-container .dt-layout-cell:has(> .dt-info):has(> .dt-length) > .dt-info {
    padding-top: 0;
}

/* Pantalla de acceso (login) */
.wd-auth-body {
    background: linear-gradient(135deg, var(--wd-navy) 0%, var(--wd-navy-2) 100%);
    min-height: 100vh;
}

.wd-auth-wrapper {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
}

.wd-auth-box {
    width: 100%;
    max-width: 420px;
}

/* El logotipo va sobre el degradado navy, no dentro de la card: el archivo
   es blanco sobre transparente (el mismo del sidebar). */
.wd-auth-logo {
    text-align: center;
    margin-bottom: 1.5rem;
    line-height: 0;
}

.wd-auth-logo img {
    height: 44px;
    width: auto;
}

.wd-auth-card {
    background-color: #fff;
    border-radius: 0.75rem;
    box-shadow: 0 20px 45px rgba(13, 27, 42, 0.35);
    padding: 2.5rem;
}

.wd-google-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.65rem;
    width: 100%;
    padding: 0.65rem 1rem;
    background-color: #fff;
    border: 1px solid #dadce0;
    border-radius: 0.35rem;
    color: #3c4043;
    font-weight: 500;
    font-size: 0.9rem;
    text-decoration: none;
    transition: box-shadow 0.15s, background-color 0.15s;
}

.wd-google-btn:hover {
    background-color: #f8f9fa;
    color: #3c4043;
    box-shadow: 0 1px 3px rgba(60, 64, 67, 0.3);
}

/* ---------------------------------------------------------------------
   La card de emails del cliente (app/Views/shared/_emails.php)

   El alto maximo con scroll propio es la decision de diseno, no un
   adorno: la card va al final de la ficha y con el scroll de la pagina
   creceria sin fin al ir cargando paginas, dejando las oportunidades y
   los proyectos a diez pantallas de distancia. Con su propio alto, la
   ficha no cambia de tamano por muchos correos que se carguen.

   Ese contenedor es ademas el `root` del IntersectionObserver que pide
   la pagina siguiente, asi que si se le quita el overflow, el scroll
   infinito deja de funcionar.
   --------------------------------------------------------------------- */
.wd-emails-cuerpo {
    max-height: 480px;
    overflow-y: auto;
}

/* ---------------------------------------------------------------------
   El offcanvas "Crear tarea" cuando se abre ENCIMA de un modal.

   Pasa en la pestana "Tareas" del popup de una oportunidad, que es un
   modal (capa 1055, con su fondo en la 1050) mientras que un offcanvas
   vive en la 1045: el formulario salia por debajo del popup y de su
   fondo oscuro, visible pero apagado y sin poder tocarlo.

   Aqui hay que tener presente lo que hace tareas.js, o estos numeros no
   se entienden: **mientras dura ese caso, el offcanvas se mete DENTRO
   del modal** (para que el modal no le robe el foco a sus campos). Y un
   modal con z-index crea contexto de apilamiento, asi que todo lo que
   cuelga de el queda encerrado en la capa 1055 pase lo que pase: el
   z-index de aqui abajo solo lo coloca **dentro** del popup, por encima
   del dialogo.

   De ahi el numero del fondo oscuro del offcanvas, que es la parte que
   no es evidente. Ese fondo lo crea Bootstrap en el <body>, o sea FUERA
   del modal, asi que se compara con el modal entero: con el 1060 que
   Bootstrap le pondria, tapaba el popup **y el formulario que ahora vive
   dentro de el** — exactamente el sintoma que se venia a arreglar. Va en
   1054, justo debajo del modal: oscurece la pantalla y deja nitido lo de
   arriba.

   La clase la pone y la quita tareas.js, solo mientras dura ese caso:
   subir la capa siempre seria pelearse con el popup de la propia tarea
   el dia que se abra desde otro sitio.
   --------------------------------------------------------------------- */
body.wd-tarea-sobre-modal #wdTareaOffcanvas {
    z-index: 1065;
}

body.wd-tarea-sobre-modal .offcanvas-backdrop {
    z-index: 1054;
}

/* ---------------------------------------------------------------------
   Leer o contestar un correo desde la pestana "Emails" del popup de una
   oportunidad: un modal ENCIMA de otro.

   Bootstrap pone todos los modales en la misma capa (1055) y todos sus
   fondos en la 1050, asi que el segundo fondo quedaba por debajo del
   popup —que no se oscurecia— y el correo compartia capa con el. Los dos
   modales de la card suben a la 1065 y su fondo a la 1060: por encima
   del popup y por debajo del correo.

   El fondo se reconoce por ser el SEGUNDO del <body> (`~`): Bootstrap lo
   crea alli al abrir, detras del del popup. La clase del <body> la pone
   y la quita emails-ficha.js solo mientras hay uno de esos modales
   abierto, asi que esto no toca a ningun otro par de modales.
   --------------------------------------------------------------------- */
.modal.wd-emails-modal-encima {
    z-index: 1065;
}

body.wd-email-sobre-modal .modal-backdrop ~ .modal-backdrop {
    z-index: 1060;
}

/* El buscador de la cabecera. Ancho fijo y no "lo que quede": la
   cabecera es flex y con un ancho automatico la caja crece o encoge
   segun lo largo que sea el contador de correos que tiene al lado, y se
   ve moverse al cargar cada pagina.

   En pantalla estrecha pasa a ocupar la linea entera (la cabecera ya
   lleva flex-wrap), que es mejor que una caja de dos centimetros donde
   no se lee lo que se escribe. */
.wd-emails-buscador .input-group {
    width: 15rem;
}

@media (max-width: 575.98px) {
    .wd-emails-acciones,
    .wd-emails-buscador,
    .wd-emails-buscador .input-group {
        width: 100%;
    }
}

/* Las filas son <button> (para que se pulsen con el teclado), asi que
   hay que quitarles lo que Bootstrap y el navegador les ponen. El
   text-align a la izquierda es el que mas se nota si falta. */
.wd-email-fila {
    display: flex;
    gap: 0.6rem;
    width: 100%;
    padding: 0.55rem 1rem;
    border: none;
    border-bottom: 1px solid var(--wd-gray-200);
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
}

.wd-email-fila:focus-visible {
    outline: 2px solid var(--wd-teal);
    outline-offset: -2px;
}

.wd-email-fila:last-of-type {
    border-bottom: none;
}

.wd-email-fila:hover {
    background-color: var(--wd-gray-50);
}

.wd-email-sentido {
    flex-shrink: 0;
    padding-top: 0.1rem;
}

/* min-width a 0 para que el text-truncate de dentro funcione: sin esto
   un asunto largo estira la fila en vez de cortarse con puntos. Es el
   clasico de flexbox y cuesta un rato averiguarlo. */
.wd-email-medio {
    flex: 1 1 auto;
    min-width: 0;
}

.wd-email-asunto {
    color: var(--wd-gray-900);
}

/* El clip de "lleva adjuntos", pegado al final del asunto. No se
   encoge (flex-shrink a 0 lo pone el HTML) porque lo que tiene que
   cortarse cuando no hay sitio es el asunto, no el icono: un asunto
   con puntos suspensivos se entiende, y medio clip no. */
.wd-email-clip {
    display: inline-flex;
    align-items: center;
    gap: 0.1rem;
    font-size: 0.8rem;
}

/* El fragmento a dos lineas: una no dice de que iba el correo y tres
   convierten la lista en un muro. */
.wd-email-fragmento {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* El cuerpo del correo en el modal. pre-wrap para que se vean los
   saltos del texto, y break-word porque un correo puede traer una
   URL de doscientos caracteres sin un espacio. */
.wd-email-modal-cuerpo {
    white-space: pre-wrap;
    overflow-wrap: break-word;
}

/* El cuerpo del redactor de "Contestar desde tu cuenta", que es un editor
   con formato (contenteditable) y no un textarea.

   Tipografía proporcional y no monoespaciada, al revés que cuando esto
   enviaba texto plano: ahora el correo sale en HTML, así que lo que se ve
   aquí es lo que va a llegar — incluida la firma de Gmail, que viene
   maquetada.

   El alto es mínimo y máximo a la vez: el mínimo para que la cita no deje
   el sitio de escribir en dos líneas, y el máximo para que un hilo largo
   no empuje el pie del modal fuera de la pantalla y deje el botón de
   Enviar sin alcanzar. */
.wd-email-responder-texto {
    font-size: 0.9rem;
    min-height: 12rem;
    max-height: 26rem;
    overflow-y: auto;
}

/* Las imágenes de una firma no pueden desbordar el editor: las hay de
   1.200 px de ancho pensadas para el correo, no para un modal. */
.wd-email-responder-texto img {
    max-width: 100%;
    height: auto;
}

/* La cita del original, en gris y con su raya, como en cualquier cliente
   de correo. Es solo dentro del editor: el correo se lleva su propio
   `style` en línea, porque ahí no hay hoja de estilos del CRM. */
.wd-email-responder-texto blockquote {
    color: var(--wd-gray-600, #6c757d);
}

/* Las tres filas en gris de la primera carga. */
.wd-emails-barra {
    height: 0.6rem;
    margin: 0.35rem 0;
    border-radius: 0.2rem;
    background-color: var(--wd-gray-200);
    animation: wd-emails-latido 1.2s ease-in-out infinite;
}

.wd-emails-barra-corta {
    width: 35%;
}

@keyframes wd-emails-latido {
    50% {
        opacity: 0.45;
    }
}

/* ------------------------------------------------------------------
   El aviso de emails de la cabecera (app/Views/shared/_avisos_emails.php)
   ------------------------------------------------------------------ */

/* El desplegable se pinta en TODAS las pantallas, así que su ancho no
   puede depender de lo que lleve dentro: un asunto de reenvío mide 200
   caracteres y ensancharía la barra de arriba. De ahí el ancho fijo más
   el `text-truncate` de cada línea. */
.wd-avisos-menu {
    width: 22rem;
    max-width: calc(100vw - 1.5rem);
    padding-bottom: 0;
}

/* Y el alto tampoco: quince filas de tres líneas se salen de la pantalla
   en un portátil. Scroll solo en la lista, para que la cabecera del
   desplegable y el pie de "desconectar" se queden siempre a la vista. */
.wd-avisos-lista {
    max-height: 24rem;
    overflow-y: auto;
}

.wd-aviso:last-child {
    border-bottom: 0 !important;
}

/* La lista de contactos de la ficha de un lead o un cliente (shared/
   _contactos.php): mismo motivo que .wd-avisos-lista de arriba. Con
   muchos contactos, un tope de alto y scroll solo ahí, para que la
   cabecera de la card ("Contactos (N)" y "Nuevo contacto") no se vaya
   página abajo detrás de la lista. */
.wd-contactos-lista {
    max-height: 400px;
    overflow-y: auto;
}

.wd-aviso:hover {
    background-color: var(--wd-gray-50);
}

/* El remitente cede antes que el asunto cuando no cabe todo: el asunto es
   lo que pide el encargo y el remitente es la ayuda para leerlo. */
.wd-aviso-de {
    font-size: 0.75rem;
    min-width: 0;
}

/* El botón del sobre: un círculo del MISMO tamaño que el avatar, que está
   al lado. Los 34px van en píxeles y no en rem a propósito, como los
   avatares y las otras tres medidas fijas de esta hoja: la raíz de este
   CRM está al 80%, así que en rem el círculo y el avatar se moverían
   juntos pero cualquier retoque de esa raíz los descuadraría respecto al
   sobre de dentro. Si cambia `.wd-avatar`, cambia esto. */
.wd-avisos-boton {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    flex: 0 0 auto;
    background-color: var(--wd-gray-200);
    color: var(--wd-gray-900);
    transition: background-color 0.15s ease-in-out, color 0.15s ease-in-out;
}

.wd-avisos-boton:hover,
.wd-avisos-boton:focus {
    color: var(--wd-gray-900);
    background-color: var(--wd-gray-400);
}

/* Con avisos: amarillo oscuro y el sobre en blanco. La enciende el
   JavaScript al pintar el badge.

   El amarillo es oscuro por necesidad y no por gusto: el `bg-warning` de
   Bootstrap (#ffc107) deja el sobre blanco en 1,63:1, o sea invisible.
   Éste da 3,82:1, que es lo que pide la WCAG para un elemento gráfico
   (1.4.11). */
.wd-avisos-boton.wd-avisos-hay,
.wd-avisos-boton.wd-avisos-hay:hover,
.wd-avisos-boton.wd-avisos-hay:focus {
    background-color: var(--wd-amber-dark);
    color: #fff;
}

/* En píxeles por lo mismo que el círculo: el sobre se centra en 34px y no
   debe moverse si algún día cambia el tamaño de la letra. */
.wd-avisos-icono {
    font-size: 16px;
    line-height: 1;
}

/* El badge sobre el sobre. Bootstrap 5.3 lo coloca solo con
   `position-absolute top-0 start-100 translate-middle`; el tamaño mínimo
   es para que un "99+" no crezca hasta deformar la barra.

   El anillo blanco sí es necesario, y se ve al medirlo: el badge solo
   aparece cuando hay avisos, o sea SIEMPRE sobre el círculo amarillo, y
   el rojo de `bg-danger` contra ese amarillo da 1,19:1 — el número de
   dentro se lee, pero el contorno del badge se empasta con el fondo y
   deja de leerse como una chapa. El anillo lo despega, y siendo blanco
   se funde con la barra, que es `bg-white`. */
.wd-avisos-badge {
    font-size: 0.65rem;
    padding: 0.2rem 0.35rem;
    min-width: 1.1rem;
    box-shadow: 0 0 0 2px #fff;
}

/* Es un enlace haciendo de botón: sin subrayado ni anillo de `btn-link`,
   pero CON un foco visible al llegar con el teclado. Quitar el `outline`
   sin poner nada en su sitio deja el icono inalcanzable a ciegas. */
.wd-avisos-boton:focus {
    box-shadow: none;
}

.wd-avisos-boton:focus-visible {
    outline: 2px solid var(--wd-teal);
    outline-offset: 2px;
}

/* ==========================================================================
   Los históricos (opportunities/_panel_historial.php y el de proyecto):
   el "Leer más" de una nota larga y los enlaces de su detalle
   ========================================================================== */

/* Una dirección de Drive no tiene espacios y no cabe en el panel, que es
   estrecho: sin esto se sale por la derecha en vez de partirse. Va solo
   en el texto de estas entradas, y no en los enlaces de todo el CRM,
   donde partir una palabra por la mitad no hace falta. */
.wd-log-texto {
    overflow-wrap: anywhere;
}

/* Un correo en el histórico de una oportunidad de lead: la entrada
   entera es un <button> que lo abre, así que se le quita lo que le
   ponen Bootstrap y el navegador, como a .wd-email-fila. */
.wd-log-email {
    display: block;
    width: 100%;
    border: none;
    border-radius: inherit;
    background: none;
    color: inherit;
    font: inherit;
    text-align: left;
}

.wd-log-email:hover {
    background-color: var(--wd-gray-50);
}

.wd-log-email:focus-visible {
    outline: 2px solid var(--wd-teal);
    outline-offset: -2px;
}

/* El <details> no se ve como tal: ni triángulo ni cursor de texto. Lo que
   se ve es la línea de siempre y, detrás, un "Leer más" que parece un
   enlace — que es lo que la gente ya sabe pulsar. */
.wd-log-nota > summary {
    cursor: pointer;
    list-style: none;
}

.wd-log-nota > summary::-webkit-details-marker {
    display: none;
}

.wd-log-nota-abrir,
.wd-log-nota-cerrar {
    color: var(--wd-teal-dark);
    text-decoration: underline;
    white-space: nowrap;
}

/* Abierto: fuera el extracto (son las primeras palabras de lo que se
   acaba de desplegar) y el enlace pasa a decir "Leer menos". */
.wd-log-nota-cerrar,
.wd-log-nota[open] .wd-log-nota-extracto,
.wd-log-nota[open] .wd-log-nota-abrir {
    display: none;
}

.wd-log-nota[open] .wd-log-nota-cerrar {
    display: inline;
}

.wd-log-nota > summary:focus-visible {
    outline: 2px solid var(--wd-teal);
    outline-offset: 2px;
}

/* `pre-wrap` porque una nota lleva párrafos y saltos de línea, y sin esto
   el HTML los comería y quedaría un ladrillo de texto seguido. El borde
   de la izquierda es lo que la separa de la línea del resumen: se lee
   como lo que es, algo citado. */
.wd-log-nota-cuerpo {
    white-space: pre-wrap;
    color: var(--wd-gray-700);
    border-left: 2px solid var(--wd-gray-200);
    padding-left: 0.5rem;
}

/* ==========================================================================
   La rejilla del calendario de Google del dashboard (FullCalendar)
   ========================================================================== */

/* Los colores van por las variables de FullCalendar y no por reglas
   propias, y no es un capricho: la librería se sirve del CDN en su
   versión "global", que se inyecta el CSS ella sola al cargar. Ese
   estilo puede acabar DESPUÉS de este fichero en el <head>, y entonces
   un `.wd-evento-crm { background-color: … }` con la misma especificidad
   pierde. Con las variables no hay carrera: las lee su propia regla. */

/* Lo que salió del CRM: en el verde de la casa, para que se distinga de
   un vistazo de lo que uno tiene en su calendario por otros motivos.
   El tono oscuro y no --wd-teal porque el texto va en blanco encima:
   sobre el claro se queda en 2,98:1 y no se lee. */
.wd-evento-crm {
    --fc-event-bg-color: var(--wd-teal-dark);
    --fc-event-border-color: var(--wd-teal-dark);
    --fc-event-text-color: #fff;
}

/* Lo demás de la agenda: gris, presente pero sin pelearse con lo del
   CRM. Con el texto oscuro, que sobre este gris es lo legible. */
.wd-evento-google {
    --fc-event-bg-color: var(--wd-gray-200);
    --fc-event-border-color: var(--wd-gray-400);
    --fc-event-text-color: var(--wd-gray-900);
}

/* Sin confirmar todavía (el `tentative` de Google): mismo color, borde
   de puntos. El color ya dice de quién es; esto dice que aún no es
   seguro, y son dos cosas distintas que tienen que poder verse a la vez. */
.wd-evento-tentativo {
    border-style: dashed;
}

/* Una TAREA de Google, no un evento (ver dashboard-calendario.js): va
   siempre en la fila de "todo el día", porque la API de Tasks no sabe
   guardar la hora, y por eso no se puede distinguir de un evento por
   dónde cae. Se distingue por el borde de la izquierda y por la casilla
   del título — igual que hace Google en su propia rejilla.

   Y NO lleva color propio: el verde de .wd-evento-crm o el gris de
   .wd-evento-google siguen mandando, porque lo primero que hay que
   saber de algo en esta rejilla es si salió del CRM o no. Que además
   sea una tarea es el segundo dato, y por eso va en el borde. */
.wd-evento-tarea {
    border-left-width: 4px;
}

/* La que ya está hecha, tachada y más apagada: sigue ocupando su día
   —es el registro de que ese día se trabajó— pero no compite con lo que
   queda por hacer. */
.wd-evento-tarea-hecha {
    opacity: 0.65;
    text-decoration: line-through;
}

/* Un evento de "otro calendario" (ver dashboard-calendario.js): sin
   variables propias porque su color no es fijo —es el que ESE calendario
   tenga en Google, uno por usuario y por calendario que no se sabe de
   antemano—, así que viaja por evento (backgroundColor/borderColor/
   textColor) y no por aquí. Este color de refresco solo se ve si por lo
   que sea no viniera ninguno: mismo aire que .wd-evento-google, para que
   nunca se quede sin color un evento cuyo calendario no tuviera uno en
   Google. */
.wd-evento-otro {
    --fc-event-bg-color: var(--wd-gray-200);
    --fc-event-border-color: var(--wd-gray-400);
    --fc-event-text-color: var(--wd-gray-900);
}

/* La rejilla entera un punto más pequeña que el resto de la página: son
   siete columnas de horas en una card, y con el tamaño normal los
   títulos no caben en el hueco de media hora. */
.wd-calendario .fc {
    font-size: 0.85rem;
}

/* En el móvil, la cabecera de la rejilla en tres filas: flechas y "Hoy"
   arriba, la fecha en medio y Semana/Día abajo. De serie FullCalendar
   las pone las tres en una fila, y en una pantalla estrecha la fecha
   rompe en varias líneas y empuja los botones de la vista fuera de la
   card. El orden del DOM ya es ese (left, center, right), así que basta
   con pasar la fila a columna.

   Con `#wdCalendario` delante por lo mismo que los bloques de más abajo:
   la librería inyecta su CSS al cargar y puede quedar después de esta
   hoja. */
@media (max-width: 575.98px) {
    #wdCalendario .fc-header-toolbar {
        flex-direction: column;
        align-items: center;
        gap: 0.5rem;
    }

    #wdCalendario .fc-toolbar-title {
        font-size: 1.25rem;
        text-align: center;
    }
}

/* El clic abre la ficha del CRM o el evento en Google (ver
   dashboard-calendario.js), así que el cursor tiene que decirlo. */
.wd-calendario .fc-event {
    cursor: pointer;
}

/* El título de un evento ocupa UNA línea y lo que no cabe se corta con
   puntos suspensivos; el título entero se lee en el tooltip al pasar el
   ratón (lo monta dashboard-calendario.js, igual que los del sidebar).

   Antes saltaba de línea —el bloque crecía hacia abajo para que cupiera—
   y el resultado era que un evento de media hora con un título largo se
   comía el hueco del siguiente: la rejilla dejaba de servir para lo que
   está, que es ver dónde hay sitio. Con una línea por evento, todos los
   bloques miden lo que dura su evento y el texto completo sigue a un
   ratón de distancia (encargo de 2026-09-18).

   El recorte se pone en el `.fc-event-title` y no en sus capas de fuera:
   es el elemento que tiene el ancho de la columna (`width:100%`, más
   abajo), y `text-overflow` solo pinta los puntos en quien recorta de
   verdad. Las capas de arriba se quedan con el overflow visible que
   traen, que es lo que deja que el título esté en su propia línea debajo
   de la hora.

   Selector con el ID del contenedor (`#wdCalendario`) y no con la clase
   `.wd-calendario`: la librería inyecta su propio CSS al cargar el CDN
   (ver la nota de más arriba sobre los colores) y puede acabar DESPUÉS
   de esta hoja; con la misma especificidad de clases ganaría ella. Un
   ID gana siempre, entre en el orden que entre. */
#wdCalendario .fc-event-title {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    /* Sin esto, un título largo estira su caja flex en vez de recortarse:
       el mínimo por defecto de un hijo flex es su contenido. */
    min-width: 0;
}

#wdCalendario .fc-event-title-container,
#wdCalendario .fc-event-main-frame {
    overflow: visible;
    white-space: normal;
}

#wdCalendario .fc-timegrid-event {
    overflow: visible;
}

/* ---------------------------------------------------------------------
   La hora en su línea y el título debajo, a todo el ancho del bloque.

   De serie FullCalendar pinta "09:00 - 09:30 - Reunión Producción" en
   una sola línea, y eso lo hace SOLO con los eventos cortos: a los que
   duran menos de una franja les pone la clase `fc-timegrid-event-short`
   y con ella cambia tres cosas —la caja pasa a fila, mete el " - " con
   un `::after` sobre la hora, y encoge el título—. Como aquí la franja
   es de media hora, "corto" son casi todas las reuniones, así que ese
   caso excepcional suyo era el normal nuestro.

   Con la hora arriba y el título debajo, el título dispone del ancho
   entero de la columna y rompe en las líneas que haga falta, en vez de
   ir colgando de la hora en un hueco de la mitad de ancho.

   El tamaño del título se devuelve al normal a propósito: lo encogía
   por compartir línea con la hora, y ahora ya no la comparte. Sin esto,
   una reunión de media hora tendría el título más pequeño que una de
   dos, con el mismo aspecto por lo demás — una diferencia que antes
   tenía motivo y ahora sería arbitraria.

   Todo con `#wdCalendario` delante por lo mismo que el bloque de arriba:
   la librería inyecta su CSS al cargarse y puede quedar después de esta
   hoja, así que con clases ganaría ella. Un id gana siempre.
   --------------------------------------------------------------------- */
/* Los eventos largos ya van en columna (`.fc-v-event .fc-event-main-frame`
   de la librería); esto es para que los cortos vayan igual. */
#wdCalendario .fc-timegrid-event .fc-event-main-frame {
    flex-direction: column;
}

/* El " - " que la librería pega detrás de la hora en los eventos cortos
   (`content:"\00a0-\00a0"`). Con la hora y el título en líneas distintas,
   ese guion se quedaba suelto al final de la primera. */
#wdCalendario .fc-timegrid-event .fc-event-time::after {
    content: none;
}

/* El ancho entero de la capa para el título, y el tamaño de letra de
   siempre: la librería lo encoge solo en los cortos
   (`--fc-small-font-size`) porque compartían línea con la hora. */
#wdCalendario .fc-timegrid-event .fc-event-title-container,
#wdCalendario .fc-timegrid-event .fc-event-title {
    width: 100%;
    font-size: 1em;
}

/* El bloque de un evento CON hora (a diferencia del "todo el día", que
   crece con su fila) está posicionado en absoluto y estirado al hueco
   exacto de su franja (`top:0;bottom:0`, CSS propio de FullCalendar), y
   ese hueco lo mide la DURACIÓN, no el contenido: en una franja de media
   hora no caben las dos líneas que ocupan la hora y el título, así que el
   fondo se cortaría por la mitad de la segunda. Quitar el `bottom:0` y
   dejar que la altura la marque el contenido (con un mínimo del 100% de
   la franja, para no encoger los eventos cortos) hace que el fondo
   acompañe siempre al texto.

   Sigue haciendo falta con el título de una sola línea: lo que desborda
   la franja no es el título largo —ese ya se recorta— sino que la hora y
   el título van uno debajo del otro. */
#wdCalendario .fc-timegrid-event-harness > .fc-timegrid-event {
    bottom: auto;
    height: auto;
    min-height: 100%;
}

/* El desplegable de "otros calendarios" (ver dashboard-calendario.js): un
   ancho máximo y scroll propio para cuando alguien está suscrito a
   muchos, y el punto de color de cada fila, del mismo color con el que
   luego se pintan sus eventos en la rejilla — para reconocer cuál es
   cuál sin tener que marcarlo primero. */
.wd-calendario-otros-menu {
    max-height: 260px;
    min-width: 220px;
    overflow-y: auto;
}

.wd-calendario-otro-color {
    display: inline-block;
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    flex: 0 0 auto;
}

/* ==========================================================================
   El buscador general de la cabecera (ver app/Views/shared/_buscador.php)
   ========================================================================== */

/* El ancho es un `max-width` y no un ancho fijo: en una pantalla de 1280
   con el sidebar desplegado, un campo de 420 px deja sitio de sobra para
   los tres botones y el avatar de la derecha, y en una de 1024 se
   encoge en vez de empujarlos fuera de la barra. */
.wd-buscador {
    position: relative;
    flex: 1 1 auto;
    max-width: 420px;
}

/* La lupa va DENTRO del campo, y el hueco se lo hace el padding del
   input de Tom Select de más abajo. Sin eventos de puntero para que
   pulsar sobre ella sea pulsar sobre el campo. */
.wd-buscador-icono {
    position: absolute;
    left: 0.7rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--wd-gray-400);
    pointer-events: none;
    z-index: 2;
}

.wd-buscador .ts-wrapper {
    margin: 0;
}

.wd-buscador .ts-control {
    padding-left: 2.1rem;
    border-radius: 999px;
    background-color: var(--wd-gray-50);
    /* El borde por defecto de Tom Select es el gris clarito de
       Bootstrap (--bs-border-color): apenas se nota sobre el fondo
       oscuro de la cabecera, y el buscador general de todas las
       pantallas necesita verse a la primera. */
    border: 1.5px solid var(--wd-gray-700);
}

.wd-buscador .ts-control:focus-within {
    background-color: #fff;
    border-color: var(--wd-teal);
}

/* El desplegable, más ancho que el campo: los subtítulos de los
   resultados ("Acme S.L. · Director comercial · j.perez@acme.es") no
   caben en 420 px y truncarlos deja fuera justo lo que distingue a dos
   personas con el mismo nombre. */
.wd-buscador .ts-dropdown {
    width: 460px;
    max-width: 90vw;
    /* Cinco grupos de ocho no caben en pantalla. Con scroll dentro del
       desplegable y no en la página. */
    max-height: 70vh;
    overflow-y: auto;
}

.wd-buscador .ts-dropdown .optgroup-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--wd-gray-600);
    background-color: var(--wd-gray-50);
}

/* El contador del grupo ("8+"), en gris y sin peso: dice "hay más, afina
   una letra" y no debe competir con el nombre de los resultados. */
.wd-buscador-grupo-total {
    font-weight: 400;
    color: var(--wd-gray-400);
}

.wd-buscador-fila {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.wd-buscador-fila-icono {
    color: var(--wd-gray-400);
    flex-shrink: 0;
}

/* `min-width: 0` es lo que permite que el texto se trunque dentro de un
   flex: sin él, un nombre largo estira la fila y empuja el badge fuera. */
.wd-buscador-fila-texto {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1 1 auto;
}

.wd-buscador-fila-titulo,
.wd-buscador-fila-sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.wd-buscador-fila-sub {
    font-size: 0.78rem;
    color: var(--wd-gray-600);
}

.wd-buscador-aviso {
    padding: 0.6rem 0.8rem;
    color: var(--wd-gray-600);
    font-size: 0.85rem;
}

.wd-buscador-cerrar {
    display: none;
}

/* ---------------------------------------------------------------------
   La card de tareas de Teamwork (app/Views/teamwork/_tareas_seccion.php)
   cuando se le pone alto máximo.

   Solo la de la ficha del CLIENTE la lleva, y es la misma decisión que
   la card de emails: va al final de una ficha larga y trae las tareas de
   TODOS los proyectos del cliente, así que sin tope crecería hasta dejar
   las oportunidades y los proyectos a varias pantallas de distancia. La
   pestaña del proyecto no la lleva: ahí la tabla es lo único que hay en
   la pestaña y el scroll de la página es el bueno.

   El alto va en `.table-responsive` y NO en `.wd-tw-tareas-lista`, que
   sería lo obvio. El motivo es el `<thead>` pegajoso de aquí abajo: un
   elemento sticky se pega a su contenedor de scroll más cercano, y
   `.table-responsive` ya es uno (`overflow-x: auto` convierte también el
   eje vertical en `auto`). Poniéndolo fuera, la cabecera se pegaría al
   contenedor equivocado y se iría con el scroll.

   Y así el aviso de "hay más tareas de las que caben", que va fuera de
   `.table-responsive`, se queda fijo arriba en vez de esconderse dentro
   del scroll.
   --------------------------------------------------------------------- */
.wd-tw-tareas--limitada .table-responsive {
    max-height: var(--wd-tw-tareas-alto, 400px);
    overflow-y: auto;
}

/* Con scroll, una tabla sin cabecera a la vista son siete columnas de
   números sin nombre. */
.wd-tw-tareas--limitada thead th {
    position: sticky;
    top: 0;
    z-index: 1;
}

/* Lo mismo para la card de tickets de Desk, que en la ficha del cliente
   va justo debajo de la de tareas y por el mismo motivo. Son dos reglas
   y no una compartida a propósito: son dos componentes con sus propias
   clases, y unirlas obligaría a que el día que una necesite otro alto
   haya que separarlas otra vez. */
.wd-tw-tickets--limitada .table-responsive {
    max-height: var(--wd-tw-tickets-alto, 400px);
    overflow-y: auto;
}

.wd-tw-tickets--limitada thead th {
    position: sticky;
    top: 0;
    z-index: 1;
}

/* En móvil el buscador no cabe al lado de los tres botones y el avatar,
   así que se esconde y lo despliega la lupa de la barra (#wdBuscadorLupa,
   ver buscador.js), tapándola entera mientras está abierto. Es el mismo
   elemento y el mismo Tom Select: montar un segundo buscador para la
   pantalla pequeña serían dos sitios que arreglar cada vez. */
@media (max-width: 767.98px) {
    .wd-buscador {
        display: none;
    }

    .wd-buscador.wd-buscador--abierto {
        display: flex;
        align-items: center;
        gap: 0.25rem;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        height: 60px;
        max-width: none;
        padding: 0 0.75rem;
        background-color: #fff;
        z-index: 1030;
    }

    .wd-buscador--abierto .ts-wrapper {
        flex: 1 1 auto;
    }

    .wd-buscador--abierto .wd-buscador-cerrar {
        display: inline-flex;
    }

    .wd-buscador .ts-dropdown {
        width: 100%;
    }
}

/* Campo de formulario que contiene un enlace, con el botón que lo abre en
   una pestaña nueva (ver wd_input_enlace() y assets/js/enlaces-abrir.js).

   El botón está siempre en el DOM y lo que se quita es este modificador,
   no el nodo: mientras se teclea, el JavaScript solo tiene que poner y
   quitar una clase.

   Y se esconde desde el envoltorio y no con un `display:none` en el propio
   botón porque un hijo de .input-group escondido SIGUE siendo el último
   hijo: Bootstrap le quita al input las esquinas redondeadas de la derecha
   con un `:not(:last-child)`, y el campo vacío se vería cortado al aire.
   De ahí que haya que devolvérselas aquí a mano. */
.wd-grupo-enlace--vacio .wd-abrir-enlace {
    display: none;
}

.wd-grupo-enlace--vacio .form-control {
    border-top-right-radius: var(--bs-border-radius-sm);
    border-bottom-right-radius: var(--bs-border-radius-sm);
}

/* La pestaña "Actividad" del popup de la oportunidad (ver
   opportunities/_actividad.php y assets/js/opportunity-actividad.js):
   la ficha a la izquierda y la línea de tiempo a la derecha. El fondo
   gris lo pone su columna (.wd-opp-modal-contenido, más arriba).

   Los colores van en variables propias de la pestaña y no en las de la
   paleta general: son los de cada TIPO de cosa que pasa (un correo, un
   evento, un cambio de etapa…), y se leen en el icono, en el filete de la
   tarjeta y en el filtro a la vez. Cada fila elige el suyo con --tone. */
.wd-actividad {
    --wd-act-mail: #2f6bd8;
    --wd-act-in: #0e8a73;
    --wd-act-meet: #b97a00;
    --wd-act-stage: #6a4ed6;
    --wd-act-task: #2e8b3e;
    --wd-act-note: #a2527a;
    --wd-act-data: #687385;
    --wd-act-bad: #c0392f;
    --wd-act-ink: #18212e;
    --wd-act-ink-2: #4a5566;
    --wd-act-ink-3: #6b7585;
    --wd-act-line: #dde2ea;
    --wd-act-line-2: #ebeef3;

    display: grid;
    grid-template-columns: 290px minmax(0, 1fr);
    gap: 1.75rem;
    align-items: start;
    color: var(--wd-act-ink);
    font-variant-numeric: tabular-nums;
}

/* Una encima de otra cuando el panel no da para las dos: por lo que mide
   el panel (el contenedor wd-opp-panel) y no la ventana, porque el menú
   del popup se come parte del ancho. Por debajo de 800px a la línea le
   quedarían menos de 500. */
@container wd-opp-panel (max-width: 800px) {
    .wd-actividad {
        grid-template-columns: minmax(0, 1fr);
        gap: 1rem;
    }
}

/* --- La ficha ---------------------------------------------------- */
/* Fija mientras se recorre la línea, y con su propio scroll si no cabe:
   una ficha pegada más alta que el modal dejaría los contactos fuera de
   la vista para siempre. El top deja el mismo aire que el padding del
   panel, para que no se pegue a la cabecera del popup. */
.wd-act-ficha {
    position: sticky;
    top: 1rem;
    max-height: calc(100vh - 9rem);
    overflow-y: auto;
    background: #fff;
    border: 1px solid var(--wd-act-line);
    border-radius: 10px;
    padding: 1.1rem;
}

@container wd-opp-panel (max-width: 800px) {
    .wd-act-ficha {
        position: static;
        max-height: none;
    }
}

.wd-act-nombre {
    font-size: 1.35rem;
    line-height: 1.25;
    font-weight: 700;
    margin: 0 0 0.3rem;
    overflow-wrap: anywhere;
}

.wd-act-vinculo {
    margin: 0 0 1rem;
    font-size: 0.95rem;
}

.wd-act-vinculo a {
    color: var(--wd-act-ink-2);
    text-decoration: none;
}

.wd-act-vinculo a:hover {
    text-decoration: underline;
}

.wd-act-facts {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.75rem 0.9rem;
    margin: 0 0 0.5rem;
}

.wd-act-facts > div {
    min-width: 0;
}

.wd-act-facts dt {
    font-size: 0.85rem;
    font-weight: 400;
    color: var(--wd-act-ink-3);
}

.wd-act-facts dd {
    margin: 0;
    font-weight: 600;
    overflow-wrap: anywhere;
}

.wd-act-facts dd small {
    display: block;
    font-weight: 400;
    color: var(--wd-act-ink-3);
}

.wd-act-facts .big {
    font-size: 1.25rem;
}

.wd-act-score {
    display: flex;
    gap: 3px;
    margin-top: 4px;
}

.wd-act-score i {
    width: 9px;
    height: 6px;
    border-radius: 2px;
    background: var(--wd-act-line);
}

.wd-act-score i.on {
    background: var(--wd-act-task);
}

.wd-act-h2 {
    font-family: var(--wd-font-sans);
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--wd-act-ink-3);
    margin: 1.1rem 0 0.5rem;
}

.wd-act-texto {
    font-size: 0.95rem;
    margin: 0;
}

.wd-act-alertas {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 6px;
}

.wd-act-alertas li {
    font-size: 0.92rem;
    padding: 0.45rem 0.65rem;
    border-radius: 6px;
    background: color-mix(in srgb, var(--wd-act-meet) 13%, transparent);
}

.wd-act-alertas li.ok {
    background: color-mix(in srgb, var(--wd-act-task) 12%, transparent);
}

.wd-act-alertas li.info {
    background: var(--wd-act-line-2);
}

.wd-act-recorrido {
    list-style: none;
    margin: 0;
    padding: 0;
}

.wd-act-recorrido li {
    position: relative;
    padding: 0 0 0.6rem 1.2rem;
    font-size: 0.92rem;
}

.wd-act-recorrido li::before {
    content: "";
    position: absolute;
    left: 3px;
    top: 0.45em;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--wd-act-stage);
}

.wd-act-recorrido li:not(:last-child)::after {
    content: "";
    position: absolute;
    left: 6px;
    top: calc(0.45em + 8px);
    bottom: 0;
    width: 1px;
    background: var(--wd-act-line);
}

.wd-act-recorrido li:last-child {
    font-weight: 600;
}

.wd-act-recorrido small,
.wd-act-contactos span {
    display: block;
    font-weight: 400;
    color: var(--wd-act-ink-3);
    overflow-wrap: anywhere;
}

.wd-act-contactos {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.92rem;
    display: grid;
    gap: 0.4rem;
}

/* --- Controles y cifras ------------------------------------------ */
.wd-act-bar {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    margin-bottom: 0.4rem;
}

.wd-act-h {
    font-family: var(--wd-font-sans);
    font-size: 1.2rem;
    font-weight: 600;
    margin: 0 auto 0 0;
}

.wd-act-chip {
    border: 1px solid var(--wd-act-line);
    background: #fff;
    border-radius: 999px;
    padding: 0.25rem 0.8rem;
    font-size: 0.92rem;
    display: inline-flex;
    gap: 0.4rem;
    align-items: center;
    color: var(--wd-act-ink);
}

.wd-act-chip b {
    font-weight: 600;
    color: var(--wd-act-ink-3);
}

.wd-act-chip[aria-pressed="false"] {
    opacity: 0.5;
    text-decoration: line-through;
}

.wd-act-chip .sw {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--tone);
}

/* Las cifras, en dos filas: el número de columnas lo pone
   opportunity-actividad.js en --cols según cuántas haya. */
.wd-act-stats {
    display: grid;
    grid-template-columns: repeat(var(--cols, 7), minmax(0, 1fr));
    gap: 1.1rem 0;
    margin: 0.8rem 0 1.2rem;
    padding: 1rem 0.5rem;
    background: #fff;
    border: 1px solid var(--wd-act-line);
    border-radius: 10px;
}

.wd-act-stats div {
    padding: 0 0.75rem;
    font-size: 0.85rem;
    line-height: 1.3;
    color: var(--wd-act-ink-3);
}

/* En una fila, la etiqueta pasa a dos líneas cuando no cabe; la raya
   separa una cifra de la siguiente. */
.wd-act-stats div + div {
    border-left: 1px solid var(--wd-act-line);
}

/* Con el panel estrecho (ficha encima de la línea, móvil), siete
   columnas dejarían cada etiqueta partida en trozos: ahí, de dos en
   dos y sin rayas. */
@container wd-opp-panel (max-width: 560px) {
    .wd-act-stats {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1.1rem 1rem;
    }

    .wd-act-stats div + div {
        border-left: 0;
    }
}

.wd-act-stats strong {
    display: block;
    font-size: 1.3rem;
    color: var(--wd-act-ink);
    font-weight: 600;
}

/* --- La línea ---------------------------------------------------- */
.wd-act-dia {
    margin: 0 0 0.5rem;
}

/* El día se queda arriba mientras se recorren sus eventos, con el fondo
   del cuerpo del modal —que es el que hace scroll— para que las tarjetas
   no se vean pasar por debajo. El top negativo es el padding del
   .modal-body: sin él se quedaría un hueco por encima. */
.wd-act-dia-h {
    position: sticky;
    top: -1rem;
    z-index: 2;
    background: #f3f5f8;
    padding: 0.5rem 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 0.6rem;
}

.wd-act-dia-h h4 {
    font-family: var(--wd-font-sans);
    margin: 0;
    font-size: 1rem;
    font-weight: 600;
}

.wd-act-dia-h h4::first-letter {
    text-transform: uppercase;
}

.wd-act-dia-h span {
    font-size: 0.9rem;
    color: var(--wd-act-ink-3);
}

.wd-act-silencio {
    margin: 0.8rem 0 1.1rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--wd-act-bad);
    font-size: 0.9rem;
    font-weight: 500;
}

.wd-act-silencio::before,
.wd-act-silencio::after {
    content: "";
    flex: 1;
    border-top: 1px dashed color-mix(in srgb, var(--wd-act-bad) 50%, transparent);
}

.wd-act-evs {
    list-style: none;
    margin: 0;
    padding: 0;
}

.wd-act-ev {
    display: grid;
    grid-template-columns: 42px 28px minmax(0, 1fr);
    gap: 0 0.6rem;
}

.wd-act-ev time {
    font-size: 0.9rem;
    color: var(--wd-act-ink-3);
    padding-top: 0.55rem;
    text-align: right;
}

.wd-act-rail {
    position: relative;
    display: flex;
    justify-content: center;
}

.wd-act-rail::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--wd-act-line-2);
}

.wd-act-ico {
    position: relative;
    margin-top: 6px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--tone);
    color: var(--tone);
    display: grid;
    place-items: center;
    font-size: 0.8rem;
}

.wd-act-card {
    background: #fff;
    border: 1px solid var(--wd-act-line);
    border-left: 3px solid var(--tone);
    border-radius: 6px;
    padding: 0.55rem 0.8rem;
    margin: 4px 0 0.6rem;
    min-width: 0;
}

.wd-act-card.muted {
    opacity: 0.62;
}

.wd-act-ev-head {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 0.6rem;
    align-items: baseline;
    margin: 0;
}

.wd-act-ev-head strong {
    font-size: 0.95rem;
    color: var(--tone);
}

.wd-act-ev-head .who {
    font-size: 0.85rem;
    color: var(--wd-act-ink-3);
}

.wd-act-subj {
    font-weight: 600;
    margin: 0.2rem 0 0;
    overflow-wrap: anywhere;
}

.wd-act-route,
.wd-act-nota {
    font-size: 0.85rem;
    color: var(--wd-act-ink-3);
    margin: 0.15rem 0 0;
    overflow-wrap: anywhere;
}

/* Dos líneas y "Ver más". `pre-line` porque el resumen de un evento
   finalizado y las notas traen saltos de línea que hay que respetar. */
.wd-act-snip {
    margin: 0.35rem 0 0;
    color: var(--wd-act-ink-2);
    white-space: pre-line;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.wd-act-snip.open {
    -webkit-line-clamp: unset;
    display: block;
}

.wd-act-link {
    background: none;
    border: 0;
    padding: 0;
    margin: 0.25rem 0.9rem 0 0;
    color: var(--wd-act-mail);
    font-size: 0.9rem;
}

.wd-act-link:hover {
    text-decoration: underline;
}

.wd-act-detalle {
    margin: 0.25rem 0 0;
    padding-left: 1rem;
    font-size: 0.9rem;
    color: var(--wd-act-ink-2);
    overflow-wrap: anywhere;
}

.wd-act-etapa {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.3rem;
    font-size: 0.92rem;
}

.wd-act-etapa .pill {
    padding: 0.1rem 0.6rem;
    border-radius: 999px;
    background: var(--wd-act-line-2);
    font-weight: 500;
}

.wd-act-etapa .pill.to {
    background: color-mix(in srgb, var(--wd-act-stage) 16%, transparent);
}

.wd-act-cambios {
    width: 100%;
    border-collapse: collapse;
    margin-top: 0.3rem;
    font-size: 0.92rem;
}

.wd-act-cambios td {
    padding: 0.15rem 0;
    vertical-align: top;
    overflow-wrap: anywhere;
}

.wd-act-cambios td:first-child {
    color: var(--wd-act-ink-3);
    width: 34%;
    padding-right: 0.6rem;
}

.wd-act-cambios del {
    color: var(--wd-act-ink-3);
}

.wd-act-recips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 0.35rem;
}

.wd-act-recips span {
    font-size: 0.85rem;
    padding: 0.05rem 0.5rem;
    border-radius: 999px;
    background: var(--wd-act-line-2);
}

.wd-act-vacio {
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--wd-act-ink-3);
}
