/* ============================================================
   Universal-Log S.A. — Landing de pre-lanzamiento
   Concepto: "En tránsito". El sitio se presenta como un envío
   en curso. Colores extraídos del logo original al píxel.
   ============================================================ */

/* --- 1. Tipografías (self-hosted, sin llamadas a terceros) --- */
@font-face {
  font-family: "Archivo";
  /* format("woff2") a propósito y no "woff2-variations": Safari viejo
     descarta la declaración con el segundo y se queda sin la fuente.
     Los ejes variables se detectan igual desde el archivo. */
  src: url("/assets/fonts/archivo-var-latin.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("/assets/fonts/plexmono-400-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Plex Mono";
  src: url("/assets/fonts/plexmono-500-latin.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* --- 2. Tokens --- */
:root {
  /* Paleta de marca — medida sobre el logo original */
  --navy-900: #06213E;
  --navy-800: #0A3161;
  --navy-700: #0C447C;   /* azul de marca */
  --amber:    #F5A623;   /* ámbar de marca */
  /* Ámbar aclarado, solo para el punto viajero: al ir sobre el trazo —que ya
     es ámbar— con el color de marca se confundía con el fondo. */
  --amber-lo: #FFC65C;
  /* El ámbar de marca sobre el papel crema da 1,8:1 y desaparece. Este es
     el mismo tono tostado, para los pocos casos en que el acento va sobre
     fondo claro (hoy: el corazón de la firma). */
  --amber-ink: #C77800;
  --cream:    #FAF9F5;   /* fondo del logo */
  --gray:     #5F5E5A;

  /* Roles */
  --ink:         var(--navy-700);
  --ink-soft:    var(--gray);
  --rule:        rgb(12 68 124 / .13);
  --grid:        rgb(12 68 124 / .045);
  --panel-ink:   var(--cream);
  --panel-soft:  rgb(250 249 245 / .66);
  /* Texto tenue: .68 es el piso para cumplir AA (4.5:1) sobre el panel.
     Por debajo de eso el contraste cae a ~2.7:1. No bajar. */
  --panel-faint: rgb(250 249 245 / .68);
  /* Solo para trazos y bordes, nunca para texto. */
  --panel-line:  rgb(250 249 245 / .34);
  --panel-rule:  rgb(250 249 245 / .16);

  /* Tipografía */
  --sans: "Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Un solo lenguaje de movimiento: mismo easing, mismo tempo */
  --ease: cubic-bezier(.16, 1, .3, 1);
  --dur: 760ms;
  --beat: 2.4s;

  /* Ritmo espacial */
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --radius: clamp(14px, 2.2vw, 24px);

  /* Geometría del riel de la ruta, compartida por el trazo y el punto viajero */
  --rail-top: clamp(3rem, 6vw, 4.25rem);
  --route-gap: clamp(1rem, 3vw, 2.5rem);
}

/* --- 3. Reset acotado --- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, p, ol, li { margin: 0; padding: 0; }
ol { list-style: none; }
img { display: block; max-width: 100%; height: auto; }

/* --- 4. Base --- */
body {
  /* Cada medida en svh lleva antes su versión en vh o vw. Un navegador que
     no entienda svh descarta la declaración ENTERA —no solo esa unidad— y
     sin respaldo el gap se iría a cero y el diseño se apretaría. */
  min-height: 100vh;
  min-height: 100svh;
  font-family: var(--sans);
  color: var(--ink);
  background-color: var(--cream);
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 68px 68px;
  background-position: center;
  -webkit-font-smoothing: antialiased;
}

/* Viñeta suave: evita que el papel se lea plano */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background: radial-gradient(120% 90% at 50% 0%, transparent 40%, rgb(12 68 124 / .07) 100%);
}

/* El guion de "Universal-Log" es un punto de corte válido para el navegador
   y partía el nombre de la marca en dos líneas. */
.nowrap { white-space: nowrap; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -140%);
  z-index: 10;
  padding: .7rem 1.2rem;
  background: var(--navy-700);
  color: var(--cream);
  font: 500 .85rem/1 var(--mono);
  letter-spacing: .04em;
  border-radius: 0 0 8px 8px;
  text-decoration: none;
  transition: transform 220ms var(--ease);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

:focus-visible {
  outline: 2px solid var(--navy-700);
  outline-offset: 3px;
  border-radius: 3px;
}

/* --- 5. Estructura --- */
.sheet {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  max-width: 1160px;
  margin-inline: auto;
  padding: clamp(1.5rem, 4vw, 3rem) var(--gutter) clamp(1.25rem, 3vw, 2rem);
  padding: clamp(1.5rem, 4svh, 3rem) var(--gutter) clamp(1.25rem, 3svh, 2rem);
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* El aire vertical se mide en svh y no en vw: así la página entra completa
     en una laptop de 900px —donde antes sobraban 78px de scroll— y respira
     en un monitor grande, sin recortar nada a mano. */
  gap: clamp(1.75rem, 4vw, 3.25rem);
  gap: clamp(1.75rem, 3.6svh, 3.25rem);
}

main {
  display: grid;
  align-content: center;
  gap: clamp(2rem, 5vw, 3.5rem);
}

/* --- 6. Cabecera --- */
.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  padding-bottom: clamp(1rem, 2.5vw, 1.5rem);
  border-bottom: 1px solid var(--rule);
}
.masthead__logo {
  width: clamp(196px, 27vw, 292px);
  height: auto;
}

/* --- 7. El panel: la guía de embarque --- */
.consignment {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  padding: clamp(1.75rem, 4.2vw, 3.25rem) clamp(1.5rem, 4.2vw, 3.5rem) clamp(1.5rem, 3.4vw, 2.75rem);
  padding: clamp(1.75rem, 4.4svh, 3.25rem) clamp(1.5rem, 4.2vw, 3.5rem) clamp(1.5rem, 3.4svh, 2.75rem);
  color: var(--panel-ink);
  background: radial-gradient(110% 120% at 88% -10%, var(--navy-800) 0%, var(--navy-900) 62%);
  box-shadow:
    0 1px 2px rgb(6 33 62 / .16),
    0 18px 40px -18px rgb(6 33 62 / .42);
}

/* Misma trama que el papel, invertida */
.consignment::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgb(250 249 245 / .035) 1px, transparent 1px),
    linear-gradient(90deg, rgb(250 249 245 / .035) 1px, transparent 1px);
  background-size: 68px 68px;
  background-position: center;
}

