/* ============================================
   LUNEXUS — Estilos específicos por página
   Hoy: solo el visual "orbit" del hero del Home.
   Cada nueva página que necesite CSS propio se agrega aquí,
   con un comentario de qué página lo usa.
   ============================================ */

/* ---------- Home: ORBIT — visual del hero ---------- */
.orbit {
  position: relative;
  width: 480px;
  height: 480px;
  margin: 0 auto;
}
/* Solo los 3 orbit-badge son interactivos (enlaces reales a los planes) — el
   resto del visual (anillos, líneas, nodos, core) sigue siendo puramente
   decorativo: no reacciona al mouse y queda fuera del árbol de accesibilidad
   vía aria-hidden en cada elemento (antes era un solo aria-hidden en .orbit,
   pero eso también ocultaba los badges ahora que son navegación real). */
.orbit-ring,
.orbit-node,
.orbit-core,
.orbit-lines {
  pointer-events: none;
}
.orbit-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px dashed rgba(145, 185, 103, 0.22);
}
.orbit-ring.r2 { inset: 46px; border-style: solid; border-color: rgba(115, 166, 61, 0.14); }
.orbit-ring.r3 { inset: 92px; border-style: dashed; border-color: rgba(145, 185, 103, 0.16); }

.orbit-spin-slow { animation: orbit-spin 50s linear infinite; }
.orbit-spin-slow-rev { animation: orbit-spin 70s linear infinite reverse; }

@keyframes orbit-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.orbit-core {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 200px; height: 200px;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 30%, #16241B 0%, #0A0F0C 72%);
  border: 1px solid var(--color-border);
  box-shadow: 0 0 60px rgba(115, 166, 61, 0.18), inset 0 0 30px rgba(115, 166, 61, 0.08);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3;
  animation: orbit-pulse 4s ease-in-out infinite;
}
.orbit-core img { height: 78px; width: auto; }

@keyframes orbit-pulse {
  0%, 100% { box-shadow: 0 0 60px rgba(115, 166, 61, 0.18), inset 0 0 30px rgba(115, 166, 61, 0.08); }
  50% { box-shadow: 0 0 84px rgba(115, 166, 61, 0.3), inset 0 0 34px rgba(115, 166, 61, 0.14); }
}

.orbit-node {
  position: absolute;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: 0 0 10px rgba(145, 185, 103, 0.8);
  animation: node-pulse 3s ease-in-out infinite;
}
@keyframes node-pulse {
  0%, 100% { opacity: 0.55; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.5); }
}

.orbit-badge {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(17, 24, 20, 0.9);
  border: 1px solid var(--color-border);
  backdrop-filter: blur(6px);
  border-radius: 20px;
  padding: 8px 16px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 13px;
  color: var(--color-text);
  z-index: 4;
  animation: badge-float 5s ease-in-out infinite;
  /* Ahora es un <a> real (lleva a /soporte#planes): color/fondo son la única
     señal de interactividad posible aquí — un translateY en :hover/:active
     competiría con badge-float (misma propiedad, mismo elemento) y el
     navegador le daría prioridad a la animación por sobre la transición,
     dejando el estado sin efecto visible. Mismo par duración/easing que el
     resto de los elementos clicables del sitio (§2.6). */
  transition: border-color var(--duration-fast) var(--ease-out), background var(--duration-fast) var(--ease-out);
}
.orbit-badge:hover,
.orbit-badge:focus-visible {
  border-color: var(--color-accent);
  background: rgba(115, 166, 61, 0.22);
}
.orbit-badge:active { background: rgba(115, 166, 61, 0.32); }
.orbit-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--color-accent); }
@keyframes badge-float {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

.orbit-lines { position: absolute; inset: 0; z-index: 1; }
.orbit-lines path { stroke: rgba(115, 166, 61, 0.35); stroke-width: 1; fill: none; stroke-dasharray: 4 5; animation: dash-flow 18s linear infinite; }
@keyframes dash-flow { to { stroke-dashoffset: -300; } }

@media (max-width: 860px) {
  /* min() en vez de 320px fijo: el mockup original recortaba el orbit en
     teléfonos angostos (ej. 375px de ancho lógico, como iPhone SE) porque
     320px + el padding de .wrap (32px por lado) no cabía. calc(100vw - 64px)
     replica ese mismo padding para que nunca se desborde, sin cambiar el
     tamaño en ningún teléfono donde sí cabía. */
  .orbit { width: min(320px, calc(100vw - 64px)); height: min(320px, calc(100vw - 64px)); transform: scale(0.9); }
  .orbit-core { width: 140px; height: 140px; }
  .orbit-core img { height: 54px; }
  .orbit-badge { font-size: 11px; padding: 6px 12px; }
}

/* ---------- Metodología: timeline y fila de paso (StepRow) ---------- */
.step-timeline { display: flex; flex-direction: column; }
.step-row {
  display: grid;
  grid-template-columns: 60px 1fr;
  gap: var(--space-6);
  /* 28px → 24px (--space-6): un timeline numerado de 6 filas lee mejor
     compacto y rítmico que con aire de más — no es un bloque de lectura. */
  padding: var(--space-6) 0;
  border-bottom: 1px solid var(--color-border);
}
.step-row:last-child { border-bottom: none; }
.step-row .step-number {
  font-family: var(--font-heading);
  font-weight: 800;
  font-size: 28px;
  color: var(--color-border);
}
.step-row p { margin-top: var(--space-4); color: var(--color-text-muted); font-size: 15.5px; }
.step-row.is-highlighted .step-number { color: var(--color-brand); }
.step-row.is-highlighted h3 { color: var(--color-accent); }

/* ============================================
   Home (index.php)
   ============================================ */

/* ---------- Hero ---------- */
.home-hero { padding-top: var(--space-24); padding-bottom: var(--space-24); overflow: hidden; position: relative; }
/* Textura de fondo — pedido explícito 2026-08-11 ("más tecnológica"):
   grilla de puntos tenue + un brillo que sigue el cursor, en la misma capa
   (dos radial-gradient en un solo background, ver abajo) para no gastar los
   dos únicos pseudo-elementos disponibles en uno solo. --mx/--my los
   escribe assets/js/interactions.js en mousemove sobre .home-hero; sin JS
   o con reduced-motion (que desactiva el listener), quedan fijos en
   50%/20% — el spotlight no se mueve, pero la grilla se ve igual. */
.home-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(420px circle at var(--mx, 50%) var(--my, 20%), rgba(166, 230, 110, 0.12), transparent 70%),
    radial-gradient(rgba(145, 185, 103, 0.16) 1px, transparent 1px);
  background-size: auto, 26px 26px;
  mask-image: radial-gradient(ellipse 75% 65% at 30% 40%, black 40%, transparent 92%);
  -webkit-mask-image: radial-gradient(ellipse 75% 65% at 30% 40%, black 40%, transparent 92%);
}
/* Brillo difuso de marca, deriva lenta — el mismo lenguaje de "profundidad
   técnica" del recorrido de revisión, ya aprobado. */
.home-hero::after {
  content: '';
  position: absolute;
  z-index: 0;
  pointer-events: none;
  top: -180px;
  right: -160px;
  width: 520px;
  height: 520px;
  border-radius: 50%;
  background: var(--color-brand);
  filter: blur(120px);
  opacity: 0.16;
  animation: home-hero-orb-drift 20s var(--ease-in-out) infinite;
}
@keyframes home-hero-orb-drift {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-36px, 30px); }
}
.home-hero .wrap {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--space-10);
  align-items: center;
}
/* eyebrow → h1: mismo valor que el resto del sitio (--space-4). Antes 22px,
   el único de los 3 heros que no coincidía con los otros dos. */
