/* Estilos exclusivos del landing principal. */

@layer features {
html {
  scroll-behavior: smooth;
}

/*
  EL FONDO DE LA PÁGINA, y por qué se mudó acá desde `.hero`.

  Ese gradiente vivía en `.hero` y era OPACO, así que tapaba cualquier capa
  puesta detrás: con el lienzo de partículas fijo a la ventana, la primera
  pantalla se quedaba sin fondo animado.

  Fijo y en `--z-behind` queda por debajo del lienzo y por encima del color
  plano del `body`, que es el orden que hace falta:

      body (color plano)  <  este gradiente  <  #particles-fondo  <  contenido
*/
body::before {
  content: "";
  position: fixed;
  inset: 0 0 auto 0;

  /* Mismo anclaje que `#particles-fondo`: las dos capas del fondo van en
     PAREJA y tienen que terminar en el mismo borde.
     Cuando el lienzo pasó a `100lvh` y este gradiente se quedó con
     `inset: 0` —atado al viewport dinámico—, con las barras del navegador
     visibles quedaba más corto: en la franja sobrante seguían dibujándose
     partículas sobre el color plano del body en vez del final del degradado,
     y se veía una línea horizontal de corte. */
  height: 100vh;
  height: 100lvh;

  z-index: var(--z-behind);
  pointer-events: none;
  background: linear-gradient(180deg, #020408 0%, #050a11 62%, #06131b 100%);
}

/*
  El lienzo de partículas de toda la página.

  Fijo, no absoluto: así el mismo canvas acompaña el scroll en lugar de
  terminarse con su sección. `pointer-events: none` porque este fondo no
  reacciona al mouse —el que sí lo hace es el revelado de "Quiénes somos", y
  ése tiene su propio contenedor.
*/
#particles-fondo {
  position: fixed;
  inset: 0 0 auto 0;

  /* El alto se ancla a la ventana GRANDE, no al viewport que encoge.
   *
   * En móvil, la barra de direcciones se retrae al empezar a deslizar desde
   * arriba y el viewport crece de golpe. Con `inset: 0` esta caja seguía ese
   * alto, y tsParticles reconstruye su canvas —3,4 MP a DPR 3, medido— cada
   * vez que la caja cambia de tamaño: el trabón caía justo en el arranque del
   * scroll.
   *
   * No alcanza con apagar `interactivity.events.resize` en la config: se
   * probó el 2026-09-01 y el lienzo se reconstruyó igual, porque la librería
   * observa su propio canvas por dentro al margen de esa opción.
   *
   * `lvh` no se mueve mientras la barra aparece o desaparece. El fondo queda
   * algo más alto que la pantalla con la barra visible, que es justo lo que
   * se quiere de un fondo fijo: cubrir de más, nunca de menos. El `vh` de
   * arriba es el respaldo para navegadores sin `lvh`, y en móvil ya se
   * comporta igual. */
  height: 100vh;
  height: 100lvh;

  z-index: var(--z-base);
  pointer-events: none;
}

.hero {
  min-height: 100vh;
  min-height: 100svh;
  /* Sin fondo propio: lo pinta `body::before`, por debajo del lienzo. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}

/*
  Las dos capas atmosféricas del hero, con su disolvencia al pie.

  EL PORQUÉ DE LA MÁSCARA. Las dos llegan con intensidad al borde inferior —el
  glow está centrado en `50% 112%`, o sea justo debajo, y la viñeta oscurece
  hasta 0.78 de alfa en los bordes— y el hero tiene `overflow: hidden`. Sin
  disolverlas, ese borde es un CORTE: una línea horizontal dura que cruza la
  pantalla al scrollear.

  Antes no se notaba porque las secciones de abajo pintaban un color plano
  parecido al del corte. Desde que son transparentes —para dejar ver el fondo
  de partículas— el salto quedó a la vista.

  Se disuelven en el último 22% en vez de recortarse, así la transición al
  resto de la página es continua.
*/
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  z-index: var(--z-base);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
}

.hero::before {
  inset: 0;
  background:
    radial-gradient(ellipse at 50% 112%, rgba(0, 180, 220, 0.14) 0%, rgba(0, 82, 120, 0.07) 30%, transparent 58%),
    radial-gradient(ellipse at 18% 72%, rgba(24, 82, 210, 0.07) 0%, transparent 38%);
}

.hero::after {
  inset: 0;
  background: radial-gradient(ellipse at center, transparent 38%, rgba(1, 4, 8, 0.18) 66%, rgba(0, 2, 5, 0.78) 100%);
}

/* El glow lo dibuja tsParticles por partícula (shadow en home.js); un filter
   sobre el canvas se re-aplicaría al lienzo completo en cada frame. */
#particles-fondo canvas {
  opacity: 0.88;
}