/* Estado */
.status {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  font: 500 clamp(.66rem, 1.3vw, .78rem)/1 var(--mono);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--amber);
}
.status__dot {
  position: relative;
  width: .5em; height: .5em;
  border-radius: 50%;
  background: var(--amber);
}
.status__sep, .status__en { color: var(--panel-faint); }

/* Titular */
.headline {
  margin-top: clamp(1.1rem, 3vw, 1.75rem);
  font-weight: 700;
  font-size: clamp(2rem, 6.2vw, 4rem);
  line-height: 1.04;
  letter-spacing: -.032em;
}
.headline__line { display: block; }
/* La línea con el mensaje va en crema pleno y la de arranque atenuada:
   al revés se leía como texto deshabilitado justo en la frase que importa. */
.headline__line:first-child { color: var(--panel-soft); }

/* Bloque intermedio: texto a la izquierda, contacto a la derecha.
   El contacto vive DENTRO del panel para que el correo — la única
   conversión de la página — entre siempre en la primera pantalla. */
.brief {
  display: grid;
  gap: clamp(1.75rem, 4vw, 2.5rem);
  margin-top: clamp(1rem, 2.4vw, 1.4rem);
}

.lede {
  max-width: 52ch;
  font-size: clamp(.98rem, 1.75vw, 1.1rem);
  line-height: 1.6;
  color: var(--panel-soft);
  text-wrap: pretty;
}
.lede__en {
  /* Más ancho que el texto en español: es una fuente menor, y con el mismo
     límite de 52ch la última línea quedaba con dos palabras sueltas. */
  max-width: 64ch;
  font-size: clamp(.88rem, 1.57vw, .99rem);
  line-height: 1.6;
  color: var(--panel-faint);
  font-style: italic;
  text-wrap: balance;
}
/* Sin JavaScript esta celda queda vacía y no debe ocupar lugar. */
.contact__accion:empty { display: none; }