.home-hero h1 { margin-top: var(--space-4); }
/* Rol "lead" (18px) — antes 19px, la tercera variante distinta que existía. */
.home-hero .hero-lead {
  margin-top: var(--space-5);
  font-size: 18px;
  line-height: 1.6;
  color: var(--color-text-muted);
  max-width: 520px;
}
/* Grupo de 2 CTAs (no un botón suelto): un paso más de aire que un CTA
   único — ver .page-hero .btn — para que se lea como su propia "zona de
   acción", no como una continuación directa del párrafo. */
.home-hero .hero-ctas { margin-top: var(--space-8); display: flex; gap: var(--space-4); flex-wrap: wrap; }

/* Bloque de cifras de confianza: deliberadamente más separado que las CTAs
   de arriba (64px, no 32px) — es un tipo de contenido distinto (prueba
   social, no una acción) y necesita su propio "respiro" para no leerse
   como parte del mismo grupo que los botones.
   Grid de 3 columnas iguales (no flex con wrap): con flex, el tercer
   indicador caía a una segunda fila dentro del ancho acotado de la columna
   de texto del hero — un grid fuerza las 3 columnas al mismo ancho, misma
   fila, misma jerarquía, sin importar el largo del texto de cada una. */
.hero-stats {
  margin-top: var(--space-16);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-6);
  color: var(--color-text-muted);
  font-size: 14.5px;
}
.hero-stats strong {
  display: block;
  color: var(--color-text);
  font-family: var(--font-heading);
  font-size: 22px;
}

@media (max-width: 860px) {
  .home-hero { padding-top: var(--space-16); padding-bottom: var(--space-16); }
  .home-hero .wrap { grid-template-columns: 1fr; }
  /* Misma fila de 3 columnas, pero más compacta: en mobile el ancho total
     es mucho menor y los 3 textos siguen debiendo caber sin volver a
     apilarse verticalmente. */
  .hero-stats { gap: var(--space-3); }
  .hero-stats strong { font-size: 19px; }
}

/* ---------- Teaser Producto ancla ---------- */
.producto-ancla-teaser .grid-2 { align-items: center; }
.producto-ancla-teaser h2 { margin-top: var(--space-4); }
.producto-ancla-teaser .teaser-lead { margin-top: var(--space-5); color: var(--color-text-muted); font-size: 18px; line-height: 1.6; }
.producto-ancla-teaser .btn { margin-top: var(--space-6); }

.notif-preview {
  margin-top: var(--space-4);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}
.notif-preview + .notif-preview { margin-top: var(--space-4); }
.notif-preview .notif-meta { font-size: 13.5px; color: var(--color-text-muted); }
.notif-preview .notif-message { margin-top: var(--space-2); font-size: 15px; }
.notif-preview .notif-message.is-accent { color: var(--color-accent); }

/* La tarjeta parte vacía (sin avisos) y, al pasar el mouse, llegan en
   secuencia — primero WhatsApp Business, luego el panel de clientes — igual
   que llegarían en la vida real, no los dos a la vez.
   Escondido solo en `(hover: hover) and (pointer: fine)` (mouse real, no
   touch): en celulares no existe ":hover" para revelar el contenido, así que
   ahí se sigue viendo completo desde el primer render, como el resto del
   sitio — ocultarlo sin una forma de mostrarlo sería un candado de
   contenido, no una animación. Sin JS: transition simple, con
   transition-delay en el segundo aviso para la secuencia. */
@media (hover: hover) and (pointer: fine) {
  .producto-ancla-teaser .notif-preview {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity var(--duration-slow) var(--ease-out), transform var(--duration-slow) var(--ease-out);
  }
  .producto-ancla-teaser .notif-preview + .notif-preview {
    transition-delay: 380ms;
  }
  .producto-ancla-teaser .card:hover .notif-preview {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------- Flujo conectado (Home) ---------- */
/* Reemplaza la antigua grilla de 3 feature_card ("Tres formas de resolver el
   mismo problema"): en vez de 3 tarjetas sueltas, un flujo animado que conecta
   las 3 líneas de negocio ya presentadas en el hero (Presencia Digital →
   Gestión Operacional → Inteligencia Operacional), para reforzar que son un
   mismo ecosistema conectado, no servicios aislados. Adaptado de un mockup de
   referencia (flujo-automatizacion.html) a los tokens/componentes del sitio —
   ver docs/DESIGN-SYSTEM.md. */
.flow {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 64px 1fr 64px 1fr;
  align-items: center;
  margin-top: var(--space-10);
}

.flow-card {
  position: relative;
  /* Espacio para que el badge circular (position:absolute, top:-18px) no
     choque con el borde de la fila superior — mismo problema que resuelve
     .plan-card-tag, aquí con margin en vez de posición absoluta del padre. */
  margin-top: var(--space-6);
  transition: border-color var(--duration-fast) var(--ease-out);
}
/* Borde a --color-accent al pasar el mouse (mismo lenguaje que .plan-card),
   pero SIN --shadow-2: esa sombra está reservada a tarjetas que son un link
   completo (docs/DESIGN-SYSTEM.md §2.5) — esta tarjeta no navega a ningún
   lado, solo revela una vista previa animada, así que su única señal de
   interactividad es el borde, no una elevación. */
.flow-card:hover,
.flow-card.is-active { border-color: var(--color-accent); }
.flow-card h3 { margin-top: var(--space-3); }

.flow-card-badge {
  position: absolute;
  top: -18px;
  left: var(--space-6);
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  background: linear-gradient(150deg, var(--color-accent), var(--color-brand));
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 14px;
  color: var(--color-text-dark);
  /* Mismo halo que .plan-card.is-highlighted (--shadow-highlight) + un anillo
     del color de fondo de página, para que el badge se recorte limpio sobre
     el borde de la tarjeta — mismo truco que el mockup de referencia, con
     los tokens de sombra del sistema en vez de un valor propio. */
  box-shadow: var(--shadow-highlight), 0 0 0 4px var(--color-bg);
  transition: transform var(--duration-base) var(--ease-out);
}
.flow-card:hover .flow-card-badge,
.flow-card.is-active .flow-card-badge { transform: scale(1.08) rotate(-6deg); }

/* ---------- Conector entre tarjetas: línea base + chispa animada ---------- */
.flow-connector { position: relative; height: 2px; }
.flow-connector svg {
  position: absolute;
  top: 50%; left: 0;
  width: 100%; height: 120px;
  transform: translateY(-50%);
  overflow: visible;
}
.flow-base-line { stroke: var(--color-border); stroke-width: 1.5; fill: none; }
.flow-spark-line {
  stroke: var(--color-accent);
  stroke-width: 2;
  fill: none;
  stroke-dasharray: 14 210;
  stroke-dashoffset: 224;
  filter: drop-shadow(0 0 6px rgba(145, 185, 103, 0.7));
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}
/* Indica orden de lectura y cambio de estado (la tarjeta anterior "envía" la
   siguiente) — uno de los 5 objetivos válidos de movimiento, ver
   docs/DESIGN-SYSTEM.md §2.6. */
.flow-connector.is-active .flow-spark-line {
  opacity: 1;
  animation: flow-spark-travel 0.9s var(--ease-out) forwards;
}
@keyframes flow-spark-travel { to { stroke-dashoffset: -14; } }
.flow-node-dot {
  position: absolute;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--color-border);
  top: 50%;
  transform: translate(-50%, -50%);
  transition: background var(--duration-base) var(--ease-out), box-shadow var(--duration-base) var(--ease-out);
}
.flow-node-dot.start { left: 0; }
.flow-node-dot.end { left: 100%; }
.flow-connector.is-active .flow-node-dot.end {
  background: var(--color-accent);
  box-shadow: 0 0 12px rgba(145, 185, 103, 0.8);
}

/* ---------- Vista previa dentro de cada tarjeta ---------- */
.flow-stage {
  margin-top: var(--space-5);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  overflow: hidden;
  height: 220px;
  position: relative;
}

/* Etapa 1 (Presencia Digital) — navegador simulado + botón de WhatsApp */
.flow-browser-bar {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.flow-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--color-border); }
.flow-browser-url {
  margin-left: var(--space-2);
  background: var(--color-bg-elevated);
  border-radius: var(--radius-sm);
  padding: 4px var(--space-3);
  font-size: 10.5px;
  color: var(--color-text-muted);
  flex: 1;
}
.flow-browser-body { padding: var(--space-4); position: relative; height: calc(100% - 37px); }
.flow-wire { height: 8px; border-radius: 4px; background: var(--color-bg-elevated); margin-bottom: var(--space-2); }
.flow-wire.w80 { width: 80%; }
.flow-wire.w60 { width: 60%; }
.flow-wire.w40 { width: 40%; }
.flow-wire-cta {
  margin-top: var(--space-3);
  width: 110px; height: 28px;
  border-radius: var(--radius-sm);
  background: rgba(115, 166, 61, 0.18);
  border: 1px solid rgba(115, 166, 61, 0.32);
}
.flow-chat-fab {
  position: absolute; right: var(--space-4); bottom: var(--space-4);
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--color-brand);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 16px rgba(115, 166, 61, 0.4);
}
.flow-chat-fab svg { width: 20px; height: 20px; color: var(--color-text-dark); }
.flow-click-ring {
  position: absolute; right: var(--space-4); bottom: var(--space-4);
  width: 40px; height: 40px; border-radius: 50%;
  border: 2px solid var(--color-accent);
  opacity: 0; pointer-events: none;
}
.flow-cursor {
  position: absolute; width: 18px; height: 18px;
  right: 66px; bottom: 56px; color: var(--color-text);
  opacity: 0; pointer-events: none;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
}
.flow-card.is-active[data-stage="site"] .flow-cursor { animation: flow-cursor-move 1.1s var(--ease-in-out) forwards; }
@keyframes flow-cursor-move {
  0%   { opacity: 0; right: 120px; bottom: 100px; }
  35%  { opacity: 1; }
  75%  { opacity: 1; right: 24px; bottom: 24px; }
  100% { opacity: 0; right: 24px; bottom: 24px; }
}
.flow-card.is-active[data-stage="site"] .flow-click-ring { animation: flow-click-ripple 0.5s var(--ease-out) 0.85s; }
@keyframes flow-click-ripple { 0% { opacity: 0.9; transform: scale(0.6); } 100% { opacity: 0; transform: scale(1.9); } }
.flow-card.is-active[data-stage="site"] .flow-chat-fab { animation: flow-fab-pulse 0.4s var(--ease-out) 0.85s; }
@keyframes flow-fab-pulse { 50% { transform: scale(0.88); } }

