/**
 * Trexia shell & components — layout del prototipo con tokens de color existentes.
 * Usa var(--color-*) de color-tokens.scss / project bundle (sin cambiar paleta).
 */

:root {
  --trexia-sidebar-width: 236px;
  /* Alias hacia el DS; el valor vive en trexia-tokens.css. */
  --trexia-topbar-height: var(--topbar-height, 58px);
  /* 10px no existía en el DS, que topea en --radius-xl 4px: el golden es
     prácticamente recto y a 10px el shell se leía como un producto más blando
     que la vista de al lado. */
  --trexia-radius: var(--radius-xl);
  /* #0f2137 existed in no token file — a saturated navy competing with the
     desaturated slate ramp of the DS. --blue-900 is the ramp's darkest step. */
  --trexia-sidebar-bg: var(--blue-900, #182530);
  --trexia-sidebar-text: #aab4c8;
  --trexia-content-bg: var(--color-bg-muted, #f9fafb);
  --trexia-line: var(--color-border, #e5e7eb);
  --trexia-card-bg: var(--color-bg, #ffffff);
  --trexia-text: var(--color-text, #000000);
  --trexia-text-muted: var(--color-text-muted, #6b7280);
  --trexia-accent: var(--color-primary, #2563eb);
  --trexia-accent-soft: var(--color-primary-soft, #eff6ff);

  /* Tableros (Siguiendo/Pipeline y Tareas) comparten estas medidas: columna,
     cabecera y tarjeta. Viven acá y no en trexia-tokens.css porque ese archivo
     es copia del DS, y company-tasks.css no carga en Siguiendo/Pipeline. */
  --trexia-board-col-width: 260px;
  --trexia-board-col-radius: var(--radius-md);
  --trexia-board-col-bg: var(--surface-page);
  --trexia-board-col-pad: 10px;
  --trexia-board-head-size: 13px;
  --trexia-board-head-weight: 700;
  --trexia-board-card-radius: var(--radius-md);
}

/* ===== SHELL ===== */
.trexia-shell {
  display: flex;
  min-height: 100vh;
  background: var(--trexia-content-bg);
  color: var(--trexia-text);
}

.trexia-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin-left: var(--trexia-sidebar-width);
}

.trexia-content {
  flex: 1;
  overflow: auto;
  /* Was 28px 32px 60px — none of the three on the 8pt grid, and the 28px
     inline value disagreed with the topbar's, so the bar overhung the content
     column. Both now share --space-8. */
  padding: var(--space-6) var(--space-8) var(--space-16);
}

/* Bootstrap's .container-fluid adds its own 12px inside the content padding, so
   cards stopped 12px short of where the topbar's own content ended — the 13px
   overhang between the two most prominent right-aligned elements on the page.
   One gutter, owned by .trexia-content. */
.trexia-content > .container-fluid,
.trexia-content > * > .container-fluid {
  padding-inline: 0;
}

/* ===== SIDEBAR ===== */
.trexia-sidebar {
  width: var(--trexia-sidebar-width);
  background: var(--trexia-sidebar-bg);
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  padding: 20px 0;
  position: fixed;
  top: 0;
  left: 0;
  /* 100dvh evita el recorte con chrome del browser (100vh a veces queda
     corto y el nav no entra: scrollbar interna con poco contenido visible). */
  height: 100vh;
  height: 100dvh;
  min-height: 100vh;
  min-height: 100dvh;
  z-index: 1000;
  overflow-x: hidden;
  overflow-y: auto;
  box-sizing: border-box;
}

.trexia-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 22px 22px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  margin-bottom: 14px;
}

.trexia-brand .mark {
  width: 30px;
  height: 30px;
  border-radius: 7px;
  background: var(--trexia-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 800;
  font-size: 14px;
  flex-shrink: 0;
}

.trexia-brand .name {
  color: #fff;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
}

.trexia-brand .tag {
  color: #8e9bb0;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.trexia-nav-item {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 22px;
  color: var(--trexia-sidebar-text);
  font-size: 13.5px;
  font-weight: 500;
  border-left: 3px solid transparent;
  transition: 0.15s;
  text-decoration: none;
}

.trexia-nav-item svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}

.trexia-nav-item:hover {
  background: rgba(255, 255, 255, 0.04);
  color: #fff;
}

.trexia-nav-item.active {
  color: #fff;
  /* Was rgba(37, 99, 235, 0.15) — Tailwind blue at 15% over the navy, which
     computed to #122b51, a value in no token file. */
  background: var(--blue-800, #203241);
  border-left-color: var(--trexia-accent);
}

.trexia-nav-item.active svg {
  color: var(--trexia-accent);
}

.trexia-nav-sub {
  padding-left: 2.75rem;
  font-size: 12.5px;
  color: rgba(170, 180, 200, 0.75);
}

.trexia-nav-sub.active {
  color: #fff;
  font-weight: 700;
}

.trexia-nav-badge {
  margin-left: auto;
  background: var(--trexia-accent);
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  border-radius: 10px;
  padding: 1px 6px;
}

.trexia-sidebar-country {
  padding: 0 22px 14px;
  margin-bottom: 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.trexia-sidebar-country .dropdown-toggle {
  color: #fff;
  font-size: 13px;
}

/* Bootstrap's .btn-outline-secondary draws its border from $secondary, which
   lands on #5b5f63 (--ink-600, a *text* token). That gave a secondary filter
   toggle the heaviest outline on the page — heavier than the card holding the
   whole dataset, whose rule is --border-subtle. */
.trexia-content .btn-outline-secondary {
  border-color: var(--border-default);
  color: var(--text-body);
}

.trexia-content .btn-outline-secondary:hover {
  background: var(--surface-sunken);
  border-color: var(--border-strong);
  color: var(--text-strong);
}

/* ===== TOPBAR ===== */
.trexia-topbar {
  background: #fff;
  border-bottom: 1px solid var(--trexia-line);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Shares the content column's inline gutter so the bar stops overhanging it. */
  padding: 0 var(--space-8);
  height: var(--trexia-topbar-height);
  flex-shrink: 0;
}

.trexia-topbar-title {
  font-size: 13.5px;
  font-weight: var(--weight-medium);
  color: var(--trexia-text);
}

/* El h1 de la página vive en la barra: hereda el tamaño del contenedor en vez
   de traer el de un h1 suelto, y sin margen para no descentrarlo verticalmente
   en los 58px de la barra. */
.trexia-topbar-heading {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  line-height: inherit;
  color: inherit;
}

.trexia-user {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--trexia-sidebar-bg);
}

.trexia-avatar {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--trexia-sidebar-bg);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  flex-shrink: 0;
}

/* ===== PAGE ===== */
/* 21px no era un escalón de la escala (--font-size-lg 20 / --font-size-xl 24) y el
   margen de 3px estaba fuera de grilla. */
.trexia-page-title {
  font-size: var(--font-size-xl);
  margin: 0 0 var(--space-1);
  color: var(--trexia-text);
}

/* Fila de título: el control opcional de la vista va acá, como el "Filtros" del
   golden. Antes vivía en su propia banda de 86px entre los KPIs y el encabezado
   de sección, que no pertenecía a ninguno de los dos bloques y apilaba dos
   controles alineados a la derecha a 25px uno del otro. */
.trexia-page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
}

.trexia-page-head > .trexia-btn {
  flex-shrink: 0;
  /* 39.7px contra los 31.7 del "Filtros" del golden, que es el mismo rol de
     control secundario en la misma posición. El alto lo hacía el padding
     vertical de --space-2; con --space-1 cae en 31.7 clavado. Acotado a la
     cabecera del dashboard: el mismo .trexia-btn viste la topbar, que tiene su
     propia métrica. En mobile lo vuelve a subir el mínimo táctil, más abajo. */
  padding-block: var(--space-1);
}

.trexia-page-sub {
  font-size: var(--font-size-sm);
  color: var(--text-muted);
  margin-bottom: var(--space-6);
}

/* ===== KPI ===== */
.trexia-kpi-row {
  display: flex;
  /* 14px no estaba en la escala. */
  gap: var(--space-4);
  margin-bottom: var(--space-8);
  flex-wrap: wrap;
}

/* Las cinco tarjetas eran slabs --blue-900, la misma tinta que el sidebar: el
   valor más oscuro del producto gastado en conteos secundarios, y con eso el
   valor dejaba de codificar capa (chrome fijo vs contenido en flujo). El golden
   reserva esa tinta al sidebar y mantiene el plano de contenido claro.
   Columna flex + margin-top:auto en .num porque el eyebrow "ANALIZADAS POR IA"
   sólo existe en dos de las cinco y empujaba sus cifras ~25px por debajo de la
   baseline de sus vecinas dentro de la misma fila. */
.trexia-kpi {
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: var(--trexia-radius);
  padding: var(--space-5);
  color: var(--text-strong);
  min-width: 140px;
  flex: 1;
  display: flex;
  flex-direction: column;
}

.trexia-kpi .num {
  font-size: var(--font-size-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  margin-top: auto;
  margin-bottom: var(--space-1);
}

/* Como flex child, .num hereda min-width:auto del tamaño mínimo automático de
   flexbox: un valor largo sin salto de línea (Bootstrap .text-truncate) puede
   forzar el ancho de la tarjeta en vez de truncarse con ellipsis. */
.trexia-kpi .num.text-truncate {
  min-width: 0;
}

.trexia-kpi .lbl {
  font-size: var(--font-size-xs);
  color: var(--text-muted);
}

/* El eyebrow era el único gradiente del producto
   (linear-gradient(135deg, --blue-900 55%, --brand 130%)), que además hacía que
   dos tarjetas se leyeran de otro color que sus tres vecinas de la misma fila.
   Sobre superficie clara el acento se sostiene solo. */
.trexia-kpi .tagline {
  font-size: var(--font-size-xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--brand);
  font-weight: var(--weight-semibold);
  margin-bottom: var(--space-2);
  display: block;
}

/* ===== BADGES ===== */
.trexia-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  /* 11px / 3px 10px / 20px: ninguno en la escala. El pill se reserva a los
     badges, que es el único lugar del DS donde --radius-pill aplica. */
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  white-space: nowrap;
  /* Sin altura de línea propia heredaba --leading-normal (1.55), que sobre
     12px daba una píldora de 26.6px: un 60% más alta que la del golden para la
     misma cadena. El golden llega ahí con 10px de tipografía, que no está en la
     escala; el ajuste que sí es de token es la interlínea. */
  line-height: var(--leading-tight);
}

.trexia-badge .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.trexia-badge.vigente {
  background: var(--trexia-accent-soft);
  color: var(--trexia-accent);
}

/* Urgency has to be readable at a glance: painting `atencion` blue made
   "Vence mañana" and "Faltan 21 días" identical. compute_deadline_urgency
   already emits the right variant — only the paint was wrong.

   The hue rides on the fill and the dot, not the text: --warn on --warn-soft
   is 2.52:1 and --bad on --bad-soft is 3.55:1, both under the 4.5:1 floor for
   10px text. --text-strong on the same fills gives 13.6:1 and 12.7:1 while
   keeping the semantic colour doing the signalling.
   See .design/proposed-tokens.md — this palette has no amber dark enough to
   carry small text. */
.trexia-badge.atencion {
  background: var(--warn-soft);
  /* Vuelve el texto teñido: el escalón 700 despeja 4.5:1 sobre este fondo,
     que es lo que el 600 no lograba y obligó a la tinta neutra. */
  color: var(--warn-strong);
}

.trexia-badge.atencion .dot {
  background: var(--warn);
}

.trexia-badge.vencido {
  background: var(--bad-soft);
  /* Vuelve el texto teñido: el escalón 700 despeja 4.5:1 sobre este fondo,
     que es lo que el 600 no lograba y obligó a la tinta neutra. */
  color: var(--bad-strong);
}

.trexia-badge.vencido .dot {
  background: var(--bad);
}

.trexia-badge.archivado {
  background: var(--surface-sunken);
  color: var(--text-muted);
}

.trexia-badge.informativo {
  background: var(--trexia-accent-soft);
  color: var(--trexia-accent);
}

/* Puente visual (TRE-999): que un `badge bg-*` / `text-bg-*` de Bootstrap se
   vea como `.trexia-badge` sin tocar los ~60 templates que todavía usan la
   clase vieja — migrarlos al include components/trexia/badge.html es la
   tanda 2. Bootstrap pinta `.bg-*` con `!important` y `--bs-bg-opacity`, así
   que el puente necesita `!important` en background-color/color para
   ganarle; el resto de las propiedades ya gana por especificidad sobre el
   `.badge` de project.scss (dos clases contra una).
   Se excluyen los contadores, que no son un badge de estado y no deben
   cambiar de tamaño: `.kanban-count` (columna del kanban), cualquier badge
   dentro de un `.nav-link` (los números de las pestañas Requisitos/Ítems/
   Modificaciones comparten bg-secondary con la variante "archivado") y
   `.trexia-filter-btn > .badge` (el contador de esquina de los toolbars de
   filtro, que ya trae su propio font-size/line-height/padding reducido en
   la regla de arriba — sin esta exclusión, las cinco clases de :not() de
   este puente (especificidad 0,5,0) le ganarían a esa regla de dos clases
   (0,2,0) y el contador de esquina pasaría a medir lo mismo que una
   píldora de estado). */
.badge.bg-success:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-success:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-warning:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-warning:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-danger:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-danger:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-secondary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-secondary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-light:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-info:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-primary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-info:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-primary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  display: inline-flex;
  align-items: center;
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  line-height: var(--leading-tight);
  text-transform: none;
  letter-spacing: normal;
}

.badge.bg-success:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-success:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  background-color: var(--trexia-accent-soft) !important;
  color: var(--trexia-accent) !important;
}

.badge.bg-warning:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-warning:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  background-color: var(--warn-soft) !important;
  color: var(--warn-strong) !important;
}

.badge.bg-danger:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-danger:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  background-color: var(--bad-soft) !important;
  color: var(--bad-strong) !important;
}

.badge.bg-secondary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-secondary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-light:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  background-color: var(--surface-sunken) !important;
  color: var(--text-muted) !important;
}