/* --- 8. La ruta --- */
.route {
  --stop-gap: 1.6rem;
  position: relative;
  margin-top: clamp(1.5rem, 4.2vw, 3rem);
  margin-top: clamp(1.5rem, 3.2svh, 3rem);
  padding-top: clamp(1.75rem, 4vw, 2.5rem);
  border-top: 1px solid var(--panel-rule);
  display: grid;
  gap: var(--stop-gap);
}

.route__stop {
  position: relative;
  padding-left: 2rem;
}

/* Riel vertical en mobile: sin él los tres hitos quedan sueltos
   y se pierde la idea de ruta, que es todo el concepto. */
.route__stop:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1.15em;
  height: calc(100% - 0.95em + var(--stop-gap));
  width: 1.5px;
}
.is-done::after {
  background: linear-gradient(180deg, var(--panel-soft), var(--amber));
}

/* El punto viajero en mobile, bajando por el riel vertical. Sin esto el
   anillo del hito destellaba solo, acusando una llegada que nunca pasaba.
   La caja mide exactamente el trecho —el alto del hito más el gap— y el
   punto va dibujado arriba, así translateY(100%) lo deja sobre el marcador
   siguiente, igual que la versión horizontal. */
.is-done::before {
  content: "";
  position: absolute;
  left: -3.5px;
  top: calc(.18em + 5.5px - 9px);
  width: 18px;
  height: calc(100% + var(--stop-gap));
  opacity: 0;
  pointer-events: none;
  background-repeat: no-repeat;
  background-image:
    radial-gradient(circle at 9px 9px, var(--amber-lo) 0 3.4px, transparent 3.9px),
    radial-gradient(circle at 9px 9px, var(--dot-halo) 0 8px, transparent 8.5px),
    radial-gradient(ellipse 4.5px 30px at 9px -12px, var(--dot-trail), transparent 72%);
}
.is-current::after {
  background-image: linear-gradient(180deg, var(--panel-line) 50%, transparent 0);
  background-size: 1.5px 7px;
}
.route__marker {
  position: absolute;
  left: 0; top: .18em;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--panel-soft);
}
.is-pending .route__marker {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--panel-line);
}
.is-current .route__marker {
  background: var(--amber);
  box-shadow: 0 0 12px 1px rgb(245 166 35 / .5);
}
/* El anillo del hito actual: acusa recibo cuando el punto viajero llega.
   No late por su cuenta —así eran tres cosas parpadeando a destiempo— sino
   que está sincronizado con la llegada, y el resto del ciclo permanece
   invisible. Deja de ser ruido y pasa a ser consecuencia. */
.is-current .route__marker::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1.5px solid var(--amber);
  opacity: 0;
}

.route__label {
  font: 500 clamp(.6rem, 1.1vw, .68rem)/1 var(--mono);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--panel-faint);
}
.is-current .route__label { color: var(--amber); }
.route__label span { opacity: .82; }

.route__title {
  margin-top: .6em;
  font-size: clamp(.96rem, 1.7vw, 1.08rem);
  font-weight: 500;
  line-height: 1.3;
  color: var(--panel-ink);
}
.is-pending .route__title { color: var(--panel-soft); }

.route__state {
  margin-top: .28em;
  font: 400 clamp(.72rem, 1.3vw, .82rem)/1.4 var(--mono);
  color: var(--panel-faint);
}
.is-current .route__state { color: var(--panel-soft); }

