/* ══════════════════════════════════════════
   MOVIMIENTO COMÚN (shared/js/ui/motion.js)
   Panel de filtros que se despliega, botón «Limpiar» que barre y tarjetas
   resumen cuyas cifras cuentan al subir o bajar. Solo transform/opacity
   salvo el alto del panel (animado por JS con la Web Animations API).
   Como los gráficos, no se apagan con «reducir movimiento» (motion.js).
══════════════════════════════════════════ */

/* ── 1. Panel de filtros ── */
/* Mientras se pliega, el panel ya no es `.open` pero sigue visible. */
.filter-panel.is-closing {
  display: grid;
  position: relative;
  z-index: 30;
  pointer-events: none;
}
/* Durante el despliegue nada sobresale (los desplegables se abren después). */
.filter-panel.is-animando { overflow: hidden; }
/* El alto lo anima JS: el fundido CSS de respaldo no se suma. */
.filter-panel.open.is-animando { animation: none; }

@media (max-width: 640px) {
  .filter-panel.is-closing {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }
}

/* Embudo del botón de filtros: «vierte» al abrir y se recoloca al cerrar. */
.filter-toggle svg { transform-origin: 50% 35%; }
.filter-toggle.dc-filtro-abre svg { animation: dcFiltroAbre 520ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.filter-toggle.dc-filtro-cierra svg { animation: dcFiltroCierra 420ms cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes dcFiltroAbre {
  0%   { transform: none; }
  30%  { transform: translateY(2px) scale(1.12, 0.84); }
  60%  { transform: translateY(-2px) scale(0.94, 1.08); }
  100% { transform: none; }
}
@keyframes dcFiltroCierra {
  0%   { transform: none; }
  35%  { transform: rotate(-14deg) scale(0.9); }
  70%  { transform: rotate(6deg); }
  100% { transform: none; }
}

/* ── 2. Botón «Limpiar»: barrido ── */
.is-limpiando {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border-color: var(--accent);
  color: var(--accent);
  animation: dcLimpiarFrote 620ms cubic-bezier(0.36, 0.07, 0.19, 0.97) var(--dc-t, 0ms);
}
/* Destello que cruza el botón, como una pasada de escoba. */
.is-limpiando::after {
  content: '';
  position: absolute;
  inset: -2px;
  pointer-events: none;
  background: linear-gradient(100deg,
    transparent 20%,
    color-mix(in srgb, var(--accent) 45%, transparent) 46%,
    color-mix(in srgb, var(--text) 25%, transparent) 50%,
    color-mix(in srgb, var(--accent) 20%, transparent) 58%,
    transparent 80%);
  transform: translateX(-110%);
  animation: dcLimpiarBarrido 620ms cubic-bezier(0.4, 0, 0.2, 1) var(--dc-t, 0ms) forwards;
}
/* Chispas de «limpio» que aparecen tras la pasada. */
.is-limpiando::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at 14% 28%, var(--accent) 0 1.8px, transparent 2.6px),
    radial-gradient(circle at 86% 24%, var(--accent) 0 2.2px, transparent 3px),
    radial-gradient(circle at 74% 80%, var(--accent) 0 1.6px, transparent 2.4px),
    radial-gradient(circle at 30% 82%, var(--accent) 0 1.4px, transparent 2px),
    radial-gradient(circle at 52% 14%, var(--text) 0 1.2px, transparent 1.8px);
  opacity: 0;
  transform: scale(0.6);
  animation: dcLimpiarChispas 520ms ease-out calc(360ms + var(--dc-t, 0ms)) forwards;
}
.is-limpiando svg {
  transform-origin: 50% 90%;
  animation: dcLimpiarEscoba 620ms cubic-bezier(0.36, 0.07, 0.19, 0.97) var(--dc-t, 0ms);
}
/* Si limpiar oculta el botón, termina la pasada y se desvanece. */
.is-limpiando.is-hidden {
  display: inline-flex !important;
  pointer-events: none;
  animation:
    dcLimpiarFrote 620ms cubic-bezier(0.36, 0.07, 0.19, 0.97) var(--dc-t, 0ms),
    dcLimpiarSalida 240ms ease-in calc(640ms + var(--dc-t, 0ms)) forwards;
}

/* Portfolio IA oculta también la fila que lo contiene (.table-filter-actions):
   sigue visible mientras dura la pasada. */
.table-filter-actions:has(.is-limpiando) { display: flex !important; }

@keyframes dcLimpiarFrote {
  0%, 100% { transform: none; }
  15% { transform: translateX(-2px) rotate(-3deg); }
  35% { transform: translateX(2px) rotate(2.5deg); }
  55% { transform: translateX(-1px) rotate(-1.5deg); }
  75% { transform: translateX(1px) rotate(0.8deg); }
}
@keyframes dcLimpiarBarrido {
  to { transform: translateX(110%); }
}
@keyframes dcLimpiarChispas {
  0%   { opacity: 0; transform: scale(0.6); }
  40%  { opacity: 1; transform: scale(1.05); }
  100% { opacity: 0; transform: scale(1.2); }
}
@keyframes dcLimpiarEscoba {
  0%, 100% { transform: none; }
  20% { transform: rotate(-22deg); }
  45% { transform: rotate(18deg); }
  70% { transform: rotate(-8deg); }
}
@keyframes dcLimpiarSalida {
  to { opacity: 0; transform: scale(0.92); }
}

/* ── 4. Favorito: la estrella salta con anillo y chispas / se encoge al quitarla ──
   --dc-t (retraso negativo) lo pone motion.js cuando la estrella se repinta a
   mitad de la animación: el botón nuevo sigue donde iba el anterior. */
.dc-fav-on,
.dc-fav-off { position: relative; overflow: visible; }
.dc-fav-on svg { animation: dcFavOn 620ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms) both; }
.dc-fav-off svg { animation: dcFavOff 420ms cubic-bezier(0.22, 1, 0.36, 1) var(--dc-t, 0ms) both; }
/* Anillo que se expande desde la estrella. */
.dc-fav-on::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 2px solid var(--favorite);
  pointer-events: none;
  opacity: 0;
  animation: dcFavAnillo 560ms cubic-bezier(0.22, 1, 0.36, 1) var(--dc-t, 0ms) forwards;
}
/* Chispas alrededor (cardinales y diagonales). */
.dc-fav-on::before {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 40px; height: 40px;
  margin: -20px 0 0 -20px;
  pointer-events: none;
  background:
    radial-gradient(circle at 50% 4%, var(--favorite) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 96% 50%, var(--favorite) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 50% 96%, var(--favorite) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 4% 50%, var(--favorite) 0 1.8px, transparent 2.4px),
    radial-gradient(circle at 18% 18%, var(--favorite) 0 1.3px, transparent 1.9px),
    radial-gradient(circle at 82% 18%, var(--favorite) 0 1.3px, transparent 1.9px),
    radial-gradient(circle at 82% 82%, var(--favorite) 0 1.3px, transparent 1.9px),
    radial-gradient(circle at 18% 82%, var(--favorite) 0 1.3px, transparent 1.9px);
  opacity: 0;
  animation: dcFavChispas 620ms ease-out calc(120ms + var(--dc-t, 0ms)) forwards;
}