/* Etapa 2 (Gestión Operacional) — conversación de WhatsApp */
.flow-chat-head { display: flex; align-items: center; gap: var(--space-2); padding: var(--space-3); border-bottom: 1px solid var(--color-border); }
.flow-chat-avatar { width: 22px; height: 22px; border-radius: 50%; background: rgba(115, 166, 61, 0.14); display: flex; align-items: center; justify-content: center; }
.flow-chat-avatar svg { width: 12px; height: 12px; color: var(--color-accent); }
.flow-chat-name { font-size: 12px; font-weight: 600; }
.flow-chat-status { font-size: 10px; color: var(--color-accent); margin-left: auto; display: flex; align-items: center; gap: 4px; }
.flow-pulse-dot { width: 5px; height: 5px; border-radius: 50%; background: var(--color-accent); animation: flow-pulse-op 1.6s ease-in-out infinite; }
@keyframes flow-pulse-op { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
.flow-chat-body { padding: var(--space-4); display: flex; flex-direction: column; gap: var(--space-2); height: calc(100% - 39px); overflow: hidden; }

.flow-bubble {
  max-width: 82%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: 11.5px;
  line-height: 1.4;
  opacity: 0;
  transform: translateY(6px);
  /* Base de salida: cuando is-fading le quita la animación de entrada, este
     transition es lo que hace que el mensaje se desvanezca suave en vez de
     cortarse de golpe — ver comentario junto a .is-fading, abajo. */
  transition: opacity var(--duration-slow) var(--ease-out), transform var(--duration-slow) var(--ease-out);
}
.flow-bubble.is-user { align-self: flex-end; background: var(--color-bg-elevated); border-bottom-right-radius: 3px; }
.flow-bubble.is-bot { align-self: flex-start; background: rgba(115, 166, 61, 0.12); border: 1px solid rgba(115, 166, 61, 0.25); border-bottom-left-radius: 3px; }
.flow-bubble.is-confirm { align-self: flex-start; background: var(--color-accent); color: var(--color-text-dark); font-weight: 600; border-bottom-left-radius: 3px; }

.flow-card.is-active[data-stage="chat"] .flow-bubble { animation: flow-bubble-in 0.4s var(--ease-out) forwards; }
.flow-card.is-active[data-stage="chat"] .flow-bubble:nth-child(1) { animation-delay: 0.1s; }
.flow-card.is-active[data-stage="chat"] .flow-bubble:nth-child(2) { animation-delay: 0.7s; }
.flow-card.is-active[data-stage="chat"] .flow-bubble:nth-child(3) { animation-delay: 1.3s; }
.flow-card.is-active[data-stage="chat"] .flow-bubble:nth-child(4) { animation-delay: 1.9s; }
@keyframes flow-bubble-in { to { opacity: 1; transform: translateY(0); } }

/* "El mensaje llega, se queda el tiempo suficiente para leerlo y luego
   desaparece de forma natural" (mejora pedida sobre el mockup de
   referencia, que solo tenía la entrada). is-fading la agrega
   assets/js/home-flow.js con un temporizador, no con :hover directo — el
   mensaje se desvanece solo, siga o no el cursor sobre la tarjeta.
   `animation: none` retira la animación de entrada (que dejaba opacity:1
   fijo vía forwards) y deja que el `transition` ya declarado en .flow-bubble
   anime el cambio a opacity:0 — mismo par duración/easing que las entradas
   de scroll-reveal (--duration-slow + --ease-out, ver DESIGN-SYSTEM.md §2.6),
   porque es el mismo tipo de movimiento: contenido completo entrando/saliendo
   de la vista, no un simple cambio de color. */
.flow-card.is-fading[data-stage="chat"] .flow-bubble {
  animation: none;
  opacity: 0;
  transform: translateY(-6px);
}

/* Etapa 3 (Inteligencia Operacional) — panel actualizándose solo */
.flow-dash-head { display: flex; align-items: center; padding: var(--space-3); border-bottom: 1px solid var(--color-border); }
.flow-dash-title { font-family: var(--font-heading); font-size: 11px; font-weight: 600; color: var(--color-text-muted); letter-spacing: 0.06em; text-transform: uppercase; }
.flow-dash-body { padding: var(--space-4); position: relative; height: calc(100% - 39px); }
.flow-toast {
  position: absolute; top: var(--space-2); right: var(--space-2); left: var(--space-2);
  background: var(--color-bg-elevated);
  border: 1px solid rgba(115, 166, 61, 0.32);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
  display: flex; align-items: center; gap: var(--space-2);
  opacity: 0;
  transform: translateY(-10px);
}
.flow-toast svg { width: 14px; height: 14px; color: var(--color-accent); flex-shrink: 0; }
.flow-toast span { font-size: 11px; }
.flow-card.is-active[data-stage="panel"] .flow-toast { animation: flow-toast-in 0.4s var(--ease-out) forwards 0.2s; }
@keyframes flow-toast-in { to { opacity: 1; transform: translateY(0); } }

/* margin-top:52px es un offset de posición (el alto real de .flow-toast, que
   flota encima en position:absolute), no espaciado de lectura — misma
   categoría de excepción que .plan-card-tag { top:-14px }, ver §2.3. */
.flow-inv-row { margin-top: 52px; background: var(--color-bg-elevated); border: 1px solid var(--color-border); border-radius: var(--radius-sm); padding: var(--space-3); display: flex; align-items: center; justify-content: space-between; }
.flow-inv-name { font-size: 11.5px; font-weight: 600; }
.flow-inv-sub { display: block; font-size: 9.5px; color: var(--color-text-muted); font-weight: 400; margin-top: 2px; }
.flow-inv-stock { font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: var(--space-2); }
.flow-inv-stock .old-val { color: var(--color-text-muted); text-decoration: line-through; font-size: 11px; opacity: 0; }
.flow-inv-stock .new-val { color: var(--color-accent); opacity: 0; }
.flow-inv-stock .arrow { opacity: 0; font-size: 11px; color: var(--color-text-muted); }
.flow-card.is-active[data-stage="panel"] .old-val,
.flow-card.is-active[data-stage="panel"] .new-val,
.flow-card.is-active[data-stage="panel"] .arrow { animation: flow-fade-in 0.3s var(--ease-out) forwards 1.1s; }
@keyframes flow-fade-in { to { opacity: 1; } }

.flow-mini-bar { margin-top: var(--space-3); height: 6px; border-radius: 3px; background: var(--color-bg-elevated); overflow: hidden; }
.flow-mini-bar .fill { height: 100%; width: 100%; background: var(--color-brand); transform-origin: left; transform: scaleX(0.65); }
.flow-card.is-active[data-stage="panel"] .flow-mini-bar .fill { animation: flow-bar-grow 0.6s var(--ease-out) forwards 1.1s; }
@keyframes flow-bar-grow { to { transform: scaleX(0.78); } }

/* CTA después del flujo — antes comparado con .metodologia-teaser .btn,
   esa regla se quitó el 2026-08-12 junto con el botón "Ver el proceso
   completo" (la página metodologia.php se eliminó, pedido explícito). */
.home-flow .btn { margin-top: var(--space-8); }

@media (max-width: 860px) {
  .flow { grid-template-columns: 1fr; gap: var(--space-8); margin-top: var(--space-8); }
  .flow-connector { display: none; }
}

/* ---------- Teaser Metodología ---------- */
/* Grid nunca se había escrito — el layout venía sin ancho de columnas, la
   imagen se veía a ancho completo debajo del texto. Pedido explícito
   2026-08-11 ("que se adapte mejor al espacio"): 2 columnas, contenido a
   la izquierda / imagen a la derecha (mismo orden que ya tenía el markup). */
.metodologia-teaser-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-12);
  align-items: center;
}
.metodologia-teaser-media {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 5 / 4;
  border: 1px solid var(--color-border);
}
.metodologia-teaser-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Encuadre hacia arriba: la foto real (assets/img/equipo/metodologia-equipo.webp)
     tiene a las personas en el tercio superior y el monitor abajo — cover
     centrado por defecto recortaba justo las caras. */
  object-position: center 20%;
}

