/* Dashboard — rediseño "Brix".
   Sin tarjeta-superficie: cada card flota directo sobre el lienzo lavanda,
   muy redondeada, con una sombra difusa. KPI hero con degradé índigo y velo
   glass; el resto planas. Mucho aire entre bloques. */

/* ── Neutralizar la superficie blanca heredada (solo en esta página) ── */
.main-content--surface .main-content-scroll { padding: var(--content-pad-top) var(--content-pad-x) var(--space-8); }

.surface {
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  overflow: visible;
}
.surface-deco { display: none; }

/* La topbar hace de page-header en el dashboard */
.dash-topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Aviso de error de carga */
.dash-error {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-6);
  background: var(--color-danger-bg);
  border: 1px solid var(--color-danger-border);
  border-radius: var(--radius-container);
  color: var(--color-danger-strong);
  font-size: var(--text-base);
}

/* ── Tira de KPIs ─────────────────────────────── */
.dash-kpis {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--dash-gap);
  margin-bottom: var(--dash-gap);
}

/* Card KPI: badge de ícono a la izquierda, texto a la derecha, pill de
   tendencia arriba a la derecha. Más compacta para que entre todo el panel
   sin scroll. */
.dash-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--dash-card-pad-y) var(--dash-card-pad-x);
  min-height: var(--dash-kpi-min-h);
  background: var(--color-bg-surface);
  border: none;
  border-radius: var(--radius-container);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  transition: transform 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
}

.dash-kpi:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-raised);
}

.dash-kpi-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.dash-kpi-top-right {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.dash-kpi-badge {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--icon-badge-radius);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--icon-badge-bg);
  color: var(--color-primary);
}

.dash-kpi-badge svg { width: 16px; height: 16px; }

.dash-kpi-label {
  font-size: 10px;
  font-weight: var(--weight-semibold);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  padding-top: 2px;
}

.dash-kpi-mid {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}

.dash-kpi-value {
  font-family: var(--font-family-heading);
  font-size: var(--dash-kpi-value-size);
  font-weight: var(--weight-bold);
  line-height: 1.05;
  margin: 0;
  color: var(--color-text-heading);
  letter-spacing: -0.5px;
  font-variant-numeric: tabular-nums;
}

.dash-kpi-sub {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  min-height: 1em;
  line-height: 1.4;
}

/* KPI hero — degradé sutil diagonal, texto blanco */
.dash-kpi--hero {
  background: var(--gradient-kpi-hero);
  box-shadow: var(--shadow-card);
}

.dash-kpi--hero:hover { box-shadow: var(--shadow-raised); }

.dash-kpi--hero .dash-kpi-label { color: rgba(255, 255, 255, 0.72); }
.dash-kpi--hero .dash-kpi-value { color: #fff; }
.dash-kpi--hero .dash-kpi-sub   { color: rgba(255, 255, 255, 0.82); }

.dash-kpi--hero .dash-kpi-badge {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
}

/* ── Grilla de paneles ────────────────────────── */
.dash-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
  gap: var(--dash-gap);
  margin-bottom: var(--dash-gap);
}

/* Todas las cards de una fila igualan su altura a la más alta (grid +
   stretch); estos son solo el piso. */
.dash-cols--charts .dash-panel { min-height: var(--dash-row-charts-min-h); }
.dash-cols--3 .dash-panel { min-height: var(--dash-row-panels-min-h); }

/* Fila de 3 columnas: Actividad reciente · Avance de publicación · Accesos
   rápidos. Debe entrar en pantalla junto con lo de arriba. minmax(0, 1fr)
   en vez de 1fr a secas: el track flexible de grid usa minmax(auto, 1fr)
   por defecto, así que su piso es el min-content de la card (textos con
   nowrap, etc.) — con contenido más ancho que el disponible eso desborda
   el viewport en vez de comprimir la columna. */
.dash-cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Fila de gráficos (canal + documentos): 60/40 en vez de 50/50 — el de
   columnas necesita más ancho para que las etiquetas de canal no se
   amontonen. Sin align-items:stretch: forzar la misma altura infló el
   contenedor de la dona muy por encima del gráfico y, del otro lado,
   dejó .col-chart en una cadena de alturas flexibles que colapsó a 0
   (las columnas dejaron de verse) — cada tarjeta vuelve a alto natural. */
/* La columna de la dona queda en un ancho fijo y angosto (no 2fr): con el
   anillo achicado a 210px, dejarle un 40% de fracción libre sobraba
   mucho aire a los costados — el contenedor se veía mucho más grande
   que el gráfico. El de canal se queda con todo el resto. */
/* align-items: stretch para que las dos tarjetas de la fila midan lo
   mismo — a diferencia del intento anterior, .col-chart tiene altura
   fija (190px, ver abajo) en vez de flex:1, así que estirar el panel no
   vuelve a colapsar el gráfico a 0: el aire de más queda abajo, dentro
   del panel-body, no como una tarjeta más corta y desalineada. */
.dash-cols--charts {
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--dash-donut-col));
  align-items: stretch;
}

.dash-panel {
  min-width: 0;
  background: var(--color-bg-surface);
  border-radius: var(--radius-container);
  box-shadow: var(--shadow-float);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: box-shadow 0.2s var(--ease-out);
}

.dash-panel:hover { box-shadow: var(--shadow-float-lg); }

