/* Zippy — hoja de estilo de index.html
   Solo clases propias con prefijo z-: no pisa utilidades de Tailwind.

   TODA la paleta vive en tres bloques, mismos ~43 tokens en los tres:
   1. :root          — paleta por defecto (oscura). Sirve de respaldo y es
                        la que gana si el navegador no entiende media queries.
   2. @media (prefers-color-scheme: light) — reemplaza :root cuando el
                        sistema pide claro y NO hay elección manual guardada.
   3. :root[data-tema="oscuro"] / [data-tema="claro"] — elección manual
                        (guardada en localStorage por zippy.js). Por
                        especificidad de atributo gana siempre sobre 1 y 2,
                        en los dos sentidos.
   Ningún color tiene su única definición dentro de un @media.

   No hay literales de color fuera de estos bloques, ni en esta hoja ni en
   el marcado, para que el tema se pueda tokenizar sin perseguir hex sueltos.
   Fuera de aquí solo quedan: el #000 de una máscara (es opacidad, no tema),
   el <meta name="theme-color"> y el favicon en data: URI de index.html —
   ambos resueltos en JS/HTML porque no aceptan var().

   --lane-mark es un token nuevo: las rayas de carril del asfalto (autopista
   y escena) necesitan quedar SIEMPRE claras sobre asfalto SIEMPRE oscuro,
   sin importar el tema — por eso se separó de --line-strong, que si cambia
   con el tema (borde de tarjetas/botones sobre superficies claras u
   oscuras). Compartir el mismo token entre ambos usos era lo que iba a
   dejar las rayas de carril "gris sucio" en claro. --surface-warn también
   es nuevo: el bloque ámbar de la póliza gana un fondo cálido propio en
   claro en vez de heredar el blanco genérico de las tarjetas. */

:root {
    color-scheme: dark;

    /* superficies */
    --bg: #020617;
    /* slate-950 */
    --surface: #0b1424;
    --surface-raised: #17233a;
    --surface-hover: #1e2b45;
    --surface-warn: #0b1424;

    /* escena del recorrido y asfalto */
    --road: #0a1220;
    --sky-1: #17293f;
    --sky-2: #101c31;
    --sky-3: #0e1a2d;

    /* líneas */
    --line: rgba(148, 163, 184, .14);
    --line-strong: rgba(148, 163, 184, .28);
    --line-road: rgba(148, 163, 184, .34);
    --line-top: rgba(148, 163, 184, .38);
    --line-warn: rgba(251, 191, 36, .28);
    --lane-mark: rgba(148, 163, 184, .28);
    --btn-outline: #55657e;
    --shade-inset: rgba(0, 0, 0, .9);

    /* texto — muted-dim/faint se aclararon frente al original (no pasaban
       4.5:1 contra --bg/--surface; verificado y corregido con el cálculo
       de contraste del navegador) */
    --txt: rgba(255, 255, 255, .9);
    --txt-soft: #cbd5e1;
    --muted: #94a3b8;
    --muted-dim: #8b9bb4;
    --muted-faint: #7e8ea8;
    --on-accent: #041022;

    /* acento */
    --accent: #60a5fa;
    --accent-soft: #93c5fd;
    --accent-2: #38bdf8;
    --amber: #fbbf24;

    /* carro de perfil (usa var(--accent): sigue el acento del tema solo) */
    --car-body: var(--accent);
    --car-glass: #0b1424;
    --car-tyre: #1e293b;
    --car-hub: #475569;
    --car-spoke: #94a3b8;
    --car-lamp: #fbbf24;

    /* Luces de los vehículos: encendidas de noche, apagadas de día. En el
       tema claro valen transparent y se apagan solas por la cascada, sin JS
       ni marcado duplicado. El rojo es la luz de un carro, no un color de
       estado de la interfaz: los avisos de la página siguen en ámbar. */
    --lamp-front: #fff3d1;
    --lamp-rear: #ef4444;
    --beam: #fff3d1;

    /* Azul de marca de Silky, el mismo que usa la app. No cambia con el tema:
       una marca no se repinta de noche. La etiqueta va en blanco por decisión
       del fundador: blanco sobre este azul da 3,52:1, que solo cumple como
       TEXTO GRANDE, así que .z-btn--silky fuerza 19 px en peso 700 (el umbral
       es 18,66 px en negrita). Si alguien baja ese tamaño, el botón deja de
       cumplir. El hover oscurece en vez de aclarar: aclararlo bajaría el
       contraste a 2,77:1. */
    --silky: #0092CA;
    --silky-hover: #007CAC;
    --on-silky: #ffffff;

    /* tonos de repintado de la tarjeta de latonería. Estables entre temas
       igual que --traffic-*: el escenario es asfalto en claro y en oscuro.
       Ninguno choca con el azul de marca ni con el ámbar de avisos. */
    /* destello del detailing: mismo criterio estable, la tarjeta es asfalto
       en los dos temas */
    --shine: #cfeaff;
    --shine-core: #ffffff;

    --paint-1: #e2e8f0;
    --paint-2: #b91c1c;
    --paint-3: #10b981;

    /* tráfico del hero — silueta de carros sobre el asfalto, que no
       cambia de tema; se dejan igual a propósito en los dos temas */
    --traffic-1: #334155;
    --traffic-2: #60a5fa;
    --traffic-3: #475569;
    --traffic-4: #38bdf8;

    /* ilustración cenital del acta — icono autocontenido, mismo criterio */
    --top-body: #162943;
    --top-glass: #0c1e35;
    --top-roof: #1e3a5c;
    --top-metal: #475569;

    /* wizard */
    --overlay: rgba(2, 6, 23, .88);
    --track: rgba(148, 163, 184, .2);

    /* No es color: decide si la autopista del hero se desvanece por los
       bordes. En claro se apaga (none) porque el degradado deja un borrón
       gris sucio contra el fondo claro; ahí corta a ras el overflow:hidden. */
    --highway-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    --highway-fade: transparent;

    --ease: cubic-bezier(.16, 1, .3, 1);
}

/* Tema claro por preferencia del sistema, sin elección manual guardada. */
@media (prefers-color-scheme: light) {
    :root {
        color-scheme: light;

        --bg: #f3f6fb;
        --surface: #ffffff;
        --surface-raised: #eef2f8;
        --surface-hover: #e3e9f2;
        --surface-warn: #fdf6ec;

        --sky-1: #bfe0fb;
        --sky-2: #dcedfb;
        --sky-3: #eef6fd;

        --line: rgba(15, 23, 42, .10);
        --line-strong: rgba(15, 23, 42, .18);
        --line-road: rgba(15, 23, 42, .30);
        --line-warn: rgba(180, 83, 9, .35);
        --lane-mark: rgba(203, 213, 225, .42);
        --btn-outline: #78869a;

        --txt: #0f172a;
        --txt-soft: #1e293b;
        --muted: #46566b;
        --muted-dim: #55677d;
        --muted-faint: #5c6b81;
        --on-accent: #f8fbff;

        --accent: #1d4ed8;
        --accent-soft: #2563eb;
        --accent-2: #0369a1;
        --amber: #92400e;

        --track: rgba(71, 85, 105, .3);

        --highway-mask: none;
        --highway-fade: var(--road);

        --lamp-front: transparent;
        --lamp-rear: transparent;
        --beam: transparent;

        /* Azul de marca de Silky, el mismo que usa la app. No cambia con el tema:
           una marca no se repinta de noche. La etiqueta va en tinta oscura y no
           en blanco porque blanco sobre este azul da 3,52:1 y no pasa 4.5:1;
           con la tinta oscura sube a 5,4:1 con texto de tamaño normal. */
        --silky: #0092CA;
        --silky-hover: #007CAC;
        --on-silky: #ffffff;
    }
}

/* Elección manual (zippy.js la guarda en localStorage): gana siempre sobre
   el sistema, en los dos sentidos, por especificidad de atributo. */