/* ---------- Teaser Metodología: línea de proceso conectada ----------
   Ajustado 2026-08-12 — no solo el contenido de la sección "Proceso" de
   trybot.cl, el EFECTO: cada etapa tiene su propio tramo de línea, que se
   "rellena" hacia la etapa siguiente al entrar en pantalla (no una línea
   fija siempre dibujada, como en el primer intento del 2026-08-11).
   Reutiliza .step-row (mismo componente y mismos datos de metodologia.php,
   ver index.php + components/StepRow.php opción 'connector') — todo esto
   vive bajo .teaser-timeline a propósito: metodologia.php usa el .step-row
   plano de siempre, esta variante no lo toca. El cascade por fila lo
   controla assets/js/teaser-timeline.js (un observer por fila, no el
   scroll-reveal.js genérico que anima el bloque completo de una sola vez). */
.teaser-timeline .step-row {
  border-bottom: none;
}
.teaser-timeline .step-number-col {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.teaser-timeline .step-number {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-brand);
  color: var(--color-text-dark);
  font-size: 15px;
  flex: none;
}
/* La etapa destacada (Support, "no termina") lleva el mismo halo que ya usa
   .plan-card.is-highlighted — no un color nuevo, el mismo lenguaje de
   "esto es lo permanente" que el resto del sitio. */
.teaser-timeline .step-row.is-highlighted .step-number {
  box-shadow: var(--shadow-highlight), 0 0 0 4px var(--color-bg-light);
  /* .step-row.is-highlighted .step-number (regla original, sin scope) pinta
     el número en --color-brand — mismo verde que el fondo del círculo acá,
     el "06" quedaba invisible. Mayor especificidad para ganarle. */
  color: var(--color-text-dark);
}
/* --color-accent se diseñó para texto sobre fondo oscuro — sobre el papel
   claro de esta sección se lava. Mismo verde oscuro que ya usa .tag-on-light
   para "marca sobre claro", no un tono nuevo. */