@keyframes dcFavOn {
  0%   { transform: scale(0.4) rotate(-35deg); }
  55%  { transform: scale(1.35) rotate(12deg); }
  78%  { transform: scale(0.92) rotate(-4deg); }
  100% { transform: none; }
}
@keyframes dcFavOff {
  0%   { transform: scale(1.12); }
  40%  { transform: scale(0.62) rotate(-18deg); opacity: 0.6; }
  100% { transform: none; opacity: 1; }
}
@keyframes dcFavAnillo {
  0%   { opacity: 0.9; transform: scale(0.3); }
  100% { opacity: 0; transform: scale(1.55); }
}
@keyframes dcFavChispas {
  0%   { opacity: 0; transform: scale(0.45); }
  35%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.25) rotate(20deg); }
}

/* ── 5. Botones de acción (editar, importar, exportar, añadir, actualizar) ──
   data-dc-tipo lo pone motion.js. Editar y actualizar animan el icono al
   pasar el ratón y otra vez al hacer clic (copia «B» del fotograma, para
   poder relanzarlo); importar y exportar solo al hacer clic (como «Limpiar»);
   añadir crece un poco al pasar el ratón y estalla al pulsarlo. Todos con un
   anillo de pulsación de acento. */
[data-dc-tipo] svg { transform-origin: 50% 50%; }
[data-dc-tipo="editar"] svg { transform-origin: 15% 85%; }
[data-dc-tipo="anadir"] svg { transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1); }

@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="editar"]:hover:not(:disabled) svg     { animation: dcIcoEditar 640ms ease-in-out; }
  [data-dc-tipo="actualizar"]:hover:not(:disabled) svg { animation: dcIcoActualizar 700ms cubic-bezier(0.34, 1.3, 0.64, 1); }
  /* «+»: pequeño zoom (sin girar). */
}
[data-dc-tipo="editar"].dc-pulsado svg     { animation: dcIcoEditarB 640ms ease-in-out var(--dc-t, 0ms); }
[data-dc-tipo="actualizar"].dc-pulsado svg { animation: dcIcoActualizarB 700ms cubic-bezier(0.34, 1.3, 0.64, 1) var(--dc-t, 0ms); }
[data-dc-tipo].dc-pulsado { animation: dcPulsoAccion 560ms ease-out var(--dc-t, 0ms); }

/* Importar y exportar: el botón ENTERO se llena (exportar = descarga, de
   izquierda a derecha; importar = subida, de abajo arriba), destella en verde
   al completarse y el color se disipa como humo (se difumina, crece y se
   desvanece, con volutas que suben). La flecha del icono acompaña. La clase
   dura DC_MS_CARGA (motion.js). */
[data-dc-tipo="exportar"].dc-pulsado,
[data-dc-tipo="importar"].dc-pulsado {
  --dc-relleno: color-mix(in srgb, var(--accent) 42%, transparent);
  position: relative;
  overflow: hidden;
  isolation: isolate;
  animation: dcPulsoAccion 560ms ease-out var(--dc-t, 0ms);
}
/* Sobre un botón azul, el relleno es claro para que se vea. */
.btn-primary:is([data-dc-tipo="exportar"], [data-dc-tipo="importar"]).dc-pulsado { --dc-relleno: color-mix(in srgb, var(--on-accent) 30%, transparent); }
[data-dc-tipo="exportar"].dc-pulsado svg {
  transform-origin: 50% 100%;
  animation: dcIcoExportar 760ms cubic-bezier(0.34, 1.3, 0.64, 1) var(--dc-t, 0ms);
}
[data-dc-tipo="importar"].dc-pulsado svg {
  animation: dcIcoImportar 760ms cubic-bezier(0.22, 1, 0.36, 1) var(--dc-t, 0ms);
}
:is([data-dc-tipo="exportar"], [data-dc-tipo="importar"]).dc-pulsado::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--dc-relleno);
  will-change: transform, opacity, filter;
}
[data-dc-tipo="exportar"].dc-pulsado::after {
  transform-origin: 0 50%;
  transform: scaleX(0);
  animation: dcCargaDescarga 1300ms cubic-bezier(0.4, 0, 0.2, 1) var(--dc-t, 0ms) forwards;
}
[data-dc-tipo="importar"].dc-pulsado::after {
  transform-origin: 50% 100%;
  transform: scaleY(0);
  animation: dcCargaSubida 1300ms cubic-bezier(0.4, 0, 0.2, 1) var(--dc-t, 0ms) forwards;
}
/* Volutas de humo: aparecen al completarse y suben mientras se desvanecen. */
:is([data-dc-tipo="exportar"], [data-dc-tipo="importar"]).dc-pulsado::before {
  content: '';
  position: absolute;
  inset: -20% -10%;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(circle at 20% 70%, var(--dc-relleno) 0 18%, transparent 30%),
    radial-gradient(circle at 50% 60%, var(--dc-relleno) 0 22%, transparent 36%),
    radial-gradient(circle at 80% 72%, var(--dc-relleno) 0 16%, transparent 28%);
  animation: dcCargaHumo 1300ms ease-out var(--dc-t, 0ms) forwards;
}

/* Añadir: el «+» se encoge y estalla hacia fuera con chispas de acento que
   salen en las cuatro direcciones, como algo nuevo que aparece. */
[data-dc-tipo="anadir"].dc-pulsado { position: relative; overflow: visible; }
[data-dc-tipo="anadir"].dc-pulsado svg {
  animation: dcIcoAnadirB 560ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms);
}
[data-dc-tipo="anadir"].dc-pulsado::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 56px; height: 56px;
  margin: -28px 0 0 -28px;
  pointer-events: none;
  background:
    radial-gradient(circle at 50% 6%, var(--accent) 0 2.2px, transparent 2.8px),
    radial-gradient(circle at 94% 50%, var(--accent) 0 2.2px, transparent 2.8px),
    radial-gradient(circle at 50% 94%, var(--accent) 0 2.2px, transparent 2.8px),
    radial-gradient(circle at 6% 50%, var(--accent) 0 2.2px, transparent 2.8px),
    radial-gradient(circle at 20% 20%, var(--accent2) 0 1.6px, transparent 2.2px),
    radial-gradient(circle at 80% 20%, var(--accent2) 0 1.6px, transparent 2.2px),
    radial-gradient(circle at 80% 80%, var(--accent2) 0 1.6px, transparent 2.2px),
    radial-gradient(circle at 20% 80%, var(--accent2) 0 1.6px, transparent 2.2px);
  opacity: 0;
  animation: dcAnadirChispas 620ms cubic-bezier(0.22, 1, 0.36, 1) calc(90ms + var(--dc-t, 0ms)) forwards;
}