.badge.bg-info:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.bg-primary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-info:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ),
.badge.text-bg-primary:not(.kanban-count):not(.nav-link .badge):not(
    .trexia-filter-btn > .badge
  ) {
  background-color: var(--trexia-accent-soft) !important;
  color: var(--trexia-accent) !important;
}

/* ===== SECTIONS & CARDS ===== */
.trexia-sec-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--space-4);
}

.trexia-sec-head .title {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-base);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}

/* #f3f4f6 era un gris frío sin token detrás, sobre la página cálida --ink-25.
   Y --surface-sunken está a un par de puntos de --surface-page, así que la
   píldora no se veía: quedaba un número flotando con 8px de hueco sin
   explicación. Sin fondo ni radio, el gap de .title ya lo separa. */
.trexia-sec-head .count {
  color: var(--text-muted);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-medium);
}

.trexia-sec-head .all {
  font-size: var(--font-size-sm);
  color: var(--text-link);
  font-weight: var(--weight-medium);
  text-decoration: none;
}

.trexia-sec-head .all:hover {
  text-decoration: underline;
}

/* 14px y 34px no estaban en la escala, y 14 además discrepaba por 2px del
   --space-4 de la fila de KPIs: dos grillas en la misma pantalla con canaletas
   distintas. */
.trexia-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}

/* El alto uniforme se compraba reservando SIEMPRE dos filas de píldoras (ver
   más abajo), y con align-content: end el sobrante caía entre el texto y las
   píldoras: medido, 43px de blanco a mitad de tarjeta en las seis del
   dashboard contra 8px en las que sí llevan dos filas. Tres huecos distintos
   en la misma grilla.
   Acá la altura la iguala la fila de la grilla —stretch— y el sobrante lo
   absorbe el cuerpo, que ya trae .flex-grow-1. Así sólo queda hueco cuando la
   fila mezcla tarjetas de una y de dos píldoras, que es cuando es inevitable.
   Va acotado a .trexia-cards, que sólo usa el dashboard: el tablero y el
   pipeline apilan .opp-card en una columna flex, donde no hay fila que iguale
   nada, y ahí la reserva en em sigue siendo lo único que los mantiene parejos
   (verificación 113). */
.trexia-cards .opp-card {
  display: flex;
  flex-direction: column;
}

.trexia-cards .opp-card > .d-flex.flex-wrap {
  height: auto;
  align-content: start;
}

/* --trexia-line resuelve a --color-border, un gris frío, mientras las tarjetas
   KPI 90px arriba usan --border-subtle, cálido: dos bordes haciendo el mismo
   trabajo en la misma columna sobre la página cálida --ink-25. */
.opp-card {
  border-radius: var(--trexia-radius) !important;
  border: 1px solid var(--border-subtle) !important;
  background: var(--surface-card) !important;
  box-shadow: none !important;
  transition: 0.15s;
}