.hero__logo-stage {
  position: relative;
  z-index: var(--z-content);
  isolation: isolate;
  display: grid;
  place-items: center;
  width: min(70vw, 114.96vh);
  aspect-ratio: 1432 / 872;
  opacity: 1;
  visibility: visible;
  transition: visibility 0s;
  animation:
    hero-logo-reveal 1.15s cubic-bezier(0.16, 1, 0.3, 1) both,
    hero-logo-float 4.2s ease-in-out 1.15s infinite;
}

.hero__logo-stage::before {
  content: "";
  position: absolute;
  inset: 7% 4% 10%;
  z-index: var(--z-base);
  pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(24, 82, 210, 0.42) 0%, rgba(24, 82, 210, 0.14) 34%, transparent 68%),
    radial-gradient(circle, color-mix(in srgb, var(--color-accent) 38%, transparent) 0%, color-mix(in srgb, var(--color-accent) 12%, transparent) 34%, transparent 68%);
  background-repeat: no-repeat;
  background-size: 62% 82%, 62% 82%;
  background-position: -22% 46%, 122% 54%;
  filter: blur(44px);
  animation:
    hero-logo-glow-in 1.25s ease-out 0.1s both,
    hero-logo-glow-pulse 6.2s ease-in-out 1.35s infinite alternate;
}

.hero__logo {
  position: relative;
  z-index: var(--z-elevated);
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  mix-blend-mode: screen;
  opacity: 1;
  transform: scale(1) translateY(0);
  transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
}