/* --- 9. Contacto --- */
.contact {
  padding-top: clamp(1.25rem, 3vw, 1.5rem);
  border-top: 1px solid var(--panel-rule);
}
.contact__label {
  font: 500 clamp(.66rem, 1.3vw, .74rem)/1 var(--mono);
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--amber);
}
/* La jerarquía va por peso y no por opacidad: atenuar estos grises
   los tira por debajo de AA. */
.contact__label span { font-weight: 400; color: var(--panel-faint); }

.contact__mail {
  display: inline-block;
  /* El padding vertical lleva el área táctil a 44px sin agrandar la
     tipografía: a 390px el texto solo daba 22px de alto y no llegaba al
     mínimo de 24px que pide WCAG 2.2 (2.5.8). Es la única conversión de
     la página, así que conviene que sobre. El margin baja a 0 porque el
     propio padding ya aporta la separación con la etiqueta. */
  margin-top: 0;
  padding-block: 11px;
  font-size: clamp(1.16rem, 3.1vw, 1.72rem);
  font-weight: 600;
  letter-spacing: -.02em;
  line-height: 1.2;
  color: var(--panel-ink);
  text-decoration: none;
  overflow-wrap: anywhere;
  background-image: linear-gradient(var(--amber), var(--amber));
  background-repeat: no-repeat;
  /* El subrayado se levanta el mismo padding, para quedar pegado al texto. */
  background-position: 0 calc(100% - 11px);
  background-size: 0% 2px;
  transition: background-size 420ms var(--ease), color 260ms var(--ease);
}
.contact__mail:hover,
.contact__mail:focus-visible {
  color: var(--amber);
  background-size: 100% 2px;
}

/* Copiar la dirección. Lo inyecta app.js: sin JavaScript no existe, así que
   nunca queda un botón que no hace nada.
   Va como anotación tipográfica y no como botón con caja: el lenguaje de esta
   pieza es editorial —mono en versalitas con tracking, igual que las etiquetas
   de la ruta— y un botón con borde y fondo lo rompe. Los 44px de área táctil
   salen del padding, que no se ve. */
.contact__copy {
  /* inline-flex para que el botón exponga su propia línea base: como flex,
     la grilla no podía alinearlo contra el texto en inglés. Vive en su
     celda, así que no necesita forzar salto de línea. */
  display: inline-flex;
  align-items: center;
  gap: .7em;
  margin-top: -.2rem;
  padding: 16px 0;
  font: 500 clamp(.6rem, 1.1vw, .66rem)/1 var(--mono);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--panel-faint);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color 260ms var(--ease);
}
.contact__copy:hover,
.contact__copy:focus-visible { color: var(--amber); }
.contact__copy[data-estado="ok"] { color: var(--amber); }

.contact__copy svg { width: 13px; height: 13px; flex: none; overflow: visible; }
/* El ícono pasa de "dos hojas" a un tilde cuando la dirección ya se copió. */
.contact__copy .ico-listo { display: none; }
.contact__copy[data-estado="ok"] .ico-copiar { display: none; }
.contact__copy[data-estado="ok"] .ico-listo { display: inline; }

/* El enlace de la 404 sí es una acción principal y conserva su caja. */
.pill {
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: 44px;
  padding: .5rem .95rem;
  font: 500 clamp(.64rem, 1.2vw, .7rem)/1 var(--mono);
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--panel-soft);
  background: transparent;
  border: 1px solid var(--panel-rule);
  border-radius: 7px;
  text-decoration: none;
  transition: color 260ms var(--ease), border-color 260ms var(--ease);
}
.pill:hover,
.pill:focus-visible {
  color: var(--amber);
  border-color: var(--amber);
}