/* La tarjeta insetaba su contenido 12px contra una canaleta de grilla de 16 y
   un gutter de página de 32: el valor más apretado de la pantalla era el que
   envuelve el contenido más denso, y encima quedaba 8px por debajo de los 20
   de las tarjetas KPI, que están en la misma columna.
   Las utilidades de espaciado de Bootstrap (.px-3, .pt-3, .pb-3) salen con
   !important, así que la regla sin él se escribe y no se aplica. */
.opp-card > .opp-card-header,
.opp-card > .flex-grow-1,
.opp-card > .d-flex.flex-wrap {
  padding-inline: var(--space-4) !important;
}

.opp-card > .opp-card-header {
  padding-top: var(--space-4) !important;
}

.opp-card > .d-flex.flex-wrap {
  padding-bottom: var(--space-4) !important;
}

/* Los iconos del encabezado son hijos de un contenedor flex y no declaraban
   flex-shrink: la estrella, que va suelta y no dentro de un botón, se aplastaba
   a 7x16 en mobile cuando sus vecinos pasaron al mínimo táctil de 44px. El
   atributo width decía 16 y el navegador dibujaba 7. */
.opp-card-header svg {
  flex: none;
}

/* Alto uniforme de la tarjeta.
 *
 * Medían entre 170 y 244px en la misma pantalla, por tres variables que se
 * combinan: el objeto entra en una o dos líneas, el organismo envuelve o no, y
 * las dos píldoras caen en una fila o en dos. Ocho combinaciones, cuatro
 * alturas distintas una al lado de otra.
 *
 * Se le reserva a cada bloque su caso más alto, en em para que siga a la
 * tipografía y no a un píxel fijo. Con `height` y no `min-height`: con el
 * mínimo, una línea se pasaba un píxel del redondeo y dejaba tarjetas de 241
 * junto a otras de 244.
 *
 * Vive acá y no en el <style> del kanban porque la misma tarjeta la incluyen
 * el tablero, la grilla de búsqueda y el dashboard — arreglarla sólo en el
 * kanban dejaba el dashboard como estaba. */
.opp-card > .px-3 > h4 {
  /* .lh-sm de Bootstrap es 1.25; dos líneas del clamp que el markup ya trae. */
  height: 2.5em;
}

.opp-card > .px-3 > p.small {
  /* Sin tope, un organismo largo era la tarjeta más alta de la pantalla. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  height: 3em;
}

/* Dos filas de píldoras: dos alturas, el gap entre filas y el padding vertical
   del bloque (pt-1 + pb-3). "Faltan N días para apertura" no entra al lado del
   estado, así que la segunda fila hay que reservarla siempre.
   Con align-content: end las píldoras se apoyan en el fondo de la tarjeta y la
   fila que sobra queda arriba: el hueco de las tarjetas de una sola píldora
   cae entre el texto y ellas, no debajo. */
.opp-card > .d-flex.flex-wrap {
  height: calc(2 * 1.65em + 0.5rem + 1.25rem);
  align-content: end;
}

.opp-card:hover {
  box-shadow: var(--shadow-md) !important;
  transform: translateY(-1px);
  border-color: var(--border-default) !important;
}

/* Con el menú abierto la tarjeta no puede tener transform: un transform (incluido
   el del hover) hace de bloque contenedor del panel fixed y lo vuelve a recortar. */
.opp-card.opp-card--menu-open {
  transform: none;
}

/* El sujeto de la tarjeta es el objeto de la compra, no el número de
   expediente. La metadata iba en acento a peso 700 y el objeto en tinta plana a
   regular, así que el ojo caía primero en "D193794A /2026" en las seis
   tarjetas. El acento además aparecía tres veces por tarjeta (tipo, número,
   badge EN TRÁMITE) y el badge dejaba de leerse como estado. */
.opp-card .opp-type,
.opp-card .opp-num {
  /* Seguía siendo la línea más pesada de la tarjeta: 13.125px a peso 500 en
     --ink-950, contra un objeto de 15px a peso 400 en la MISMA tinta. Bajarle
     el acento no alcanzó — lo que la hacía leerse como titular era el peso y
     la tinta, no el color. */
  font-size: var(--font-size-xs);
  font-weight: var(--weight-regular);
  color: var(--text-muted);
}

/* 16px contra los 15px del encabezado de sección: un píxel de diferencia entre
   el título que gobierna y las seis tarjetas gobernadas. Se queda en 15px por
   eso mismo; lo que lo vuelve el sujeto de la tarjeta es el peso, que estaba
   en 400 — el mismo que el organismo y la fecha. */
.opp-card h4 {
  font-size: var(--font-size-base);
  font-weight: var(--weight-semibold);
  color: var(--text-strong);
}

/* .small de Bootstrap es 0.875em, que sobre 15px da 13.125px: un tamaño que no
   está en la escala. Y .text-muted resuelve a --ink-800 al 75% de alfa, que
   compone rgb(103,105,107) — un gris que no es ningún token, a un paso del
   --ink-600 que usan el subtítulo de la página y las etiquetas de los KPI a
   90px de distancia. Cuatro líneas en dos tintas pasan a tres niveles.

   El !important no es decorativo: .text-muted es una utilidad de Bootstrap y
   las utilidades salen con !important, así que sin él la regla se escribe y no
   se aplica. Medido con getComputedStyle, no leído del CSS. */
.opp-card > .flex-grow-1 > p.small {
  font-size: var(--font-size-sm);
  color: var(--text-body) !important;
}

.opp-card > .flex-grow-1 > .d-flex.small {
  font-size: var(--font-size-xs);
  color: var(--text-muted) !important;
}

/* El grupo estrella / marcador / ··· tenía tres pesos de tinta en 60px:
   opacity-25 (se leía deshabilitado), acento sólido, y opacity-75. */
.opp-card .opp-icon-idle {
  color: var(--border-strong);
}

.opp-card .opp-icon-muted {
  color: var(--text-muted);
}

/* El año de la tarjeta de pipeline llevaba el acento en un style inline con el
   hex crudo #2563eb, que no es el acento: --trexia-accent resuelve a #3e5a72. */
.opp-card .opp-year {
  font-weight: 500;
  color: var(--trexia-accent);
}

/* La estrella inactiva se dibujaba como una mancha sólida: el marcador de al
   lado, que está en el mismo grupo y en el mismo estado, va en contorno. Una
   estrella llena además se lee como "ya destacada", que es un estado que no
   está puesto. El path es cerrado, así que el contorno sale de CSS — no hace
   falta dibujo nuevo ni tocar el template.
   1.5 no es un token: el DS no tipifica grosores de trazo. Queda anotado en
   .design/proposed-tokens.md; el marcador vecino usa stroke-width 2 sobre un
   viewBox de 24, o sea 1.33px efectivos, y 1.5 sobre el viewBox de 16 de la
   estrella es lo más cerca que se llega sin inventar una escala. */
.opp-card svg.opp-icon-idle {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}

/* Dos píldoras azules por tarjeta: el estado y el plazo salían las dos en
   --trexia-accent-soft / --trexia-accent, así que una cuenta regresiva pesaba
   lo mismo que el estado del trámite y el acento terminaba decorando en vez de
   señalar. En el golden la misma cadena va como texto plano.
   El selector es el hermano adyacente y no :last-child: cuando la urgencia no
   existe, el chip de estado ES el último, y la regla lo despintaría a él. El
   estado va siempre primero en el template, así que "badge que sigue a otro
   badge" es exactamente la píldora de plazo. Las ámbar (atencion/vencido)
   quedan como están: ahí el color sí es la señal.

   Van las dos variantes porque la píldora de plazo es `vigente`, no
   `informativo` — y las dos pintan exactamente igual (--trexia-accent-soft
   sobre --trexia-accent), así que en la captura son indistinguibles. La
   primera versión de esta regla nombraba sólo `informativo`, no matcheaba
   ningún elemento del DOM y el chip seguía azul. Verificado en el CSSOM, no
   mirando la pantalla. */
.opp-card .trexia-badge + .trexia-badge.informativo,
.opp-card .trexia-badge + .trexia-badge.vigente {
  background: none;
  color: var(--text-muted);
  padding-inline: 0;
}

.opp-card .trexia-badge + .trexia-badge.informativo .dot,
.opp-card .trexia-badge + .trexia-badge.vigente .dot {
  background: var(--ink-300);
}

/* Con Tab real, el primer control de la tarjeta no mostraba nada: outline-style
   medía `none` estando enfocado, mientras el botón de la cabecera de página sí
   dibujaba su anillo. La regla global .btn:focus-visible existe desde hace tres
   iteraciones y la pierde .btn-link, que trae el outline apagado desde
   Bootstrap. Mismo token, más especificidad. */
.opp-card .btn:focus-visible,
.opp-card a:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* ===== PIPELINE ===== */
.trexia-board {
  display: flex;
  gap: 16px;
  overflow-x: auto;
  padding-bottom: 10px;
}

.trexia-col {
  width: var(--trexia-board-col-width);
  background: var(--trexia-board-col-bg);
  border-radius: var(--trexia-board-col-radius);
  padding: var(--trexia-board-col-pad);
  flex-shrink: 0;
}

.trexia-col-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--trexia-board-head-size);
  font-weight: var(--trexia-board-head-weight);
  color: var(--trexia-sidebar-bg);
  padding: 4px 6px 10px;
}