:root[data-tema="oscuro"] {
    color-scheme: dark;

    --bg: #020617;
    --surface: #0b1424;
    --surface-raised: #17233a;
    --surface-hover: #1e2b45;
    --surface-warn: #0b1424;

    --sky-1: #17293f;
    --sky-2: #101c31;
    --sky-3: #0e1a2d;

    --line: rgba(148, 163, 184, .14);
    --line-strong: rgba(148, 163, 184, .28);
    --line-road: rgba(148, 163, 184, .34);
    --line-warn: rgba(251, 191, 36, .28);
    --lane-mark: rgba(148, 163, 184, .28);
    --btn-outline: #55657e;

    --txt: rgba(255, 255, 255, .9);
    --txt-soft: #cbd5e1;
    --muted: #94a3b8;
    --muted-dim: #8b9bb4;
    --muted-faint: #7e8ea8;
    --on-accent: #041022;

    --accent: #60a5fa;
    --accent-soft: #93c5fd;
    --accent-2: #38bdf8;
    --amber: #fbbf24;

    --track: rgba(148, 163, 184, .2);

    --lamp-front: #fff3d1;
    --lamp-rear: #ef4444;
    --beam: #fff3d1;

    /* Azul de marca de Silky, el mismo que usa la app. No cambia con el tema:
       una marca no se repinta de noche. La etiqueta va en blanco por decisión
       del fundador: blanco sobre este azul da 3,52:1, que solo cumple como
       TEXTO GRANDE, así que .z-btn--silky fuerza 19 px en peso 700 (el umbral
       es 18,66 px en negrita). Si alguien baja ese tamaño, el botón deja de
       cumplir. El hover oscurece en vez de aclarar: aclararlo bajaría el
       contraste a 2,77:1. */
    --silky: #0092CA;
    --silky-hover: #007CAC;
    --on-silky: #ffffff;

    --highway-mask: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    --highway-fade: transparent;
}

:root[data-tema="claro"] {
    color-scheme: light;

    --bg: #f3f6fb;
    --surface: #ffffff;
    --surface-raised: #eef2f8;
    --surface-hover: #e3e9f2;
    --surface-warn: #fdf6ec;

    --sky-1: #bfe0fb;
    --sky-2: #dcedfb;
    --sky-3: #eef6fd;

    --line: rgba(15, 23, 42, .10);
    --line-strong: rgba(15, 23, 42, .18);
    --line-road: rgba(15, 23, 42, .30);
    --line-warn: rgba(180, 83, 9, .35);
    --lane-mark: rgba(203, 213, 225, .42);
    --btn-outline: #78869a;

    --txt: #0f172a;
    --txt-soft: #1e293b;
    --muted: #46566b;
    --muted-dim: #55677d;
    --muted-faint: #5c6b81;
    --on-accent: #f8fbff;

    --accent: #1d4ed8;
    --accent-soft: #2563eb;
    --accent-2: #0369a1;
    --amber: #92400e;

    --track: rgba(71, 85, 105, .3);

    --lamp-front: transparent;
    --lamp-rear: transparent;
    --beam: transparent;

    /* Azul de marca de Silky, el mismo que usa la app. No cambia con el tema:
       una marca no se repinta de noche. La etiqueta va en blanco por decisión
       del fundador: blanco sobre este azul da 3,52:1, que solo cumple como
       TEXTO GRANDE, así que .z-btn--silky fuerza 19 px en peso 700 (el umbral
       es 18,66 px en negrita). Si alguien baja ese tamaño, el botón deja de
       cumplir. El hover oscurece en vez de aclarar: aclararlo bajaría el
       contraste a 2,77:1. */
    --silky: #0092CA;
    --silky-hover: #007CAC;
    --on-silky: #ffffff;

    --highway-mask: none;
    --highway-fade: var(--road);
}

/* ---------- reset y utilidades: lo que antes traía Tailwind ----------
   La página cargaba https://cdn.tailwindcss.com. Eso NO es una hoja de
   estilo: es un compilador que se descarga como JavaScript, lee el DOM y
   fabrica las reglas en el momento. En el teléfono del fundador no llegaba a
   correr —enlaces subrayados y azules, el header descolgado en dos filas—
   mientras que en el escritorio, con red buena, siempre llegaba a tiempo. De
   ahí que el mismo ancho se viera bien en el navegador y roto en el móvil.

   El propio Tailwind avisa de que ese CDN no es para producción. Se ha
   quitado: el reset (esto) y las 84 utilidades que usa el marcado (al final
   de la hoja) están escritas a mano aquí. La página ya no depende de ninguna
   llamada externa para maquetarse; lo único que queda fuera es la
   tipografía. */

*,
::before,
::after {
    box-sizing: border-box;
}

body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
figure,
blockquote,
dl,
dd,
hr,
pre {
    margin: 0;
}

/* Los títulos toman su tamaño de .z-title / .z-h2 / .z-svc__name o de las
   utilidades; sin esto, un <h3> suelto sale con el tamaño del navegador. */
h1,
h2,
h3,
h4,
h5,
h6 {
    font-size: inherit;
    font-weight: inherit;
}

