/**
 * 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);
}

/* ===== 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;
  height: 100vh;
  z-index: 1000;
  overflow-y: auto;
}

.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: 700;
  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);
}

.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;
}

/* Legacy kpi-card alias. -highlight y -amber salieron: cero usos en plantillas,
   eran selectores muertos adentro de una regla viva. */
.kpi-card {
  background: var(--trexia-sidebar-bg);
  border-radius: var(--trexia-radius);
  color: var(--text-inverse);
}

/* ===== 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);
}

/* ===== 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;
}

/* 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 {
  background: #eef1f6;
  border-radius: var(--trexia-radius);
  padding: 12px;
  min-width: 250px;
  flex-shrink: 0;
}

.trexia-col-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 800;
  color: var(--trexia-sidebar-bg);
  margin-bottom: 10px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.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);
}

/* ===== 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;
}

/* .badge.bg-warning venía en blanco sobre --amber-600: 3.09:1, bajo el piso de
   4.5. El botón equivalente (.btn-warning) ya usaba tinta oscura. Mismo criterio
   que los badges de plazo: el hue lo lleva el fondo, no el texto. */
.badge.bg-warning {
  color: var(--text-strong) !important;
}

/* 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. */
  .trexia-content .badge,
  .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;
    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;
    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: #dc2626;
}
.opportunity-priority-dot--high {
  background: #ea580c;
}
.opportunity-priority-dot--medium {
  background: #ca8a04;
}
.opportunity-priority-dot--low {
  background: #64748b;
}
.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);
}