/* Lápiz: garabatea como si escribiera. */
@keyframes dcIcoEditar {
  0%, 100% { transform: none; }
  20% { transform: rotate(-16deg) translate(-1px, 1px); }
  40% { transform: rotate(8deg) translate(1px, -1px); }
  60% { transform: rotate(-10deg) translate(-1px, 0); }
  80% { transform: rotate(4deg); }
}
@keyframes dcIcoEditarB {
  0%, 100% { transform: none; }
  20% { transform: rotate(-16deg) translate(-1px, 1px); }
  40% { transform: rotate(8deg) translate(1px, -1px); }
  60% { transform: rotate(-10deg) translate(-1px, 0); }
  80% { transform: rotate(4deg); }
}
/* Exportar (descarga): dos caídas a la bandeja con aplastamiento. */
@keyframes dcIcoExportar {
  0%   { transform: none; }
  18%  { transform: translateY(-3px); }
  34%  { transform: translateY(3px) scale(1.08, 0.86); }
  50%  { transform: translateY(-2px); }
  66%  { transform: translateY(2px) scale(1.05, 0.9); }
  100% { transform: none; }
}
/* Relleno de la descarga y de la subida: se llena (0–55 %), destella en verde
   al completarse (55–68 %) y se disipa como humo (68–100 %). */
@keyframes dcCargaDescarga {
  0%   { transform: scaleX(0); opacity: 1; filter: blur(0); }
  55%  { transform: scaleX(1); opacity: 1; filter: blur(0); background: var(--dc-relleno); }
  68%  { transform: scale(1); opacity: 1; filter: blur(0); background: color-mix(in srgb, var(--green) 45%, transparent); }
  100% { transform: scale(1.18, 1.5); opacity: 0; filter: blur(10px); background: color-mix(in srgb, var(--green) 30%, transparent); }
}
@keyframes dcCargaSubida {
  0%   { transform: scaleY(0); opacity: 1; filter: blur(0); }
  55%  { transform: scaleY(1); opacity: 1; filter: blur(0); background: var(--dc-relleno); }
  68%  { transform: scale(1); opacity: 1; filter: blur(0); background: color-mix(in srgb, var(--green) 45%, transparent); }
  100% { transform: scale(1.18, 1.5) translateY(-8%); opacity: 0; filter: blur(10px); background: color-mix(in srgb, var(--green) 30%, transparent); }
}
@keyframes dcCargaHumo {
  0%, 60% { opacity: 0; transform: translateY(20%) scale(0.9); filter: blur(2px); }
  75%     { opacity: 0.8; }
  100%    { opacity: 0; transform: translateY(-35%) scale(1.35); filter: blur(7px); }
}
/* Importar (subida): sale por arriba y vuelve a entrar desde abajo. */
@keyframes dcIcoImportar {
  0%   { transform: none; opacity: 1; }
  40%  { transform: translateY(-11px); opacity: 0; }
  41%  { transform: translateY(10px); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
/* Añadir: se encoge, rebota grande y se posa (sin girar). */
@keyframes dcIcoAnadirB {
  0%   { transform: scale(1.2); }
  25%  { transform: scale(0.65); }
  60%  { transform: scale(1.4); }
  100% { transform: none; }
}
@keyframes dcAnadirChispas {
  0%   { opacity: 0; transform: scale(0.35); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.35); }
}
/* Actualizar: las flechas dan una vuelta completa. */
@keyframes dcIcoActualizar {
  from { transform: none; }
  to   { transform: rotate(360deg); }
}
@keyframes dcIcoActualizarB {
  from { transform: none; }
  to   { transform: rotate(360deg); }
}

/* ── 6. Interruptor Sí/No y opciones Sí/Pendiente/No ──
   El botón del interruptor se estira como una gota mientras viaja (rebote de
   muelle), el texto «Sí»/«No» entra deslizándose y, al pasar a «Sí», sale una
   onda verde desde la pista. La opción de tres estados da un pequeño salto. */
.switch-toggle .switch-toggle-thumb { transition: transform 360ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.switch-toggle .switch-toggle-track { transition: background-color 260ms ease-out, box-shadow 260ms ease-out; }
.dc-switch-on .switch-toggle-thumb,
.dc-switch-off .switch-toggle-thumb { animation: dcSwitchGota 380ms cubic-bezier(0.22, 1, 0.36, 1); }
.dc-switch-on .switch-toggle-track { animation: dcSwitchOnda 560ms ease-out; }
.dc-switch-on .switch-toggle-label { animation: dcSwitchTextoSi 360ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.dc-switch-off .switch-toggle-label { animation: dcSwitchTextoNo 320ms cubic-bezier(0.22, 1, 0.36, 1); }
.dc-toggle-pop { animation: dcTogglePop 440ms cubic-bezier(0.34, 1.56, 0.64, 1); }

/* La gota se estira desde su lado de salida (el translate final lo pone la
   transición; aquí solo cambia su anchura aparente). */
@keyframes dcSwitchGota {
  0%   { width: var(--dc-gota, 14px); }
  40%  { width: calc(var(--dc-gota, 14px) * 1.45); }
  100% { width: var(--dc-gota, 14px); }
}
@keyframes dcSwitchOnda {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 55%, transparent); }
  100% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--green) 0%, transparent); }
}
@keyframes dcSwitchTextoSi {
  0%   { opacity: 0; transform: translateY(6px) scale(0.8); }
  100% { opacity: 1; transform: none; }
}
@keyframes dcSwitchTextoNo {
  0%   { opacity: 0; transform: translateY(-6px); }
  100% { opacity: 1; transform: none; }
}
@keyframes dcTogglePop {
  0%   { transform: scale(0.9); }
  45%  { transform: scale(1.08); }
  100% { transform: none; }
}
.form-group .switch-toggle { --dc-gota: 18px; }
/* Anillo de pulsación del botón. */
@keyframes dcPulsoAccion {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 55%, transparent); }
  100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* ── 5b. Guardar, confirmar y eliminar (botón entero; suelen ser solo texto) ── */
/* Guardar: el botón se hunde como al sellar y lo cruza un destello. */
[data-dc-tipo="guardar"].dc-pulsado {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  animation:
    dcPulsoAccion 560ms ease-out var(--dc-t, 0ms),
    dcGuardarSello 460ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms);
}
[data-dc-tipo="guardar"].dc-pulsado::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent 25%,
    color-mix(in srgb, var(--on-accent) 38%, transparent) 50%,
    transparent 75%);
  transform: translateX(-110%);
  animation: dcGuardarDestello 560ms cubic-bezier(0.4, 0, 0.2, 1) calc(80ms + var(--dc-t, 0ms)) forwards;
}
[data-dc-tipo="guardar"].dc-pulsado svg { animation: dcGuardarSello 460ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }

/* Confirmar: late dos veces con un anillo verde (resultado positivo). */
[data-dc-tipo="confirmar"].dc-pulsado {
  animation:
    dcPulsoConfirmar 640ms ease-out var(--dc-t, 0ms),
    dcConfirmarLatido 560ms ease-in-out var(--dc-t, 0ms);
}
[data-dc-tipo="confirmar"].dc-pulsado svg { animation: dcConfirmarIcono 520ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }

/* Eliminar: «niega» con un temblor y un anillo rojo; la papelera levanta la
   tapa al pasar el ratón y al pulsar. */