ul,
ol {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Este era el fallo más visible en el teléfono: sin esto el navegador pinta
   los enlaces subrayados y en su azul de origen. */
a {
    color: inherit;
    text-decoration: inherit;
}

b,
strong {
    font-weight: bolder;
}

img,
svg,
video,
canvas,
iframe,
embed,
object {
    display: block;
    vertical-align: middle;
}

img,
video {
    max-width: 100%;
    height: auto;
}

button,
input,
optgroup,
select,
textarea {
    font-family: inherit;
    font-size: 100%;
    font-weight: inherit;
    line-height: inherit;
    color: inherit;
    margin: 0;
    padding: 0;
}

button,
select {
    text-transform: none;
}

button,
[type='button'],
[type='reset'],
[type='submit'] {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
    border: 0;
}

button,
[role='button'] {
    cursor: pointer;
}

:disabled {
    cursor: default;
}

[hidden] {
    display: none;
}

/* ---------- iconos ----------
   Eran lucide desde unpkg. Ahora son <symbol> dentro del propio HTML, y el
   trazo se define aquí: fill, stroke, grosor y remates se heredan hacia
   dentro del <use>, así que el mismo dibujo sirve a todos los tamaños sin
   duplicar geometría. Una llamada externa menos que puede fallar. */

.z-icon {
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.z-icon--fino {
    stroke-width: 1.6;
}

html {
    scroll-behavior: smooth;
    line-height: 1.5;
    /* Safari en iOS y Chrome en Android agrandan por su cuenta los párrafos
       de los bloques que consideran anchos. Eso descuadra la maqueta en el
       teléfono aunque en el escritorio a ancho estrecho se vea perfecta, y
       es lo que hacía que dos botones con la misma caja midieran distinto.
       100% = respeta el tamaño que pide la hoja. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    background-color: var(--bg);
    color: var(--txt);
    -webkit-font-smoothing: antialiased;
    /* Con viewport-fit=cover el lienzo llega al borde físico de la pantalla.
       En vertical estos valores son 0 y no cambia nada; en horizontal, sobre
       un teléfono con muesca, evitan que el texto se meta debajo de ella. */
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
}

/* Tailwind pinta text-white a blanco puro; en fondo tan oscuro quema. */
.text-white {
    color: var(--txt);
}

::selection {
    background: var(--accent);
    color: var(--on-accent);
}

.z-sprite {
    position: absolute;
    width: 0;
    height: 0;
    overflow: hidden;
}

/* ---------- tipografía y bloques base ---------- */

.z-eyebrow {
    font-size: .75rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: var(--accent);
    font-weight: 600;
}

.z-eyebrow--warn {
    color: var(--amber);
}

.z-title {
    font-size: clamp(2.1rem, 8.5vw, 4.5rem);
    line-height: 1.03;
    letter-spacing: -.035em;
    font-weight: 600;
}

.z-h2 {
    font-size: clamp(1.7rem, 5.5vw, 3rem);
    line-height: 1.08;
    letter-spacing: -.03em;
    font-weight: 600;
}

.z-lead {
    color: var(--muted);
    font-weight: 300;
    line-height: 1.65;
    font-size: clamp(1rem, 3.6vw, 1.2rem);
}

.z-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 20px;
}

.z-card--warn {
    background: var(--surface-warn);
    border-color: var(--line-warn);
}

.z-rule {
    border-top: 1px solid var(--line);
}

.z-ink-warn {
    color: var(--amber);
}

/* Botones sólidos, nunca fantasma. */
/* Todas las variantes comparten caja: mismo alto, mismo tipo y un borde de
   1 px aunque sea transparente, para que pasar una a solo borde no la haga
   1 px más alta que la otra. El min-height evita que un icono más grande
   estire un botón y no el de al lado. */
.z-btn {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .6rem;
    min-height: 3.4rem;
    padding: .8rem 1.6rem;
    border: 1px solid transparent;
    border-radius: 100px;
    font-weight: 700;
    font-size: 1.1875rem;
    line-height: 1;
    transition: transform .25s var(--ease), background-color .25s;
    text-align: center;
}

.z-btn:active {
    transform: scale(.97);
}

.z-btn--primary {
    background: var(--accent);
    color: var(--on-accent);
}

/* En un táctil no existe el puntero: el navegador aplica :hover al tocar y
   lo deja pegado hasta que tocas otra cosa. Todo lo de hover va dentro de
   @media (hover: hover) para que en el teléfono sencillamente no exista. */
@media (hover: hover) {
    .z-btn--primary:hover {
        background: var(--accent-soft);
    }
}

/* Botón de solo borde. El borde lleva token propio porque --line-strong es
   para separar superficies y en claro se queda por debajo de 3:1 contra el
   fondo, que es el mínimo para el contorno de un control. */
.z-btn--ghost {
    background: transparent;
    color: var(--txt);
    border: 1px solid var(--btn-outline);
}

@media (hover: hover) {
    .z-btn--ghost:hover {
        background: var(--surface-raised);
    }
}

/* Segundo CTA de igual peso que el primario: mismo tamaño, igual de sólido,
   distinto color. No es un botón de repuesto. */
.z-btn--silky {
    background: var(--silky);
    color: var(--on-silky);
    /* La onda se recorta al radio del botón y la etiqueta va por encima. */
    position: relative;
    overflow: hidden;
}

/* Al pulsar crece, no encoge: es la variante de marca. */
.z-btn--silky:active {
    transform: scale(1.03);
}

.z-btn--silky .z-btn__label,
.z-btn--silky .z-silky-icon {
    position: relative;
    z-index: 1;
}

/* Un solo nodo por botón, reutilizado en cada toque. Nace donde cayó el dedo:
   zippy.js pasa la posición por --rx/--ry. Solo se anima transform y opacity. */
.z-ripple {
    position: absolute;
    left: var(--rx, 50%);
    top: var(--ry, 50%);
    width: 14px;
    height: 14px;
    border-radius: 50%;
    /* Era el único #hex suelto de la hoja. La onda solo existe en el botón de
       Silky, así que es exactamente el mismo blanco de su etiqueta. */
    background: var(--on-silky);
    opacity: 0;
    pointer-events: none;
    transform: translate(-50%, -50%) scale(0);
    z-index: 0;
}

.z-ripple.is-on {
    animation: z-ripple .58s ease-out;
}

@keyframes z-ripple {
    from {
        opacity: .42;
        transform: translate(-50%, -50%) scale(0);
    }

    to {
        opacity: 0;
        transform: translate(-50%, -50%) scale(28);
    }
}

@media (hover: hover) {
    .z-btn--silky:hover {
        background: var(--silky-hover);
    }
}

/* Logo de Silky: alto fijo, ancho por proporción, y sigue al tema por
   currentColor porque va inline como <symbol>, no como imagen. */
/* El viewBox del símbolo se recortó a la caja óptica del dibujo (era
   736x1024 con el trazo ocupando solo 415x433 en el centro), así que esta
   altura es la del logo de verdad y no la de un marco medio vacío. */
.z-silky-icon {
    height: 1.45rem;
    width: auto;
    flex: none;
    margin-right: .1rem;
}

/* A 375 px el header ya lleva interruptor de tema y botón de WhatsApp: aquí
   el de Silky queda como icono solo, con su aria-label. Desde sm, con texto. */
/* En el header el botón va compacto; a 375 px la etiqueta se queda en
   "Silky" porque con 19 px en negrita el texto largo no cabe junto al logo
   y al interruptor de tema. */
.z-silky-btn {
    gap: .45rem;
    min-height: 2.6rem;
    padding: .45rem .95rem;
}

/* El del header no se ve al cargar: entra cuando el del hero sale de pantalla
   (zippy.js lo decide con un IntersectionObserver sobre el del hero).

   Se oculta con visibility, no solo con opacity, por dos motivos: así deja de
   ser enfocable con Tab y de existir para un lector de pantalla, y además
   sigue ocupando su caja, con lo que el header nunca se recoloca al entrar o
   salir el botón — el logo y el interruptor no se mueven ni un pixel.

   El truco del retardo en `visibility` deja que la salida sea una transición
   y no un corte: al aparecer cambia al instante, al irse espera a que termine
   el desvanecido. */
.z-silky-btn--header {
    opacity: 0;
    visibility: hidden;
    transform: translate3d(0, -8px, 0);
    transition: opacity .35s var(--ease), transform .35s var(--ease), visibility 0s linear .35s;
}

.z-silky-btn--header.is-in {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition: opacity .35s var(--ease), transform .35s var(--ease), visibility 0s;
}

.z-btn--sm {
    min-height: 2.5rem;
    padding: .5rem 1.1rem;
    font-size: .875rem;
    font-weight: 600;
}

/* Utilidades de tinta: el marcado nombra el papel, no el color. */
.z-ink-soft {
    color: var(--txt-soft);
}

.z-ink-muted {
    color: var(--muted);
}

.z-ink-dim {
    color: var(--muted-dim);
}

.z-ink-faint {
    color: var(--muted-faint);
}

.z-ink-accent {
    color: var(--accent-2);
}

.z-link-inline {
    color: var(--accent);
    text-decoration: underline;
    text-underline-offset: 3px;
    transition: color .25s;
}

@media (hover: hover) {
    .z-link-inline:hover {
        color: var(--accent-soft);
    }
}

.z-link-dim {
    color: var(--muted-dim);
    transition: color .3s;
}

@media (hover: hover) {
    .z-link-dim:hover {
        color: var(--txt);
    }
}

/* ---------- header ---------- */

.z-header {
    position: sticky;
    top: 0;
    z-index: 50;
    transition: background-color .35s, border-color .35s;
    border-bottom: 1px solid transparent;
}

.z-header.is-scrolled {
    background: var(--bg);
    background: color-mix(in srgb, var(--bg) 82%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom-color: var(--line);
}

/* 34 px -> 51 px por decisión del fundador (2026-08-24): el rayo es una
   diagonal muy fina y a 34 px casi no se leía. Medido antes de escalar: al
   contrario que la marca de Silky, este viewBox NO está medio vacío —el
   trazo ocupa el 94% del alto y el 88% del ancho de la caja—, así que no hay
   nada que recortar y subir el tamaño es lo único que da tinta de verdad.
   El header tiene alto fijo (h-16 / h-20, 64 y 80 px) y el logo va centrado:
   a 51 px sigue holgado dentro de los 64 px del móvil y no mueve nada. */
.z-logo {
    height: 51px;
    width: auto;
    fill: var(--txt);
}

.z-logo--footer {
    height: 3.5rem;
}

/* El logo del pie se mantiene pulsado; que no se seleccione ni salte el
   menú contextual al hacerlo en el teléfono. */
.z-footmark {
    /* El logo mide 26 px de ancho: sin este relleno el objetivo queda por
       debajo de lo que un dedo acierta. El relleno es transparente y los
       márgenes negativos lo compensan, así que nada se mueve a la vista. */
    padding: .4rem 1.4rem;
    margin-top: -.4rem;
    margin-left: -1.4rem;
    margin-right: -1.4rem;
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
    /* manipulation quita la espera del doble toque para hacer zoom, pero deja
       pasar el desplazamiento: si el usuario baja con el dedo apoyado aquí,
       el navegador se lleva el puntero y la pulsación se cancela sola. */
    touch-action: manipulation;
    transition: opacity .35s var(--ease);
}

/* La única señal, y solo cuando el dedo ya lleva 1,5 s encima: el logo sube
   de opacidad durante los 3,5 s que quedan y llega al máximo justo al
   cumplirse los cinco segundos. Antes de tocar no hay pista de nada. La
   clase la pone zippy.js; gana a la utilidad opacity-40 del marcado por
   especificidad, sin necesidad de !important. */
.z-footmark.is-holding {
    opacity: 1;
    transition: opacity 3.5s linear;
}

/* ---------- interruptor de tema ---------- */

.z-theme-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 100px;
    background: var(--surface-raised);
    border: 1px solid var(--line-strong);
    color: var(--txt);
    transition: background-color .25s, border-color .25s;
}

@media (hover: hover) {
    .z-theme-toggle:hover {
        background: var(--surface-hover);
    }
}

.z-theme-toggle__icon {
    width: 1.15rem;
    height: 1.15rem;
}

/* Por defecto (oscuro): luna visible, sol oculto. */
.z-theme-toggle__icon--sun {
    display: none;
}

.z-theme-toggle__icon--moon {
    display: block;
}

/* Sistema en claro, sin elección manual: se invierte. */
@media (prefers-color-scheme: light) {
    .z-theme-toggle__icon--sun {
        display: block;
    }

    .z-theme-toggle__icon--moon {
        display: none;
    }
}

/* Elección manual: gana siempre, en los dos sentidos. */
:root[data-tema="oscuro"] .z-theme-toggle__icon--sun {
    display: none;
}

:root[data-tema="oscuro"] .z-theme-toggle__icon--moon {
    display: block;
}

:root[data-tema="claro"] .z-theme-toggle__icon--sun {
    display: block;
}

:root[data-tema="claro"] .z-theme-toggle__icon--moon {
    display: none;
}

/* El haz nunca se anima: un faro que parpadea parece una avería. */
.z-beam {
    pointer-events: none;
}

/* ---------- reveal al hacer scroll ----------
   El estado por defecto es VISIBLE. Quien oculta es el JS: el script del
   <head> pone .js-anima en el <html> (y solo si no hay movimiento
   reducido), y es esa clase la que autoriza a esconder para revelar
   después. Si el JS no llega, si falla, o si el IntersectionObserver no
   dispara, no hay nada oculto y la página se lee entera.

   El escalonado se pide desde el marcado con transition-delay, en pasos de
   90 ms: más que eso y el usuario ya terminó de leer mientras la pieza
   sigue subiendo. */

.z-reveal {
    transition: opacity 1s var(--ease), transform 1s var(--ease);
}

:root.js-anima .z-reveal {
    opacity: 0;
    transform: translate3d(0, 26px, 0);
    will-change: transform, opacity;
}

/* Al terminar se suelta el will-change: dejar dos docenas de capas de
   composición vivas para siempre es memoria de vídeo regalada, y en un
   teléfono modesto se nota. */
:root.js-anima .z-reveal.is-visible {
    opacity: 1;
    transform: none;
    will-change: auto;
}

/* ---------- carro de perfil ---------- */

.z-car {
    display: block;
    color: var(--car-body);
}

.z-car .car-glass {
    fill: var(--car-glass);
}

.z-car .car-tyre {
    fill: var(--car-tyre);
}

/* Faro y stop del carro del recorrido. Es el más grande de la página, así
   que se permite un halo muy corto; en claro los tokens son transparent y
   el drop-shadow de una forma transparente no pinta nada, se apaga solo.
   Las luces van pegadas a la geometría, no a la pantalla: cuando el carro
   se voltea con scaleX(-1) al volver del taller, el faro sigue adelante. */
.z-car .car-lamp {
    fill: var(--lamp-front);
    filter: drop-shadow(0 0 1.5px var(--lamp-front));
}

.z-car .car-lamp--rear {
    fill: var(--lamp-rear);
    filter: drop-shadow(0 0 1.5px var(--lamp-rear));
}

.z-car .car-hub {
    transform-box: fill-box;
    transform-origin: center;
}

.z-car .car-hub__core {
    fill: var(--car-hub);
}

.z-car .car-hub__spoke {
    fill: var(--car-spoke);
}

/* ---------- autopista del hero ---------- */

.z-highway {
    position: relative;
    overflow: hidden;
    height: clamp(168px, 34vw, 210px);
    background: linear-gradient(180deg, var(--highway-fade), var(--road) 34%, var(--road));
    border-top: 1px solid var(--line);
    /* El #000 del degradado es opacidad, no color de tema. En claro el token
       vale none y el corte lo hace el overflow:hidden de arriba. */
    -webkit-mask-image: var(--highway-mask);
    mask-image: var(--highway-mask);
}

.z-highway__lane {
    position: absolute;
    left: 0;
    height: 2px;
    width: 200%;
    background: repeating-linear-gradient(90deg, var(--lane-mark) 0 34px, transparent 34px 74px);
    animation: z-lane 1.6s linear infinite;
}

.z-highway__lane--a {
    top: 46%;
}

.z-highway__lane--b {
    top: 76%;
    opacity: .55;
    animation-duration: 2.4s;
}

@keyframes z-lane {
    from {
        transform: translate3d(0, 0, 0);
    }

    to {
        transform: translate3d(-108px, 0, 0);
    }
}

.z-highway__car {
    position: absolute;
    /* El haz del faro se dibuja fuera del viewBox 0 0 140 56; sin esto lo
       corta el borde del SVG. La banda ya recorta con su overflow:hidden. */
    overflow: visible;
    left: 0;
    width: var(--w);
    height: auto;
    animation: z-drive var(--dur) linear var(--delay) infinite;
    will-change: transform;
}

/* Cada carril: tamaño, ritmo y tinte. Nada de esto vive en el marcado. */
.z-highway__car--1 {
    --w: 78px;
    --dur: 11s;
    --delay: -2s;
    --static-x: 6vw;
    top: 10%;
    color: var(--traffic-1);
}

.z-highway__car--2 {
    --w: 96px;
    --dur: 8s;
    --delay: -6s;
    --static-x: 42vw;
    top: 31%;
    color: var(--traffic-2);
}

.z-highway__car--3 {
    --w: 114px;
    --dur: 6s;
    --delay: -1s;
    --static-x: 72vw;
    top: 52%;
    color: var(--traffic-3);
}

.z-highway__car--4 {
    --w: 132px;
    --dur: 4.6s;
    --delay: -3.4s;
    --static-x: 20vw;
    top: 72%;
    color: var(--traffic-4);
}

@keyframes z-drive {
    from {
        transform: translate3d(-160px, 0, 0);
    }

    to {
        transform: translate3d(calc(100vw + 160px), 0, 0);
    }
}

/* ---------- recorrido: sección pegajosa ----------
   .z-journey es un carril alto y vacío; .z-journey__stage se queda pegado
   a la pantalla y lo recorre. El progreso del scroll dentro del carril es
   lo único que decide el paso activo y dónde está el carro (zippy.js).

   El alto del carril: una pantalla la ocupa el escenario pegado, y las dos
   restantes son el recorrido de verdad — media pantalla por paso. Con menos
   los cuatro pasos se atropellan en un gesto; con más la sección se hace
   eterna y el usuario se pierde. En un teléfono son ~3 deslizamientos para
   pasarla entera, en escritorio ~4 golpes de rueda por paso.

   Nada de scroll-jacking: aquí no hay preventDefault ni saltos forzados. El
   usuario baja a su ritmo y al acabarse el carril la escena se despega. */

.z-journey {
    position: relative;
}

/* Los pasos van SIEMPRE en una columna en el teléfono: una línea de tiempo de
   cuatro nodos no se lee en una rejilla de 2x2, y en columna la línea cae
   vertical por la izquierda, que es como se leen las líneas de tiempo en un
   teléfono. La rejilla de cuatro, con la línea en horizontal, entra en
   escritorio. Sin JS y con movimiento reducido se queda en columna a
   cualquier ancho. */
.z-steps {
    /* Las dos medidas de las que cuelga la línea, en variables para poder
       apretarlas de una pieza en pantallas bajas sin recalcular offsets a
       mano: el hueco de la rejilla (que es lo que mide cada tramo de más
       para llegar al nodo siguiente) y la altura del nodo dentro de su
       tarjeta (alineado con el numeral 01/02/03/04). */
    --hueco: .75rem;
    --nodo-y: 14px;
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--hueco);
    /* Carril de la línea, a la izquierda. Va aquí y no detrás de .js-anima
       porque en columna la línea existe SIEMPRE —también sin JS y con
       movimiento reducido—, y sin este hueco los nodos se salen por el borde
       izquierdo de la pantalla. Solo la rejilla de cuatro lo cambia por el
       carril de arriba. */
    padding-left: 1.75rem;
}