/* Tarjeta del tablero de Siguiendo/Pipeline (.opp-card dentro de .kanban-card).
   Tareas usa .ct-board-card, con el mismo token, en company-tasks.css. El ancho
   no se fija: la tarjeta llena la columna, o sea col-width menos el padding.
   Lleva !important porque .opp-card (arriba) declara border-radius con !important:
   sin él, esta regla pierde aunque tenga más especificidad. */
.kanban-card .opp-card {
  border-radius: var(--trexia-board-card-radius) !important;
}

.trexia-col-head .n {
  margin-left: auto;
  background: #fff;
  color: var(--trexia-text-muted);
  border-radius: 10px;
  padding: 1px 8px;
  font-size: 11px;
}

/* Acá vivían ~145 líneas de un cluster de 'página de detalle' del DS
   (.trexia-pcard, .trexia-crumb, .trexia-detail-head, .trexia-metric*,
   .trexia-dtabs/.trexia-dtab, .trexia-panel, .trexia-field*, .trexia-ai-*)
   que nunca se cableó a ninguna plantilla: cero usos de cada clase. El
   componente ai-callout.html, único consumidor previsto de las últimas, se
   borra en este mismo commit por el mismo motivo. */

/* Bootstrap sets `outline: 0` on .btn and carries focus in a box-shadow instead,
   but the app already puts an elevation box-shadow on .btn, so the focus shadow
   never lands. Measured on the focused primary button of Future Opportunities:
   `outline-style: none` and no ring of any kind — the control is unreachable by
   eye when tabbing. The token layer's rule is on the bare `button` element and
   loses to `.btn` on specificity, so the ring is re-asserted here at class level. */
.btn:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* 12.5px y 7px no estaban en ninguna escala; el radio de 7px además era
   visiblemente más redondo que las cajas de al lado, que están en --radius-xl. */
.trexia-btn {
  font-size: var(--font-size-sm);
  font-weight: var(--weight-semibold);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-xl);
  border: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  text-decoration: none;
}

.trexia-btn-primary {
  background: var(--trexia-accent);
  color: #fff;
}

.trexia-btn-primary:hover {
  background: var(--color-primary-hover, #1d4ed8);
  color: #fff;
}

/* 1.4px de --blue-900 le daba al control secundario el contorno más oscuro y
   pesado de la columna de contenido, por encima del 1px --border-subtle de las
   tarjetas que sí tienen el contenido. */
.trexia-btn-secondary {
  background: var(--surface-card);
  border: 1px solid var(--border-default);
  color: var(--text-body);
}

/* El relleno de hover era --surface-sunken (#f2f1ec) sobre un botón blanco en
   una página --ink-25 (#f8f7f3): el botón se hundía hasta el color del fondo,
   así que el estado hover se leía MENOS interactivo que el de reposo. Medido a
   1:1, el recorte de hover y el de reposo eran indistinguibles. El botón
   mantiene su blanco y el cambio lo llevan el borde y la sombra, que es donde
   el golden lo lleva. */
.trexia-btn-secondary:hover {
  background: var(--surface-card);
  border-color: var(--border-strong);
  color: var(--text-strong);
  box-shadow: var(--shadow-sm);
}

/* ===== OPPORTUNITY TABS =====
   Las nueve pestañas del detalle eran nav-tabs de Bootstrap: subrayado bajo la
   activa y nada más. Con nueve ítems y seis de ellos con contador, el subrayado
   de 2px es la única señal de dónde estás en un strip de ~1000px.

   Píldoras, con el activo en blanco y el inactivo relleno. El orden importa y
   es lo que hizo falta medir: con `.ct-pill` tal cual del gestor -- inactivo
   --surface-sunken (#f2f1ec) y activo --brand-soft (#eef3f5) -- el activo es
   MÁS CLARO que el inactivo. Con dos píldoras se tolera; con nueve la jerarquía
   se invierte y la activa se lee como un hueco en la fila. Así que el inactivo
   sube a --ink-100 y el activo pasa a blanco con sombra, que es la forma que
   este producto ya usa para "seleccionado" (ver .trexia-btn-secondary:hover
   justo arriba: el cambio lo llevan el borde y la sombra).

   Esto vive acá y no en company-tasks.css porque esa hoja se carga sólo en las
   pantallas del gestor. trexia.css se carga en base_enhanced.html DESPUÉS del
   bundle de Bootstrap, así que estas reglas ganan por cascada con la misma
   especificidad y sin !important.

   Las reglas móviles de .opportunity-tabs viven más abajo, dentro de
   @media (max-width: 768px), y siguen ganando por venir después. Debajo de
   768px el strip además se oculta entero cuando mobile-section-list.js mete su
   lista, así que esto es de hecho el tratamiento de escritorio. */
.opportunity-tabs {
  /* El riel subrayado de Bootstrap: con píldoras, dos señales compitiendo. */
  border-bottom: 0;
  gap: var(--space-2);
  /* El <ul> lleva `overflow-auto` de Bootstrap (`overflow: auto !important`),
     así que es un contenedor de scroll y recorta la tinta en su caja de
     padding. Sin este hueco abajo se perdía la sombra de la píldora activa,
     que es una de las dos señales de "seleccionado". */
  padding-block-end: var(--space-1);
  /* Misma altura de fila: sin esto, al estrechar el strip el texto wrappea
     (3 líneas vs 1) y las píldoras quedan desparejas. */
  align-items: center;
  flex-wrap: nowrap;
}

.opportunity-tabs .nav-item {
  flex: 0 0 auto;
}

.opportunity-tabs .nav-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-sm);
  font-weight: var(--weight-semibold);
  color: var(--text-muted);
  background: var(--ink-100);
  /* Una sola línea → altura uniforme en todo el strip. */
  white-space: nowrap;
  /* Bootstrap deja `margin-bottom: -1px` para que la pestaña activa solape el
     riel subrayado. Este bloque borra ese riel, así que el -1px sólo bajaba la
     píldora un pixel y le empujaba el borde inferior fuera del recorte. */
  margin-bottom: 0;
}

.opportunity-tabs .nav-link:hover {
  background: var(--ink-200);
  color: var(--text-body);
  /* Sin esto sobrevive el borde de hover de Bootstrap
     (`--bs-nav-tabs-link-hover-border-color`), que dibuja un arco inferior
     más claro: residuo del diseño de subrayado, no de una píldora. */
  border-color: var(--ink-200);
}

.opportunity-tabs .nav-link.active {
  background: var(--surface-card);
  color: var(--text-strong);
  border-color: var(--border-default);
  box-shadow: var(--shadow-sm);
}

/* El anillo de foco, y es la razón por la que esta regla existe: Bootstrap
   dibuja el foco de teclado COMO box-shadow y apaga el nativo con `outline: 0`
   (`.nav-link:focus-visible`, especificidad 0,2,0). La regla `.active` de
   arriba es 0,3,0 y le pisaba ese box-shadow, así que la pestaña activa
   quedaba sin ningún indicador -- y es la ÚNICA tabulable, porque el plugin de
   Bootstrap pone `tabindex="-1"` en las inactivas. Mismo token y misma forma
   que .btn:focus-visible acá arriba; el offset va negativo, como en
   .section-row:focus-visible, porque el contenedor recorta lo que sale. */
.opportunity-tabs .nav-link:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: -2px;
}

/* Los badges NO se tocan: el bundle ya trae `.badge { border-radius: 999px }`,
   así que los contadores ya son pastillas en producción. Una regla acá sería un
   no-op. La nota previa que decía que se veían como "números sueltos" salió de
   un preview que no cargaba el bundle -- el mismo error que ya nos costó una
   ronda en el header del detalle. */