.hero__light-overlay {
  position: absolute;
  inset: 0;
  z-index: var(--z-highlight);
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.hero__light-path {
  fill: none;
  stroke: url("#logo-edge-beam-gradient");
  stroke-width: 8;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 3 5 18 74;
  stroke-dashoffset: 100;
  opacity: 0;
}

.hero__light-path--core {
  animation: hero-light-core 5.8s ease-in-out 0.3s infinite;
}

.hero__light-path--left,
.hero__light-path--right {
  animation: hero-light-outer 5.8s ease-in-out 0.3s infinite;
}

.hero__light-path--right {
  --hero-light-offset: -100;
}

.hero--logo-hidden .hero__logo {
  opacity: 0;
  transform: scale(0.8) translateY(-20px);
}

.hero--logo-hidden .hero__logo-stage {
  visibility: hidden;
  transition-delay: 0.5s;
}

/* Fuera de vista, el hero se calla.
 *
 * `visibility: hidden` deja de PINTAR, pero no detiene una animación CSS: el
 * estilo se sigue recalculando en cada frame. Medido el 2026-09-01, las cinco
 * animaciones infinitas de acá seguían corriendo con la clase puesta — el
 * flotar del logo, el pulso de su resplandor (sobre un pseudo-elemento con
 * `filter: blur(44px)`, de lo más caro que hay en móvil) y los tres trazos
 * del SVG.
 *
 * Y eso se ve en el FONDO, no en el hero: tsParticles avanza cada partícula
 * según el delta de tiempo entre frames, así que un frame largo no la frena,
 * la hace recuperar de un tirón lo que se atrasó. De ahí el salto brusco al
 * deslizar en móvil.
 *
 * Se reanudan solas: la clase se quita al volver arriba. */
.hero--logo-hidden .hero__logo-stage,
.hero--logo-hidden .hero__logo-stage::before,
.hero--logo-hidden .hero__light-path {
  animation-play-state: paused;
}

@keyframes hero-logo-reveal {
  0% {
    opacity: 0;
    filter: blur(26px) brightness(0.55);
    transform: translateY(34px) scale(0.72);
  }

  68% {
    opacity: 1;
    filter: blur(0) brightness(1.22);
    transform: translateY(-6px) scale(1.05);
  }

  100% {
    opacity: 1;
    filter: blur(0) brightness(1);
    transform: translateY(0) scale(1);
  }
}

/* Animaciones infinitas solo con opacity/transform: animar filter o
   background-position fuerza un repaint del elemento en cada frame. */
@keyframes hero-logo-float {
  0%,
  100% {
    transform: translateY(0) scale(1);
  }

  50% {
    transform: translateY(-14px) scale(1.018);
  }
}

@keyframes hero-logo-glow-in {
  0% {
    opacity: 0;
    transform: scale(0.72);
  }

  65% {
    opacity: 0.42;
    transform: scale(1.06);
  }

  100% {
    opacity: 0.34;
    transform: scale(1);
  }
}

@keyframes hero-logo-glow-pulse {
  0% {
    opacity: 0.22;
    transform: scale(0.98);
  }

  50% {
    opacity: 0.44;
    transform: scale(1.05);
  }

  100% {
    opacity: 0.26;
    transform: scale(1);
  }
}

/*
  El pico va a 0.7 y no a 1: el degradado del trazo pasa por blanco puro a
  propósito (index.html, #logo-edge-beam-gradient) y con el halo del
  feGaussianBlur encima, al 100% pegaba más que el propio logo. Bajar la
  opacidad del keyframe atenúa trazo y halo en la misma proporción sin tocar
  el SVG.
*/
@keyframes hero-light-core {
  0%,
  6% {
    opacity: 0;
    stroke-dashoffset: 100;
  }

  11% {
    opacity: 0.7;
  }

  36% {
    opacity: 0.7;
    stroke-dashoffset: 0;
  }

  43%,
  100% {
    opacity: 0;
    stroke-dashoffset: 0;
  }
}

@keyframes hero-light-outer {
  0%,
  24% {
    opacity: 0;
    stroke-dashoffset: var(--hero-light-offset, 100);
  }

  30% {
    opacity: 0.7;
  }

  72% {
    opacity: 0.7;
    stroke-dashoffset: 0;
  }

  80%,
  100% {
    opacity: 0;
    stroke-dashoffset: 0;
  }
}

/*
  Base común de las secciones del landing. align-content centra el contenido
  cuando sobra alto y lo deja crecer cuando lo excede, así que sirve tanto
  para una sección corta como para .services, que supera el viewport.
  El espacio del navbar lo reserva scroll-padding-top en html (styles.css).
*/
.home__section {
  min-height: 100vh;
  min-height: 100svh;
  /* Apilamiento explícito: el lienzo de partículas es `fixed` en `--z-base`,
     así que sin esto el contenido de las secciones quedaría por debajo. */
  position: relative;
  z-index: var(--z-content);
  display: grid;
  /*
    minmax(0, 1fr) y no 1fr: sin el mínimo en 0 la pista se estira al
    max-content del hijo más ancho, y .carousel__track mide varios miles de
    píxeles por diseño. Eso arrastraría al resto del contenido fuera del
    viewport.
  */
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  padding-block: clamp(2.5rem, 6vw, 5rem);
  /*
    SIN fondo propio, y no es un descuido.

    Tenía `background-color: var(--color-background)`, que era redundante —el
    `body` ya pinta ese mismo color— y además TAPABA el lienzo de partículas,
    que es `fixed` por debajo. Ése era exactamente el motivo de que el fondo
    "desapareciera" al scrollear: no es que las partículas se terminaran, es
    que estas secciones las cubrían.

    Si alguna sección necesitara un fondo distinto, va con transparencia o
    sobre un pseudo-elemento, nunca con un color pleno.
  */
}

.about {
  position: relative;
  overflow: hidden;
  justify-items: center;
  padding-inline: 2rem;
  text-align: center;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 3s ease, transform 3s ease;
}

.about--visible {
  opacity: 1;
  transform: translateY(0);
}

/*
  Bruma en lugar de caja (decisión del 2026-09-16, sobre el lienzo de diseño
  "Quiénes somos sin bordes"). La tarjeta era la tapa del revelado: un borde y
  un radio alrededor del canvas de tsParticles. La tapa se queda —el revelado
  y el hover no cambian— pero crece más allá del texto y sus bordes se funden
  con las estrellas por una máscara radial, sin filo.

  El canvas sigue DENTRO de .about__content para acoplarse a la tarjeta; el
  inset negativo lo estira hacia afuera y `.about { overflow: hidden }` lo
  recorta en pantallas angostas para que nunca produzca scroll horizontal.
*/
.about__particles {
  position: absolute;
  inset: -4rem -6rem;
  z-index: var(--z-base);
  overflow: hidden;
  -webkit-mask-image: radial-gradient(ellipse 62% 66% at 50% 50%, #000 34%, transparent 100%);
  mask-image: radial-gradient(ellipse 62% 66% at 50% 50%, #000 34%, transparent 100%);
}

/*
  Las clases `panel panel--spacious` siguen en el HTML (ui-consolidation.test.js
  las exige) y panel.css no se toca; la caja se anula desde acá, en @layer
  features, igual que .contact__panel. El relleno de panel--spacious se queda.
*/
.about__content {
  position: relative;
  z-index: var(--z-content);
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
}

.about__title,
.about__description {
  position: relative;
  z-index: var(--z-content);
}

.about__title {
  font-size: 2.5rem;
  font-weight: 700;
  margin-bottom: 2rem;
  background: var(--gradient-heading);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  line-height: 1.2;
}

.about__description {
  font-size: 1.1rem;
  color: var(--color-text-muted);
  line-height: 1.7;
  margin-bottom: 1.5rem;
}

.services {
  padding-inline: 1.25rem;
}

/*
  Luz en lugar de caja, como el contacto (decisión del 2026-09-08): las
  tarjetas dejaron de ser rectángulos sólidos sobre las estrellas y el
  contenido se apoya en un resplandor radial detrás de toda la sección. Mismo
  contrato que .contact::before: pseudo-elemento por debajo del contenido,
  nunca un background de la sección (tapa el lienzo de partículas). El foco
  cae al centro porque acá no hay una columna dominante como la del
  formulario.
*/
.services::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;
  background:
    radial-gradient(ellipse 45% 40% at 50% 55%, rgba(0, 180, 220, 0.08), transparent 70%),
    radial-gradient(ellipse 65% 55% at 50% 50%, rgba(24, 82, 210, 0.16), transparent 70%);
}

/* .home__section es grid y .u-contenedor trae margin-inline: auto. Un ítem
   de grid con márgenes auto deja de estirarse y se dimensiona a fit-content:
   el contenedor medía lo que sus tarjetas (4 × 260 + 3 × 40 = 1160) y
   arrancaba 20px adentro del eje del logo. El 100% lo devuelve al ancho del
   track; el tope de 1200 sigue siendo de .u-contenedor. */
.services__container {
  inline-size: 100%;
}

.services__title {
  text-align: center;
  font-size: 2.5rem;
  margin-bottom: 60px;
  font-weight: 700;
}

.services__grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 40px;
}

.services__card {
  /* Crecen para repartirse la fila: cuatro a 1200 dan 270 cada una y tocan
     el eje del logo. El tope de 320 evita que una tarjeta sola en la última
     fila se estire a todo el ancho. */
  flex: 1 1 260px;
  max-width: 320px;
  /* Sin fondo ni sombra: la caja se fue con la decisión de "luz, no caja".
     El radio se queda porque le da forma al halo del hover (::after). */
  padding: 30px 10px;
  border-radius: 15px;
  text-align: center;
  opacity: 0;
  transition: transform 0.3s ease;
  position: relative;
}

/* El halo del hover es un pseudo-elemento que sólo cambia de opacidad: los
   degradados no interpolan, así que animar `background` saltaría en vez de
   encenderse. Va detrás del contenido de la tarjeta y no roba el hover. */
.services__card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(ellipse 70% 60% at 50% 35%, rgba(24, 82, 210, 0.22), transparent 70%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.services__card:hover {
  transform: translateY(-15px);
  z-index: var(--z-hover);
}

.services__card:hover::after {
  opacity: 1;
}

.services__card--visible {
  animation: services-fade-up 0.8s ease-out forwards;
}

.services__card--visible:nth-child(1) {
  animation-delay: 0.5s;
}

.services__card--visible:nth-child(2) {
  animation-delay: 1.2s;
}

.services__card--visible:nth-child(3) {
  animation-delay: 1.9s;
}

.services__card--visible:nth-child(4) {
  animation-delay: 2.5s;
}

.services__card-image {
  width: 180px;
  height: 180px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 20px;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.1), 0 0 25px rgba(255, 255, 255, 0.25);
}

.services__card-title {
  margin-bottom: 15px;
  font-size: 1.3rem;
  font-weight: 600;
}

.services__card-description {
  font-size: 1rem;
  line-height: 1.5;
}

@keyframes services-fade-up {
  from {
    opacity: 0;
    translate: 0 30px;
  }

  to {
    opacity: 1;
    translate: 0 0;
  }
}

.technology {
  padding-inline: 1.25rem;
  text-align: center;
  overflow: hidden;
}

.technology__title {
  font-size: 2.5rem;
  margin-bottom: 40px;
  background: var(--gradient-heading);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.technology__description {
  font-size: 1.1rem;
  max-width: 800px;
  margin: 0 auto 3rem;
  line-height: 1.6;
  color: var(--color-text-muted);
}

.technology__carousels {
  display: flex;
  flex-direction: column;
  gap: 40px;
}

.carousel {
  overflow: hidden;
  mask-image: linear-gradient(to right, transparent 0%, black 20%, black 80%, transparent 100%);
}

.carousel__track {
  display: flex;
  animation: carousel-scroll-left 50s linear infinite;
  width: max-content;
}

.carousel__group {
  display: flex;
  gap: 60px;
  padding-right: 60px;
}

.carousel__track--reverse {
  animation-direction: reverse;
}

.carousel__track--paused {
  animation-play-state: paused;
}

.carousel__item {
  height: 80px;
  filter: brightness(1.2);
  transition: transform 0.3s ease;
}

.carousel__item:hover {
  transform: scale(1.1);
}

@keyframes carousel-scroll-left {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-25%);
  }
}