/* Lo pegajoso vive entero detrás de .js-anima. Sin JS, este carril mediría
   tres pantallas de vacío con un escenario clavado que nadie mueve: por eso
   el alto y el sticky solo existen cuando hay quien los conduzca. */
:root.js-anima .z-journey {
    /* En vh a propósito, no en dvh: es un separador que NO debe cambiar de
       tamaño cuando la barra del navegador entra y sale, que es justo lo que
       pasa mientras se baja. */
    height: 300vh;
}

:root.js-anima .z-journey__stage {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* vh primero como respaldo para navegadores sin unidades dinámicas.
       Manda svh, que es la altura CON la barra puesta: así la escena nunca
       queda cortada. Se descarta dvh justamente porque cambia en pleno
       recorrido y redimensionaría el escenario mientras el dedo baja. */
    min-height: 100vh;
    min-height: 100svh;
    /* Hueco para el header pegajoso, que va por encima. */
    padding-top: 5rem;
    padding-bottom: 1.5rem;
}

@media (min-width: 768px) {
    :root.js-anima .z-steps {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        /* El carril pasa de la izquierda a la franja de arriba. */
        padding-left: 0;
        padding-top: 2rem;
    }
}

/* ---------- escena del recorrido ---------- */

.z-scene {
    position: relative;
    overflow: hidden;
    border-radius: 20px;
    border: 1px solid var(--line);
    background: radial-gradient(130% 90% at 50% 0%, var(--sky-1) 0%, var(--sky-2) 62%, var(--sky-3) 100%);
    height: clamp(220px, 46vw, 310px);
}