[data-dc-tipo="eliminar"] svg { transform-origin: 20% 20%; }
@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="eliminar"]:hover:not(:disabled) svg { animation: dcEliminarTapa 560ms cubic-bezier(0.34, 1.56, 0.64, 1); }
}
[data-dc-tipo="eliminar"].dc-pulsado {
  animation:
    dcPulsoEliminar 600ms ease-out var(--dc-t, 0ms),
    dcEliminarNiega 460ms cubic-bezier(0.36, 0.07, 0.19, 0.97) var(--dc-t, 0ms);
}
[data-dc-tipo="eliminar"].dc-pulsado svg { animation: dcEliminarTapaB 560ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }

@keyframes dcGuardarSello {
  0%   { transform: none; }
  30%  { transform: scale(0.93) translateY(1px); }
  65%  { transform: scale(1.03); }
  100% { transform: none; }
}
@keyframes dcGuardarDestello {
  to { transform: translateX(110%); }
}
@keyframes dcConfirmarLatido {
  0%, 100% { transform: none; }
  20% { transform: scale(1.06); }
  40% { transform: scale(0.98); }
  60% { transform: scale(1.04); }
}
@keyframes dcConfirmarIcono {
  0%   { transform: scale(0.6) rotate(-12deg); }
  60%  { transform: scale(1.25) rotate(4deg); }
  100% { transform: none; }
}
@keyframes dcPulsoConfirmar {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 60%, transparent); }
  45%  { box-shadow: 0 0 0 8px color-mix(in srgb, var(--green) 0%, transparent); }
  46%  { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 45%, transparent); }
  100% { box-shadow: 0 0 0 12px color-mix(in srgb, var(--green) 0%, transparent); }
}
@keyframes dcEliminarNiega {
  0%, 100% { transform: none; }
  18% { transform: translateX(-3px) rotate(-1.5deg); }
  36% { transform: translateX(3px) rotate(1.5deg); }
  54% { transform: translateX(-2px); }
  72% { transform: translateX(1px); }
}
@keyframes dcPulsoEliminar {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--red) 55%, transparent); }
  100% { box-shadow: 0 0 0 10px color-mix(in srgb, var(--red) 0%, transparent); }
}
@keyframes dcEliminarTapa {
  0%, 100% { transform: none; }
  35% { transform: rotate(-14deg) translateY(-2px); }
  65% { transform: rotate(6deg); }
}
@keyframes dcEliminarTapaB {
  0%, 100% { transform: none; }
  35% { transform: rotate(-14deg) translateY(-2px); }
  65% { transform: rotate(6deg); }
}

/* ── 3. Tarjetas resumen: la cifra cuenta y se desliza en su sentido ── */
.stat-value { font-variant-numeric: tabular-nums; }
.stat-card.is-sube .stat-value { animation: dcCifraSube 650ms cubic-bezier(0.22, 1, 0.36, 1); }
.stat-card.is-baja .stat-value { animation: dcCifraBaja 650ms cubic-bezier(0.22, 1, 0.36, 1); }
/* Un velo suave recorre la tarjeta mientras cambia la cifra. */
.stat-card.is-sube::after,
.stat-card.is-baja::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(180deg, color-mix(in srgb, var(--text) 6%, transparent), transparent 60%);
  animation: dcCifraPulso 700ms ease-out forwards;
}

@keyframes dcCifraSube {
  0%   { transform: translateY(6px); opacity: 0.55; }
  100% { transform: none; opacity: 1; }
}
@keyframes dcCifraBaja {
  0%   { transform: translateY(-6px); opacity: 0.55; }
  100% { transform: none; opacity: 1; }
}
@keyframes dcCifraPulso {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}


/* ── 7. Barra superior: Casa, Wiki y Ajustes (#hub-home, #wiki-toggle, #settings-toggle) ──
   Al pasar el ratón cada icono anticipa su función; al pulsarlo (clase
   dc-topbar-pulsado, motion.js) la completa, con un anillo de acento.
   - Casa: da un saltito al pasar; al pulsar «despega» hacia el inicio.
   - Wiki: el libro se entreabre al pasar; al pulsar pasa las páginas.
   - Ajustes: el engranaje gira un poco al pasar; al pulsar da una vuelta
     completa con inercia y se posa. */
#hub-home svg { transform-origin: 50% 100%; }
#wiki-toggle svg { transform-origin: 50% 50%; }
#settings-toggle svg {
  transform-origin: 50% 50%;
  transition: transform 420ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
@media (hover: hover) and (pointer: fine) {
  #hub-home:hover svg { animation: dcCasaSalto 520ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  #wiki-toggle:hover svg { animation: dcWikiAbre 560ms cubic-bezier(0.22, 1, 0.36, 1); }
  #settings-toggle:hover svg { transform: rotate(60deg); }
}
.dc-topbar-pulsado { animation: dcPulsoAccion 560ms ease-out; }
#hub-home.dc-topbar-pulsado svg { animation: dcCasaDespega 560ms cubic-bezier(0.22, 1, 0.36, 1); }
#wiki-toggle.dc-topbar-pulsado svg { animation: dcWikiPaginas 620ms cubic-bezier(0.22, 1, 0.36, 1); }
#settings-toggle.dc-topbar-pulsado svg { animation: dcAjustesVuelta 760ms cubic-bezier(0.34, 1.3, 0.64, 1); }

@keyframes dcCasaSalto {
  0%   { transform: none; }
  30%  { transform: translateY(1px) scale(1.12, 0.86); }
  60%  { transform: translateY(-4px) scale(0.94, 1.08); }
  100% { transform: none; }
}
@keyframes dcCasaDespega {
  0%   { transform: none; opacity: 1; }
  25%  { transform: translateY(1px) scale(1.15, 0.8); }
  55%  { transform: translateY(-12px) scale(0.85, 1.1); opacity: 0; }
  56%  { transform: translateY(10px) scale(0.9); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
@keyframes dcWikiAbre {
  0%, 100% { transform: none; }
  45% { transform: perspective(60px) rotateY(-28deg) scale(1.08); }
}
@keyframes dcWikiPaginas {
  0%   { transform: none; }
  30%  { transform: perspective(60px) rotateY(-90deg) scale(1.1); }
  31%  { transform: perspective(60px) rotateY(90deg) scale(1.1); }
  65%  { transform: perspective(60px) rotateY(-12deg); }
  100% { transform: none; }
}
@keyframes dcAjustesVuelta {
  0%   { transform: rotate(60deg); }
  100% { transform: rotate(420deg); }
}

/* ── 5c. Tipos «abrir», «cancelar» y «generico» (motion.js) ──
   - abrir (ver ficha, abrir en Jira, revisar…): el icono avanza hacia la
     derecha al pasar el ratón y «sale» y vuelve al pulsar.
   - cancelar (Cancelar, Cerrar, Volver): pulsación suave con anillo neutro.
   - generico (cualquier otro: Manager Nivel 1, Copiar, Hoy…): el icono se
     eleva al pasar el ratón y salta al pulsarlo, con el anillo de acento. */
[data-dc-tipo="abrir"] svg,
[data-dc-tipo="generico"] svg { transition: transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="abrir"]:hover:not(:disabled) svg    { transform: translateX(2px); }
  [data-dc-tipo="generico"]:hover:not(:disabled) svg { transform: translateY(-2px) scale(1.12); }
}
[data-dc-tipo="abrir"].dc-pulsado svg    { animation: dcIcoAbrir 560ms cubic-bezier(0.22, 1, 0.36, 1) var(--dc-t, 0ms); }
[data-dc-tipo="generico"].dc-pulsado svg { animation: dcIcoGenerico 520ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }
[data-dc-tipo="generico"].dc-pulsado:not(:has(svg)) { animation: dcPulsoAccion 560ms ease-out var(--dc-t, 0ms), dcTogglePop 440ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }
[data-dc-tipo="cancelar"].dc-pulsado { animation: dcPulsoNeutro 520ms ease-out var(--dc-t, 0ms), dcCancelarPresion 360ms ease-out var(--dc-t, 0ms); }

@keyframes dcIcoAbrir {
  0%   { transform: translateX(2px); opacity: 1; }
  40%  { transform: translateX(10px); opacity: 0; }
  41%  { transform: translateX(-8px); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
@keyframes dcIcoGenerico {
  0%   { transform: translateY(-2px) scale(1.12); }
  30%  { transform: translateY(1px) scale(0.8); }
  65%  { transform: translateY(-3px) scale(1.25); }
  100% { transform: none; }
}
@keyframes dcCancelarPresion {
  0%, 100% { transform: none; }
  35% { transform: scale(0.95); }
}
@keyframes dcPulsoNeutro {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--text3) 45%, transparent); }
  100% { box-shadow: 0 0 0 9px color-mix(in srgb, var(--text3) 0%, transparent); }
}