.contact {
  padding-inline: 1.5rem;
}

/*
  Luz en lugar de caja (decisión del 2026-09-05). La sección dejó de pintarse
  como tarjeta: el texto se apoya en un resplandor radial detrás de todo el
  ancho, el mismo recurso que ya usa .hero::before. Va en un pseudo-elemento y
  no en un background de la sección —ver el comentario de .home__section: un
  fondo ahí tapa el lienzo de partículas.

  Las alphas (0.16 azul, 0.10 cian) son más altas que las del hero (0.07 /
  0.14) a propósito: acá el resplandor es la única estructura visual, y a la
  alpha del hero se perdía en el fondo. El segundo foco cae a 68%, el centro
  real de la columna del formulario en desktop; en columna única es sólo
  atmósfera.
*/
.contact::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: var(--z-behind);
  pointer-events: none;
  background:
    radial-gradient(ellipse 35% 45% at 68% 50%, rgba(0, 180, 220, 0.10), transparent 70%),
    radial-gradient(ellipse 60% 55% at 50% 50%, rgba(24, 82, 210, 0.16), transparent 70%);
}

/*
  Las clases `panel panel--…` siguen en el HTML (ui-consolidation.test.js las
  exige) y panel.css no se toca: lo comparten privacidad, notas y el extractor.
  La caja se anula desde acá, que vive en @layer features y le gana a
  components sin importar la especificidad. El padding se conserva para que
  las columnas no se muevan de donde estaban.
*/
.contact__panel {
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
  padding: 60px;
  display: flex;
  gap: 60px;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
}