.dash-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--dash-card-pad-y) var(--dash-card-pad-x) var(--space-2);
}

.dash-panel-title {
  margin: 0;
  font-family: var(--font-family-heading);
  font-size: var(--text-panel-title);
  font-weight: var(--weight-bold);
  color: var(--color-text-heading);
  letter-spacing: -0.01em;
}

.dash-panel-hint {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Ícono de header — "Actividad reciente" / "Avance de publicación" /
   "Acciones rápidas", mismo tratamiento visual entre las tres. */
.dash-panel-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--color-text-muted);
  margin-right: 2px;
}
.dash-panel-icon svg { width: 18px; height: 18px; }

/* Acciones de la cabecera de "Por área terapéutica": selector de
   agrupación + botón de opciones adicionales, en el lugar donde antes
   iba el link "Ver detalle →". */
.dash-panel-head-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}

.dash-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-bg-surface);
  color: var(--color-text-secondary);
  font-family: var(--font-family);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: border-color 0.15s var(--ease-out), color 0.15s var(--ease-out);
}
.dash-select:hover { border-color: var(--color-primary-border); color: var(--color-primary); }
.dash-select svg { width: 13px; height: 13px; flex-shrink: 0; }

.icon-btn-round--sm {
  width: 30px;
  height: 30px;
}
.icon-btn-round--sm svg { width: 16px; height: 16px; }

.dash-menu--right { left: auto; right: 0; }

.dash-panel-body {
  padding: var(--dash-card-pad-y) var(--dash-card-pad-x) var(--dash-card-pad-x);
  flex: 1;
}

.dash-panel-body--flush {
  padding: 2px 0 6px;
}

.dash-placeholder {
  padding: var(--space-4) var(--space-6);
  color: var(--color-text-muted);
  font-size: var(--text-base);
  list-style: none;
}

/* ── Gráfico de columnas (migración GTIN) — reales vs. temporales.
   Reemplaza al medidor circular: con la dona de al lado en la misma fila,
   dos gráficos "de torta" seguidos leían mal; esto es el tipo de barras
   que faltaba (como en la referencia), con eje Y de topes limpios y un
   tooltip flotante sobre la barra que importa ("reales"). ── */
/* El panel-body de este gráfico se vuelve columna flex para que .col-chart
   pueda crecer y ocupar el alto real de la tarjeta: con align-items:
   stretch en la fila de gráficos (mismo alto que la dona), una altura
   fija acá dejaba un bloque de aire vacío abajo — las barras se veían
   chatas comparadas con el resto de la tarjeta. Esta vez .col-bar-track
   ya tenía flex:1 desde el fix de las bases (no hace falta tocarlo). */
.dash-panel-body:has(> .col-chart) {
  display: flex;
  flex-direction: column;
}

.col-chart {
  display: flex;
  align-items: stretch;
  gap: var(--space-4);
  flex: 1;
  min-width: 0;
  min-height: var(--dash-chart-col-h);
  margin-top: var(--space-2);
}

/* Líneas guía horizontales detrás de las barras (una por cada nivel del eje
   Y: 0/25/50/75/100), para leer mejor la altura de cada columna. */
.col-chart-plot { position: relative; }
.col-chart-plot::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 28px; /* deja fuera la franja de etiquetas, igual que .col-chart-ticks */
  background-image: repeating-linear-gradient(to top, var(--color-border-light) 0 1px, transparent 1px 25%);
  pointer-events: none;
}

.col-chart-ticks {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 0 0 28px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* align-items: stretch (no flex-end): con flex-end, cada columna se
   alineaba por su ALTURA TOTAL (barra + etiqueta) contra el piso del
   gráfico — una etiqueta de 2 líneas empujaba esa columna entera hacia
   arriba respecto a una de 1 línea, y las barras terminaban "arrancando"
   a alturas distintas en vez de compartir la misma base. Con stretch,
   todas las columnas miden lo mismo (alto completo del plot) y la barra
   vive en .col-bar-track (flex:1, ver abajo), así que la base de cada
   barra queda siempre en el mismo lugar sin importar cuántas líneas
   ocupe el nombre del canal debajo. Sin línea de base: no aportaba nada
   (terminaba pegada a las etiquetas, no a la barra) y quedaba fea. */
.col-chart-plot {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: var(--chart-bar-gap);
}

.col-chart-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  flex: 1 1 0;
  min-width: 0;
  max-width: var(--dash-bar-col-max);
}

.col-bar-track {
  position: relative;
  width: 100%;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: flex-end;
}

.col-bar-fill {
  display: block;
  width: 100%;
  border-radius: 8px 8px 0 0;
  background: var(--color-border);
  transition: height 0.7s cubic-bezier(0.16, 1, 0.3, 1), filter 0.15s var(--ease-out), transform 0.15s var(--ease-out);
  transform-origin: bottom center;
}

/* Entrada escalonada — cada columna arranca un poco después que la
   anterior, para que el gráfico se sienta "vivo" al cargar en vez de
   crecer todo de golpe. */
.col-chart-col:nth-child(1) .col-bar-fill { transition-delay: 0s; }
.col-chart-col:nth-child(2) .col-bar-fill { transition-delay: 0.06s; }
.col-chart-col:nth-child(3) .col-bar-fill { transition-delay: 0.12s; }
.col-chart-col:nth-child(4) .col-bar-fill { transition-delay: 0.18s; }
.col-chart-col:nth-child(5) .col-bar-fill { transition-delay: 0.24s; }
.col-chart-col:nth-child(n+6) .col-bar-fill { transition-delay: 0.3s; }