.teaser-timeline .step-row.is-highlighted h3 { color: #3A6B1F; }
.teaser-timeline .step-row p { color: var(--color-text-dark-mut); }

/* Track (siempre visible, tenue) + relleno (la parte que anima) — mismo
   patrón de dos capas que usa trybot.cl para su línea de proceso. */
.teaser-timeline .step-connector {
  width: 2px;
  flex: 1 1 auto;
  min-height: var(--space-6);
  margin-top: 4px;
  background: rgba(115, 166, 61, 0.18);
  overflow: hidden;
}
.teaser-timeline .step-connector-fill {
  width: 100%;
  height: 100%;
  background: var(--color-brand);
  transform: scaleY(1); /* visible por defecto, ver nota de abajo */
  transform-origin: top center;
}

/* Estado "oculto hasta que entra en pantalla" — SOLO si
   assets/js/teaser-timeline.js confirmó que puede animar (agrega
   .js-timeline a <html>). Sin JS, sin IntersectionObserver, o con
   prefers-reduced-motion, cada fila y su línea ya están completas desde
   el primer render — mismo principio que .js-reveal en base.css. */
.js-timeline .teaser-timeline .step-row {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 520ms var(--ease-out), transform 520ms var(--ease-out);
}
.js-timeline .teaser-timeline .step-row.is-in-view {
  opacity: 1;
  transform: translateY(0);
}
.js-timeline .teaser-timeline .step-connector-fill {
  transform: scaleY(0);
  /* 640ms, no --duration-slow: es su propio efecto de "línea que se llena",
     no una transición de UI genérica — mismo criterio que --lux-breathe en
     components.css tiene su propio ritmo en vez de reusar los tokens de
     --duration-*. 120ms de retraso: se nota primero el número, después el
     tramo que baja hacia el siguiente. */
  transition: transform 640ms var(--ease-out) 120ms;
}
.js-timeline .teaser-timeline .step-row.is-in-view .step-connector-fill {
  transform: scaleY(1);
}

@media (max-width: 860px) {
  .metodologia-teaser-grid { grid-template-columns: 1fr; gap: var(--space-8); }
  .metodologia-teaser-media { aspect-ratio: 4 / 3; }
}

/* ---------- Nosotros: hero + Misión/Visión/Valores (efecto trybot.cl) ---------- */
/* Adaptado de "{01} Sobre Nosotros" de trybot.cl — no una réplica, mismo
   EFECTO con el contenido propio de Lunexus. Pedido explícito 2026-08-16:
   título y tarjetas comparten la columna izquierda; la foto de equipo
   (assets/img/equipo/nosotros-equipo-cutout.webp, sin fondo) queda fija a
   la derecha mientras se hace scroll. */
.nosotros-hero-grid {
  display: grid;
  grid-template-columns: 1fr 0.9fr;
  gap: var(--space-10);
  align-items: start;
}
.nosotros-hero-media {
  position: sticky;
  top: 100px;
}
.nosotros-hero-media img {
  width: 100%;
  height: auto;
  display: block;
}

/* Tarjetas apiladas: por defecto (sin JS, o con prefers-reduced-motion)
   es una lista vertical normal — nunca queda roto ni superpuesto sin
   ayuda de JS. El apilado con scroll real (cada tarjeta sube y tapa a la
   anterior) lo agrega assets/js/nosotros-values-stack.js, que mide el
   scroll real y mueve cada tarjeta con translateY — dos variantes
   solo-CSS (top creciente; mismo top + margin-top negativo) se probaron
   y descartaron antes de esto, ver el comentario largo en ese archivo
   para el porqué. */
.nosotros-value-item + .nosotros-value-item {
  margin-top: var(--space-6);
}
.nosotros-value-item .card {
  /* Sombra propia, no --shadow-2 (esa es solo para tarjetas-link, ver nota
     en .flow-card más arriba) — acá es para dar profundidad al apilado,
     no una señal de interactividad. */
  box-shadow: 0 20px 40px -16px rgba(0, 0, 0, 0.35);
}
.js-values-stack .nosotros-values-track {
  position: relative;
}
.js-values-stack .nosotros-values-sticky {
  position: sticky;
  top: 100px;
  /* Sin overflow:hidden a propósito — en reposo la tarjeta 2 y la 3
     asoman más abajo (parte de la lista, ver nosotros-values-stack.js),
     no deben quedar cortadas por los bordes de este contenedor. */
}
.js-values-stack .nosotros-value-item {
  position: absolute;
  inset: 0;
  margin-top: 0;
  will-change: transform;
}
.js-values-stack .nosotros-value-item .card {
  height: 100%;
}
@media (max-width: 860px) {
  .nosotros-hero-grid { grid-template-columns: 1fr; gap: var(--space-6); }
  .nosotros-hero-media { position: static; max-width: 360px; margin: 0 auto; }
  .js-values-stack .nosotros-values-track { height: auto !important; }
  .js-values-stack .nosotros-values-sticky {
    position: static;
    height: auto !important;
    overflow: visible;
  }
  .js-values-stack .nosotros-value-item {
    position: static;
    transform: none !important;
    margin-top: var(--space-6);
  }
  .js-values-stack .nosotros-value-item .card { height: auto; }
}

/* ---------- Nosotros: Filosofía — figura de círculos ----------
   Adaptado de https://codepen.io/inclushe/pen/OPWreWR (pedido explícito
   2026-08-16), colores llevados a la paleta de marca (verde) en vez del
   azul original.

   --phi-amplitude/--phi-scale viven en .phi-orbit (no en cada .phi-item
   como el original) — inherits:true las baja a los 6 .phi-item Y al
   anillo SVG, así todo respira sincronizado leyendo un solo valor en vez
   de 6 copias idénticas. --phi-angle sí es por tarjeta (usa --i).

   Distribución en sentido HORARIO, pedido explícito: --phi-angle = i×60°
   medido en relojero desde arriba (i=0 arriba, i=1 a las 2, i=2 a las 4…),
   con x=sin(ángulo)×amplitud e y=-cos(ángulo)×amplitud — no el
   cos/sin+offset del original (ese giraba antihorario).

   Segundo/tercer pedido explícito: cuando el diseño está "cerrado"
   (círculos grandes, encimados), las frases no deben leerse planas
   arriba de cada círculo — ahí se cortan por la superposición — PERO
   tampoco deben salirse del círculo a un anillo exterior (primer intento
   descartado): tienen que quedar DENTRO de su propio círculo, con forma
   curva siguiendo el borde de ese mismo círculo, como un sello/badge.
   Por eso .phi-phrase-arc es un <svg> propio de cada .phi-item —
   viewBox 0 0 110 110 (mismo tamaño base que .phi-circle) con un arco
   interior (radio 38, de -70° a 70° medido desde arriba) — que crece
   junto con su círculo vía la misma transform:scale(var(--phi-scale)),
   sin el rotate() cosmético que sí lleva .phi-circle (ese es solo la
   dirección del degradé; el texto tiene que quedar derecho siempre).
   textLength+lengthAdjust fuerza que cada frase quepa exacta en su arco
   sin desbordarse, sin importar cuántos caracteres tenga. .phi-name
   (chico) y .phi-phrase-arc (grande) se cruzan con la misma lógica de
   opacidad de siempre. */
@property --phi-amplitude {
  syntax: '<length>';
  initial-value: 150px;
  inherits: true;
}
@property --phi-scale {
  syntax: '<number>';
  initial-value: 1;
  inherits: true;
}
.nosotros-filosofia-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr;
  gap: var(--space-12);
  align-items: center;
}
.phi-orbit-panel {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  /* Sin caja — antes tenía fondo oscuro porque mix-blend-mode:lighten lo
     necesitaba para verse; ahora usa multiply (funciona sobre fondos
     claros, como el section-light de esta sección). */
}
.phi-orbit {
  position: relative;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  --phi-amplitude: 150px;
  --phi-scale: 1;
  /* Antes 4.01s (el valor original del CodePen) — pedido explícito
     2026-08-16: se sentía lento, "se tarda mucho en realizar los
     movimientos". */
  animation: phi-expand 2s var(--ease-in-out) infinite alternate;
}
.phi-item {
  position: absolute;
  --phi-angle: calc(var(--i) * 60deg);
  transform: translate(
    calc(sin(var(--phi-angle)) * var(--phi-amplitude)),
    calc(-1 * cos(var(--phi-angle)) * var(--phi-amplitude))
  );
}
.phi-circle {
  position: absolute;
  top: 50%;
  left: 50%;
  /* ~1.7x el tamaño anterior (64px) — pedido explícito "más grande,
     tal vez el doble, para que se lea mejor". */
  width: 110px;
  height: 110px;
  border-radius: 50%;
  background: radial-gradient(
    circle at 50% 0%,
    var(--color-accent-soft),
    var(--color-brand),
    var(--color-brand-hover)
  );
  mix-blend-mode: multiply;
  transform: translate(-50%, -50%) scale(var(--phi-scale))
    rotate(calc(var(--phi-angle) + 17deg));
}
.phi-label {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 130px;
  text-align: center;
  pointer-events: none;
}
.phi-name {
  display: block;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 15px;
  color: var(--color-text);
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
  transition: opacity var(--duration-fast) var(--ease-out);
  /* Nombre visible con el círculo chico, se apaga al agrandarse — el
     anillo de frases (.phi-ring) queda fuera del alcance de los
     círculos y no necesita este mismo cruce de opacidad, se ve siempre. */
  opacity: clamp(0, calc((1.8 - var(--phi-scale)) * 2.5), 1);
}

/* Anillo de frases alrededor de todo el conjunto — ver nota larga en
   nosotros.php. Radio fijo en % del panel (no depende de --phi-scale ni
   --phi-amplitude): siempre por fuera del punto más lejano que alcanzan
   los círculos abiertos, así nunca se cruza con ellos. */
.phi-ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.phi-ring text {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 4.2px;
  /* No var(--color-text) — ese token es el claro del tema oscuro por
     defecto (ver tokens.css) y el anillo va directo sobre el fondo
     claro de la sección (.section-light), no sobre los círculos. */
  fill: var(--color-text-dark);
}