/*
  Los campos conservan su caja —son inputs, la caja es su affordance— pero
  pierden dureza. El borde se suaviza redefiniendo el token que .field lee, y
  NO con border-color: una declaración directa desde esta capa le ganaría a
  :focus, [aria-invalid] y [data-match] de field.css, y el campo dejaría de
  marcarse en cian al enfocar y en rojo al fallar.
*/
.contact__field {
  --color-border: rgba(255, 255, 255, 0.12);
  background: rgba(30, 30, 30, 0.7);
}

.contact__info {
  flex: 1 1 40%;
  min-inline-size: 0;
}

.contact__title {
  font-size: 2.4rem;
  margin-bottom: 1rem;
  background: var(--gradient-heading);
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
}

.contact__description {
  font-size: 1.1rem;
  margin-bottom: 2rem;
  color: var(--color-text-muted);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.contact__form {
  flex: 1 1 50%;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-inline-size: 0;
}

.contact__row {
  display: flex;
  gap: 1rem;
}

.contact__row > * {
  min-inline-size: 0;
}

.contact__field-group {
  flex: 1;
  min-inline-size: 0;
}

.contact__submit {
  align-self: flex-start;
}

.footer {
  background-color: var(--color-background-deep);
  color: var(--color-text-muted);
  padding: 40px 20px;
  text-align: center;
  border-top: var(--border-subtle);
  font-size: 0.95rem;
  margin-top: 60px;
}

.footer__container {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-start;
  gap: 2.5rem;
  text-align: left;
}

.footer__logo {
  height: 50px;
  filter: brightness(1.2);
  margin-bottom: 0.5rem;
}

.footer__detail {
  margin: 0.3rem 0;
  overflow-wrap: anywhere;
}

.footer__link {
  color: var(--color-text-muted);
  text-decoration: none;
  transition: color 0.3s ease;
}

.footer__link:hover {
  color: var(--color-accent);
}

.footer__brand {
  flex: 1 1 240px;
}

.footer__column {
  flex: 0 1 160px;
  min-inline-size: 0;
  max-inline-size: 100%;
}

.footer__column--wide {
  flex-basis: 230px;
}

.footer__heading {
  font-size: 0.95rem;
  color: var(--color-text);
  margin-bottom: 0.75rem;
}

.footer__list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.footer__link--pending {
  color: var(--color-text-subtle);
  cursor: default;
}

.footer__bottom {
  margin-block-start: 2rem;
  padding-top: 1.5rem;
  border-top: var(--border-subtle);
  text-align: center;
}

.footer__copyright {
  color: var(--color-text-subtle);
  font-size: 0.8rem;
  line-height: 1.6;
}

.footer__copyright + .footer__copyright {
  margin-top: 0.3rem;
}

@media (max-width: 760px) {
  .contact__panel {
    padding: 30px;
    flex-direction: column;
    /* Sin esto, el flex-wrap:wrap de la regla base (pensado para la fila de
       desktop) combina con flex-basis:100% de los hijos —que en dirección
       columna es 100% de ALTO, no de ancho— y el segundo hijo termina
       envuelto en una columna aparte a la derecha, empujando scroll
       horizontal en vez de apilarse debajo. */
    flex-wrap: nowrap;
    gap: 40px;
    /* En columna, align-items rige el ANCHO. El flex-start de la regla base
       (pensado para alinear arriba en la fila de escritorio) encogía el
       formulario a su ancho intrínseco: 236px con 282 disponibles a 390px. */
    align-items: stretch;
  }

  .contact__info,
  .contact__form {
    flex: 1 1 100%;
  }

  .footer__container {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 2rem;
  }

  .footer__list {
    align-items: center;
  }

  .contact__row {
    flex-direction: column;
  }
}

@media (max-width: 360px) {
  .contact {
    padding-inline: 1rem;
  }

  .contact__panel {
    padding: 1.25rem;
  }

  .contact__title {
    font-size: 2rem;
    overflow-wrap: anywhere;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .hero__logo-stage,
  .hero__logo-stage::before,
  .hero__light-path--core,
  .hero__light-path--left,
  .hero__light-path--right,
  .carousel__track {
    animation: none;
  }

  .hero__logo {
    transition: none;
  }

  .services__card--visible {
    animation: none;
    opacity: 1;
    transform: none;
  }
}
}