/* ── 8. Controles de interfaz (clase dc-clic, motion.js) ──
   Una animación por familia, igual en las tres apps. */

/* Barra de herramientas: Vistas parpadea como un ojo; Columnas se baraja. */
.views-toggle.dc-clic svg { animation: dcOjoParpadeo 460ms ease-in-out; }
.columns-toggle.dc-clic svg { animation: dcColumnasBaraja 520ms cubic-bezier(0.34, 1.56, 0.64, 1); }

/* Interruptores de vista y selectores (épicas, edición, agrupar, ocultar
   terminadas, escala, tabla/tarjetas, pestañas, opciones segmentadas, leyendas
   e «i»): pequeño salto al elegirlos. */
.epics-btn.dc-clic, .editmode-btn.dc-clic, .collapse-btn.dc-clic, .group-btn.dc-clic,
.hide-done-btn.dc-clic, .scale-btn.dc-clic, .toggle-btn.dc-clic, .tab-btn.dc-clic,
.segmented-option.dc-clic, .dashboard-donut-legend-item.dc-clic, .metric-info-button.dc-clic,
.sidebar-toggle.dc-clic, .topbar-mobile-toggle.dc-clic, .wiki-nav-toggle.dc-clic {
  animation: dcTogglePop 440ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Cerrar (X del popup, borrar búsqueda, quitar chip): la X gira un cuarto de
   vuelta al pasar el ratón y da media vuelta encogiéndose al pulsarla. */
.modal-close svg, .field-clear svg, .filter-chip-x svg { transition: transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@media (hover: hover) and (pointer: fine) {
  .modal-close:hover svg, .field-clear:hover svg, .filter-chip-x:hover svg { transform: rotate(90deg); }
}
.modal-close.dc-clic svg, .field-clear.dc-clic svg, .filter-chip-x.dc-clic svg { animation: dcCerrarGiro 380ms cubic-bezier(0.4, 0, 0.2, 1); }

/* Volver: la flecha retrocede al pasar el ratón y sale por la izquierda al pulsar. */
.topbar-back svg { transition: transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@media (hover: hover) and (pointer: fine) {
  .topbar-back:hover svg { transform: translateX(-3px); }
}
.topbar-back.dc-clic svg { animation: dcVolverSale 460ms cubic-bezier(0.22, 1, 0.36, 1); }

/* Menú lateral y menú de usuario: el icono se adelanta al pasar el ratón y
   salta al elegir; «Cerrar sesión» sale por la puerta. */
.nav-item svg, .user-dropdown-item svg { transition: transform 240ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@media (hover: hover) and (pointer: fine) {
  .nav-item:hover svg { transform: translateX(2px) scale(1.08); }
  .user-dropdown-item:hover svg { transform: translateX(3px); }
}
.nav-item.dc-clic svg { animation: dcIcoGenerico 480ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.user-dropdown-item.dc-clic svg { animation: dcIcoAbrir 520ms cubic-bezier(0.22, 1, 0.36, 1); }

/* Wiki y enlaces de texto: avanzan un poco al pasar el ratón. */
.wiki-tab, [data-action="wiki-tab"], [data-action="wiki-open-model"], .wiki-toc-link { transition: transform 200ms ease-out; }
@media (hover: hover) and (pointer: fine) {
  .wiki-tab:hover, [data-action="wiki-tab"]:hover, [data-action="wiki-open-model"]:hover, .wiki-toc-link:hover { transform: translateX(3px); }
  .btn-link:hover svg { transform: translateX(2px); }
}
.btn-link svg { transition: transform 200ms ease-out; }
.wiki-tab.dc-clic, [data-action="wiki-tab"].dc-clic, [data-action="wiki-open-model"].dc-clic, .wiki-toc-link.dc-clic { animation: dcTogglePop 400ms cubic-bezier(0.34, 1.56, 0.64, 1); }
.btn-link.dc-clic svg { animation: dcIcoAbrir 480ms cubic-bezier(0.22, 1, 0.36, 1); }

@keyframes dcOjoParpadeo {
  0%, 100% { transform: none; }
  30% { transform: scaleY(0.1); }
  55% { transform: scaleY(1.1); }
}
@keyframes dcColumnasBaraja {
  0%   { transform: none; }
  30%  { transform: scaleX(0.6); }
  65%  { transform: scaleX(1.18); }
  100% { transform: none; }
}
@keyframes dcCerrarGiro {
  0%   { transform: rotate(90deg); }
  60%  { transform: rotate(200deg) scale(0.7); }
  100% { transform: rotate(270deg); }
}
@keyframes dcVolverSale {
  0%   { transform: translateX(-3px); opacity: 1; }
  40%  { transform: translateX(-10px); opacity: 0; }
  41%  { transform: translateX(8px); opacity: 0; }
  100% { transform: none; opacity: 1; }
}

/* La X de los popups es el carácter «×» (sin svg): gira el propio botón. */
.modal-close:not(:has(svg)) { transition: transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1), background-color 160ms ease-out, color 160ms ease-out; }
@media (hover: hover) and (pointer: fine) {
  .modal-close:not(:has(svg)):hover { transform: rotate(90deg); }
}
.modal-close:not(:has(svg)).dc-clic { animation: dcCerrarGiro 380ms cubic-bezier(0.4, 0, 0.2, 1); }

/* ── 3c. Otras tarjetas con cifras (mini-indicadores del Dashboard del Hub,
   contadores de Alertas, cifras de Capacidad): la cifra cuenta (motion.js) y
   se desliza en el sentido del cambio, igual que las tarjetas resumen. */
.mini-metric.is-sube .value, .pd-cifra.is-sube .pd-cifra-valor, .jira-cifra.is-sube .jira-cifra-valor, .alertas-nivel-counter.is-sube > b {
  animation: dcCifraSube 650ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mini-metric.is-baja .value, .pd-cifra.is-baja .pd-cifra-valor, .jira-cifra.is-baja .jira-cifra-valor, .alertas-nivel-counter.is-baja > b {
  animation: dcCifraBaja 650ms cubic-bezier(0.22, 1, 0.36, 1);
}
.mini-metric .value, .pd-cifra-valor, .jira-cifra-valor, .alertas-nivel-counter > b { font-variant-numeric: tabular-nums; }
/* Las transformaciones no se aplican a elementos en línea. */
.alertas-nivel-counter > b { display: inline-block; }

/* ── 10. Popups: un solo «Cancelar» abajo (motion.js §10) ──
   La X de la cabecera desaparece: su función la hace «Cancelar». */
.modal .modal-header .modal-close { display: none !important; }

/* «Cancelar»: al pasar el ratón, borde y texto amarillos (aviso) sobre un amarillo pastel muy
   suave. Al pulsarlo, una raya amarilla tacha el texto mientras el botón se
   encoge con un pequeño vaivén de «no»; al terminar, se cierra el popup (o
   pregunta si hay cambios sin guardar). */
.dc-cancelar {
  position: relative;
  overflow: hidden;
  transition: border-color 180ms ease-out, color 180ms ease-out, background-color 180ms ease-out;
}
@media (hover: hover) and (pointer: fine) {
  .modal-footer .dc-cancelar:hover:not(:disabled),
  .dc-cancelar:hover:not(:disabled) {
    border-color: var(--warning);
    color: var(--warning);
    background: color-mix(in srgb, var(--warning) 8%, transparent);
  }
}
.dc-cancelar:focus-visible { outline-color: var(--warning); }
.dc-cancelar.dc-cancelando {
  border-color: var(--warning);
  color: var(--warning);
  background: color-mix(in srgb, var(--warning) 10%, transparent);
  animation: dcCancelarNo 420ms cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
/* La raya que tacha el texto. */
.dc-cancelar::after {
  content: '';
  position: absolute;
  left: 12%;
  right: 12%;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  border-radius: 2px;
  background: var(--warning);
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
}
.dc-cancelar.dc-cancelando::after { animation: dcCancelarTachar 320ms cubic-bezier(0.4, 0, 0.2, 1) forwards; }

@keyframes dcCancelarTachar {
  to { transform: scaleX(1); }
}
@keyframes dcCancelarNo {
  0%, 100% { transform: none; }
  20% { transform: translateX(-3px) scale(0.97); }
  45% { transform: translateX(3px) scale(0.95); }
  70% { transform: translateX(-1px) scale(0.96); }
}

/* «Eliminar» en el pie: rojo, con papelera y siempre a la izquierda. */
.modal-footer .modal-footer-delete { order: -1; margin-right: auto; }

/* ── 11. Botón «+»: al pasar el ratón, el «+» crece con un rebote de gelatina
   y un halo suave de acento se expande desde el botón (sin girar). ── */
@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="anadir"]:hover:not(:disabled) svg {
    transform: scale(1.08);
    animation: dcAnadirGelatina 620ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  [data-dc-tipo="anadir"]:hover:not(:disabled):not(.dc-pulsado) {
    animation: dcAnadirHalo 900ms ease-out;
  }
}
@keyframes dcAnadirGelatina {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.3, 0.85); }
  50%  { transform: scale(0.88, 1.18); }
  70%  { transform: scale(1.12, 0.95); }
  85%  { transform: scale(0.98, 1.04); }
  100% { transform: scale(1.08); }
}
@keyframes dcAnadirHalo {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  100% { box-shadow: 0 0 0 12px color-mix(in srgb, var(--accent) 0%, transparent); }
}

/* ── 12. Botones azules: el halo de acento del «+» en todos ──
   Todo botón azul (principal, «+» de la barra superior, el de crear de la
   barra lateral, los de icono principales…) suelta al pasar el ratón el mismo
   halo suave de acento que se expande desde el botón. Se anima el botón
   (box-shadow); el icono sigue con la animación de su tipo. */
@media (hover: hover) and (pointer: fine) {
  :is(.btn-primary, .ms-btn, .topbar-btn.add-init-btn, .sidebar-create-initiative, .btn-icon.btn-icon-primary, .edicion-add-btn):hover:not(:disabled):not(.dc-pulsado):not([aria-busy="true"]) {
    animation: dcAnadirHalo 900ms ease-out;
  }
  /* Guardar: la marca de verificación se sella; confirmar: el círculo late. */
  [data-dc-tipo="guardar"]:hover:not(:disabled) svg   { animation: dcGuardarSello 460ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  [data-dc-tipo="confirmar"]:hover:not(:disabled) svg { animation: dcConfirmarIcono 520ms cubic-bezier(0.34, 1.56, 0.64, 1); }
}
/* Los botones de los popups que reciben icono (motion.js, dcAsegurarIconoAccion). */
.modal-footer .btn-primary > svg { width: 14px; height: 14px; flex: 0 0 auto; }

/* ── 13. Subir / bajar en una lista ordenada (tipo «mover») ──
   La flecha de «Subir» sube y la de «Bajar» baja: un pequeño desplazamiento al
   pasar el ratón y, al pulsar, un salto en su sentido con rebote. */
[data-dc-tipo="mover"] svg { transition: transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1); }
@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="mover"][data-sentido="-1"]:hover:not(:disabled) svg { transform: translateY(-3px); }
  [data-dc-tipo="mover"][data-sentido="1"]:hover:not(:disabled) svg  { transform: translateY(3px); }
}
[data-dc-tipo="mover"][data-sentido="-1"].dc-pulsado svg { animation: dcMoverSube 520ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }
[data-dc-tipo="mover"][data-sentido="1"].dc-pulsado svg  { animation: dcMoverBaja 520ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--dc-t, 0ms); }
@keyframes dcMoverSube {
  0%   { transform: translateY(0); }
  35%  { transform: translateY(-7px); opacity: 0.4; }
  36%  { transform: translateY(7px); opacity: 0; }
  70%  { transform: translateY(-2px); opacity: 1; }
  100% { transform: translateY(0); }
}
@keyframes dcMoverBaja {
  0%   { transform: translateY(0); }
  35%  { transform: translateY(7px); opacity: 0.4; }
  36%  { transform: translateY(-7px); opacity: 0; }
  70%  { transform: translateY(2px); opacity: 1; }
  100% { transform: translateY(0); }
}