.z-scene__place {
    position: absolute;
    bottom: 46%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .45rem;
    color: var(--muted);
    transition: color .4s, opacity .4s;
    opacity: .55;
}

.z-scene__place svg {
    width: clamp(44px, 11vw, 68px);
    height: auto;
}

.z-scene__place span {
    font-size: clamp(.62rem, 2.6vw, .78rem);
    letter-spacing: .1em;
    text-transform: uppercase;
    font-weight: 600;
    white-space: nowrap;
}

.z-scene__place.is-active {
    color: var(--accent);
    opacity: 1;
}

.z-scene__place--home {
    left: 4%;
}

.z-scene__place--shop {
    right: 4%;
}

.z-scene__road {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 42%;
    background: var(--road);
    border-top: 1px solid var(--line-road);
    box-shadow: inset 0 12px 24px -14px var(--shade-inset);
    overflow: hidden;
}

.z-scene__lane {
    position: absolute;
    top: 62%;
    left: 0;
    height: 2px;
    width: 200%;
    background: repeating-linear-gradient(90deg, var(--lane-mark) 0 26px, transparent 26px 58px);
}

/* Las rayas del carril se quedan quietas: ahora el que se desplaza por la
   escena es el carro, así que mover también el asfalto contaría dos veces
   el mismo movimiento. */

.z-scene__rail {
    position: absolute;
    left: 3%;
    right: 3%;
    top: 6%;
}

/* El avance: lo escribe el requestAnimationFrame a partir del scroll, y por
   eso NO lleva transición — una transición encima iría siempre un paso por
   detrás del dedo y se sentiría como un tirón. */
.z-scene__cab {
    transform: translate3d(var(--car-x, 0px), 0, 0);
    will-change: transform;
}

.z-scene__car {
    /* mismo motivo: el haz vive fuera del viewBox */
    overflow: visible;
    display: block;
    width: clamp(84px, 22vw, 132px);
    /* El volteo sí se anima: el carro tarda un momento en darse la vuelta en
       el taller, y hacerlo de golpe se lee como un salto. Va en una capa
       aparte del avance justamente para poder animarse solo él. */
    transform: scaleX(var(--car-dir, 1));
    transition: transform .45s var(--ease);
}

/* Las ruedas ruedan de verdad: el ángulo sale de los píxeles recorridos, no
   de un temporizador, así que si el scroll se para, se paran. */
.z-scene__car .car-hub {
    transform: rotate(var(--wheel, 0deg));
}

/* pasos */

/* La frase de cada paso vive dentro del propio paso: el marcado la tiene
   aunque el JS no llegue, y por defecto se VE. Solo se esconde cuando hay
   recorrido, porque entonces la misma frase la pinta el panel de abajo. */
.z-step__c {
    display: block;
    font-size: .9rem;
    font-weight: 300;
    line-height: 1.55;
    color: var(--muted);
    margin-top: .35rem;
}

:root.js-anima .z-step__c {
    display: none;
}

/* Y al revés con el panel: solo tiene sentido si hay un paso "actual". */
.z-journey .z-step__panel {
    display: none;
}

:root.js-anima .z-journey .z-step__panel {
    display: block;
}

/* ---------- línea de tiempo de los pasos ----------
   Cuatro nodos unidos por tres tramos. El relleno de cada tramo sale de la
   MISMA variable de progreso que mueve el carro: el rAF escribe --f en cada
   paso a partir del mismo p. No hay un segundo observador ni un segundo
   cálculo, así que línea y carro no se pueden desincronizar.

   Cada tramo se llena mientras su paso es el actual, de modo que un nodo se
   enciende exactamente en el mismo instante en que se enciende su tarjeta.
   Nada de dos señales para lo mismo en dos momentos distintos.

   La geometría vive en cada paso y no en un carril global: el tramo mide su
   propia tarjeta más el hueco de la rejilla, así que llega justo al centro
   del nodo siguiente sin que nadie mida nada por JS, y da igual cuántas
   columnas haya o que las tarjetas no midan lo mismo.

   Por defecto —sin JS o con movimiento reducido— la línea está COMPLETA y
   los cuatro nodos encendidos: nunca una barra a medio llenar que no mueve
   nadie. Es .js-anima quien autoriza a vaciarla para que la llene el scroll. */

.z-step__dot,
.z-step__seg {
    position: absolute;
    pointer-events: none;
}

/* Vertical. Es lo del teléfono, y también lo que se ve sin JS y con
   movimiento reducido, donde los pasos van en columna a cualquier ancho. */
.z-step__dot {
    left: -23px;
    top: var(--nodo-y);
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid var(--accent);
    transition: background-color .3s, border-color .3s, transform .3s var(--ease);
}

.z-step__seg {
    left: -18px;
    /* +6 px: el tramo arranca en el CENTRO del nodo, no en su borde. */
    top: calc(var(--nodo-y) + 6px);
    width: 2px;
    /* 100% es la caja de relleno de la tarjeta, 2 px menos que su caja de
       borde. Sumando esos 2 px y el hueco de la rejilla, el tramo termina
       exactamente en el centro del nodo siguiente. */
    height: calc(100% + 2px + var(--hueco));
    background: var(--line-strong);
    border-radius: 100px;
}

/* El último no une con nada. */
.z-step:last-child .z-step__seg {
    display: none;
}

.z-step__fill {
    display: block;
    width: 100%;
    height: 100%;
    border-radius: 100px;
    background: var(--accent);
    transform: scaleY(1);
    transform-origin: top;
}

/* Con recorrido, los nodos nacen apagados y los tramos vacíos. */
:root.js-anima .z-step__dot {
    background: var(--bg);
    border-color: var(--line-strong);
}

:root.js-anima .z-step.is-done .z-step__dot {
    background: var(--accent);
    border-color: var(--accent);
}

/* "Estás aquí": el nodo del paso actual crece un punto. Sin halos ni
   parpadeos — se nota sin gritar, que la página es sobria. */
:root.js-anima .z-step.is-active .z-step__dot {
    transform: scale(1.4);
}

:root.js-anima .z-step__fill {
    transform: scaleY(var(--f, 0));
}

/* Horizontal, en escritorio. Va detrás de .js-anima igual que la rejilla de
   cuatro: sin JS los pasos siguen en columna a cualquier ancho, y ahí la
   línea tiene que seguir cayendo vertical. */
@media (min-width: 768px) {
    :root.js-anima .z-step__dot {
        left: 50%;
        top: -24px;
        /* margen y no translate: el translate lo gasta el nodo activo. */
        margin-left: -6px;
    }

    :root.js-anima .z-step__seg {
        left: 50%;
        top: -19px;
        width: calc(100% + 2px + var(--hueco));
        height: 2px;
    }

    :root.js-anima .z-step__fill {
        transform: scaleX(var(--f, 0));
        transform-origin: left;
    }
}

/* Teléfono corto (un iPhone SE, un 8): con los pasos en columna el escenario
   pide ~744 px y ahí no hay tantos. Como lo pegajoso no puede desplazarse por
   dentro, lo que sobra no se ve nunca: se comería justo la frase del paso. Se
   aprieta todo el conjunto —hueco del header, escena, tarjetas y línea— en vez
   de dejar que recorte. Solo en teléfono: en un escritorio bajo no hace falta
   y sería un recorte gratis. */
@media (max-width: 767px) and (max-height: 760px) {
    :root.js-anima .z-journey__stage {
        padding-top: 3.5rem;
        padding-bottom: .5rem;
    }

    :root.js-anima .z-scene {
        height: clamp(180px, 46vw, 230px);
    }

    :root.js-anima .z-steps {
        --hueco: .5rem;
        --nodo-y: 10px;
    }

    :root.js-anima .z-step {
        padding: .55rem .9rem;
    }
}

/* Y por debajo de eso —un teléfono en horizontal— no hay recorrido que valga:
   la sección se despega y pasa a ser la misma lista que se ve sin JS. El JS lo
   detecta solo, porque el carril deja de tener recorrido, y pinta el estado
   final: línea completa y los cuatro nodos encendidos. */