/* Página 404: el estado no es un aviso de marca, va en neutro. */
.status--lost { color: var(--panel-soft); }
.status--lost .status__dot {
  background: var(--panel-soft);
  /* Sin el latido ámbar: acá el punto no señala actividad, señala un error. */
  animation: none;
}
.backhome { margin-top: clamp(2rem, 4.5vw, 3rem); }

/* --- 10. Pie y firma Auratech --- */
.colophon {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  /* En mobile los dos bloques se apilan, y alineados a la izquierda quedaban
     descolgados. Centrado cierra mejor la página. Desde 40rem vuelven a los
     extremos, que es donde tiene sentido cuando entran en una sola línea. */
  justify-content: center;
  text-align: center;
  gap: .5rem 1.5rem;
  padding-top: clamp(1rem, 2.5vw, 1.5rem);
  border-top: 1px solid var(--rule);
  font: 400 clamp(.68rem, 1.2vw, .76rem)/1.5 var(--mono);
  color: var(--ink-soft);
}
.colophon__brand { letter-spacing: .04em; }
.colophon__heart { color: var(--amber-ink); font-style: normal; }
.colophon__by a {
  color: var(--ink);
  font-weight: 500;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: border-color 260ms var(--ease);
}
.colophon__by a:hover,
.colophon__by a:focus-visible { border-bottom-color: var(--amber); }
.colophon__tm {
  font-size: .74em;
  vertical-align: .38em;
  letter-spacing: 0;
  margin-left: .05em;
}
.colophon__tag {
  display: block;
  font-size: .88em;
  letter-spacing: .05em;
}

/* Desde 56rem el texto y el contacto conviven en una fila:
   llena el vacío de la derecha del panel y sube el correo. */
@media (min-width: 56rem) {
  .brief {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "texto    contacto"
      "traducido accion";
    /* baseline es la clave: alinea cada fila por la línea base de su texto.
       Así el control de copiar queda a la altura exacta del texto en inglés
       sin depender de cuántas líneas ocupe ni de qué columna sea más alta.
       Con márgenes o alineación al fondo no salía, porque quién define la
       altura de la fila cambia según el ancho. */
    align-items: baseline;
    column-gap: clamp(2rem, 5vw, 4.5rem);
    row-gap: .55em;
  }
  .lede         { grid-area: texto; }
  .contact      { grid-area: contacto; }
  /* La fila de abajo se alinea por la ÚLTIMA línea, no por la primera:
     cuando el texto en inglés ocupa dos renglones, "baseline" a secas
     pegaba el control al primero. */
  .lede__en {
    grid-area: traducido;
    align-self: last baseline;
  }

  .contact,
  .contact__copy {
    justify-self: end;
    width: 100%;
    padding-top: 0;
    padding-left: clamp(1.5rem, 3vw, 2.5rem);
    border-top: 0;
    border-left: 1px solid var(--panel-rule);
  }

  /* El contenedor se disuelve para que el BOTÓN sea la celda de la grilla.
     Un div no propaga la línea base de lo que tiene adentro; el botón, que
     es inline-flex, sí. Con el div en el medio no había forma de alinearlo
     sin compensar a mano, y la compensación se rompía cada vez que el texto
     en inglés cambiaba de una línea a dos. */
  .contact__accion { display: contents; }
  .contact__copy {
    grid-area: accion;
    align-self: last baseline;
    margin-top: 0;
    padding-block: 16px;
  }
}

/* ============================================================
   11. Ruta en horizontal (desde 40rem)
   ============================================================ */