/* ── 14. Botón amarillo de aviso («Ver alertas» de una ficha) ──
   Mismo comportamiento que los azules pero en amarillo de aviso: halo amarillo
   al pasar el ratón, el triángulo se balancea como una campana y, al pulsar,
   late con un anillo amarillo. */
[data-dc-tipo="alerta"] svg { transform-origin: 50% 10%; }
@media (hover: hover) and (pointer: fine) {
  .btn-warning:hover:not(:disabled):not(.dc-pulsado) { animation: dcAvisoHalo 900ms ease-out; }
  [data-dc-tipo="alerta"]:hover:not(:disabled) svg { animation: dcAvisoCampana 700ms ease-in-out; }
}
[data-dc-tipo="alerta"].dc-pulsado { animation: dcAvisoHalo 620ms ease-out var(--dc-t, 0ms), dcConfirmarLatido 560ms ease-in-out var(--dc-t, 0ms); }
[data-dc-tipo="alerta"].dc-pulsado svg { animation: dcAvisoCampana 620ms ease-in-out var(--dc-t, 0ms); }
@keyframes dcAvisoHalo {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warning) 55%, transparent); }
  100% { box-shadow: 0 0 0 12px color-mix(in srgb, var(--warning) 0%, transparent); }
}
@keyframes dcAvisoCampana {
  0%, 100% { transform: rotate(0); }
  20% { transform: rotate(14deg); }
  40% { transform: rotate(-12deg); }
  60% { transform: rotate(8deg); }
  80% { transform: rotate(-4deg); }
}