@media (max-width: 900px) and (max-height: 585px) {
    :root.js-anima .z-journey {
        height: auto;
    }

    :root.js-anima .z-journey__stage {
        position: static;
        display: block;
        min-height: 0;
        padding-top: 0;
        padding-bottom: 0;
    }

    :root.js-anima .z-steps {
        grid-template-columns: 1fr;
        padding-left: 1.75rem;
        padding-top: 0;
    }

    :root.js-anima .z-step__c {
        display: block;
    }

    :root.js-anima .z-journey .z-step__panel {
        display: none;
    }

    :root.js-anima .z-step__dot {
        left: -23px;
        top: var(--nodo-y);
        margin-left: 0;
    }

    :root.js-anima .z-step__seg {
        left: -18px;
        top: calc(var(--nodo-y) + 6px);
        width: 2px;
        height: calc(100% + 2px + var(--hueco));
    }
}

.z-step {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: .3rem;
    align-items: flex-start;
    text-align: left;
    padding: .8rem .9rem;
    border-radius: 14px;
    border: 1px solid var(--line);
    background: transparent;
    transition: background-color .3s, border-color .3s;
    width: 100%;
}

@media (hover: hover) {
    .z-step:hover {
        border-color: var(--line-strong);
    }
}

.z-step__n {
    font-size: .7rem;
    font-weight: 700;
    letter-spacing: .12em;
    color: var(--muted-dim);
    transition: color .3s;
}

.z-step__t {
    font-size: clamp(.9rem, 3.4vw, 1rem);
    font-weight: 600;
    color: var(--muted);
    transition: color .3s;
}

.z-step.is-active {
    background: var(--surface-raised);
    border-color: var(--accent);
}

.z-step.is-active .z-step__n {
    color: var(--accent);
}

.z-step.is-active .z-step__t {
    color: var(--txt);
}

.z-step__panel {
    min-height: 3.2rem;
}

.z-step__panel p {
    animation: z-fade .5s var(--ease);
}

@keyframes z-fade {
    from {
        opacity: 0;
        transform: translate3d(0, 8px, 0);
    }
}

/* ---------- la red: una tarjeta por tipo de taller ----------
   Cada tarjeta reutiliza el mismo <symbol> del hero. El escenario es asfalto
   (--road) y los efectos solo usan tokens que NO cambian con el tema
   (--traffic-*, --paint-*, --car-*, --lane-mark), porque el asfalto se queda
   oscuro en claro y en oscuro.

   Las animaciones solo existen bajo .is-live, que zippy.js pone al entrar la
   tarjeta en pantalla y quita al salir: nada se anima fuera de la vista, y
   entran escalonadas para no arrancar las cinco de golpe.

   El estado de reposo de cada dibujo es el estado FINAL, no el inicial: el
   carro con un color, la carrocería limpia y el rin grande ya puesto. Así
   con prefers-reduced-motion la tarjeta sigue contando lo mismo sin moverse. */

.z-svc-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: .75rem;
    padding: 0;
}

/* Cinco tarjetas no llenan filas de 2 ni de 3: con flex centrado, la última
   fila queda centrada en vez de dejar un hueco a un lado. */
.z-svc {
    flex: 0 1 calc(50% - .375rem);
    list-style: none;
    border: 1px solid var(--line);
    border-radius: 18px;
    padding: .6rem .6rem .9rem;
    background: var(--surface);
}

@media (min-width: 768px) {
    .z-svc-grid {
        gap: 1rem;
    }

    .z-svc {
        flex-basis: calc(33.333% - .667rem);
    }
}

.z-svc__art {
    display: block;
    width: 100%;
    height: auto;
    border-radius: 12px;
    /* El escenario es asfalto en los dos temas, así que el tinte del carro se
       fija aquí con tokens estables; heredar color del texto lo volvería
       negro sobre negro en el tema claro. */
    color: var(--traffic-2);
}

.z-svc--wash .z-svc__art,
.z-svc--diag .z-svc__art {
    color: var(--traffic-4);
}

.z-svc--paint .z-svc__art {
    color: var(--paint-1);
}

.z-svc__stage {
    fill: var(--road);
}

.z-svc__ground {
    fill: var(--lane-mark);
}

.z-svc__name {
    margin-top: .7rem;
    text-align: center;
    font-size: clamp(.78rem, 3vw, .9rem);
    font-weight: 600;
    letter-spacing: -.01em;
}

/* --- mecánica: el carro sube en el elevador --- */

.z-svc__lift-post {
    fill: var(--traffic-3);
}

.z-svc__lift-arm {
    fill: var(--car-spoke);
}

.z-svc.is-live .z-svc__lift {
    animation: z-svc-lift 3.4s ease-in-out infinite;
}

@keyframes z-svc-lift {

    0%,
    12% {
        transform: translate3d(0, 0, 0);
    }

    45%,
    72% {
        transform: translate3d(0, -14px, 0);
    }

    100% {
        transform: translate3d(0, 0, 0);
    }
}

/* --- mantenimiento: la llave aprieta --- */

.z-svc__wrench {
    fill: var(--car-spoke);
    transform-box: fill-box;
    transform-origin: 50% 22%;
}

.z-svc.is-live .z-svc__wrench {
    animation: z-svc-wrench 2.4s ease-in-out infinite;
}

@keyframes z-svc-wrench {

    0%,
    100% {
        transform: rotate(-24deg);
    }

    50% {
        transform: rotate(12deg);
    }
}

/* --- latonería y pintura: la carrocería cambia de color ---
   El cuerpo del <symbol> se pinta con currentColor, así que basta animar
   `color` en el SVG de la tarjeta. Es una sola propiedad en un solo
   elemento pequeño; no toca layout. */

.z-svc.is-live .z-svc__art {
    animation: none;
}

.z-svc--paint.is-live .z-svc__art {
    animation: z-svc-paint 7.5s ease-in-out infinite;
}

@keyframes z-svc-paint {

    0%,
    30% {
        color: var(--paint-1);
    }

    36%,
    63% {
        color: var(--paint-2);
    }

    69%,
    96% {
        color: var(--paint-3);
    }

    100% {
        color: var(--paint-1);
    }
}

/* --- lavado y detailing: primero la espuma, después el brillo ---
   Las dos animaciones comparten duración para no desfasarse. La espuma es
   un grupo de círculos solapados con feGaussianBlur: se lee como una nube,
   no como bolitas sueltas. */

.z-svc__foam {
    fill: var(--car-spoke);
    opacity: 0;
}

.z-svc.is-live .z-svc__foam {
    animation: z-svc-foam 6s ease-in-out infinite;
}

@keyframes z-svc-foam {
    0% {
        opacity: 0;
        transform: translate3d(0, 5px, 0);
    }

    14%,
    36% {
        opacity: .85;
        transform: translate3d(0, 0, 0);
    }

    48%,
    100% {
        opacity: 0;
        transform: translate3d(0, 0, 0);
    }
}

/* El brillo ya no es un relleno plano: lo pinta el degradado #zshine, con
   un núcleo estrecho y muy claro y dos flancos que se disuelven. El núcleo
   se queda por debajo de opacidad 1 a propósito, para que el destello pase
   POR ENCIMA del color de la carrocería sin quemarlo. Va recortado a la
   silueta con clip-path, así que no se sale del carro. */
.z-svc__shine {
    opacity: 0;
}

.z-svc.is-live .z-svc__shine {
    animation: z-svc-shine 6s ease-in-out infinite;
}

@keyframes z-svc-shine {

    0%,
    50% {
        transform: translate3d(-46px, 0, 0);
        opacity: 0;
    }

    57%,
    93% {
        opacity: 1;
    }

    100% {
        transform: translate3d(76px, 0, 0);
        opacity: 0;
    }
}

/* --- lujos y accesorios: entra un rin más grande ---
   El rin del <symbol> no se puede tocar desde aquí (vive en el shadow tree
   del <use>), así que el grande se dibuja encima y lo tapa por completo al
   crecer. En reposo queda puesto, que es el estado que cuenta la historia. */

.z-svc__rim {
    transform-box: fill-box;
    transform-origin: center;
    opacity: 1;
}

.z-svc__rim-tyre {
    fill: var(--car-tyre);
}

.z-svc__rim-hub {
    fill: var(--car-spoke);
}

.z-svc__rim-bolt {
    fill: var(--road);
}

.z-svc.is-live .z-svc__rim {
    animation: z-svc-rim 4.2s ease-out infinite;
    animation-delay: var(--r, 0s);
}

@keyframes z-svc-rim {
    0% {
        transform: scale(.5);
        opacity: 0;
    }

    16% {
        transform: scale(1.08);
        opacity: 1;
    }

    24%,
    86% {
        transform: scale(1);
        opacity: 1;
    }

    96%,
    100% {
        transform: scale(.5);
        opacity: 0;
    }
}

/* --- diagnóstico: una línea escanea el carro --- */

.z-svc__scan {
    fill: var(--traffic-4);
    opacity: .65;
}