@keyframes phi-expand {
  0% { --phi-amplitude: 150px; --phi-scale: 1; }
  50% { --phi-amplitude: 100px; --phi-scale: 1.9; }
  100% { --phi-amplitude: 100px; --phi-scale: 1.9; }
}
@media (prefers-reduced-motion: reduce) {
  .phi-orbit { animation: none; --phi-amplitude: 125px; --phi-scale: 1.6; }
}
@media (max-width: 860px) {
  .nosotros-filosofia-grid { grid-template-columns: 1fr; gap: var(--space-8); }
  .phi-orbit-panel { max-width: 460px; margin: 0 auto; }
  /* Círculos grandes + amplitud grande no caben en una columna angosta de
     mobile — se reducen a algo cercano al tamaño original de este
     componente. */
  .phi-circle { width: 68px; height: 68px; }
  .phi-orbit { --phi-amplitude: 95px; }
  @keyframes phi-expand {
    0% { --phi-amplitude: 95px; --phi-scale: 1; }
    50% { --phi-amplitude: 65px; --phi-scale: 1.9; }
    100% { --phi-amplitude: 65px; --phi-scale: 1.9; }
  }
}

/* ---------- Card horizontal de Soporte ---------- */
.support-cta-card {
  padding: var(--space-12);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
}
.support-cta-card .support-cta-content { max-width: 520px; }
/* font-size en rem fijo (no clamp) a propósito: esta card es más angosta
   que una sección completa — el tamaño "heading" normal se vería
   desproporcionado aquí. Valor del mockup, no una escala derivada. */
.support-cta-card h2 { margin-top: var(--space-4); font-size: 2rem; }
.support-cta-card p { margin-top: var(--space-4); color: var(--color-text-muted); }

/* ============================================
   Producto ancla (producto-ancla.php)
   ============================================ */
.comparison-card h3 { color: var(--color-accent); }
.comparison-card p { margin-top: var(--space-4); color: var(--color-text-muted); font-size: 15px; }

/* ---------- Hero: píldora rotativa ----------
   Adaptado de notion.com/product (pedido explícito 2026-08-16) — ver la
   nota larga en producto-ancla.php sobre qué se copió (el resize +
   cross-fade) y qué no (el cambio de color por palabra, ahí ligado a un
   sistema multi-hue que este sitio no usa). Mismo lenguaje visual que
   .icon-box (fondo verde translúcido + texto en --color-accent) en vez
   de inventar un tratamiento nuevo. El ancho lo anima
   assets/js/hero-rotator.js con la medición explicada ahí; sin JS (o con
   prefers-reduced-motion) la píldora se queda del ancho natural de la
   primera frase, quieta — nunca rota, nunca se ve rota a medias. */
.hero-rotator {
  display: inline-block;
  vertical-align: middle;
}
.hero-rotator-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.32em;
  padding: 0.1em 0.5em 0.1em 0.4em;
  border-radius: var(--radius-pill);
  background: rgba(115, 166, 61, 0.16);
  white-space: nowrap;
  overflow: hidden;
  vertical-align: middle;
  transition: width var(--duration-base) var(--ease-out);
}
.hero-rotator-dot {
  width: 0.24em;
  height: 0.24em;
  min-width: 6px;
  min-height: 6px;
  border-radius: 50%;
  background: var(--color-brand);
  flex: none;
}
.hero-rotator-text {
  color: var(--color-accent);
  transition: opacity 200ms var(--ease-out);
}

/* Video del flujo integrado debajo del hero (pedido explícito
   2026-08-16, ver nota larga en producto-ancla.php). Título reducido en
   la misma pasada ("se ve demasiado grande") vía .page-hero h1.is-reduced,
   ya existente en components.css — no se inventa un tamaño nuevo.
   Segunda vuelta (pedido explícito 2026-08-17): el video vuelve a ir AL
   LADO del texto (no debajo a ancho completo, como el intento anterior)
   pero conservando el tamaño grande — como las dos columnas compiten por
   el mismo ancho, la que cede es el texto (columna angosta, h1/lead más
   chicos que el .is-reduced normal de .page-hero), nunca el video (columna
   más ancha de las dos).
   Tercera vuelta (pedido explícito 2026-08-18): "el doble del tamaño
   actual" — se agrandó (wrap más ancho + más proporción de la grilla),
   pero el video en sí (960x540) nunca llena su propio cuadro: el mockup
   grabado ocupa solo una parte del frame y varía de escena en escena, así
   que agrandar la CAJA del video (sin recortar el video) agrandó también
   ese espacio muerto dentro de ella — se veía como "toda la página
   descentrada" cuando en realidad el grid sí estaba centrado (confirmado
   con getBoundingClientRect antes de este cambio). Cuarta vuelta (mismo
   día, tras ver una captura real): pedido explícito "si tienes que achicar
   el contenedor del video achícalo" — se vuelve a un wrap normal (sin el
   max-width:1600 de la vuelta anterior) y una proporción de columnas más
   moderada, para que ese espacio muerto ocupe un área absoluta más chica
   y se note menos, en vez de seguir agrandando la caja. */
.producto-hero-grid {
  display: grid;
  grid-template-columns: 0.8fr 1.2fr;
  gap: var(--space-8);
  align-items: center;
}
.producto-hero-text h1.hero-rotator-heading {
  /* Más específico que .page-hero h1.is-reduced (components.css) a
     propósito — necesita ganarle a esa regla, no solo empatar. */
  font-size: clamp(1.7rem, 2.3vw, 2.3rem);
}
.producto-hero-text p.page-hero-lead {
  font-size: 16px;
  max-width: none;
}
.producto-hero-media {
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.producto-hero-media video {
  width: 100%;
  height: auto;
  display: block;
}
@media (max-width: 860px) {
  .producto-hero-grid { grid-template-columns: 1fr; gap: var(--space-6); }
  .producto-hero-text h1.hero-rotator-heading { font-size: clamp(2.2rem, 4vw, 3.4rem); }
  .producto-hero-text p.page-hero-lead { font-size: 18px; max-width: 560px; }
}

/* ============================================
   Soporte (soporte.php + soporte-basico/premium/profesional.php)
   ============================================ */
.pricing-note { margin-top: var(--space-6); font-size: 13px; color: var(--color-text-muted); text-align: center; }

/* ============================================
   Nosotros (nosotros.php)
   ============================================ */
/* Mismo rol "lead" que el resto del sitio (18px) — es la única frase de la
   card (Misión/Visión), cumple el mismo papel que un párrafo introductorio
   aunque no vaya debajo de un h1/h2. */
.mission-card-text { margin-top: var(--space-4); font-size: 18px; line-height: 1.6; color: var(--color-text-muted); }

/* ============================================
   Contacto (contacto.php)
   ============================================ */

.contact-section { padding-top: var(--space-20); padding-bottom: var(--space-20); }

/* ---------- Las dos tarjetas: Agenda (calendario) + Formulario ---------- */
/* Pedido explícito 2026-08-18: la landing de agenda que vivía aparte se
   integra acá. Sin intro visible arriba (se quitó "Cuéntanos qué te está
   quitando tiempo..." — pedido explícito, ver el <h1 class="visually-hidden">
   en contacto.php) — las dos tarjetas son lo primero que se ve, el padding
   de .contact-section ya da el aire de sobra hacia arriba. */
.contact-columns { align-items: start; }
.contact-agenda-card .btn { margin-top: var(--space-6); }

.contact-rows { margin-top: var(--space-10); display: flex; flex-direction: column; gap: var(--space-5); }
.contact-row { display: flex; gap: var(--space-3); align-items: center; }
.contact-row .icon-box { margin: 0; }
.contact-row-label { font-size: 14px; color: var(--color-text-muted); }
.contact-row-value { font-weight: 600; }
.contact-row-value a:hover { color: var(--color-accent); }

/* ---------- Formulario real ---------- */
/* 20px (--space-5), no --space-4 (16px): un formulario se escanea campo por
   campo, no se lee como párrafo — un poco más de aire entre filas ayuda a
   distinguir dónde empieza cada una sin necesidad de mirar el label. */
.contact-form { margin-top: var(--space-6); display: flex; flex-direction: column; gap: var(--space-5); }
.contact-form-field { display: flex; flex-direction: column; }
.contact-form-label {
  font-size: 13.5px;
  color: var(--color-text-muted);
  font-family: var(--font-heading);
  font-weight: 600;
  margin-bottom: var(--space-2);
}
.contact-form-input,
.contact-form-textarea {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-4);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 15px;
  transition: border-color var(--duration-fast) var(--ease-out);
}
.contact-form-input::placeholder,
.contact-form-textarea::placeholder { color: var(--color-text-muted); }
.contact-form-textarea { min-height: 90px; resize: vertical; }
.contact-form-input:focus,
.contact-form-textarea:focus {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
  border-color: var(--color-accent);
}
.contact-form-input[aria-invalid="true"],
.contact-form-textarea[aria-invalid="true"] { border-color: var(--color-danger); }