/* ── 15. Cambio de nivel y horquillas (motion.js §15) ──
   .dc-nivel-cambia: la tarjeta cuyo nivel cambia gira un instante en X y suelta un
   destello del color de su borde (el tono de su nivel). .dc-rango-despliega: un
   rango (mínimo → máximo) se despliega desde su valor esperado hacia los extremos
   (--ev-origen = posición del esperado dentro del rango). */
.dc-nivel-cambia { animation: dcNivelGiro 640ms var(--ease-out, ease-out); }
.dc-nivel-cambia .stat-value { animation: dcNivelDestello 640ms ease-out; }
@keyframes dcNivelGiro {
  0%   { transform: perspective(600px) rotateX(0); }
  40%  { transform: perspective(600px) rotateX(-18deg) scale(1.02); }
  100% { transform: perspective(600px) rotateX(0) scale(1); }
}
@keyframes dcNivelDestello {
  0%   { text-shadow: 0 0 0 transparent; }
  35%  { text-shadow: 0 0 14px currentColor; }
  100% { text-shadow: 0 0 0 transparent; }
}
.dc-rango-despliega { transform-origin: var(--ev-origen, 50%) 50%; animation: dcRangoDespliega 900ms var(--ease-out, ease-out) both; }
@keyframes dcRangoDespliega {
  0%   { transform: scaleX(0); opacity: .4; }
  100% { transform: scaleX(1); opacity: 1; }
}

/* ── 16. Celebración: confeti (motion.js §16, dcConfeti) ── */
.dc-confeti { position: fixed; inset: 0; z-index: 10000; pointer-events: none; overflow: hidden; }
.dc-confeti-pieza {
  position: absolute; left: var(--x0); top: var(--y0); width: 10px; height: 16px; margin: -8px 0 0 -5px; border-radius: 2px;
  background: var(--c); opacity: 0; animation: dcConfetiVuela var(--dur, 1.4s) cubic-bezier(.15, .75, .35, 1) var(--retardo, 0ms) forwards;
}
.dc-confeti-pieza.is-redonda { width: 11px; height: 11px; border-radius: 50%; }
@keyframes dcConfetiVuela {
  0% { opacity: 1; transform: translate(0, 0) rotate(0) scale(.4); }
  35% { opacity: 1; transform: translate(var(--dx), var(--dy)) rotate(calc(var(--giro) * .4)) scale(1); }
  100% { opacity: 0; transform: translate(calc(var(--dx) * 1.15), calc(var(--dy) + var(--caida))) rotate(var(--giro)) scale(.9); }
}

/* ── 17. Popup de alta guardado (motion.js §17, dcCerrarPopup / dcCheckOk) ──
   El popup se disuelve como una nube y aparece un check en el centro. Para que sea fluido
   solo se animan transform y opacity (la GPU las compone sin repintar): nada de
   desenfoques animados ni del backdrop-filter del fondo, que se quita al empezar. */
.modal-overlay.dc-creado:not(.open) {
  -webkit-backdrop-filter: none; backdrop-filter: none;
  transition: opacity 700ms cubic-bezier(.4, 0, .2, 1) 380ms, visibility 0s 1100ms;
}
.modal-overlay.dc-creado:not(.open) .modal {
  will-change: transform, opacity;
  animation: dcPopupNube 1100ms cubic-bezier(.33, 0, .2, 1) both;
}
@keyframes dcPopupNube {
  0% { opacity: 1; transform: translateZ(0) scale(1); }
  18% { opacity: 1; transform: translateZ(0) scale(1.025); }
  100% { opacity: 0; transform: translateZ(0) scale(1.14) translateY(-28px); }
}
/* Bocanadas de nube que brotan del popup y se deshacen (dcBocanadasNube): degradado
   radial suave (ya difuminado de por sí) que solo crece, sube y se desvanece. */
.dc-nube { position: fixed; inset: 0; z-index: 9999; pointer-events: none; overflow: hidden; contain: strict; }
.dc-nube-bocanada {
  position: absolute; left: var(--x); top: var(--y); width: var(--t); height: var(--t);
  margin: calc(var(--t) / -2) 0 0 calc(var(--t) / -2); border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--bg2) 85%, transparent) 0%, color-mix(in srgb, var(--bg2) 45%, transparent) 45%, color-mix(in srgb, var(--bg2) 12%, transparent) 75%, transparent 100%);
  opacity: 0; will-change: transform, opacity;
  animation: dcNubeBocanada 1300ms cubic-bezier(.22, .61, .36, 1) var(--retardo, 0ms) forwards;
}
@keyframes dcNubeBocanada {
  0% { opacity: 0; transform: translate3d(0, 0, 0) scale(.4); }
  30% { opacity: .9; transform: translate3d(calc(var(--dx) * .3), calc(var(--dy) * .3), 0) scale(1); }
  100% { opacity: 0; transform: translate3d(var(--dx), var(--dy), 0) scale(1.8); }
}
/* Check de «creado» con el estilo de la app: disco verde suave (--green-soft) con anillo
   fino y marca en trazo redondeado, como los iconos y los badges de valor; un halo del
   mismo verde se expande al aparecer. */
.dc-check-ok {
  position: fixed; left: 50%; top: 50%; z-index: 10001; width: 76px; height: 76px; margin: -38px 0 0 -38px;
  pointer-events: none; will-change: transform, opacity;
  animation: dcCheckOk 1.45s cubic-bezier(.22, 1, .36, 1) both;
}
.dc-check-ok::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--green);
  opacity: 0; animation: dcCheckHalo 900ms cubic-bezier(.22, 1, .36, 1) 120ms both;
}
.dc-check-ok svg { position: relative; width: 100%; height: 100%; overflow: visible; }
.dc-check-ok-circulo { fill: var(--bg2);   /* opaco y según el tema: casi blanco en claro, superficie oscura en oscuro */ stroke: var(--green); stroke-width: 1.6; }
.dc-check-ok-marca { fill: none; stroke: var(--green); stroke-width: 3.2; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 36; stroke-dashoffset: 36; animation: dcCheckTrazo 420ms cubic-bezier(.65, 0, .35, 1) 180ms forwards; }
@keyframes dcCheckOk {
  0% { opacity: 0; transform: translateZ(0) scale(.6); }
  22% { opacity: 1; transform: translateZ(0) scale(1.06); }
  36% { transform: translateZ(0) scale(1); }
  78% { opacity: 1; transform: translateZ(0) scale(1); }
  100% { opacity: 0; transform: translateZ(0) scale(.94) translateY(-6px); }
}
@keyframes dcCheckHalo {
  0% { opacity: .6; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.7); }
}
@keyframes dcCheckTrazo { to { stroke-dashoffset: 0; } }