@media (min-width: 40rem) {
  .colophon {
    justify-content: space-between;
    text-align: left;
  }
  /* Acá el riel es horizontal: el punto que baja no corresponde. */
  .is-done::before { content: none; }

  .route {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--route-gap);
    padding-top: var(--rail-top);

    /* Distancia entre dos hitos: media columna + gap + media columna.
       La comparten el trazo recorrido, el punto viajero y el punteado. */
    --leg: calc(33.3333% + var(--route-gap) / 3);
    /* Centro del primer hito: media columna desde el borde. */
    --rail-x: calc(16.6667% - var(--route-gap) / 3);
  }
  /* Cada hito centrado sobre su texto: los marcadores caen en 1/6, 1/2 y
     5/6 del ancho, así el riel queda con el mismo aire a cada lado y
     termina justo sobre Destino. */
  .route__stop {
    padding-left: 0;
    padding-top: 2.15rem;
    text-align: center;
  }
  .route__marker { top: 0; left: 50%; margin-left: -5.5px; }
  /* Acá el riel es horizontal; el conector vertical del mobile sobra.
     Va con el mismo selector que lo crea: uno más corto pierde por
     especificidad y la línea se cuela sobre el texto. */
  .route__stop:not(:last-child)::after { content: none; }

  /* El riel punteado completo: nace en el hito 1 y muere en el hito 3.
     Cuelga del primer hito, así que sus % son relativos al ancho de una
     columna: dos tramos son 200% más los dos gaps. */
  .route__stop:nth-child(1)::after {
    content: "";
    position: absolute;
    /* Muere en el BORDE de los marcadores extremos, no en su centro: en el
       hito Destino, que es un círculo hueco, la línea se veía cruzarlo por
       dentro. Se descuenta el radio (5,5px) de cada punta. */
    left: calc(50% + 5.5px);
    top: 5.5px;
    width: calc(200% + 2 * var(--route-gap) - 11px);
    height: 1.5px;
    margin-top: -.75px;
    background-image: linear-gradient(90deg, var(--panel-line) 50%, transparent 0);
    background-size: 7px 1.5px;
  }

  /* Tramo recorrido: sólido, del origen al hito actual.
     Mismo ancho que el trecho del punto viajero, para que ambos
     terminen clavados sobre el segundo marcador. */
  .route::after {
    content: "";
    position: absolute;
    left: var(--rail-x);
    width: var(--leg);
    top: calc(var(--rail-top) + 5.5px);
    height: 1.5px;
    background: linear-gradient(90deg, var(--panel-soft), var(--amber));
    transform-origin: left center;
  }

  /* El punto en tránsito.
     La caja mide exactamente el trecho entre el hito 1 y el 2, y el punto
     está dibujado en su borde izquierdo: así translateX(100%) lo deja
     clavado en el hito actual, animando solo transform (sin reflow). */
  .route::before {
    content: "";
    position: absolute;
    /* El punto se dibuja a 44px del borde de su caja: los 44px de la
       izquierda son el lugar donde vive la estela, que si no quedaría
       recortada. left lo compensa para que arranque sobre el hito 1. */
    left: calc(var(--rail-x) - 44px);
    top: calc(var(--rail-top) + 5.5px);
    width: var(--leg);
    height: 18px;
    margin-top: -9px;
    pointer-events: none;
    background-repeat: no-repeat;
    background-image:
      radial-gradient(circle at 44px 50%, var(--amber-lo) 0 3.4px, transparent 3.9px),
      radial-gradient(circle at 44px 50%, rgb(255 198 92 / .26) 0 8px, transparent 8.5px),
      /* La estela es un resplandor claro corrido hacia atrás. Va en ámbar
         claro y no en el de marca porque el punto viaja sobre el trazo, que
         ya es ámbar: del mismo color no se distinguiría. */
      radial-gradient(ellipse 34px 4.5px at 22px 50%, rgb(255 198 92 / .5), transparent 72%);
  }
}