/* ===== TREXIA PILLS (selector de scope, TRE-991) =====
   Mismo look que `.ct-pill` de companies/tasks.html (selector "Mis
   tareas/Empresa"): inactivo solo texto, sin relleno (en Tareas el
   --surface-sunken coincide con el fondo de la página y se ve así; en
   Pipeline el fondo es otro y el relleno gris se notaba), activo
   --brand-soft. Esa
   jerarquía se invierte con muchas píldoras (ver nota de OPPORTUNITY TABS
   arriba), pero con dos -- el caso de Pipeline (Públicos/Privados) -- se
   tolera, así que acá se copia el valor literal en vez de la variante
   corregida de las tabs.

   Clases nuevas y genéricas (no `.ct-pill`) porque esa vive en
   company-tasks.css, que sólo carga en pantallas del gestor de tareas;
   duplicar el VALOR acá es más seguro que mover la regla, que tiene call
   sites de test y JS anclados a `.ct-pill` (ver company-tasks.css). */
.trexia-pills {
  display: inline-flex;
  gap: 6px;
  flex-wrap: wrap;
}

.trexia-pill {
  display: inline-flex;
  align-items: center;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  color: var(--text-body);
  background: transparent;
  border: 1px solid transparent;
}

.trexia-pill.is-active {
  background: var(--brand-soft);
  color: var(--blue-700);
}

/* ===== TOOLTIP =====
   Para los botones de toolbar que son solo ícono. El `title` nativo no sirve
   acá: tarda ~1 s en aparecer, no aparece con teclado ni en touch, y es
   diminuto -- en la revisión visual "no aparece el nombre on hover". Esto lo
   dibuja el CSS, instantáneo, y también al enfocar con Tab.

   Uso: <a data-tooltip="Lista">…</a>. El nombre accesible sigue viniendo del
   <span class="visually-hidden"> de adentro; esto es sólo la afordancia visual.
   Sin `title` a la vez, o salen dos tooltips. Va en ::before y no en ::after
   porque Bootstrap dibuja el caret de .dropdown-toggle con ::after (Exportar
   PDF) y lo pisaria. */
[data-tooltip] {
  position: relative;
}

[data-tooltip]::before {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(-2px);
  z-index: 20;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
  background: var(--surface-ink);
  color: var(--text-inverse);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition:
    opacity 120ms ease,
    transform 120ms ease;
}

[data-tooltip]:hover::before,
[data-tooltip]:focus-visible::before {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  [data-tooltip]::before {
    transition: none;
  }
}

/* Botón de Filtros solo ícono: el contador de filtros activos se superpone a
   la esquina en vez de ir en fila con un texto que ya no está. Mismo gesto
   que .ct-filters-btn en el gestor de tareas, escrito acá porque
   company-tasks.css no se carga en Siguiendo ni en Pipeline. */
.trexia-filter-btn {
  position: relative;
}

.trexia-filter-btn > .badge {
  position: absolute;
  top: -6px;
  right: -6px;
  font-size: 10px;
  line-height: 1;
  padding: 2px 5px;
}