.z-svc.is-live .z-svc__scan {
    animation: z-svc-scan 2.8s ease-in-out infinite;
}

@keyframes z-svc-scan {
    0% {
        transform: translate3d(0, 0, 0);
        opacity: 0;
    }

    12%,
    82% {
        opacity: .9;
    }

    100% {
        transform: translate3d(0, 40px, 0);
        opacity: 0;
    }
}

/* ---------- acta: vista cenital ---------- */

.z-top__metal {
    fill: var(--top-metal);
}

.z-top__body {
    fill: var(--top-body);
    stroke: var(--line-top);
    stroke-width: 2;
}

.z-top__glass {
    fill: var(--top-glass);
}

.z-top__roof {
    fill: var(--top-roof);
}

.z-acta__dot {
    fill: var(--accent-2);
    transform-box: fill-box;
    transform-origin: center;
    animation: z-ping 4.8s ease-in-out infinite;
    animation-delay: var(--d, 0s);
    opacity: .28;
}

@keyframes z-ping {

    0%,
    72%,
    100% {
        opacity: .28;
        transform: scale(1);
    }

    8% {
        opacity: 1;
        transform: scale(1.7);
    }

    22% {
        opacity: .45;
        transform: scale(1);
    }
}

/* ---------- FAQ ---------- */

.z-faq {
    border-bottom: 1px solid var(--line);
}

.z-faq summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1.35rem 0;
    font-weight: 500;
    font-size: clamp(1rem, 3.8vw, 1.12rem);
}

.z-faq summary::-webkit-details-marker {
    display: none;
}

.z-faq__chev {
    flex: none;
    color: var(--muted-dim);
    transition: transform .3s var(--ease), color .3s;
}

.z-faq[open] .z-faq__chev {
    transform: rotate(180deg);
    color: var(--accent);
}

.z-faq__body {
    color: var(--muted);
    font-weight: 300;
    line-height: 1.7;
    padding-bottom: 1.4rem;
    max-width: 62ch;
    animation: z-fade .35s var(--ease);
}

/* ---------- encuesta flotante ---------- */

.z-fab {
    position: fixed;
    /* Las dos zonas seguras: abajo el indicador de inicio, a la derecha la
       muesca en horizontal. Hasta ahora estos cálculos no hacían nada porque
       al <meta viewport> le faltaba viewport-fit=cover. */
    right: calc(1rem + env(safe-area-inset-right, 0px));
    bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    z-index: 60;
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .8rem 1.15rem;
    border-radius: 100px;
    background: var(--surface-raised);
    border: 1px solid var(--line-strong);
    color: var(--txt);
    font-size: .875rem;
    font-weight: 600;
    opacity: 0;
    pointer-events: none;
    transform: translate3d(0, 14px, 0);
    transition: opacity .4s var(--ease), transform .4s var(--ease), background-color .25s;
}

.z-fab.is-ready {
    opacity: 1;
    pointer-events: auto;
    transform: none;
}

@media (hover: hover) {
    .z-fab:hover {
        background: var(--surface-hover);
    }
}

.z-fab__icon {
    color: var(--accent);
    flex: none;
}

.z-wiz {
    position: fixed;
    inset: 0;
    z-index: 200;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    /* Que el rebote del diálogo no arrastre la página de detrás. */
    overscroll-behavior: contain;
}

.z-wiz.is-open {
    display: flex;
}

.z-wiz__overlay {
    position: absolute;
    inset: 0;
    background: var(--overlay);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.z-wiz__card {
    position: relative;
    width: 100%;
    max-width: 30rem;
    /* vh como respaldo; manda svh. Con vh a secas, en iOS la barra del
       navegador se come el final de la tarjeta y el botón de WhatsApp queda
       fuera de la pantalla. */
    max-height: min(88vh, 44rem);
    max-height: min(88svh, 44rem);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 24px;
    padding: 1.5rem 1.35rem 1.35rem;
    animation: z-wiz-in .35s var(--ease);
}

@keyframes z-wiz-in {
    from {
        opacity: 0;
        transform: translate3d(0, 18px, 0) scale(.98);
    }
}

.z-wiz__close {
    position: absolute;
    top: .85rem;
    right: .85rem;
    color: var(--muted-dim);
    transition: color .25s;
}

@media (hover: hover) {
    .z-wiz__close:hover {
        color: var(--txt);
    }
}

.z-wiz__q {
    display: none;
}

.z-wiz__q.is-on {
    display: block;
    animation: z-fade .35s var(--ease);
}

.z-wiz__title {
    font-size: clamp(1.25rem, 5vw, 1.5rem);
    font-weight: 600;
    letter-spacing: -.02em;
    line-height: 1.2;
}

.z-wiz__hint {
    color: var(--muted-dim);
    font-size: .85rem;
    font-weight: 300;
    margin-top: .5rem;
}

.z-wiz__readout {
    margin: 1.6rem 0 .2rem;
    text-align: center;
}

.z-wiz__num {
    font-size: clamp(2.6rem, 13vw, 3.4rem);
    font-weight: 600;
    letter-spacing: -.04em;
    line-height: 1;
    color: var(--accent);
    display: block;
}

.z-wiz__num--word {
    font-size: clamp(1.5rem, 7vw, 2rem);
}

.z-wiz__num--money {
    font-size: clamp(1.7rem, 8.5vw, 2.3rem);
}

.z-wiz__unit {
    display: block;
    margin-top: .45rem;
    font-size: .72rem;
    letter-spacing: .16em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--muted-dim);
}

/* deslizador: pulgar grande, pensado para el dedo */
.z-wiz__slider {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 10px;
    margin: 1.4rem 0 .6rem;
    border-radius: 100px;
    outline: none;
    background: linear-gradient(90deg, var(--accent) 0 var(--fill, 50%), var(--track) var(--fill, 50%));
    touch-action: pan-y;
}

.z-wiz__slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: var(--accent);
    border: 4px solid var(--surface);
    cursor: pointer;
}

.z-wiz__slider::-moz-range-thumb {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--accent);
    border: 4px solid var(--surface);
    cursor: pointer;
}

.z-wiz__ends {
    display: flex;
    justify-content: space-between;
    font-size: .75rem;
    color: var(--muted-dim);
    font-weight: 300;
}

.z-wiz__nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 1.6rem;
}

.z-wiz__nav[hidden] {
    display: none;
}

.z-wiz__dots {
    display: flex;
    gap: .4rem;
}

.z-wiz__dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--track);
    transition: background-color .3s;
}

.z-wiz__dot.is-on {
    background: var(--accent);
}

.z-wiz__back {
    color: var(--muted-dim);
    font-size: .875rem;
    transition: color .25s, opacity .25s;
}

@media (hover: hover) {
    .z-wiz__back:hover {
        color: var(--txt);
    }
}

.z-wiz__back[hidden] {
    display: none;
}

/* resultado */

.z-wiz__result {
    text-align: center;
}

.z-wiz__big {
    font-size: clamp(3rem, 17vw, 4.5rem);
    font-weight: 600;
    letter-spacing: -.05em;
    line-height: 1;
    color: var(--accent);
}

.z-wiz__bigunit {
    margin-top: .5rem;
    font-size: .75rem;
    letter-spacing: .16em;
    text-transform: uppercase;
    font-weight: 600;
    color: var(--muted-dim);
}

/* La cuenta queda a la vista: quien quiera, la rehace con una calculadora. */
.z-wiz__math {
    margin-top: 1.35rem;
    padding: .9rem 1rem;
    border: 1px solid var(--line);
    border-radius: 14px;
    background: var(--surface-raised);
    text-align: left;
    display: grid;
    gap: .55rem;
    font-size: .85rem;
    font-weight: 300;
    line-height: 1.5;
    color: var(--muted);
}

.z-wiz__math li {
    list-style: none;
}

.z-wiz__math strong {
    color: var(--txt);
    font-weight: 600;
}

.z-wiz__note {
    color: var(--muted);
    font-weight: 300;
    line-height: 1.6;
    font-size: .95rem;
    margin-top: 1.25rem;
}

.z-wiz__fine {
    color: var(--muted-dim);
    font-weight: 300;
    font-size: .8rem;
    line-height: 1.55;
    margin-top: .85rem;
}

/* ---------- accesibilidad ---------- */

a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
    border-radius: 8px;
}

.z-skip {
    position: absolute;
    left: -9999px;
}

.z-skip:focus {
    left: 1rem;
    top: 1rem;
    z-index: 99;
    background: var(--accent);
    color: var(--on-accent);
    padding: .6rem 1rem;
    border-radius: 100px;
}

/* ---------- movimiento reducido ---------- */