.col-bar-fill--top { background: var(--color-primary); }

/* Hover — la columna bajo el cursor se realza (más brillo + un pelo de
   escala vertical) y muestra su tooltip aunque no sea la destacada. */
.col-chart-col { cursor: pointer; }
.col-chart-col:hover .col-bar-fill {
  filter: brightness(1.12);
  transform: scaleY(1.015);
}

/* Alto fijo (2 líneas siempre, aunque el nombre entre en 1) — si no, un
   canal con nombre corto queda con una etiqueta más baja que uno con
   nombre largo, y eso es justo lo que corría la base de la barra de
   una columna a otra (ver comentario en .col-chart-plot). */
.col-chart-label {
  flex-shrink: 0;
  height: 29px;
  font-size: var(--text-xs);
  text-align: center;
  line-height: 1.3;
  color: var(--color-text-secondary);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Nombres de canal reales sin espacio donde cortar ("PSICOTROPICOS"):
     sin esto, en columnas angostas (notebook, ~1024-1366px, muchas
     columnas en poco ancho) esa palabra no encuentra dónde hacer wrap,
     no se achica, y al estar centrada se desborda del ancho de SU
     columna por igual a los dos lados — se lee pisada contra la
     etiqueta vecina en vez de cortada. */
  overflow-wrap: anywhere;
}

.col-tooltip {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  padding: 5px 11px;
  border-radius: var(--radius-full);
  background: var(--color-tooltip-bg);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  box-shadow: var(--shadow-float);
  transition: bottom 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}

.col-tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--color-tooltip-bg);
}

/* Tooltip "a demanda" — todas las columnas que no son la destacada
   muestran su valor recién al pasar el mouse. */