/* Pills de "¿Qué necesitas?" — radios reales, visualmente ocultos, la
   etiqueta ES la pill (mismo aspecto que el mockup, totalmente accesible). */
.contact-form-tags { margin-top: var(--space-2); display: flex; gap: var(--space-2); flex-wrap: wrap; }
.contact-form-tag { cursor: pointer; transition: background var(--duration-fast) var(--ease-out); }
/* Hallazgo de Fase 4: la transición existía pero no había ningún :hover
   que la activara (mismo tipo de bug que H10 en Fase 1) — la pill solo
   reaccionaba al seleccionarse, nunca al simplemente pasar el mouse, pese
   a ser clicable. Va ANTES de la regla :checked a propósito: ambas tienen
   la misma especificidad, y una pill ya seleccionada debe conservar su
   fondo de "elegida" (0.18) al pasar el mouse, no bajar al de "posible"
   (0.1) — el orden en el archivo es lo que decide cuál gana. */
.contact-form-tag:hover { background: rgba(115, 166, 61, 0.1); }
.visually-hidden-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.visually-hidden-input:checked + .contact-form-tag { background: rgba(115, 166, 61, 0.18); }
.visually-hidden-input:focus-visible + .contact-form-tag {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

.contact-form-error { margin-top: var(--space-2); font-size: 13px; color: var(--color-danger); min-height: var(--space-4); }
/* margin-top extra sobre el gap del formulario (--space-5) a propósito: el
   botón es la acción, no otro campo — un poco más de distancia lo separa
   visualmente del último input en vez de leerse como "un campo más". */
.contact-form-submit { justify-content: center; margin-top: var(--space-1); }

.contact-form-status-message { margin-top: var(--space-3); font-size: 14px; color: var(--color-text-muted); }
.contact-form-status-message:empty { margin-top: 0; }

.contact-form-success { text-align: center; }
/* Tamaño de h3 a propósito: es una confirmación dentro de una card angosta,
   no un título de sección — el nivel semántico es h2 (no hay otro h2 en la
   página) pero el tamaño de sección completa se vería desproporcionado aquí. */
.contact-form-success h2 { margin-bottom: var(--space-2); font-size: clamp(1.2rem, 1.6vw, 1.4rem); }
.contact-form-success p { color: var(--color-text-muted); }

/* Honeypot anti-spam: invisible y fuera del orden de tabulación para
   personas reales, presente en el DOM para bots que rellenan todo. */
.contact-form-honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------- Modal de agenda (contacto.php) ---------- */
/* Pedido explícito 2026-08-18: agendar sin salir de /contacto — overlay +
   caja centrada, misma paleta de tarjeta que el resto del sitio
   (--color-bg-elevated) para el "marco" del modal; el cuerpo queda blanco
   porque ahí adentro vive /agendar (fondo claro, ver captura tomada al
   revisar esa página). z-index 100: por encima del header sticky (50) y de
   .lux-companion (60, el más alto que había antes de esto). */
.agenda-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  background: rgba(10, 15, 12, 0.82);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-base) var(--ease-out);
}
/* Misma especificidad que la regla de arriba (0,1,0) — gana por venir
   después: mientras JS no saque el atributo "hidden", esto le pisa el
   display:flex de la regla base. Al abrir, JS saca "hidden" antes de
   animar la opacidad, así que esta regla deja de aplicar y vuelve a
   display:flex sola. */
.agenda-modal-overlay[hidden] { display: none; }
.agenda-modal-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}
.agenda-modal {
  position: relative;
  width: min(960px, 100%);
  height: min(85vh, 800px);
  display: flex;
  flex-direction: column;
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transform: scale(0.97);
  transition: transform var(--duration-base) var(--ease-out);
}
.agenda-modal-overlay.is-open .agenda-modal { transform: scale(1); }
.agenda-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-border);
  flex: none;
}
.agenda-modal-head h2 { font-size: 1.05rem; }
.agenda-modal-close {
  flex: none;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: var(--color-text-muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out), color var(--duration-fast) var(--ease-out);
}
.agenda-modal-close:hover { background: rgba(255, 255, 255, 0.08); color: var(--color-text); }
.agenda-modal-body { position: relative; flex: 1; background: #fff; }
.agenda-modal-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-text-dark-mut);
  font-size: 14px;
}
.agenda-modal-body iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
@media (prefers-reduced-motion: reduce) {
  .agenda-modal-overlay, .agenda-modal { transition: none; }
}
@media (max-width: 640px) {
  .agenda-modal-overlay { padding: 0; }
  .agenda-modal { width: 100%; height: 100%; border-radius: 0; }
}

/* ============================================
   Casos (casos.php) — pedido explícito 2026-08-12
   ============================================ */

/* ---------- Tarjeta con logo real (CaseCard, modo "logo") ---------- */
/* Alto fijo (no ancho): Leins Technology es un wordmark ancho y bajo,
   Multipower/Reyesolutions son isotipos cuadrados — mismo criterio que
   .lux-companion img en components.css (fijar una sola dimensión es lo que
   los hace lucir "del mismo tamaño" con proporciones tan distintas). */
.case-card-logo {
  display: flex;
  align-items: center;
  height: 34px;
}
.case-card-logo img {
  height: 100%;
  width: auto;
  max-width: 100%;
  border-radius: var(--radius-sm);
}

.case-card-video {
  margin-top: var(--space-6);
  padding: var(--space-8) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  background: radial-gradient(circle at center, rgba(115, 166, 61, 0.08), transparent 70%);
}
.case-card-play {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1.5px solid var(--color-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-accent);
  /* .case-card-play también es <button> en la variante con video real
     (ver .has-video más abajo) — reset mínimo para que no herede el
     fondo/tipografía por defecto del navegador; en la variante <span>
     (placeholder) estas propiedades no cambian nada. */
  background: none;
  font: inherit;
  cursor: pointer;
}
.case-card-play svg {
  width: 18px;
  height: 18px;
  /* El triángulo de "play" pesa visualmente a la izquierda de su propio
     viewBox — 1px de margen lo centra de verdad dentro del círculo. */
  margin-left: 2px;
}
.case-card-video strong { font-family: var(--font-heading); font-size: 15px; color: var(--color-text); }
.case-card-video span { font-size: 13px; color: var(--color-text-muted); }

/* ---------- Tarjeta con video real (has-video) ---------- */
/* Mismo botón de play que el placeholder (.case-card-play), pero acá
   flota centrado sobre el <video> en vez de ser parte de una columna de
   texto — arranca en pausa mostrando 'poster' hasta que se hace clic (ver
   assets/js/case-video.js). aspect-ratio fijo (16/9) para que las 3
   tarjetas del catálogo midan lo mismo aunque los 3 videos originales no
   compartan la misma proporción exacta (Leins quedó con un recorte propio
   al sacarle la barra de Chrome) — object-fit:cover absorbe la diferencia. */