/* ===== MOBILE ===== */
.mobile-hamburger {
  top: 12px;
  left: 12px;
  z-index: 1001;
  background: #fff;
  border: 1px solid var(--trexia-line);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

@media (max-width: 768px) {
  .trexia-main {
    margin-left: 0;
  }

  .trexia-sidebar {
    transform: translateX(-100%);
    transition: transform 0.3s ease;
  }

  .trexia-sidebar.show {
    transform: translateX(0);
  }

  .trexia-content {
    /* padding-top was 3.5rem, offsetting a fixed topbar — but only the sidebar
       is fixed, so it stacked 56px of dead space under a topbar already in
       flow. */
    padding: var(--space-4);
  }

  /* Bootstrap's .container-fluid added its own 12px inside the 16px content
     padding, giving 28px gutters on a 390px viewport — 14% of the width on the
     breakpoint where the OBJETO column is already squeezed. */
  .trexia-content .container-fluid {
    padding-inline: 0;
  }

  .trexia-topbar {
    padding: 0 var(--space-4);
  }

  /* Bootstrap's .btn-sm renders a 21px-tall Filtros button and a 33px
     hamburger — both under the var(--tap-target) touch minimum. */
  .trexia-content .btn-sm,
  .trexia-topbar .btn {
    min-height: var(--tap-target);
  }
}

/* ---------------------------------------------------------------------------
 * Contraste en superficies de color sólido (medido en la vista de detalle).
 * ------------------------------------------------------------------------- */

/* .text-white en el .card-header no llega al <h5> de adentro: Bootstrap le da
   `color: var(--bs-heading-color)` a los headings, y una regla directa le gana
   a la herencia. Resultado: tinta oscura sobre --brand, 2.32:1, cuando la clase
   pedía blanco (7.22:1). */
.card-header.text-white :is(h1, h2, h3, h4, h5, h6),
.card-header[class*='bg-'] :is(h1, h2, h3, h4, h5, h6) {
  color: inherit;
}

/* Chip de evento del calendario: el color de la lista queda en el borde y el
   fondo; el texto usa tinta del sistema para que el contraste no dependa de un
   color que elige el usuario. */
.calendar-event--chip {
  color: var(--text-body);
}

/* ---------------------------------------------------------------------------
 * Targets táctiles en superficies que la regla de .saas-dt no alcanza.
 *
 * Medido a 390px con has_touch, en las tres vistas de Following. La regla de
 * ayer cubría .saas-dt-icon-btn, .btn-sm y los botones del topbar; estos
 * quedaron fuera y son los que más duelen porque son las únicas acciones por
 * tarjeta:
 *
 *   menú ··· de la tarjeta kanban   14×23   (menos de un tercio del mínimo)
 *   hamburguesa del topbar          46×34
 *   items de navegación del sidebar 236×41
 *   botón Filtros                   43×44   (por un píxel)
 *
 * El mínimo de var(--tap-target) sale de Material y de las HIG de Apple; por debajo, el
 * error de toque crece rápido en la mano no dominante.
 * ------------------------------------------------------------------------- */
@media (pointer: coarse) {
  /* Acciones por tarjeta en el kanban: el botón envuelve un icono de 14px sin
     padding propio, así que el área táctil es el icono.

     Agrandarlos a var(--tap-target) NO alcanza: quedaban a 38px entre centros, así que dos
     targets de 44 se solapaban 6px y el toque se volvía ambiguo justo en el
     medio. Material pide 8dp de separación ENTRE áreas, no sólo 48dp de área.
     El gap del contenedor es lo que garantiza el paso de 52px. */
  .trexia-content .btn-link,
  .kanban-card .btn-link,
  .trexia-content .btn.p-0 {
    min-width: var(--tap-target);
    min-height: var(--tap-target);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
  }

  /* Separación mínima entre acciones de tarjeta. Defensiva: hoy la estrella
     (259–275) y el ··· (283–327) ya guardan los 8px que pide Material, pero
     nada lo garantizaba — el ··· pasó de 14px a var(--tap-target) sin que ninguna regla
     vigilara el paso. */
  .opp-card .d-flex:has(> .btn-link) {
    gap: var(--space-2);
  }

  /* Hamburguesa: 34px de alto porque hereda el padding de .btn sin altura
     mínima. No está dentro de .trexia-topbar, por eso la regla anterior no la
     tocaba. */
  .btn.d-md-none {
    min-width: var(--tap-target);
    min-height: var(--tap-target);
  }

  /* Navegación del sidebar: 41px viene de padding 10px + línea de 21px. */
  .trexia-nav-item {
    min-height: var(--tap-target);
  }

  /* .btn-sm ya subía el alto pero no el ancho, y un botón de icono terminaba
     en 43px. */
  .trexia-content .btn-sm {
    min-width: var(--tap-target);
  }
}

/* ---------------------------------------------------------------------------
 * App bar en mobile.
 *
 * El título arrancaba en x=16 mientras la hamburguesa ocupa hasta x=58, así que
 * quedaba tapado: "Dashboard" se leía "board", "Pipeline" se leía "ne". Es la
 * única señal de en qué pantalla estás en toda la app.
 *
 * Y estaba a 13.5px, contra los 22sp del title de app bar de Material (16sp es
 * el piso). --font-size-md es 17px, el escalón del DS que más se le acerca.
 * ------------------------------------------------------------------------- */
@media (max-width: 768px) {
  .trexia-topbar-title {
    padding-inline-start: var(--space-12);
    font-size: var(--font-size-md);
  }
}

/* ---------------------------------------------------------------------------
 * Ganancias rápidas de mobile, de la crítica con criterio Material.
 * Todo acotado a (max-width: 768px): desktop no cambia.
 * ------------------------------------------------------------------------- */
@media (max-width: 768px) {
  /* El pipeline reserva calc(100dvh - topbar - 56px) para la columna: 607px
     medidos, 72% del viewport, casi todo gris vacío cuando no hay tarjetas. Y
     el "No opportunities" quedaba anclado arriba (y=246) en vez de centrado.
     En mobile el alto lo pone el contenido. */
  .pipeline-shell {
    height: auto !important;
    overflow: visible !important;
  }

  /* El alto de 607px no está en el shell sino en .kanban-column, que hereda de
     un contenedor con flex-grow. Sin acotar la columna, el shell en auto no
     cambia nada. */
  .pipeline-shell .kanban-column,
  .pipeline-shell .trexia-col {
    height: auto;
    min-height: var(--space-20);
  }

  /* El board es el que colapsaba a 12px al sacarle el alto heredado, dejando
     una tira fina y el estado vacío fuera de vista. Necesita mínimo propio, no
     sólo la columna. */
  .pipeline-shell .kanban-board {
    height: auto;
    min-height: calc(var(--space-20) * 2);
    align-items: stretch;
  }

  /* El estado vacío quedaba anclado arriba (y=246) dentro de una caja de 607px.
     Centrado en la caja que ahora sí tiene un alto razonable. */
  .pipeline-shell .kanban-column {
    display: flex;
    flex-direction: column;
  }

  .pipeline-shell .kanban-column > :last-child {
    flex: 1;
    display: grid;
    place-content: center;
  }

  /* Las tarjetas de KPI daban 81 / 81 / 124 / 124 / 105 px porque la etiqueta
     "✦ ANALIZADAS POR IA" envuelve a dos líneas y desalinea las cifras de sus
     vecinas. En una grilla de la misma familia la altura tiene que ser una.

     Pero grid-auto-rows: 1fr igualaba TODAS las filas a la más alta, y la más
     alta la fija ese eyebrow de dos líneas. Resultado medido: las tarjetas 1 y
     2 terminaban su contenido 66px por encima de su propio borde inferior —
     banda muerta abajo en unas y arriba en otras, dentro de una sola grilla.
     Sin 1fr cada fila mide lo que mide su contenido y la que no tiene eyebrow
     colapsa; dentro de una fila mixta las alturas las sigue igualando la fila.

     El gap sube a --space-4, el que ya usa la grilla de tarjetas 550px más
     abajo en la misma columna de 390px. Y el hueco entre el botón de la
     cabecera y el primer KPI medía 0.0px: los dos bordes de 1px se apilaban y
     los dos controles se leían como una sola caja rota. */
  .trexia-kpi-row {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-4);
    margin-top: var(--space-4);
  }

  /* El eyebrow salía como "✦ ANALIZADAS PO…", con elipsis a mitad de palabra,
     teniendo lugar de sobra para envolver. Chrome truncado se lee como bug. Las
     alturas ya las iguala grid-auto-rows: 1fr, que es para lo que estaba el
     nowrap. */

  /* El margin-top: 0 de mobile venía de cuando grid-auto-rows: 1fr estiraba
     todas las filas: con las alturas infladas a la fuerza, clavar la cifra
     abajo dejaba 47px de blanco arriba. Sacado el 1fr, la fila sólo se estira
     cuando mezcla una tarjeta con eyebrow y una sin él, y ahí la cifra tiene
     que volver a caer al fondo para alinearse con la de al lado — que es lo
     mismo que hace desktop. Sin esto, la 3 y la 4 quedan con los números a
     distinta altura en la misma fila. */

  /* El strip de tabs medía 88px con las etiquetas envueltas a dos líneas y la
     última tab terminando en x=760 sobre un viewport de 390 — cortada a mitad
     de palabra y sin señal de que hubiera más. Material: una línea, 48dp, y
     una porción de la siguiente visible. */
  .opportunity-tabs {
    min-height: var(--space-12);
    padding-inline-end: var(--space-6);
    -webkit-mask-image: linear-gradient(to right, #000 88%, transparent);
    mask-image: linear-gradient(to right, #000 88%, transparent);
  }

  .opportunity-tabs .nav-link {
    white-space: nowrap;
  }

  /* El handle de arrastre se dibuja en touch, donde no hay drag sin long-press
     ni afordancia que lo anuncie. */
  .opp-card [class*='grip'],
  [class*='column-header'] [class*='grip'] {
    display: none;
  }

  /* Los chips de la tarjeta kanban NO son un defecto: el contenedor ya tiene
     flex-wrap: wrap y gap-2, y ocupan dos filas porque "EN TRÁMITE
     (PUBLICADA)" + "Faltan 3 días para apertura" no entran en una línea de
     390px. Envolver es el comportamiento correcto. Medido, no asumido. */

  /* Acción primaria: vivían todas en la esquina superior derecha, el punto más
     lejano del pulgar, con espacio muerto a la izquierda. A ancho completo no
     resuelve el alcance — eso necesita una barra inferior — pero al menos deja
     de ser un target chico en la esquina. */
  /* El :not() no es defensivo de más: la fila del mes del calendario es un
     .d-flex.justify-content-between como cualquier otra, y sin excluirla los
     dos chevrones se repartían el ancho. Excluirla acá es más honesto que
     subirle especificidad a la regla del calendario, que perdía 4 clases a 2. */
  .trexia-content
    > .container-fluid
    .d-flex.justify-content-between
    > .btn:not(.calendar-monthnav__btn),
  .trexia-content .btn.float-end {
    width: 100%;
  }

  /* La navegación del mes tiene la misma forma que una fila de acción, pero no
     es una acción primaria: son dos chevrones a los lados del título. Con la
     regla de arriba medían 148px cada uno. Cuadrados, con el mínimo táctil. */
  .trexia-content .calendar-monthnav__btn {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--tap-target);
    min-width: var(--tap-target);
    min-height: var(--tap-target);
    padding: 0;
    font-size: var(--font-size-xl);
    line-height: 1;
  }

  /* Switcher de vistas (Board / List / Calendar). Es un control único, no tres
     botones sueltos: los segmentos miden 53, 44 y 65 según el largo de la
     palabra, y en la vista de lista el texto envolvía debajo del icono porque
     no entraba. Segmentos parejos y el grupo ocupa lo que necesita. */
  .trexia-content .btn-group > .btn {
    flex: 1 1 0;
    min-width: 0;
    white-space: nowrap;
  }

  /* Sin icono. Con él, "Calendar" necesita ~96px por segmento: la etiqueta se
     cortaba a mitad de palabra. Entre mostrar el icono y mostrar la palabra
     completa, la palabra dice más. */
  .trexia-content .btn-group > .btn > svg {
    display: none;
  }

  /* La fila de controles del encabezado se encogía al contenido —162px de 358
     con el switcher, 210 con los botones de future— así que los segmentos
     quedaban en 53px de caja útil y "Calendar", que pide 62, se cortaba, y
     "Crear pipeline" envolvía a dos líneas quedando 50px de alto al lado de
     un Filtros de 44. Con la fila entera entran todos en una línea. */
  .trexia-content
    .d-flex.flex-wrap.justify-content-between
    > .d-flex.gap-2.align-items-center {
    width: 100%;
    /* Con el switcher, Filtros y Export PDF en la misma fila no entran en
       358px: los segmentos caían a 49 y "Calendar" volvía a cortarse. Que
       envuelva es preferible a apretar tres controles hasta que ninguno se
       lea. */
    flex-wrap: wrap;
  }

  .trexia-content .d-flex.gap-2.align-items-center > .btn-group {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* En esta fila los botones sueltos no van a ancho completo. La regla de la
     barra de acciones del detalle les da flex 1 1 100%, y acá "Filtros" se
     llevaba 243 de 358 dejando al switcher en 36px por segmento. */
  .trexia-content .d-flex.gap-2.align-items-center:has(> .btn-group) > .btn {
    flex: 0 0 auto;
    width: auto;
  }

  /* La fila de título apila en mobile: a 390px el botón al lado del h1 deja el
     título en dos o tres líneas. Y a ancho completo deja de ser un target chico
     en la esquina superior derecha, que es el punto más lejano del pulgar. */
  .trexia-page-head {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2);
  }

  .trexia-page-head > .trexia-btn {
    width: 100%;
    justify-content: center;
  }
}

/* ---------------------------------------------------------------------------
 * Detalle y listas en mobile — segunda tanda.
 * ------------------------------------------------------------------------- */
@media (max-width: 768px) {
  /* "EN TRÁMITE (PUBLICADA)" mide 125×38 con border-radius 999px: la forma de
     píldora envuelta en dos líneas se lee como un error de render. El propio
     token file reserva el radio de píldora para una línea.
     !important en .badge: el puente visual de badges Bootstrap (TRE-999) es
     dos clases más tres :not(), especificidad 0,5,0 — le gana a esta regla
     de dos clases (0,2,0) sin el !important, y un badge Bootstrap con texto
     largo volvería a envolver en dos líneas con radio de píldora. */
  .trexia-content .badge {
    border-radius: var(--radius-xl) !important;
  }

  .trexia-content .trexia-badge {
    border-radius: var(--radius-xl);
  }

  .trexia-content .badge:not(:has(br)),
  .trexia-content .trexia-badge {
    white-space: normal;
  }

  /* Follow (95px) + Download PDF (150px) + View on ARCE (122px) dejaban hueco
     muerto a la derecha y competían con tres anchos distintos. A ancho
     completo, apiladas, con el alto mínimo de control.

     Tres contenedores distintos: Download PDF cuelga de un .d-flex.gap-2,
     Follow va envuelto en un <form class="d-inline following-form">, y View on
     ARCE vive suelto en un .card-body. Un solo selector no los alcanza. */
  .trexia-content .d-flex.gap-2 > .btn,
  .trexia-content .btn-group-vertical > .btn,
  .trexia-content .following-form,
  .trexia-content .following-form > .btn,
  .trexia-content .card-body > .btn {
    flex: 1 1 100%;
    width: 100%;
    /* --tap-target, no --space-12: convivían los dos para lo mismo y dejaban
       botones de 48 al lado de botones de 44 en la misma fila. */
    min-height: var(--tap-target);
  }

  .trexia-content .following-form {
    display: block;
  }

  /* Fila de lista de seguimiento en 39px y 23px, contra los 48dp de list item
     de Material. Y el input y el botón de crear compartían fila con alturas
     distintas, así que el borde inferior no cerraba. */
  .trexia-content .list-group-item,
  .trexia-content [class*='follow-list'] > .d-flex {
    min-height: var(--space-12);
    align-items: center;
  }

  .trexia-content .input-group > .form-control,
  .trexia-content .input-group > .btn {
    min-height: var(--tap-target);
  }
}

/* ---------------------------------------------------------------------------
 * Navegación inferior (mobile).
 *
 * Punto (a) de los hallazgos estructurales: los seis destinos de primer nivel
 * vivían detrás de un drawer anclado arriba a la izquierda — el punto más
 * lejano del pulgar derecho en 844px de alto. Material especifica bottom
 * navigation para 3–5 destinos y 80dp de alto; acá van cuatro, y Futuras y
 * Pipeline siguen en el drawer.
 * ------------------------------------------------------------------------- */
.trexia-bottom-nav {
  display: none;
}

@media (max-width: 768px) {
  .trexia-bottom-nav {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 1030;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    background: var(--surface-card);
    border-top: 1px solid var(--border-subtle);
    /* Respeta la home indicator de iOS sin dejar la barra flotando. */
    padding-bottom: env(safe-area-inset-bottom, 0);
  }

  .trexia-bottom-nav__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-height: var(--space-16);
    padding-block: var(--space-2);
    color: var(--text-muted);
    text-decoration: none;
    font-size: var(--font-size-xs);
    line-height: var(--leading-tight);
  }

  .trexia-bottom-nav__item svg {
    width: 22px;
    height: 22px;
    flex-shrink: 0;
  }

  .trexia-bottom-nav__item.is-active {
    color: var(--brand);
    font-weight: var(--weight-medium);
  }

  .trexia-bottom-nav__item:focus-visible {
    outline: 2px solid var(--focus-ring, var(--brand));
    outline-offset: -2px;
  }

  /* El contador va sobre el icono, no al lado: al lado empuja la etiqueta y
     descuadra la grilla de cuatro columnas iguales. */
  .trexia-bottom-nav__icon-wrap {
    position: relative;
    display: inline-flex;
  }

  .trexia-bottom-nav__badge {
    position: absolute;
    top: -4px;
    inset-inline-start: 12px;
    min-width: 16px;
    padding-inline: 3px;
    border-radius: var(--radius-pill);
    background: var(--bad);
    color: var(--text-inverse);
    font-size: 10px;
    line-height: 16px;
    text-align: center;
  }

  /* Sin esto la barra tapa el final del contenido en cada vista. */
  .trexia-content {
    padding-bottom: calc(
      var(--space-16) + var(--space-6) + env(safe-area-inset-bottom, 0px)
    );
  }
}