@media (prefers-reduced-motion: reduce) {

    html {
        scroll-behavior: auto;
    }

    .z-highway__lane,
    .z-highway__car,
    .z-acta__dot {
        animation: none !important;
    }

    .z-acta__dot {
        opacity: .85;
    }

    /* los carros del hero quedan quietos y repartidos, legibles */
    .z-highway__car {
        transform: translate3d(var(--static-x), 0, 0);
    }

    /* Sin sección pegajosa y sin recorrido con el scroll: el carril se
       encoge a lo que ocupa el escenario, la escena se queda en su estado
       final (el carro ya volvió a tu puerta) y los cuatro pasos se leen uno
       debajo de otro, cada uno con su frase. El panel de abajo sobra: sería
       la misma frase repetida.

       Normalmente aquí no hay nada que deshacer, porque con movimiento
       reducido el script del <head> ni siquiera pone .js-anima y el recorrido
       nace ya como lista. Esto cubre el caso de que la preferencia se active
       con la página ya abierta. */
    :root.js-anima .z-journey {
        height: auto;
    }

    :root.js-anima .z-journey__stage {
        position: static;
        display: block;
        min-height: 0;
        padding-top: 0;
        padding-bottom: 0;
    }

    :root.js-anima .z-steps {
        grid-template-columns: 1fr;
        padding-left: 1.75rem;
        padding-top: 0;
    }

    /* La línea, completa y con los cuatro nodos encendidos: nunca una barra a
       medio llenar sin contexto. Y de vuelta a vertical, porque los pasos
       vuelven a la columna a cualquier ancho. */
    .z-step__dot,
    :root.js-anima .z-step__dot {
        left: -23px;
        top: var(--nodo-y);
        margin-left: 0;
        background: var(--accent);
        border-color: var(--accent);
        transform: none;
        transition: none;
    }

    :root.js-anima .z-step__seg {
        left: -18px;
        top: calc(var(--nodo-y) + 6px);
        width: 2px;
        height: calc(100% + 2px + var(--hueco));
    }

    :root.js-anima .z-step__fill {
        transform: none;
    }

    :root.js-anima .z-step__c {
        display: block;
    }

    :root .z-journey .z-step__panel {
        display: none;
    }

    .z-scene__cab,
    .z-scene__car {
        transition: none;
    }

    :root .z-reveal,
    :root.js-anima .z-reveal {
        opacity: 1;
        transform: none;
        transition: none;
        will-change: auto;
    }

    .z-fab {
        transition: opacity .2s;
        transform: none;
    }

    .z-ripple {
        display: none;
    }

    .z-btn--silky:active {
        transform: none;
        background: var(--silky-hover);
    }

    /* Sigue apareciendo y desapareciendo, pero sin recorrido ni desvanecido. */
    .z-silky-btn--header,
    .z-silky-btn--header.is-in {
        transition: none;
        transform: none;
    }

    .z-step__panel p,
    .z-faq__body,
    .z-wiz__card,
    .z-wiz__q.is-on {
        animation: none;
    }

    /* Las seis tarjetas de la red se congelan en su estado de reposo, que ya
       está dibujado para leerse sin movimiento: elevador abajo, espuma
       puesta, pistola a un lado, brillo tenue, pieza montada, línea visible. */
    /* Las cinco tarjetas de la red se congelan en su estado de reposo, que ya
       está dibujado para leerse sin movimiento: elevador abajo, carro con un
       color, carrocería limpia y rin grande puesto. */
    .z-svc.is-live .z-svc__lift,
    .z-svc.is-live .z-svc__wrench,
    .z-svc--paint.is-live .z-svc__art,
    .z-svc.is-live .z-svc__foam,
    .z-svc.is-live .z-svc__shine,
    .z-svc.is-live .z-svc__rim,
    .z-svc.is-live .z-svc__scan {
        animation: none !important;
    }
}


/* ---------- utilidades ----------
   Las 84 clases de Tailwind que usa el marcado, escritas a mano. Van al
   final, que es donde el CDN inyectaba las suyas, para no cambiar el orden
   de la cascada respecto a lo que ya estaba probado. Valores de la escala
   por defecto de Tailwind v3, y los cortes responsivos en sus 640 px (sm) y
   768 px (md), para no mover ni un pixel de la maqueta de escritorio.

   Si aparece una utilidad nueva en el marcado, hay que añadirla aquí: ya no
   hay nadie fabricándolas al vuelo. */

/* disposición */
.relative { position: relative; }
.flex { display: flex; }
.grid { display: grid; }
.hidden { display: none; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* huecos */
.gap-2 { gap: .5rem; }
.gap-2\.5 { gap: .625rem; }
.gap-3 { gap: .75rem; }
.gap-4 { gap: 1rem; }
.gap-6 { gap: 1.5rem; }
.gap-7 { gap: 1.75rem; }
.gap-12 { gap: 3rem; }
.gap-x-6 { column-gap: 1.5rem; }
.gap-y-3\.5 { row-gap: .875rem; }

/* el hueco entre hermanos de una lista */
.space-y-4 > :not([hidden]) ~ :not([hidden]) { margin-top: 1rem; }

/* tamaños */
.w-4 { width: 1rem; }
.w-5 { width: 1.25rem; }
.w-6 { width: 1.5rem; }
.w-full { width: 100%; }
.h-4 { height: 1rem; }
.h-5 { height: 1.25rem; }
.h-6 { height: 1.5rem; }
.h-16 { height: 4rem; }
.h-auto { height: auto; }
.max-w-lg { max-width: 32rem; }
.max-w-xl { max-width: 36rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-3xl { max-width: 48rem; }
.max-w-4xl { max-width: 56rem; }
.max-w-6xl { max-width: 72rem; }

/* márgenes */
.mx-auto { margin-left: auto; margin-right: auto; }
.mt-3 { margin-top: .75rem; }
.mt-4 { margin-top: 1rem; }
.mt-5 { margin-top: 1.25rem; }
.mt-6 { margin-top: 1.5rem; }
.mt-8 { margin-top: 2rem; }
.mt-9 { margin-top: 2.25rem; }
.mt-10 { margin-top: 2.5rem; }
.mt-14 { margin-top: 3.5rem; }
.mb-8 { margin-bottom: 2rem; }
.mb-10 { margin-bottom: 2.5rem; }

/* rellenos */
.p-6 { padding: 1.5rem; }
.p-7 { padding: 1.75rem; }
.px-5 { padding-left: 1.25rem; padding-right: 1.25rem; }
.py-20 { padding-top: 5rem; padding-bottom: 5rem; }
.pt-8 { padding-top: 2rem; }
.pt-14 { padding-top: 3.5rem; }
.pt-16 { padding-top: 4rem; }
.pt-20 { padding-top: 5rem; }
.pb-12 { padding-bottom: 3rem; }
.pb-20 { padding-bottom: 5rem; }

/* texto */
.text-center { text-align: center; }
.uppercase { text-transform: uppercase; }
.font-light { font-weight: 300; }
.font-semibold { font-weight: 600; }
.leading-relaxed { line-height: 1.625; }
.tracking-tight { letter-spacing: -.025em; }
.tracking-\[\.18em\] { letter-spacing: .18em; }
.text-xs { font-size: .75rem; line-height: 1rem; }
.text-sm { font-size: .875rem; line-height: 1.25rem; }
.text-base { font-size: 1rem; line-height: 1.5rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.opacity-40 { opacity: .4; }

/* sm: a partir de 640 px */
@media (min-width: 640px) {
    .sm\:inline { display: inline; }
    .sm\:flex-row { flex-direction: row; }
    .sm\:flex-1 { flex: 1 1 0%; }
    .sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .sm\:mx-auto { margin-left: auto; margin-right: auto; }
    .sm\:max-w-xl { max-width: 36rem; }
}

/* md: a partir de 768 px */
@media (min-width: 768px) {
    .md\:flex-row { flex-direction: row; }
    .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .md\:gap-3 { gap: .75rem; }
    .md\:gap-16 { gap: 4rem; }
    .md\:h-20 { height: 5rem; }
    .md\:mt-20 { margin-top: 5rem; }
    .md\:p-9 { padding: 2.25rem; }
    .md\:p-10 { padding: 2.5rem; }
    .md\:p-12 { padding: 3rem; }
    .md\:px-8 { padding-left: 2rem; padding-right: 2rem; }
    .md\:py-28 { padding-top: 7rem; padding-bottom: 7rem; }
    .md\:pt-24 { padding-top: 6rem; }
    .md\:pt-28 { padding-top: 7rem; }
    .md\:pb-28 { padding-bottom: 7rem; }
}