.case-card-video.has-video {
  position: relative;
  margin-top: var(--space-6);
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--color-bg);
}
.case-card-video.has-video video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.case-card-video.has-video .case-card-play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: rgba(10, 15, 12, 0.55);
  backdrop-filter: blur(2px);
  transition: opacity var(--duration-fast) var(--ease-out), transform var(--duration-fast) var(--ease-out);
}
.case-card-video.has-video .case-card-play:hover { transform: translate(-50%, -50%) scale(1.08); }
.case-card-video.has-video.is-playing .case-card-play { opacity: 0; pointer-events: none; }

/* ---------- Cinta que se desliza sola (efecto "Trusted by" de framer.com) ---------- */
/* Difumina los bordes izquierdo/derecho en vez de cortar la tarjeta en seco
   — mismo truco visual que framer.com. */
.casos-ticker-viewport {
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent, black 48px, black calc(100% - 48px), transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, black 48px, black calc(100% - 48px), transparent);
}
.casos-ticker-track {
  display: flex;
  gap: var(--space-6);
  width: max-content;
  animation: casos-ticker-scroll 26s linear infinite;
}
/* Pausa al pasar el mouse — para leer/interactuar con una tarjeta sin que
   seleccione el paño (mismo criterio que .home-flow, el sitio no anima algo
   que el usuario está intentando mirar). El brillo ambiental que sigue el
   cursor (.card::before, ver components.css) recién tiene sentido con la
   cinta detenida. */
.casos-ticker-viewport:hover .casos-ticker-track { animation-play-state: paused; }
.casos-ticker-dup { display: contents; }
.casos-ticker-track .case-card {
  flex: 0 0 300px;
  width: 300px;
}
@keyframes casos-ticker-scroll {
  from { transform: translateX(0); }
  /* -50%, no -100%: el track imprime el set 2 veces (ver casos.php), así
     que la mitad del ancho total es exactamente donde termina el set real
     y empieza su copia idéntica — el loop no se nota. */
  to   { transform: translateX(-50%); }
}
/* prefers-reduced-motion: reduce ya está cubierto por la regla global de
   base.css (fuerza animation-iteration-count a 1 y la duración a ~0 en
   todo el sitio) — sin animación, el track queda en su transform base
   (sin trasladar) y .casos-ticker-viewport recorta la copia duplicada:
   se ve el catálogo real, quieto, sin la costura del loop. No hace falta
   una regla aparte acá.

   Mobile: sin cambios — la cinta ya es angosta (una tarjeta y fracción a
   la vez) y seguir deslizándose sola es, si acaso, más útil en una
   pantalla donde no entran las 3 a la vez. */

/* ============================================
   Servicios (servicios.php) — pedido explícito 2026-08-12
   Selector lista + tarjeta que gira, adaptado de "{02} Servicios" de
   trybot.cl. Reemplaza el layout "zigzag" anterior (ServiceRow, eliminado).
   ============================================ */

.servicios-selector-grid {
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: var(--space-10);
  align-items: start;
}

/* ---------- Lista (izquierda) ---------- */
.servicios-selector-list {
  display: flex;
  flex-direction: column;
  border-left: 2px solid var(--color-border);
}
.servicios-selector-item {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  margin-left: -2px;
  border-left: 2px solid transparent;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-out), border-color var(--duration-fast) var(--ease-out);
}
.servicios-selector-item-num {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 13px;
  color: var(--color-text-muted);
}
.servicios-selector-item-title {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 17px;
  color: var(--color-text-muted);
  transition: color var(--duration-fast) var(--ease-out);
}
.servicios-selector-item:hover,
.servicios-selector-item:focus-visible {
  background: var(--color-bg-elevated);
}
.servicios-selector-item.is-active {
  background: var(--color-bg-elevated);
  border-left-color: var(--color-brand);
}
.servicios-selector-item.is-active .servicios-selector-item-title { color: var(--color-text); }
.servicios-selector-item.is-active .servicios-selector-item-num { color: var(--color-brand); }

/* ---------- Tarjeta (derecha) ----------
   Estado base = SIEMPRE apiladas y visibles (fallback sin JS: nada se
   oculta, mismo principio que .js-reveal en base.css). El modo "una a la
   vez, con giro" solo se activa bajo .js-service-selector, que
   assets/js/servicios-selector.js agrega a <html> — y solo en desktop: en
   mobile no hay hover, y una tarjeta apilada por vez ya funciona bien ahí. */
.servicios-selector-panel {
  background: var(--color-bg-elevated);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
}
.servicios-selector-panel + .servicios-selector-panel { margin-top: var(--space-6); }
.servicios-selector-panel h2 { margin-top: var(--space-4); font-size: clamp(1.5rem, 2.6vw, 2.1rem); }
.servicios-selector-panel > p { margin-top: var(--space-5); color: var(--color-text-muted); font-size: 17px; line-height: 1.6; }
.servicios-selector-panel .btn { margin-top: var(--space-6); }

.servicios-selector-gif {
  margin-bottom: var(--space-6);
  padding: var(--space-10) var(--space-4);
  border: 1px dashed var(--color-border);
  border-radius: var(--radius-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  text-align: center;
  background: radial-gradient(circle at center, rgba(115, 166, 61, 0.08), transparent 70%);
}
.servicios-selector-gif svg { width: 30px; height: 30px; color: var(--color-accent); }
.servicios-selector-gif strong { font-family: var(--font-heading); font-size: 15px; color: var(--color-text); }
.servicios-selector-gif span { font-size: 13px; color: var(--color-text-muted); }

/* Con loop real (has-video): mismo lugar y mismo margin-bottom que el
   placeholder, pero sin el recuadro punteado ni el padding — el video ya
   trae su propio mockup dibujado adentro (navegador/panel/chat, ver los 3
   clips en assets/video/servicios/), un borde propio se vería como un
   marco doble. aspect-ratio fijo (16/9, la proporción real de los 3
   videos) para que el panel no salte de alto al cambiar de servicio. */
.servicios-selector-gif.has-video {
  padding: 0;
  border: none;
  background: none;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
.servicios-selector-gif.has-video video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

@media (min-width: 861px) {
  .js-service-selector .servicios-selector-panels {
    position: relative;
    /* Antes 600px — se quedó corto al sumar el video real (2026-08-16,
       ver .servicios-selector-gif.has-video): la tarjeta activa mide
       ~809px de contenido real, pero al ser position:absolute no empuja
       la altura del contenedor, así que el sobrante se salía por debajo
       y pisaba al CTA final. 840px le da margen a las 3 tarjetas (la más
       alta hoy son ~809px). */
    min-height: 840px;
    perspective: 1600px;
  }
  .js-service-selector .servicios-selector-panel {
    position: absolute;
    inset: 0;
    margin-top: 0;
    opacity: 0;
    pointer-events: none;
    transform: rotateY(-90deg);
    transform-origin: 0% 50%;
    /* Bisagra en el borde izquierdo (junto a la lista) — la tarjeta gira
       hacia la derecha para abrirse, "en sentido contrario" a donde está
       la lista que la activó. Duración propia (no --duration-*): es un
       gesto puntual de apertura, no una transición de UI genérica —
       mismo criterio que --lux-breathe en components.css. */
    transition: transform 620ms var(--ease-out), opacity 260ms var(--ease-out);
  }
  .js-service-selector .servicios-selector-panel.is-active {
    opacity: 1;
    pointer-events: auto;
    transform: rotateY(0deg);
  }
}

@media (max-width: 860px) {
  .servicios-selector-grid { grid-template-columns: 1fr; gap: var(--space-6); }
  .servicios-selector-list { flex-direction: row; flex-wrap: wrap; border-left: none; border-bottom: 1px solid var(--color-border); padding-bottom: var(--space-2); }
  .servicios-selector-item { border-left: none; border-bottom: 2px solid transparent; margin-left: 0; margin-bottom: -1px; padding: var(--space-3) var(--space-4); }
  .servicios-selector-item.is-active { border-bottom-color: var(--color-brand); }
}