/* ---------------------------------------------------------------------------
 * Barra de acciones anclada abajo (mobile) — punto (e).
 *
 * Follow y Download PDF vivían en el tercio superior, y ponerlos a ancho
 * completo por el target táctil los hizo más altos: 200px de cromo antes de
 * ver una línea de contenido, en la pantalla donde el usuario viene a leer.
 * El 66% del primer viewport era chrome.
 *
 * Ancladas arriba de la barra de navegación quedan al alcance del pulgar y
 * devuelven esos 200px al contenido.
 * ------------------------------------------------------------------------- */
@media (max-width: 768px) {
  .trexia-content .d-flex.gap-2.flex-wrap.flex-shrink-0:has(.following-form) {
    position: fixed;
    inset-inline: 0;
    /* Justo encima de la nav inferior, respetando la safe area de iOS. */
    bottom: calc(var(--space-16) + env(safe-area-inset-bottom, 0px));
    z-index: 1029;
    /* !important porque .d-flex de Bootstrap declara display:flex !important.
       Sin esto los dos botones se apilan y la barra pasa de 60 a 117px, que
       sobre los 65px de la nav son 182px de cromo fijo permanente. */
    display: grid !important;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: var(--space-2);
    margin: 0;
    padding: var(--space-2) var(--space-4);
    background: var(--surface-card);
    border-top: 1px solid var(--border-subtle);
  }

  /* Dentro de la barra los botones ya no necesitan ancho completo propio: la
     grilla los reparte. */
  .trexia-content
    .d-flex.gap-2.flex-wrap.flex-shrink-0:has(.following-form)
    > * {
    width: auto;
    margin: 0;
  }

  .trexia-content
    .d-flex.gap-2.flex-wrap.flex-shrink-0:has(.following-form)
    .btn {
    width: 100%;
    min-height: var(--tap-target);
  }

  /* El contenido necesita despejar barra de acciones + navegación. */
  body:has(
      .trexia-content .d-flex.gap-2.flex-wrap.flex-shrink-0:has(.following-form)
    )
    .trexia-content {
    padding-bottom: calc(
      var(--space-16) * 2 + var(--space-4) + env(safe-area-inset-bottom, 0px)
    );
  }

  /* Estado y Due Date: dos tarjetas de 115px, centradas, para un label y un
     valor cada una — 230px del primer viewport para dos hechos. Como filas
     label/valor alineadas al inicio ocupan menos de la mitad y se leen igual o
     mejor: el ojo no tiene que saltar al centro de cada caja. */
  .trexia-content .col-6.col-md-3 > .card > .card-body.text-center,
  .trexia-content .col-6.col-md-3 > .card-body.text-center {
    text-align: start !important;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-3);
  }

  .trexia-content .col-6.col-md-3 .badge.fs-6 {
    font-size: var(--font-size-xs) !important;
    align-self: flex-start;
  }
}

/* ---------------------------------------------------------------------------
 * Secciones del detalle en mobile — lista con drill-down.
 *
 * A 390px entran 3 de las 8 secciones y el strip scrollea 1035px sin señal de
 * que haya más. Un control segmentado tampoco entra: la HIG lo topa en cinco en
 * iPhone. El patrón de Contacts y Settings es una lista de filas con chevron
 * que empujan su propia pantalla, y cada fila puede además decir cuánto hay
 * adentro — algo que ni el strip ni un selector dan.
 *
 * Nada de esto existe arriba del breakpoint: en desktop las tabs entran.
 * ------------------------------------------------------------------------- */
.section-list,
.section-back {
  display: none;
}