/* ============================================================
   12. Movimiento — un solo easing, un solo tempo
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {

  @keyframes rise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
  }
  @keyframes reveal {
    from { opacity: 0; clip-path: inset(0 0 105% 0); transform: translateY(.2em); }
    to   { opacity: 1; clip-path: inset(0 0 -25% 0); transform: none; }
  }
  @keyframes draw {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
  .masthead,
  .status,
  .lede,
  .contact,
  .colophon,
  .route__stop {
    animation: rise var(--dur) var(--ease) backwards;
  }
  .consignment     { animation: rise var(--dur) var(--ease) backwards 140ms; }
  .headline__line  { animation: reveal var(--dur) var(--ease) backwards; }

  .masthead { animation-delay: 60ms; }
  .status   { animation-delay: 300ms; }
  .headline__line:nth-child(1) { animation-delay: 380ms; }
  .headline__line:nth-child(2) { animation-delay: 470ms; }
  .lede     { animation-delay: 580ms; }
  .contact  { animation-delay: 670ms; }
  .route__stop:nth-child(1) { animation-delay: 900ms; }
  .route__stop:nth-child(2) { animation-delay: 990ms; }
  .route__stop:nth-child(3) { animation-delay: 1080ms; }
  .colophon { animation-delay: 1200ms; }

  @media (min-width: 40rem) {
    .route::after { animation: draw 1s var(--ease) backwards 780ms; }

    /* El punto recorre el trecho y se apaga JUSTO al entrar en el marcador,
       como absorbido. El transform solo tiene dos anclas —0% y 68%— así que
       con timing lineal la llegada cae exactamente en el 68% del ciclo:
       3,264s de los 4,8s. De ahí sale el retardo del anillo, que acusa esa
       llegada. Si el punto se apagara antes, el anillo estaría acusando algo
       que no pasó, y se ve desincronizado. */
    @keyframes travel {
      0%   { transform: translateX(0);    opacity: 0; }
      8%   {                              opacity: 1; }
      64%  {                              opacity: 1; }
      68%  { transform: translateX(100%); opacity: 0; }
      100% { transform: translateX(100%); opacity: 0; }
    }
    /* Lineal a propósito: es lo único del sitio que no usa --ease.
       Con el expo-out del resto, la posición se adelantaba al 89% del
       trecho en el 27% del tiempo y el punto quedaba pegado al marcador. */
    .route::before {
      animation: travel calc(var(--beat) * 2) linear infinite 1.9s;
      opacity: 0;
    }
  }

  /* Latidos: mismo compás para todo lo que respira */
  @keyframes beacon {
    0%        { box-shadow: 0 0 0 0 rgb(245 166 35 / .5); }
    70%, 100% { box-shadow: 0 0 0 7px rgb(245 166 35 / 0); }
  }
  .status__dot { animation: beacon var(--beat) var(--ease) infinite 1.6s; }

  /* Mismo recorrido que la versión horizontal, pero bajando. Comparte
     duración y retardo, así el anillo del hito queda sincronizado también
     en mobile sin tener que calcular nada aparte. */
  @keyframes travel-v {
    0%   { transform: translateY(0);    opacity: 0; }
    8%   {                              opacity: 1; }
    64%  {                              opacity: 1; }
    68%  { transform: translateY(100%); opacity: 0; }
    100% { transform: translateY(100%); opacity: 0; }
  }
  .is-done::before {
    animation: travel-v calc(var(--beat) * 2) linear infinite 1.9s;
  }

  /* El acuse del hito. Mismo período que el viaje, y el retardo es la suma
     exacta de: los 1,9s que el punto espera antes de arrancar + el 68% de
     los 4,8s que tarda en llegar = 5,164s. Al compartir período, quedan
     sincronizados en todos los ciclos, no solo en el primero. */
  @keyframes acuse {
    0%        { opacity: .8; transform: scale(1); }
    22%, 100% { opacity: 0;  transform: scale(2.7); }
  }
  .is-current .route__marker::after {
    animation: acuse calc(var(--beat) * 2) var(--ease) infinite 5.164s;
  }
}

/* ============================================================
   13. Reducir movimiento: todo llega, nada se mueve
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition-duration: 1ms !important;
  }
  .contact__mail { background-size: 100% 1px; }
  /* Los puntos viajeros solo tienen sentido en movimiento: sin animación
     quedarían como manchas sueltas sobre el riel. */
  .route::before,
  .is-done::before { display: none; }
}