/* ── Texto que se desplaza solo (cinta de noticias, dcMedirDesplazable en motion.js) ──
   Pausa al principio, recorre lo que no cabe, pausa al final y vuelve. */
.dc-desplaza { display: block; overflow: hidden; min-width: 0; }
.dc-desplaza-interior { display: inline-block; }
.dc-desplaza.is-desplazando {
  -webkit-mask-image: linear-gradient(90deg, transparent 0, var(--shadow-color) 8px, var(--shadow-color) calc(100% - 14px), transparent);
  mask-image: linear-gradient(90deg, transparent 0, var(--shadow-color) 8px, var(--shadow-color) calc(100% - 14px), transparent);
}
.dc-desplaza.is-desplazando .dc-desplaza-interior { animation: dcDesplaza var(--dc-desplaza-dur, 8s) ease-in-out infinite; will-change: transform; }
.dc-desplaza.is-desplazando:hover .dc-desplaza-interior { animation-play-state: paused; }
@keyframes dcDesplaza {
  0%, 15% { transform: translateX(0); }
  45%, 60% { transform: translateX(var(--dc-desplaza-dist, 0)); }
  90%, 100% { transform: translateX(0); }
}

/* ── 16. Enviar: el avión de papel despega en diagonal y vuelve a su sitio ── */
@keyframes dcEnviarVuelo {
  0%   { transform: translate(0, 0) rotate(0); opacity: 1; }
  45%  { transform: translate(9px, -9px) rotate(-8deg); opacity: 0; }
  46%  { transform: translate(-7px, 7px); opacity: 0; }
  100% { transform: translate(0, 0); opacity: 1; }
}
@keyframes dcEnviarAmago { 50% { transform: translate(2px, -2px) rotate(-6deg); } }
[data-dc-tipo="enviar"] svg { transition: transform 200ms ease; }
[data-dc-tipo="enviar"].dc-pulsado svg { animation: dcEnviarVuelo 620ms cubic-bezier(0.5, 0, 0.3, 1) var(--dc-t, 0ms); }
[data-dc-tipo="enviar"].dc-pulsado { animation: dcPulsoConfirmar 640ms ease-out var(--dc-t, 0ms); }
@media (hover: hover) and (pointer: fine) {
  [data-dc-tipo="enviar"]:hover:not(:disabled):not(.dc-pulsado) svg { animation: dcEnviarAmago 520ms ease-in-out; }
}

/* ── 18. Botones de icono de la barra de herramientas: animan al pasar el ratón ──
   Filtro (embudo), Vistas (ojo), Solo favoritos (estrella), Columnas, Exportar e
   Importar. Antes solo animaban al hacer clic (o nada). Mismo criterio que el resto:
   solo el icono, un gesto corto, y nunca encima de la animación de clic (.dc-pulsado). */
@media (hover: hover) and (pointer: fine) {
  .filter-toggle:hover:not(:disabled):not(.is-closing) svg      { animation: dcFiltroHover 620ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  .views-toggle:hover:not(:disabled) svg                         { animation: dcOjoHover 560ms ease-in-out; }
  .favorite-filter-btn:hover:not(:disabled):not(.dc-pulsado) svg { animation: dcEstrellaHover 560ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  .columns-toggle:hover:not(:disabled) svg                       { animation: dcColumnasHover 560ms cubic-bezier(0.34, 1.56, 0.64, 1); }
  [data-dc-tipo="exportar"].btn-icon:hover:not(:disabled):not(.dc-pulsado) svg { animation: dcFlechaAbajoHover 620ms ease-in-out; }
  [data-dc-tipo="importar"].btn-icon:hover:not(:disabled):not(.dc-pulsado) svg { animation: dcFlechaArribaHover 620ms ease-in-out; }
}
@keyframes dcFiltroHover {
  0%   { transform: rotate(0) scale(1); }
  30%  { transform: rotate(-14deg) scale(1.16); }
  65%  { transform: rotate(9deg) scale(1.1); }
  100% { transform: rotate(0) scale(1); }
}
@keyframes dcOjoHover {
  0%, 100% { transform: scaleY(1); }
  35%      { transform: scaleY(0.12); }
  60%      { transform: scaleY(1.18); }
}
@keyframes dcEstrellaHover {
  0%   { transform: scale(1) rotate(0); }
  45%  { transform: scale(1.3) rotate(-18deg); }
  100% { transform: scale(1) rotate(0); }
}
@keyframes dcColumnasHover {
  0%, 100% { transform: scaleX(1); }
  40%      { transform: scaleX(0.78); }
  70%      { transform: scaleX(1.12); }
}
@keyframes dcFlechaAbajoHover {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(3px); }
  70%      { transform: translateY(-1px); }
}
@keyframes dcFlechaArribaHover {
  0%, 100% { transform: translateY(0); }
  40%      { transform: translateY(-3px); }
  70%      { transform: translateY(1px); }
}

/* ── 19. Enviado: avión de papel que cruza la pantalla (motion.js §19, dcAvionPapel) ──
   Confirma que algo ha quedado hecho y «sale» (publicar o crear un correo). La
   trayectoria (curva en S con el morro siguiendo la tangente) y la estela van por Web
   Animations; aquí solo el dibujo. Color de acento, superficies según el tema. */
.dc-avion-capa { position: fixed; inset: 0; z-index: 10000; pointer-events: none; overflow: hidden; contain: strict; }
.dc-avion { position: absolute; left: 0; top: 0; width: 0; height: 0; will-change: transform; }
.dc-avion-cuerpo {
  position: absolute; left: -22px; top: -22px; width: 44px; height: 44px; overflow: visible;
  transform: rotate(45deg);   /* el icono apunta arriba a la derecha: el morro, hacia delante */
  filter: drop-shadow(0 6px 10px color-mix(in srgb, var(--shadow-color) 28%, transparent));
}
.dc-avion-ala { fill: color-mix(in srgb, var(--accent) 22%, var(--bg2)); stroke: var(--accent); stroke-width: 1.5; }
.dc-avion-pliegue { fill: none; stroke: var(--accent); stroke-width: 1.5; }
/* Estela: una franja difuminada y un trazo fino que nacen en la cola y se desvanecen. */
.dc-avion-estela {
  position: absolute; right: 12px; top: 0; width: 240px; height: 14px; border-radius: 999px;
  transform-origin: right center; transform: translateY(-50%) scaleX(0); opacity: 0;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 40%, transparent));
  filter: blur(5px);
}
.dc-avion-estela.is-fina {
  width: 320px; height: 2px; filter: none;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 75%, transparent));
}