@media (max-width: 768px) {
  /* El strip deja de existir acá: la lista lo reemplaza, no lo duplica.
   *
   * Va scopeado a .has-section-list, que mobile-section-list.js pone recién
   * después de insertar la lista. Sin ese scope el strip se ocultaba aunque el
   * script no llegara a correr —bundle caído, error de JS, navegador que no lo
   * ejecuta— y entonces las ocho secciones del detalle quedaban sin ningún
   * control: ni la lista nueva ni el strip que reemplaza. El propio script
   * trae un fallback a link.click() para cuando Bootstrap no está; esta regla
   * lo anulaba desde el CSS. */
  .opportunity-sections.has-section-list .opportunity-tabs {
    display: none !important;
  }

  .section-list {
    display: block;
    /* Vive dentro de .opportunity-tabs-toolbar (fila flex), en el lugar del
       strip oculto: ocupa todo el ancho que dejaba el strip. */
    flex: 1 1 auto;
    min-width: 0;
    margin-bottom: var(--space-4);
    border: 1px solid var(--border-subtle, var(--trexia-line));
    border-radius: var(--radius-xl);
    background: var(--surface-card);
    overflow: hidden;
  }

  .opportunity-sections.is-drilled .section-list {
    display: none;
  }

  .section-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    min-height: var(--tap-target);
    padding: var(--space-3) var(--space-4);
    border: 0;
    /* El separador va entre filas y no debajo de la última, que ya tiene el
       borde de la lista. */
    border-block-start: 1px solid var(--border-subtle, var(--trexia-line));
    background: transparent;
    color: var(--text-strong);
    font-size: var(--font-size-base);
    text-align: start;
  }

  .section-row:first-child {
    border-block-start: 0;
  }

  .section-row:active {
    background: var(--surface-sunken, rgb(0 0 0 / 4%));
  }

  .section-row:focus-visible {
    outline: 2px solid var(--focus-ring, var(--brand));
    outline-offset: -2px;
  }

  .section-row__label {
    flex: 1 1 auto;
  }

  /* El conteo es dato secundario: informa sin competir con la etiqueta. */
  .section-row__count {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
  }

  .section-row__chevron {
    flex: 0 0 auto;
    width: 1.25rem;
    height: 1.25rem;
    color: var(--text-muted);
  }

  .opportunity-sections.is-drilled .section-back {
    display: flex;
    flex: 1 1 auto;
    min-width: 0;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
  }

  .section-back__btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap-target);
    height: var(--tap-target);
    /* El icono es chico pero el target no: se compensa con margen negativo
       para que la barra no se despegue del borde de la columna. */
    margin-inline-start: calc(var(--space-3) * -1);
    border: 0;
    background: transparent;
    color: var(--brand);
  }

  .section-back__btn svg {
    width: 1.5rem;
    height: 1.5rem;
  }

  .section-back__btn:focus-visible {
    outline: 2px solid var(--focus-ring, var(--brand));
    outline-offset: -2px;
    border-radius: var(--radius-md);
  }

  .section-back__title {
    font-size: var(--font-size-lg);
    font-weight: 600;
    color: var(--text-strong);
  }

  /* Las tablas de adentro de una sección no son .saas-dt, así que las reglas
     de tarjeta de los listados no las alcanzaban: Clarifications entraba con
     cuatro columnas en 390px y el nombre del PDF partía de a un carácter.
     Se apunta sólo a las que tienen encabezado — las de Purchase Items son
     pares etiqueta/valor sin thead y ya se leen bien. */
  .tab-pane table:has(thead th),
  .tab-pane table:has(thead th) > tbody,
  .tab-pane table:has(thead th) > tbody > tr {
    display: block;
    width: 100% !important;
  }

  .tab-pane table:has(thead th) > thead {
    display: none;
  }

  .tab-pane table:has(thead th) > tbody > tr {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    padding: var(--space-3) 0;
    border-block-start: 1px solid var(--border-subtle, var(--trexia-line));
  }

  /* La columna de acción viene primera en el HTML porque en la tabla ancha
     ocupa el margen izquierdo. Apilada, eso deja un botón enorme encima del
     contenido que describe: la acción baja y se encoge a su texto. */
  .tab-pane table:has(thead th) > tbody > tr > td:has(.btn, button, a.btn) {
    order: 1;
    align-self: start;
    margin-block-start: var(--space-2);
  }

  .tab-pane table:has(thead th) > tbody > tr:first-child {
    border-block-start: 0;
  }

  .tab-pane table:has(thead th) > tbody > tr > td {
    display: block !important;
    width: auto !important;
    padding: var(--space-1) 0;
    border: 0;
    /* Los nombres de archivo no tienen espacios donde cortar. */
    overflow-wrap: anywhere;
  }
}

/* ===== Opportunity triage priority (not deadline urgency) ===== */
.opportunity-priority-dot {
  display: inline-block;
  flex-shrink: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--trexia-text-muted);
}
.opportunity-priority-dot--urgent {
  background: var(--bad);
}
.opportunity-priority-dot--high {
  background: var(--warn);
}
.opportunity-priority-dot--medium {
  background: var(--brand);
}
.opportunity-priority-dot--low {
  background: var(--ink-300);
}
.opportunity-priority-dot--none {
  background: transparent;
  border: 1px solid var(--trexia-text-muted);
  box-sizing: border-box;
}

/* Chip/select trigger — same visual weight as country badge (PY), not a btn */
.opportunity-priority-btn--chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1, 0.25rem);
  max-width: 11rem;
  padding: var(--space-1, 0.25rem) var(--space-2, 0.5rem);
  border: 1px solid var(--trexia-line);
  border-radius: var(--radius-pill);
  background: var(--trexia-card-bg);
  color: var(--trexia-text);
  font-size: var(--font-size-xs, 0.75rem);
  font-weight: var(--weight-semibold, 600);
  line-height: var(--leading-tight, 1.25);
  white-space: nowrap;
  cursor: pointer;
}
.opportunity-priority-btn--chip.is-empty {
  color: var(--trexia-text-muted);
  border-color: var(--trexia-line);
  background: transparent;
}
.opportunity-priority-btn--chip:hover {
  border-color: var(--trexia-text-muted);
}
.opportunity-priority-btn--chip:focus-visible {
  outline: 2px solid var(--trexia-accent);
  outline-offset: 2px;
}
.opportunity-priority-btn--chip .opportunity-priority-label {
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.opportunity-priority-caret {
  flex-shrink: 0;
  opacity: 0.65;
  margin-left: 0.1rem;
}
.opportunity-priority-menu .opportunity-priority-option {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
/* Title row: keep chip badge-sized next to h1 / country pill */
.opportunity-title-meta .opportunity-priority-btn--chip {
  font-size: var(--font-size-xs, 0.75rem);
  font-weight: var(--weight-semibold, 600);
  line-height: var(--leading-tight, 1.25);
}
/* Slot stays in the DOM when empty so AJAX can mount/clear the badge. */
.opp-priority-badge-slot {
  display: contents;
}
/* Datatable actions: color only (label in title/aria). */
.opp-priority-dot-only {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  line-height: 0;
}
.opp-priority-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--trexia-text-muted);
}

/* ---- view toolbar (compartida) ----
   TRE-949: una sola caja (32px, radio 6, la que ya tenía el header de
   licitación en opportunity-detail.css) para el conmutador de vistas y los
   botones extra de un listado -- companies/_view_switcher.html,
   components/opportunities/_following_toolbar.html y
   opportunities/future_pipeline.html, vía components/_view_toolbar.html.
   Vive acá y no en company-tasks.css porque Siguiendo y Pipeline no cargan
   esa hoja. `.ct-view-switcher`/`.ct-view-switcher__link` se quedan con su
   nombre histórico: hay tests y JS (ver el comment de _view_toolbar.html)
   anclados a esas clases. */
.ct-view-switcher {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.ct-view-switcher__sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.ct-view-switcher__link,
.view-toolbar__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-control);
  background: var(--surface-card);
  color: var(--text-body);
  text-decoration: none;
}

.ct-view-switcher__link:hover,
.view-toolbar__btn:hover {
  background: var(--surface-sunken);
}

.ct-view-switcher__link.is-active,
.view-toolbar__btn.is-active {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--text-inverse);
}

.ct-view-switcher__link[aria-disabled='true'],
.view-toolbar__btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: default;
}

/* TRE-994: acción primaria de una barra de view-toolbar__btn (hoy, "+ Nueva
   tarea" en Tareas) -- el único acento de acción del design system es
   --brand, no el fondo neutral que comparten el resto de los botones de la
   caja. */
.view-toolbar__btn--primary {
  border-color: var(--brand);
  background: var(--brand);
  color: var(--text-inverse);
}

.view-toolbar__btn--primary:hover {
  background: var(--brand-strong);
  border-color: var(--brand-strong);
}

/* El caret de Bootstrap (`.dropdown-toggle::after`) trae su propio
   margin-left; sin anularlo el ícono queda descentrado en una caja de ancho
   fijo de 32px (antes el botón medía lo que el contenido pidiera). */
.view-toolbar__btn.dropdown-toggle::after {
  margin-left: 0;
}