.col-tooltip--onhover {
  opacity: 0;
  transform: translateX(-50%) translateY(4px);
  transition: bottom 0.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.15s var(--ease-out), transform 0.15s var(--ease-out);
  pointer-events: none;
}
.col-chart-col:hover .col-tooltip--onhover {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ── Dona (estado de documentos / avance) — anillo + callouts + leyenda al
   lado. Más compacta para que el panel entre en pantalla. ── */
.dash-panel-body--donut {
  display: grid;
  grid-template-columns: var(--dash-donut) minmax(0, 1fr);
  align-items: center;
  align-content: center;
  gap: var(--space-3) var(--space-5);
}
.dash-panel-body--donut .donut-insight { grid-column: 1 / -1; }

/* Sin badges flotantes sobre el anillo (la lectura por segmento va en la
   leyenda de al lado) y número central proporcional al anillo chico — con
   --text-donut-value (40px) se desbordaba y pisaba el trazo. */
.dash-panel-body--donut .donut-callout { display: none; }
.dash-panel-body--donut .donut-value { font-size: var(--dash-donut-value-lg); }
.dash-panel-body--donut .donut-label { font-size: var(--text-sm); margin-top: 1px; }

/* Dona chica (Avance de publicación, en columna angosta) */
.donut--sm { width: min(var(--dash-donut-sm), 100%); }
.dash-panel-body--donut:has(.donut--sm) { grid-template-columns: var(--dash-donut-sm) minmax(0, 1fr); }
.dash-panel-body--donut:has(.donut--sm) .donut-value { font-size: var(--dash-donut-value-sm); }
.dash-panel-body--donut:has(.donut--sm) .donut-label {
  font-size: 9px;
  /* 78% del ancho de la dona, no un px fijo: a los tamaños reducidos de
     notebook (--dash-donut-sm) un ancho fijo quedaba desproporcionado y
     cortaba la palabra ("Confirmad…"). */
  max-width: 78%;
  margin-top: 0;
  line-height: 1.15;
}

/* Notebook 1024–1599px con las 2 cards de gráficos todavía lado a lado
   (".dash-cols--charts" colapsa a 1 columna recién en ≤1180px, ver
   dashboard.css más abajo): acá el anillo SÍ está genuinamente apretado
   contra la mitad angosta de la fila, sin ancho libre para agrandarlo
   (a diferencia de ≤1180px, donde --dash-donut ya vuelve a agrandarse -
   ver tokens.css). "Completos"/"Confirmados" no entran ni bajando la
   fuente: se saca la etiqueta de adentro del anillo (el dato ya está
   repetido afuera - [data-doc-hint] junto al título, y [data-avance-hint]
   agregado con el mismo criterio) y el número queda solo, centrado. */
@media (max-width: 1599px) and (min-width: 1181px) {
  .dash-panel-body--donut .donut-label { display: none; }
}

.donut {
  position: relative;
  width: min(var(--dash-donut), 100%);
  aspect-ratio: 1;
  margin: 0;
  perspective: 400px;
}

.donut svg { width: 100%; height: 100%; display: block; }

/* Entrada "con vida" — la dona gira y aparece a la vez que el arco se
   dibuja (transición de stroke-dasharray, ver .donut-seg), en vez de
   quedar quieta mientras solo crece el trazo. */
@keyframes donut-spin-in {
  from { opacity: 0; transform: scale(0.85) rotate(-70deg); }
  to   { opacity: 1; transform: scale(1) rotate(0deg); }
}

.dash-ready .donut {
  animation: donut-spin-in 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes donut-center-in {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}

.dash-ready .donut-center {
  animation: donut-center-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.5s both;
}

.donut-track {
  fill: none;
  stroke: var(--color-bg-input);
  stroke-width: var(--chart-donut-stroke);
}

.dash-cols--charts .dash-panel-body { padding-top: 8px; }

.donut-seg {
  fill: none;
  stroke-width: var(--chart-donut-stroke);
  stroke-linecap: round;
  cursor: pointer;
  transition: stroke-dasharray 0.6s cubic-bezier(0.4, 0, 0.2, 1), stroke-width 0.15s var(--ease-out), filter 0.15s var(--ease-out);
}

.donut-seg:hover {
  stroke-width: calc(var(--chart-donut-stroke) + 4);
  filter: brightness(1.08);
}

/* Al pasar el mouse por un arco, su etiqueta responde con un pop 3D
   (se inclina levemente hacia el cursor y se agranda), como si estuviera
   conectada al arco — el .donut ya tiene perspective: 400px de contexto. */
.donut:has(.donut-seg--ok:hover) .donut-callout--ok,
.donut:has(.donut-seg--partial:hover) .donut-callout--partial,
.donut:has(.donut-seg--none:hover) .donut-callout--none,
.donut-callout:hover {
  transform: translate(-50%, -50%) perspective(300px) rotateX(-14deg) scale(1.22);
}

/* Tres celestes de la propia paleta de marca (oscuro -> claro), no
   ámbar/gris: "sin cargar" sigue siendo un celeste más pálido, pero con
   suficiente saturación para no perderse contra el track neutro. */
.donut-seg--ok      { stroke: var(--color-primary); }
.donut-seg--partial { stroke: var(--color-primary-light); }
.donut-seg--none    { stroke: #7FBEEA; }

.donut-center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.donut-value {
  font-family: var(--font-family-heading);
  font-size: var(--text-donut-value);
  font-weight: var(--weight-bold);
  line-height: 1;
  color: var(--color-text-heading);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.donut-label {
  margin-top: 2px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* Etiquetas DENTRO del anillo (ancladas por JS sobre el radio del propio
   trazo, no flotando afuera): texto blanco + sombra para que se lea sobre
   cualquiera de los tres celestes del segmento. Solo se imprimen cuando
   el arco es lo bastante ancho como para no pisarse con el vecino — ver
   MIN_ARC_FRAC en renderDonut; los segmentos muy chicos quedan sin
   etiqueta inline (su dato sigue disponible en el tooltip nativo <title>). */
/* Con dos líneas de texto más grandes, la etiqueta suele ser más alta
   que el propio grosor del anillo: sin un fondo propio, la mitad que
   se sale del trazo quedaba flotando sobre la tarjeta blanca y el texto
   blanco se perdía por completo ahí. La cápsula oscura semitransparente
   resuelve eso sin importar en qué parte caiga. */
.donut-callout {
  position: absolute;
  transform: translate(-50%, -50%);
  transition: transform 0.2s var(--ease-out);
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 3px 8px;
  border-radius: var(--radius-full);
  background: rgba(39, 39, 42, 0.72);
  backdrop-filter: blur(2px);
  font-size: 10.5px;
  font-weight: var(--weight-medium);
  color: #fff;
  white-space: nowrap;
  text-align: center;
}

.donut-callout strong {
  font-size: 13px;
  font-weight: var(--weight-bold);
  line-height: 1.2;
}

.donut-callout-name {
  font-weight: var(--weight-medium);
  opacity: 0.92;
}

.donut-insight {
  margin: 0;
  max-width: 46ch;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}

/* Callout de próxima acción bajo la dona de "Avance de publicación" —
   mensaje calculado a partir de gtin_temporales (dato real ya usado por
   la propia dona), no un texto fijo. */
.dash-callout {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin: 0 var(--dash-card-pad-x) var(--dash-callout-margin-b);
  padding: var(--dash-callout-pad);
  background: var(--color-primary-bg);
  border-radius: var(--radius-md);
}
.dash-callout-ic {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--dash-callout-icon);
  height: var(--dash-callout-icon);
  flex-shrink: 0;
  color: var(--color-primary);
}
.dash-callout-ic svg { width: 16px; height: 16px; }
.dash-callout-text {
  margin: 0;
  padding-top: 2px;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
  line-height: 1.4;
}
.dash-callout-text strong { color: var(--color-text-heading); font-weight: var(--weight-semibold); }

/* ── Animación de carga ───────────────────────── */
.dash-loader {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-panel);
  transition: opacity 0.4s var(--ease-out), visibility 0.4s;
}

.dash-loader--done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.dash-loader-ring {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 3px solid var(--color-primary-bg);
  border-top-color: var(--color-primary);
  animation: dash-spin 0.8s linear infinite;
}

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

/* Entrada escalonada de las tarjetas cuando el loader se retira */
.dash-ready .dash-kpis > *,
.dash-ready .dash-cols > * {
  animation: dash-rise 1.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.dash-ready .dash-kpis > :nth-child(2) { animation-delay: 0.14s; }
.dash-ready .dash-kpis > :nth-child(3) { animation-delay: 0.28s; }
.dash-ready .dash-cols > * { animation-delay: 0.38s; }
.dash-ready .dash-cols > :nth-child(2) { animation-delay: 0.52s; }

@keyframes dash-rise {
  from { opacity: 0; transform: translateY(24px) scale(0.97); filter: blur(8px); }
}

/* ═══════════════════════════════════════════════════════
   Añadidos funcionales — todo con tokens existentes, sin colores/
   radios/sombras nuevos.
   ═══════════════════════════════════════════════════════ */

/* El componente de topbar (buscador, campana, menú de usuario, elevación
   por encima de las cards de abajo) es compartido por todas las páginas
   del panel — ver layout.css. El Panel general solo la aprieta un poco
   más porque ya tiene bastante contenido debajo. */
.topbar { margin-bottom: var(--space-2); }
.main-content--surface .main-content-scroll { padding-bottom: var(--space-8); }
.dash-panel-head { flex-wrap: wrap; row-gap: 2px; }

/* ── Indicador de tendencia en las cards KPI (junto al ícono, arriba) ── */
.dash-kpi-trend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dash-kpi-trend svg { width: 11px; height: 11px; }
.dash-kpi-trend--up   { background: var(--color-success-bg); color: var(--color-success-strong); }
.dash-kpi-trend--down { background: var(--color-danger-bg);  color: var(--color-danger-strong); }
.dash-kpi-trend--flat { background: var(--color-bg-input);   color: var(--color-text-muted); }
.dash-kpi--hero .dash-kpi-trend--up,
.dash-kpi--hero .dash-kpi-trend--down,
.dash-kpi--hero .dash-kpi-trend--flat {
  background: rgba(255, 255, 255, 0.18);
  color: #fff;
}

/* ── Link "Ver detalle →" en la cabecera de los paneles ── */
.dash-panel-link {
  margin-left: auto;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-primary);
  text-decoration: none;
  white-space: nowrap;
  align-self: center;
}
.dash-panel-link:hover { text-decoration: underline; }
/* Cuando además hay hint, el link queda antes y el hint pegado a la derecha */
.dash-panel-head .dash-panel-link + .dash-panel-hint { margin-left: var(--space-3); }

/* ── Leyenda en lista del donut — a la derecha del anillo ──────── */
.donut-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--donut-legend-gap);
  min-width: 0;
}
.donut-legend li {
  display: grid;
  grid-template-columns: var(--donut-legend-dot) 1fr auto;
  align-items: center;
  gap: var(--donut-legend-gap);
  font-size: var(--text-donut-legend);
}
.donut-legend-dot { width: var(--donut-legend-dot); height: var(--donut-legend-dot); border-radius: var(--radius-full); }
.donut-legend-dot--ok      { background: var(--color-primary); }
.donut-legend-dot--partial { background: var(--color-primary-light); }
.donut-legend-dot--none    { background: #7FBEEA; } /* mismo valor que .donut-seg--none */
.donut-legend-dot--total   { background: var(--color-border); }
.donut-legend-label { color: var(--color-text-secondary); }
.donut-legend-val {
  color: var(--color-text-heading);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.donut-legend-val span { color: var(--color-text-muted); font-weight: var(--weight-regular); }

/* ── Feed de actividad reciente ───────────────── */
.dash-activity {
  list-style: none;
  margin: 0;
  padding: 0;
}
.dash-activity-item {
  display: flex;
  gap: var(--space-3);
  padding: 8px var(--space-5);
  border-bottom: 1px solid var(--color-border-light);
}
.dash-activity-item:last-child { border-bottom: none; }
.dash-activity-ic {
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  border-radius: var(--icon-badge-radius);
  background: var(--icon-badge-bg);
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}
.dash-activity-ic svg { width: 16px; height: 16px; }

/* Color por tipo de evento — verde alta de producto, violeta actualización
   de documento, azul (default arriba) el resto de las acciones sobre un
   producto. Solo los tipos que ya existen en ACCION_INFO (dashboard.js). */
.dash-activity-ic--success { background: var(--color-success-bg); color: var(--color-success-strong); }
.dash-activity-ic--violet  { background: var(--color-violet-bg);  color: var(--color-violet-strong); }
.dash-activity-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.dash-activity-text { font-size: var(--text-sm); color: var(--color-text-primary); }
.dash-activity-text strong { font-weight: var(--weight-semibold); }
.dash-activity-time { font-size: var(--text-xs); color: var(--color-text-muted); white-space: nowrap; }

/* ── Acciones rápidas — 2×2 de filas compactas (ícono + texto + chevron) ── */
.dash-quick {
  display: grid;
  /* minmax(0, 1fr), no 1fr a secas: el track flexible de grid usa
     minmax(auto, 1fr) por defecto, así que su piso es el min-content de
     la card (ícono + título + descripción) — con descripciones largas
     ("Ver y actualizar por producto") eso desbordaba el propio ancho de
     .dash-quick en vez de comprimir la tarjeta y activar el ellipsis de
     .dash-quick-desc. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--dash-gap);
  height: 100%;
  /* start, no center: con la fila estirada al alto de "Avance de
     publicación" (que ahora tiene el callout debajo del donut), centrar
     dejaba tanto aire arriba como abajo de los accesos — pegados arriba,
     el aire que sobra queda en un solo bloque abajo en vez de partido. */
  align-content: start;
}
.dash-quick-item {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--dash-quick-pad);
  border: 1px solid var(--color-border-light);
  border-radius: var(--radius-md);
  text-decoration: none;
  background: var(--color-bg-surface);
  transition: border-color 0.15s var(--ease-out), transform 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out);
}
.dash-quick-item:hover {
  border-color: var(--color-primary-border);
  transform: translateY(-2px);
  box-shadow: var(--shadow-card);
}
.dash-quick-ic {
  flex-shrink: 0;
  width: var(--dash-quick-icon);
  height: var(--dash-quick-icon);
  border-radius: var(--icon-badge-radius);
  background: var(--icon-badge-bg);
  color: var(--color-primary);
  display: flex;
  align-items: center;
  justify-content: center;
}
.dash-quick-ic svg { width: 17px; height: 17px; }
.dash-quick-body { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.dash-quick-title { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--color-text-heading); }
.dash-quick-desc  {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.35;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.dash-quick-arrow { width: 14px; height: 14px; flex-shrink: 0; color: var(--color-text-muted); }

@media (max-width: 620px) {
  .dash-quick { grid-template-columns: 1fr; }
}

/* ── Responsive ───────────────────────────────── */

/* Notebook estándar (1280–1599px) y notebook chica (<1280px): el aire,
   la tipografía y las alturas mínimas de las cards ya se achican solas
   acá arriba porque todas usan tokens --dash-*, --text-panel-title y
   --text-donut-value — ver los dos bloques @media en tokens.css que los
   redefinen para esos rangos. Nada que repetir en este archivo. */

/* overflow-x:hidden ya es la base de .main-content-scroll en layout.css
   (aplica a todo el panel, no solo acá) — la causa real de cualquier
   desborde horizontal en esta página se corrige en el origen
   (min-width:0 en .dash-panel/.col-chart/.col-chart-plot y
   minmax(0, 1fr) en los grids de arriba). */

/* La topbar en 2 filas mide más de alto que la de 1 fila original — en
   notebook se achica el margen de abajo un poco más para que no le robe
   altura útil a la última fila de cards. */
@media (max-width: 1599px) {
  .topbar { margin-bottom: var(--space-1); }
}

/* Antes, a ≤1240px, "Acciones rápidas" pasaba a su propia fila debajo de
   "Actividad reciente"/"Avance de publicación" (2 columnas + la 3ra a todo
   el ancho). Con zoom de navegador >100% (ej. 110% en una notebook de
   1366px, que da un viewport efectivo de ~1242px) eso agregaba una fila
   entera de alto extra justo debajo del umbral de la fila anterior — y
   terminaba cortando el panel contra el borde de la ventana. Las 3
   columnas ya se comprimen bien con minmax(0, 1fr) (ver arriba) y el
   grid 2×2 interno de .dash-quick, así que se mantienen parejas hasta el
   colapso a 1 columna de abajo — sin escalón intermedio que sume alto. */

@media (max-width: 1180px) {
  .dash-kpis { grid-template-columns: 1fr; }
  .dash-cols,
  .dash-cols--charts,
  .dash-cols--3 { grid-template-columns: 1fr; }
  .dash-cols--3 > :last-child { grid-column: auto; }
  .dash-quick { grid-template-columns: repeat(2, 1fr); }
}

/* .dash-card-summary oculto por defecto (fuera de cualquier media query,
   mismo criterio que .bottom-navbar/.sidebar-backdrop en layout.css): sin
   esto, en desktop el <div> se ve igual (display:block por defecto) y sus
   <svg> sin tamaño explícito fuera de la banda mobile se renderizan a su
   tamaño intrínseco enorme, encima de la card real. */
.dash-card-summary { display: none; }

/* .dash-area-mobile — misma razón que .dash-card-summary de arriba. */
.dash-area-mobile { display: none; }

/* ═══════════════════════════════════════════════════════
   Mobile (≤860px) — mismo breakpoint que layout.css/productos.css/etc.
   ═══════════════════════════════════════════════════════ */
@media (max-width: 860px) {
  /* Bug real: mi override de padding-bottom en layout.css (".main-content-scroll",
     una sola clase) perdía contra ESTA MISMA regla de acá arriba (línea 728,
     ".main-content--surface .main-content-scroll", dos clases = más
     especificidad) — quedaba en 32px en vez de los ~100px necesarios para
     no tapar el último bloque de contenido ("Acciones rápidas") detrás de
     la isla de navegación. Se pisa acá mismo, con la misma especificidad
     exacta que la regla original, para no depender de ganar por afuera. */
  .main-content--surface .main-content-scroll {
    padding-bottom: calc(var(--mobile-nav-height) + var(--mobile-nav-bottom-offset) + var(--space-4) + env(safe-area-inset-bottom, 0px));
    /* Mismo bug, ahora en padding-top: la línea 7 de este archivo
       (misma especificidad, sin media query) le da a esta página
       --content-pad-top (16px) de padding-top siempre — sin pisarlo acá
       también, quedaba una franja del canvas por encima del header
       "hero" antes de la curva (.surface ya está en 0 en esta página,
       ver arriba, así que ESTE es el único padding-top real a cancelar
       acá). */
    padding-top: 0;
  }

  /* "Acciones rápidas": el bloque de arriba (≤1180px) pisaba la regla de
     ≤620px que ya existía (misma especificidad, gana la que está despues
     en cascada) — a 320-375px el grid seguia en 2 columnas y
     ".dash-quick-desc" (con text-overflow:ellipsis) cortaba el texto en
     silencio. Ancho completo, 1 columna, entra sin elipsis. */
  .dash-quick { grid-template-columns: 1fr; }

  /* Mas separacion entre las 3 filas de cards del dashboard. Puntual en
     estos 3 selectores (no en --dash-gap): ese token tambien controla el
     gap INTERNO entre cards de la misma fila y el de .dash-quick —
     cambiarlo ahi de paso rompia mas de lo pedido. */
  .dash-kpis,
  .dash-cols,
  .dash-cols--3 { margin-bottom: var(--mobile-card-gap); }

  /* Header "hero" con degradé — base compartida en layout.css (ver ahí
     .dash-mobile-hero, incluido el margin-bottom que separa el header
     de esta sección). Acá solo lo específico de esta página: la card
     hero MANTIENE su degradé de marca de siempre (--gradient-kpi-hero,
     ya definido en la regla base de .dash-kpi--hero más arriba en este
     archivo) — no se pisa a blanco, pierde la jerarquía visual que
     tiene en desktop si se hace blanca como el resto. */

  /* CTA "Cargar producto" embebido en la card (referencia: mockup de
     Vannier armado sobre preview.webp) — reemplaza el FAB flotante que
     se usaba antes acá. initMobileHeroLayout() (topbar.js) porta el
     mismo <a>/<button> de siempre a [data-kpi-hero-cta-slot], dentro de
     .dash-kpi-mid, que pasa de columna a fila para poner value+sub a la
     izquierda y el botón a la derecha, como el balance card de la
     referencia. */
  .dash-kpi--hero .dash-kpi-mid {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
  }
  .dash-kpi--hero .dash-kpi-value-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .dash-kpi-hero-cta:empty { display: none; }
  .dash-kpi--hero .btn-primary {
    flex-shrink: 0;
    background: #fff;
    color: var(--color-primary);
    white-space: nowrap;
    padding: 8px 14px;
    font-size: 12.5px;
    border-radius: var(--radius-full);
  }

  /* Bug real: ".topbar { margin-bottom: var(--space-1) }" de la banda
     ≤1599px de este mismo archivo (línea 916) también matchea en mobile
     y, por cargar este archivo después de layout.css, le ganaba al
     margin-bottom de .dash-mobile-hero (misma especificidad, gana el
     que está más abajo en cascada) — el header quedaba pegado a la
     primera KPI con apenas 4px en vez de los ~20px del token. Se pisa
     acá mismo, más abajo en el archivo, con la misma especificidad
     exacta que la regla que lo gana. */
  .dash-mobile-hero { margin-bottom: var(--mobile-hero-gap); }

  /* Donut chico ("Avance de publicación") — .dash-panel-body--donut:has(.donut--sm)
     depende de :has(): si el navegador no lo soporta, cae al font-size
     base (--text-sm, 12.5px) sobre un anillo mucho mas chico que en
     desktop — desborda seguro. Selector de descendencia comun (sin :has())
     como refuerzo, siempre aplica sin importar soporte de :has(). Los
     --dash-donut*/--dash-donut-value-* ya se agrandan en esta banda
     (ver tokens.css) porque en 1 columna sobra ancho de verdad. */
  .donut--sm .donut-value { font-size: var(--dash-donut-value-sm); }
  .donut--sm .donut-label {
    font-size: 11px;
    max-width: 78%;
    margin-top: 0;
    line-height: 1.15;
  }

  /* ── Progressive disclosure: card resumen + hoja de detalle ──────────
     "Por área terapéutica" / "Estado de documentos" / "Avance de
     publicación" muestran solo un resumen chico por defecto; el contenido
     real (head + body [+ callout]) se oculta ACA (en su lugar de origen)
     y solo se ve cuando el JS lo "porta" (mueve, no copia) a la hoja
     compartida .dash-detail-sheet — ver initDetailSheets() en
     dashboard.js. "Actividad reciente" y "Acciones rápidas" NO llevan
     .dash-card-summary (no tienen ese markup), asi que no les afecta
     ninguna regla de este bloque. */
  /* .dash-panel--collapsible (clase explícita en el HTML, no :has()): el
     propio bug de la Parte 1.2 (font-size que no aplicaba por depender de
     :has()) es la prueba de que no conviene apoyar el mecanismo PRINCIPAL
     de este patrón en un selector que puede no tener soporte — acá se
     necesita que sí o sí funcione en cualquier navegador. */
  .dash-card-summary { display: flex; }
  .dash-panel--collapsible > .dash-panel-head,
  .dash-panel--collapsible > .dash-panel-body,
  .dash-panel--collapsible > .dash-callout {
    display: none;
  }

  /* "Por área terapéutica" ya no es progressive-disclosure (no abre hoja
     de detalle) — pasa a una lista siempre visible con los mismos datos
     reales que ya calcula renderCanales()/itemsPorArea() (dashboard.js),
     mismo patrón de la referencia (mockup sobre preview.webp). El
     selector "Por área/Por estado" y el gráfico de barras de desktop se
     ocultan acá (no se borran, siguen intactos para desktop). */
  .dash-panel--area-list > .dash-panel-head,
  .dash-panel--area-list > .dash-panel-body {
    display: none;
  }
  .dash-area-mobile { display: block; }

  .dash-card-summary {
    align-items: center;
    gap: var(--space-4);
    width: 100%;
    min-height: 44px;
    padding: var(--space-4) var(--dash-card-pad-x);
    border: none;
    background: none;
    text-align: left;
    cursor: pointer;
    font-family: var(--font-family);
  }
  .dash-card-summary:active { background: var(--color-bg-input); }
  .dash-card-summary:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: -2px;
  }

  .dash-card-summary-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--icon-badge-radius);
    background: var(--icon-badge-bg);
    color: var(--color-primary);
  }
  .dash-card-summary-icon svg { width: 20px; height: 20px; }

  .dash-card-summary-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
  }
  .dash-card-summary-title {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }
  .dash-card-summary-value {
    font-family: var(--font-family-heading);
    font-size: 24px;
    font-weight: var(--weight-bold);
    line-height: 1.15;
    color: var(--color-text-heading);
    font-variant-numeric: tabular-nums;
  }
  .dash-card-summary-sub {
    font-size: var(--text-sm);
    color: var(--color-text-secondary);
  }
  .dash-card-summary-bar {
    display: block;
    width: 100%;
    max-width: 160px;
    height: 4px;
    margin-top: 4px;
    border-radius: var(--radius-full);
    background: var(--color-bg-input);
    overflow: hidden;
  }
  .dash-card-summary-bar-fill {
    display: block;
    height: 100%;
    width: 0%;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    transition: width 0.5s var(--ease-out);
  }
  .dash-card-summary-chevron {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    color: var(--color-text-muted);
  }

  /* Lista mobile de "Por área terapéutica" — mismos tokens que el resto
     de las cards (nada nuevo fuera de la paleta), estilo de fila con
     ícono + nombre + cantidad, misma composición que la referencia. */
  .dash-area-mobile {
    padding: var(--dash-card-pad-y) var(--dash-card-pad-x);
  }
  .dash-area-mobile-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: var(--space-3);
  }
  .dash-area-mobile-head .dash-panel-title { margin: 0; }
  .dash-area-mobile-head a {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-primary);
    text-decoration: none;
    flex-shrink: 0;
  }
  .dash-area-mobile-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }
  .dash-area-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 10px 12px;
    background: var(--color-bg-panel);
    border-radius: var(--radius-control);
  }
  .dash-area-icon {
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--icon-badge-radius);
    background: var(--icon-badge-bg);
    color: var(--color-primary);
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dash-area-icon svg { width: 16px; height: 16px; }
  .dash-area-text {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
  }
  .dash-area-name {
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .dash-area-sub {
    font-size: var(--text-xs);
    color: var(--color-text-muted);
  }
  .dash-area-value {
    flex-shrink: 0;
    font-family: var(--font-family-heading);
    font-weight: var(--weight-bold);
    font-size: var(--text-base);
    font-variant-numeric: tabular-nums;
    color: var(--color-text-heading);
  }
  .dash-area-empty {
    margin: 0;
    padding: var(--space-3) 0;
    font-size: var(--text-sm);
    color: var(--color-text-muted);
  }

  /* Hoja de detalle — pantalla completa, entra desde abajo. Un solo nodo
     compartido por las 3 cards (ver dashboard.html): el body recibe el
     bloque head+body[+callout] portado desde la card que se tocó. */
  .dash-detail-sheet {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    flex-direction: column;
    background: var(--color-bg-panel);
    transform: translateY(100%);
    transition: transform var(--mobile-sheet-duration) ease-out;
    overflow-y: auto;
  }
  .dash-detail-sheet[hidden] { display: none; }
  .dash-detail-sheet--open { transform: translateY(0); }

  .dash-detail-sheet-head {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-shrink: 0;
    padding: calc(var(--space-4) + env(safe-area-inset-top, 0px)) var(--space-5) var(--space-4);
    background: var(--color-bg-surface);
    border-bottom: 1px solid var(--color-border-light);
  }
  .dash-detail-sheet-back {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    margin-left: calc(var(--space-3) * -1);
    border: none;
    border-radius: var(--radius-full);
    background: none;
    color: var(--color-text-primary);
    cursor: pointer;
  }
  .dash-detail-sheet-back:active { background: var(--color-bg-input); }
  .dash-detail-sheet-back svg { width: 24px; height: 24px; }

  .dash-detail-sheet-body {
    flex: 1;
    padding: var(--space-2) 0 var(--space-8);
  }
  /* No hace falta "reactivar" el display del head/body/callout portados:
     la regla que los oculta es ".dash-panel--collapsible > .dash-panel-head"
     (con combinador directo) — apenas el nodo se muda a
     .dash-detail-sheet-body deja de matchear ese selector por su cuenta y
     cada uno vuelve a su display normal (flex/grid/block segun su propia
     regla, ej. .dash-panel-body--donut sigue siendo grid). Agregar una
     regla propia acá pisaría esas variantes por especificidad. */
}

@media (prefers-reduced-motion: reduce) {
  .col-bar-fill,
  .col-tooltip,
  .donut-seg { transition: none; }
  .dash-loader-ring { animation-duration: 2.4s; }
  .dash-ready .dash-kpis > *,
  .dash-ready .dash-cols > *,
  .dash-ready .donut,
  .dash-ready .donut-center { animation: none; }
}
