@charset "utf-8";
/* fonts.css se enlaza aparte en el <head> para no retrasar su descubrimiento. */

/* =========================================================
   World Color Car — sistema visual
   IMPRESIÓN INDUSTRIAL SUIZA. Un solo sustrato en todo el sitio: papel de
   documentación sin blanquear, tinta carbón, un acento rojo y nada más. El
   documento se compartimenta con filetes de 2px, no con sombras ni con aire.

   Cuatro reglas que no se saltan en ningún sitio:
   1. Cero radios. Todas las esquinas a 90°.
   2. Cero desenfoques: ni sombras difusas, ni backdrop-filter, ni
      translucidez. La elevación se dibuja como una segunda tinta desplazada.
   3. Cero degradados suaves. Los cortes de color son al 100%.
   4. Una familia por función: Archivo Black para titulares y cifras, IBM Plex
      Mono para TODO dato, Barlow para texto corrido.

   Accesibilidad del rojo de marca:
   --red (#D6472B) en superficies, iconos, texto grande y texto sobre tinta
   plena (4,55:1 sobre #0A0A0A). --red-btn para relleno de botones y --red-ink
   para texto pequeño sobre papel: ambos superan el 4,5:1 de AA sobre el papel,
   cosa que #D6472B no hace.
   ========================================================= */

:root {
  /* SUSTRATO — impresión industrial suiza.
     El fondo es papel de documentación sin blanquear: exactamente el papel de
     una orden de reparación o de un informe de perito, que es el documento que
     de verdad se cruza entre este taller y sus clientes.
     Ojo, no es la crema "cálida y suave" que quité antes: aquella iba con
     esquinas redondeadas y sombras difusas y leía a estudio de interiorismo.
     El mismo tono, con esquinas de 90°, filetes negros de 2px y datos en
     monoespaciada, lee a manual técnico. El material lo define el contexto,
     no el código hexadecimal. */
  --paper: #eae8e3;
  --paper-2: #f4f4f0;
  --surface: #fff;
  --ink: #0a0a0a;
  --ink-soft: #2a2a28;
  --muted: #5d5c57;
  --line: #c9c6be;
  --line-strong: #0a0a0a;

  /* Un único acento, como manda el sistema: rojo de aviación. Es el rojo de
     marca de World Color Car, que además ya cumplía ese papel. --red-ink es la
     versión que llega a 4,5:1 en cuerpos pequeños sobre papel. */
  --red: #d6472b;
  --red-btn: #be3a21;
  --red-ink: #a5301b;

  /* El mismo rojo subido de luz para trazo fino sobre tinta, donde --red se
     hunde (4,63:1 contra la plancha del despiece, y solo 1,6:1 contra el propio
     --red del relleno). Es el tono EXACTO de --red —de ahí el `from`, para que
     no puedan separarse— llevado a L 0,78.
     Por qué 0,78 y no más arriba: en sRGB este tono no da más luz sin perder
     saturación (a L 0,78 el techo es C 0,13; a L 0,84 ya solo C 0,09 y vira a
     rosa pálido). 0,78 es el último escalón que sigue leyéndose como rojo.
     La primera declaración es el respaldo para navegadores sin color relativo:
     es el mismo color, ya resuelto. */
  --red-pieza: #ff9780;
  --red-pieza: oklch(from var(--red) 0.78 0.13 h);

  /* Y el escalón de arriba, para la pieza que el usuario está señalando. Mismo
     tono otra vez, L 0,86 y C 0,077, que es también el máximo que admite sRGB a
     esa luz: 12,87:1 contra la plancha frente a los 9,64:1 del anterior, un 33 %
     más de luminancia.
     Que sea más pálido que --red-pieza no contradice lo de arriba, lo confirma:
     allí el problema era que ocho piezas a la vez en un rosa claro dejan de
     leerse como rojo. Aquí es UNA pieza, un instante, y con las otras siete al
     22 %: en ese contexto la falta de saturación se lee como encendida, no como
     descolorida. Se probó la rampa entera —0,82 / 0,84 / 0,86 / 0,88— sobre el
     condensador y el portón; a 0,88 ya empieza a virar a rosa y a 0,84 el salto
     apenas se nota. */
  --red-pieza-on: #ffbfb1;
  --red-pieza-on: oklch(from var(--red) 0.86 0.077 h);

  --wa: #25d366;
  --wa-ink: #08210f;

  /* Muestrario de pintura: el nombre de la empresa es World COLOR Car, así que
     esto no es decoración, es la marca. El sistema pide un solo acento, pero un
     encargo con identidad propia gana a la regla genérica. Se usa una vez por
     página, como banda estructural, nunca repartido de adorno. */
  --sw-1: #d6472b;
  --sw-2: #e9a13b;
  --sw-3: #2f7f6f;
  --sw-4: #2c5f8a;
  --sw-5: #6b4c9a;
  --sw-6: #0a0a0a;

  /* Macro: Archivo Black, para que las mayúsculas formen bloques macizos.
     Micro: IBM Plex Mono, para todo dato técnico (matrículas de sección,
     horarios, CIF, coordenadas). Texto corrido: Barlow, que ya estaba.
     Las tres son OFL y están autoalojadas. */
  --ff-display: "Archivo Black", "Arial Black", system-ui, sans-serif;
  --ff-mono: "IBM Plex Mono", ui-monospace, "SFMono-Regular", Consolas, monospace;
  --ff-text: "Barlow", system-ui, -apple-system, "Segoe UI", sans-serif;

  --gutter: clamp(1rem, 4vw, 2.5rem);
  --wrap: 1280px;

  /* No hay token de radio porque no hay radios: todas las esquinas van a 90°.
     Tampoco hay token de sombra, porque en impresión no existe el desenfoque
     gaussiano; donde hace falta elevación se usa un desplazamiento sólido
     (5px 5px 0), que es como se imprime una segunda tinta descuadrada.
     --rule es el grosor único de todos los filetes del sistema: cambiar este
     número cambia la densidad de todo el documento a la vez. */
  --rule: 2px;
  --header-h: 68px;

  /* Sitio que ocupa el botón flotante de WhatsApp: su separación al borde y su
     alto. Están aquí y no dentro de .wa-float porque el botón es `fixed` y se
     pone ENCIMA de lo que haya debajo, así que cualquier cosa anclada al pie de
     la ventana necesita saber cuánto ocupa para apartarse. El botón toma su
     alto de aquí y quien se aparta mide de aquí: escrito en dos sitios, los dos
     números se separarían el día que el botón cambie. */
  --wa-gap: clamp(0.75rem, 2vw, 1.5rem);
  --wa-h: 3.125rem;

  /* Curvas de aceleración.
     Las de serie (ease, ease-out) arrancan y frenan demasiado plano: el
     movimiento se nota "digital". Estas salen rápido y frenan largo, que es
     como se mueven las cosas físicas.
     --ease-out    entradas y salidas (aparecer, desaparecer, pulsar)
     --ease-in-out desplazamientos y cambios de forma entre dos estados
     --ease-drawer apertura del panel de menú (curva de cajón tipo iOS)
     Nunca ease-in en interfaz: hace que la respuesta parezca lenta. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--ff-text);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  overflow-x: hidden;
}

img,
svg,
iframe { max-width: 100%; }

img { height: auto; display: block; }

button,
input,
select,
textarea { font: inherit; color: inherit; }

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }

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

a { color: var(--red-ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }

a:hover { color: var(--ink); }

/* El foco es un filete azul de 3px con separación: ni redondeado ni difuso.
   Se queda en azul y no en el rojo de marca a propósito —tiene que distinguirse
   de los elementos que ya son rojos por diseño— y sobre papel da 6,4:1. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--sw-4);
  outline-offset: 2px;
  border-radius: 0;
}

/* ---------- tipografía ----------
   Tres familias, tres funciones, sin solaparse. La regla es literal:
   · Archivo Black → titulares y cifras. Es una familia de UN SOLO peso; pedirle
     700 obliga al navegador a engordarla él mismo y las curvas se emborronan,
     así que en todo el archivo va declarada a 400.
   · IBM Plex Mono → todo lo que es un DATO: sobretítulos, etiquetas, navegación,
     botones, migas, horarios, matrículas de sección. Si se puede leer en una
     hoja de ruta de taller, va en monoespaciada.
   · Barlow → texto corrido. Y nada más.
   Esa separación es la que hace que el sitio se lea como documentación técnica
   y no como una web con una tipografía grande. */
h1, h2, h3, .display {
  font-family: var(--ff-display);
  font-weight: 400;
  text-transform: uppercase;
  text-wrap: balance;
}

/* La escala es deliberadamente brutal: entre el titular y la etiqueta que tiene
   al lado hay un factor de ocho. Un impreso industrial no tiene medios tonos
   tipográficos, tiene rótulo y tiene letra pequeña. Y el tracking se cierra a
   -0.045em para que las mayúsculas se toquen y formen un bloque macizo, que es
   la mitad del efecto. */
h1, .h1 { font-size: clamp(2.9rem, 8.2vw, 6.25rem); letter-spacing: -0.045em; line-height: 0.86; }
h2, .h2 { font-size: clamp(2rem, 5vw, 3.5rem); letter-spacing: -0.035em; line-height: 0.9; }
h3, .h3 { font-size: clamp(1.1rem, 1.7vw, 1.35rem); letter-spacing: -0.02em; line-height: 1.08; }

h4 {
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.lead {
  font-size: clamp(1.05rem, 1.7vw, 1.25rem);
  line-height: 1.5;
  color: var(--ink-soft);
  max-width: 48ch;
}

/* Antitipo exacto del titular: el h2 se aprieta hasta que las letras chocan, el
   sobretítulo se abre hasta 0.16em. El ojo lee dos cosas distintas, no dos
   tamaños de lo mismo. Los corchetes son cita literal del sistema —así se
   rotula un campo en una consola— y llevan texto alternativo vacío para que un
   lector de pantalla no cante "corchete de apertura". */
.kicker {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--red-ink);
  margin-bottom: 1rem;
}

.kicker::before { content: "[" / ""; }
.kicker::after { content: "]" / ""; }

.section-head .kicker { margin-bottom: 0.8rem; }

/* Tira de telemetría: la línea de datos en monoespaciada que encabeza o cierra
   un bloque. Todo lo que aparece aquí es cierto —CIF, coordenadas del Plus Code,
   horario, año de constitución—. El sistema propone rellenar estas tiras con
   cadenas inventadas tipo "REV 2.6 / UNIT D-01"; aquí no, porque el encargo
   prohíbe que nada parezca un dato real sin serlo. Con los datos de verdad
   funciona igual de bien y además informa. */
.telemetry {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1.5rem;
  font-family: var(--ff-mono);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}

.telemetry b { font-weight: 600; color: var(--ink); }
.telemetry .hot { color: var(--red-ink); font-weight: 600; }

/* Estado del taller, calculado en el navegador (site.js, módulo horario).
   Sin JS el hueco no existe —:empty lo colapsa— y el horario de al lado se lee
   exactamente igual que antes: esto no es un adorno que se rompe, es un dato
   que aparece sólo si se puede calcular.

   El rojo lo pone .hot, la clase que ya marcaba lo importante en las tiras de
   telemetría. Antes teñía el horario, que es igual de rojo a las cuatro de la
   madrugada; ahora sólo se enciende cuando de verdad puedes llamar. El único
   acento del sistema pasa a significar algo que cambia. */
.estado-horario:empty { display: none; }

/* El punto separador sólo cuando hay algo delante, para que el mismo trozo de
   HTML valga detrás del horario o solo en su propia línea. Se queda apagado
   aunque el estado esté en rojo: el separador no es el dato.
   Los espacios van dentro de content y no en el margen porque en el árbol de
   texto el horario y el estado son elementos pegados —"17:30Abierto"—: sin un
   espacio de verdad, un lector de pantalla puede leerlos como una sola palabra
   al recorrer la línea. El margen sólo ajusta lo que ya se ve. */
.telemetry .estado-horario:not(:first-child)::before {
  content: " · ";
  margin-inline: 0.15em;
  color: var(--muted);
}

/* ---------- layout ---------- */
.container {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Toda sección lleva un filete negro de 2px arriba. No es adorno: es lo que
   convierte una pila de bloques en un documento compartimentado, que es la
   diferencia entre esto y una landing corriente. Y en la esquina superior
   izquierda del filete va una cruz de registro, la marca que llevan las planchas
   de imprenta para alinear las tintas. */
.section {
  position: relative;
  padding-block: clamp(3.25rem, 7vw, 6rem);
  border-top: var(--rule) solid var(--line-strong);
}

.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

.section--flush { border-top: 0; }

.section--paper { background: var(--paper); }
.section--surface { background: var(--surface); }

/* Plancha de tinta plena. No es "modo oscuro": es la misma hoja de papel con
   un área impresa en negro al 100% y el texto calado en blanco, que es un
   recurso central del impreso suizo. Por eso el negro es carbón puro (#0a0a0a)
   y no el gris azulado de interfaz que había antes. */
.section--dark {
  background: var(--ink);
  color: var(--paper-2);
  border-top-color: var(--ink);
}

.section--dark .lead,
.section--dark p { color: #c4c2bc; }

.section--dark .kicker { color: #ff8a6b; }

.section-head { max-width: none; margin-bottom: clamp(1.75rem, 3vw, 2.75rem); }

.section-head p { color: var(--muted); margin-top: 1rem; max-width: 58ch; }

.section--dark .section-head p { color: #c4c2bc; }

.grid { display: grid; gap: clamp(1rem, 2vw, 1.5rem); }

/* Cruces de registro en las dos esquinas superiores, justo bajo el filete. Se
   dibujan con dos gradientes de 2px en lugar de con elementos propios: no añaden
   un solo nodo al DOM y toman el color del bloque, así que funcionan igual sobre
   papel que sobre tinta plena. Van dentro de la sección, no a caballo del
   filete, para que la mitad de arriba no se pierda al cambiar de sustrato. */
.section > .container::before,
.section > .container::after {
  content: "";
  position: absolute;
  top: 10px;
  width: 11px;
  height: 11px;
  background:
    linear-gradient(currentcolor, currentcolor) center / 100% 2px no-repeat,
    linear-gradient(currentcolor, currentcolor) center / 2px 100% no-repeat;
  pointer-events: none;
}

.section > .container::before { left: var(--gutter); }
.section > .container::after { right: var(--gutter); }

.section--flush > .container::before,
.section--flush > .container::after { content: none; }

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: 0.5rem;
  z-index: 200;
  background: var(--ink);
  color: #fff;
  padding: 0.75rem 1rem;
  font-weight: 600;
  /* Se esconde con translateY y no con top: top es una propiedad de layout y
     animarla obliga al navegador a recalcular la posición en cada fotograma.
     transform va en el compositor. Se mantiene visible para el foco (nada de
     visibility:hidden, que impediría enfocarlo). */
  transform: translateY(-5rem);
  transition: transform 0.16s var(--ease-out);
}

.skip-link:focus { transform: translateY(0); color: #fff; }

/* ---------- muestrario de pintura (elemento de marca) ---------- */
.swatches {
  display: flex;
  height: 6px;
  width: 100%;
  overflow: hidden;
}

.swatches i { flex: 1; background: var(--sw-6); }
.swatches i:nth-child(1) { background: var(--sw-1); flex: 1.6; }
.swatches i:nth-child(2) { background: var(--sw-2); }
.swatches i:nth-child(3) { background: var(--sw-3); }
.swatches i:nth-child(4) { background: var(--sw-4); }
.swatches i:nth-child(5) { background: var(--sw-5); }
.swatches i:nth-child(6) { background: var(--sw-6); }

.swatches--tall { height: 10px; }

/* ---------- botones ----------
   Placa rectangular con filete de 2px y rótulo en monoespaciada. El botón es la
   pieza que más delata a una plantilla: en cuanto lleva esquina redondeada y
   degradado ya es "un botón de web". Aquí es una chapa serigrafiada. */
.btn {
  --btn-bg: var(--ink);
  --btn-fg: #fff;
  --btn-bd: var(--ink);
  --btn-plate: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.8rem 1.4rem;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border: var(--rule) solid var(--btn-bd);
  border-radius: 0;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  box-shadow: 0 0 0 var(--btn-plate);
  transition: transform 0.16s var(--ease-out), box-shadow 0.16s var(--ease-out),
    background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

/* El realce al pasar el ratón solo se aplica donde hay ratón de verdad. En una
   pantalla táctil el :hover se "pega" tras el toque y el botón se queda
   levantado hasta que tocas otra cosa.
   El realce ya no es un brillo: la chapa se despega y deja ver debajo una
   segunda tinta desplazada, que es exactamente el defecto de registro de una
   serigrafía mal alineada. Nada de desenfoque: el sistema lo prohíbe y además
   una sombra dura se lee mucho mejor sobre papel. */
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    color: var(--btn-fg);
    transform: translate(-3px, -3px);
    box-shadow: 5px 5px 0 var(--btn-plate);
  }
}

/* Respuesta al pulsar, la única señal táctil que tiene el usuario de que el
   botón ha recibido el toque. Va después del bloque :hover para ganarle.
   La chapa vuelve a apoyarse: el desplazamiento y la segunda tinta se anulan a
   la vez, así que el botón "se sienta" en 0,1s. */
.btn:active {
  transform: translate(0, 0);
  box-shadow: 0 0 0 var(--btn-plate);
  transition-duration: 0.1s;
}

.btn svg { width: 1.25em; height: 1.25em; flex: none; }

.btn--primary { --btn-bg: var(--red-btn); --btn-bd: var(--red-btn); --btn-plate: var(--ink); }

.btn--wa { --btn-bg: var(--wa); --btn-bd: var(--wa-ink); --btn-fg: var(--wa-ink); --btn-plate: var(--ink); }

.btn--ghost { --btn-bg: transparent; --btn-fg: var(--ink); --btn-bd: var(--line-strong); --btn-plate: var(--ink); }
.btn--ghost:hover { --btn-bg: var(--ink); --btn-fg: #fff; }

/* Sobre tinta plena la segunda tinta tiene que ser el papel, no el negro:
   una sombra negra sobre negro no existe. */
:is(.section--dark, .cta-final, .site-footer) .btn--ghost {
  --btn-fg: var(--paper-2);
  --btn-bd: var(--paper-2);
  --btn-plate: var(--red);
}

:is(.section--dark, .cta-final, .site-footer) .btn--ghost:hover { --btn-bg: var(--paper-2); --btn-fg: var(--ink); }
:is(.section--dark, .cta-final, .site-footer) .btn--primary { --btn-plate: var(--paper-2); }

.btn--sm { padding: 0.55rem 0.95rem; font-size: 0.75rem; letter-spacing: 0.11em; }
.btn--block { width: 100%; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.75rem;
}

.link-more {
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--red-ink);
  text-decoration: none;
  display: inline-flex;
  gap: 0.5rem;
  align-items: center;
  border-bottom: var(--rule) solid currentcolor;
  padding-bottom: 2px;
}

/* La flecha del sistema es ASCII, no una entidad tipográfica: ">>>" es cómo se
   marca una dirección en una consola y encaja con la monoespaciada. */
.link-more::after { content: ">>>" / ""; transition: transform 0.15s var(--ease-out); }

@media (hover: hover) and (pointer: fine) {
  .link-more:hover { color: var(--ink); }
  .link-more:hover::after { transform: translateX(4px); }
}

/* ---------- cabecera ----------
   Opaca y con filete negro de 2px. La cabecera translúcida con desenfoque que
   había antes es el tic visual de 2020 y además el sistema prohíbe la
   transparencia: en un impreso el papel no deja ver la página siguiente. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 90;
  background: var(--paper-2);
  border-bottom: var(--rule) solid var(--line-strong);
}

.site-header .bar {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: var(--header-h);
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  color: var(--ink);
  margin-right: auto;
  padding-block: 0.5rem;
}

/* El distintivo de la red Bosch Car Service. Es marca ajena: va tal y como la
   entregan —cuadrada, con su fondo blanco y sin recortar—, y no se recolorea
   para acercarla a la paleta del sitio. El blanco no sobra: forma parte del
   logotipo, y en el pie, sobre tinta, es justo la placa clara sobre la que
   Bosch pide que se vea su marca en fondos oscuros.

   El tamaño no es decorativo, es el mínimo al que se lee. A 36 px el rótulo
   "BOSCH Service" sale emborronado y el inducido del símbolo se cierra en una
   mancha; a 44 los dos resuelven. Y 44 caben: la cabecera mide 68 px y el logo
   deja 8 px de aire arriba y abajo. El archivo mide 176, que son 4x justos:
   nunca se amplía. width/height van también en el HTML para que la cabecera no
   dé un salto mientras carga. */
.logo__bosch {
  display: block;
  width: 44px;
  height: 44px;
  flex: none;
}

.logo__name {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: 1.2rem;
  line-height: 1;
  letter-spacing: -0.035em;
  text-transform: uppercase;
}

.logo__name span { color: var(--red-ink); }

.logo__sub {
  display: block;
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 4px;
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--ink);
  color: #fff;
  border: 0;
  border-radius: 0;
  padding: 0.65rem 0.9rem;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform 0.16s var(--ease-out);
}

.nav-toggle:active { transform: scale(0.97); transition-duration: 0.1s; }

.nav-toggle__box { display: grid; gap: 3px; width: 18px; }

/* Las tres barras se transforman en aspa: es un cambio de forma entre dos
   estados, así que lleva ease-in-out y no ease-out. */
.nav-toggle__box i {
  height: 2px;
  background: currentcolor;
  transition: transform 0.18s var(--ease-in-out), opacity 0.18s var(--ease-in-out);
}
.nav-toggle[aria-expanded="true"] .nav-toggle__box i:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__box i:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__box i:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

/* Un único menú en el HTML: en escritorio es una barra, en móvil el mismo
   contenedor se convierte en panel desplegable. */
.site-menu { display: contents; }

.site-nav ul {
  display: flex;
  align-items: center;
  gap: clamp(0.75rem, 1.8vw, 1.6rem);
}

.site-nav a {
  display: block;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.75rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  padding: 0.35rem 0;
  border-bottom: var(--rule) solid transparent;
}

.site-nav a:hover { border-bottom-color: var(--red); }
.site-nav a[aria-current] { border-bottom-color: var(--ink); color: var(--ink); }

.menu-actions { display: flex; align-items: center; gap: 0.6rem; }

.menu-actions .phone {
  display: inline-flex;
  flex-direction: column;
  line-height: 1.05;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: 0.02em;
}

.menu-actions .phone small {
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 3px;
}

/* En la cabecera el estado ocupa el renglón donde ponía "Llámanos", que encima
   de un número de teléfono no informaba de nada. No se añade un elemento nuevo
   porque en la barra no cabe: a 1024 px el hueco entre el logotipo y el menú es
   exactamente el gap del flex, cero. Ocupando un renglón que ya existía, la
   cabecera no crece ni un píxel en ningún ancho.
   Va en versión corta —ABIERTO / CERRADO— por lo mismo: "Cerrado · abre mañana
   a las 8:00" mide 198 px y la caja del teléfono da 80. La frase entera se lee
   en la tira de telemetría, que sí tiene sitio. */
.menu-actions .phone small.hot { color: var(--red-ink); font-weight: 600; }

@media (max-width: 1023px) {
  .site-menu {
    display: block;
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    background: var(--paper-2);
    border-bottom: var(--rule) solid var(--line-strong);
    padding: 1rem var(--gutter) 1.5rem;
    max-height: calc(100dvh - var(--header-h));
    overflow-y: auto;
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    /* Esta declaración es la del CIERRE: se aplica al volver al estado base. */
    transition: opacity 0.15s var(--ease-out), transform 0.15s var(--ease-out), visibility 0.15s;
  }

  /* Y esta la de la APERTURA. Entrada y salida no duran lo mismo a propósito:
     abrir el menú es un gesto deliberado y admite 240ms; cerrarlo es "quítate
     de en medio" y tiene que ser inmediato. */
  [data-nav-open="true"] .site-menu {
    transform: none;
    opacity: 1;
    visibility: visible;
    transition-duration: 0.24s;
    transition-timing-function: var(--ease-drawer);
  }

  .site-nav ul { display: block; }

  /* En el panel móvil los enlaces suben a Archivo Black: no son "un menú", son
     el índice del documento, y ocupan la pantalla entera como tal. */
  .site-nav a {
    padding: 0.7rem 0;
    font-family: var(--ff-display);
    font-weight: 400;
    font-size: 1.75rem;
    letter-spacing: -0.035em;
    border-bottom: 1px solid var(--line);
  }

  .site-nav a:hover,
  .site-nav a[aria-current] { border-bottom-color: var(--line); }

  .site-nav a[aria-current] { color: var(--red-ink); }

  .menu-actions {
    display: grid;
    gap: 0.6rem;
    margin-top: 1.1rem;
  }

  .menu-actions .phone {
    align-items: center;
    border: var(--rule) solid var(--line-strong);
    padding: 0.6rem;
  }
}

@media (min-width: 1024px) {
  .nav-toggle { display: none; }
}

/* ---------- hero ----------
   Aquí estaba el problema de verdad del diseño anterior: una caja oscura de
   pantalla y media, con el titular en una columna estrecha al lado de una foto,
   que es la portada de cualquier SaaS. Ahora el hero es papel, el titular ocupa
   el ancho completo a 176px y la foto baja a su sitio, que es el de una lámina
   dentro de un documento, no el de un decorado detrás del texto. */
.hero {
  position: relative;
  background: var(--paper);
  color: var(--ink);
  border-bottom: var(--rule) solid var(--line-strong);
  overflow: hidden;
}

.hero::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 10px;
  /* Cortes duros al 100%: son seis rectángulos de tinta plana, no un degradado.
     Es el muestrario de la marca, el mismo que va en el logotipo y en el pie. */
  background: linear-gradient(
    90deg,
    var(--sw-1) 0 26%,
    var(--sw-2) 26% 41%,
    var(--sw-3) 41% 56%,
    var(--sw-4) 56% 71%,
    var(--sw-5) 71% 86%,
    var(--sw-6) 86% 100%
  );
}

/* position:relative para que el contenido pinte por encima de .hero__plano, que
   es absoluto y va antes en el marcado. */
.hero > .container {
  position: relative;
  padding-block: clamp(1.25rem, 2.5vw, 2rem) clamp(3rem, 6vw, 4.5rem);
}

/* Cabecera de expediente: los datos identificativos del taller antes que nada,
   como en la primera línea de un parte de trabajo. */
.hero__telemetry {
  padding-bottom: 0.85rem;
  border-bottom: 1px solid var(--line-strong);
  margin-bottom: clamp(1.5rem, 4vw, 3rem);
}

/* Una palabra por línea. El interlineado a 0.8 hace que los bloques de
   mayúsculas se toquen casi entre sí y el conjunto deje de leerse como texto
   para leerse como un muro.

   Los números del clamp están medidos en el navegador, no estimados. La línea
   más ancha ("Y MECÁNICA") ocupa 6,61em con este tracking, así que el cuerpo
   máximo que cabe es contenedor/6,61:

   - 12,5vw en vez de 13,5. A 13,5vw la línea medía 286px en una caja de 288px
     en un móvil de 320: dos píxeles de margen. El límite exacto son 13,6vw, o
     sea que 13,5 estaba pegado al borde y cualquier retoque del texto lo
     rompía. A 12,5vw sobra un 8%.
   - Techo de 10rem, no 11rem. A 176px la línea ocupa 1163px y el contenedor
     útil son 1200px (97%); a 160px ocupa 1058px (88%), que sí es un margen.

   Un dato que conviene no perder: Archivo Black y Arial Black miden EXACTAMENTE
   lo mismo (661px las dos a cuerpo 100 en esta cadena). Por eso el respaldo de
   la pila es Arial Black y no una genérica: con font-display:swap el titular es
   el LCP, y al llegar la fuente real no se mueve ni un píxel. Si algún día se
   cambia el respaldo, hay que volver a medir o aparecerá un salto de
   maquetación en el elemento más grande de la página. Lo mismo si se cambia el
   texto del titular. */
.hero__title {
  /* Referencia de la línea de cota de la intro, que se cuelga de aquí. */
  position: relative;
  font-size: clamp(2.6rem, 12.5vw, 10rem);
  line-height: 0.8;
  letter-spacing: -0.05em;
}

.hero__title span { display: block; }

/* La promesa no compite con el titular: baja a monoespaciada y se apoya en una
   barra roja, que es el único sitio del hero donde aparece el acento. */
.hero__title em {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  font-style: normal;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: clamp(0.8125rem, 1.4vw, 1.0625rem);
  letter-spacing: 0.14em;
  line-height: 1.35;
  color: var(--red-ink);
  margin-top: clamp(1.1rem, 2.5vw, 1.75rem);
}

.hero__title em::before {
  content: "";
  width: clamp(2rem, 7vw, 6rem);
  height: 0.55rem;
  background: var(--red);
  flex: none;
}

.hero__inner {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
  margin-top: clamp(2rem, 5vw, 3.25rem);
  align-items: start;
}

@media (min-width: 900px) {
  .hero__inner { grid-template-columns: 1fr 1fr; }
}

.hero__lead {
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 44ch;
}

/* LOS DOS COMPROMISOS QUE DECIDEN LA LLAMADA.
   Coche de sustitución y garantía del fabricante estaban en la tabla de abajo,
   en mono a 12 px, con exactamente el mismo peso que "limpieza incluida". No
   valen lo mismo: son los dos que contesta todo el mundo cuando pregunta por
   qué no lleva el coche al concesionario. Aquí suben a cuerpo de titular
   pequeño y se colocan entre el texto de entrada y los botones, que es por
   donde pasa la vista al terminar de leer y justo antes de decidir.

   No hay elemento nuevo que aprender: la pestaña roja de 8 px a la izquierda es
   la de .panel, el recuadro de nota técnica que el sitio ya usa para "esto es
   lo importante", y el filete de 2 px entre las dos filas es el hueco de la
   rejilla sobre fondo de tinta, el mismo truco que .hero__facts. Lo que cambia
   respecto a la tabla de abajo es la jerarquía tipográfica, no el lenguaje.

   Va dentro de la columna de texto, así que hereda su ancho y no necesita
   ninguna media query: en una sola columna ocupa el ancho de la página y en dos
   la mitad, como el párrafo que tiene encima. */
.hero__promises {
  display: grid;
  gap: var(--rule);
  margin-top: clamp(1.4rem, 3vw, 2rem);
  background: var(--line-strong);
  border: var(--rule) solid var(--line-strong);
  border-left: 8px solid var(--red);
  list-style: none;
}

.hero__promises li {
  background: var(--paper-2);
  padding: clamp(0.8rem, 1.8vw, 1.05rem) clamp(0.9rem, 2vw, 1.2rem);
}

/* Cuerpo de titular pequeño: es la misma voz del H1, tres escalones más abajo.
   El interlineado va corto porque son dos líneas como mucho y tienen que leerse
   como un bloque, no como un párrafo. */
.hero__promises b {
  display: block;
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: clamp(1.0625rem, 1.9vw, 1.35rem);
  line-height: 1.1;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: var(--ink);
}

.hero__promises span {
  display: block;
  margin-top: 0.35rem;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

/* Los compromisos de segundo orden dejan de ser una lista con ticks —el patrón
   por defecto de cualquier landing— y pasan a ser cuatro celdas de una tabla,
   separadas por filetes de 2px hechos con el hueco de la rejilla. */
.hero__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--rule);
  background: var(--line-strong);
  border: var(--rule) solid var(--line-strong);
  margin-top: clamp(2rem, 4vw, 3rem);
}

.hero__facts li {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  background: var(--paper);
  padding: 0.95rem 1rem;
  font-family: var(--ff-mono);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.05em;
  line-height: 1.45;
  text-transform: uppercase;
  color: var(--ink);
}

/* El verde de estos ticks era el verde de WhatsApp, que en el resto del sitio
   significa "botón de WhatsApp". Ahora son cruces rojas del acento único. */
.hero__facts svg { width: 0.95rem; height: 0.95rem; color: var(--red-ink); flex: none; margin-top: 0.15rem; }

.hero__media { position: relative; }

.hero__media figure {
  position: relative;
  margin: 0;
  border: var(--rule) solid var(--line-strong);
}

.hero__media img {
  width: 100%;
  border-radius: 0;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* Trama de semitono. Una fotografía impresa en offset no tiene tono continuo:
   tiene puntos. Esto la reproduce con un solo radial-gradient repetido cada
   4px, sin pedir ni un byte extra al servidor, y multiplicado sobre la imagen
   para que oscurezca en vez de velarla. Al 32% se nota el grano de cerca y
   desaparece de lejos, que es exactamente lo que hace una trama de verdad. */
.hero__media figure::after,
.frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: radial-gradient(circle at center, rgb(0 0 0 / 70%) 0.85px, transparent 1.15px);
  background-size: 4px 4px;
  mix-blend-mode: multiply;
  opacity: 0.32;
}

.hero__media .swatches {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 10px;
  z-index: 1;
}

/* La nota de Google es una placa de tinta plena apoyada en la esquina de la
   lámina, no una pastilla flotante con sombra difusa. */
.rating-chip {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
  background: var(--ink);
  color: var(--paper-2);
  border-radius: 0;
  padding: 0.7rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.rating-chip strong {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: 1.75rem;
  line-height: 1;
  letter-spacing: -0.04em;
}

.rating-chip small {
  display: block;
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #b6b3ac;
  line-height: 1.3;
  margin-top: 0.3rem;
}

/* ---------- intro de plano técnico ----------
   La portada se abre como se abre un plano: primero la cuadrícula del papel,
   después la cota que mide el titular, y sólo entonces el contenido. Es la
   misma técnica del despiece de /servicios/ —un @property numérico que hace de
   mando único, stroke-dashoffset para trazar y el <pattern> de rejilla de 40 px
   sobre currentcolor al 16 %—, sin librerías ni un solo temporizador en JS.

   Lo que aquí NO se copia del despiece es el color. Allí el fondo es la plancha
   #060606 y el acento fino es #ff8a6b, elegido por sus 8,78:1 contra ella. El
   hero es al revés: fondo --paper #eae8e3. Ese mismo #ff8a6b sobre papel da
   1,88:1, o sea nada; es un trazo que desaparece. La cota va por tanto en
   --red-ink #a5301b, 5,64:1 sobre papel, que es exactamente la variable que ya
   existía para "el rojo cuando el sustrato es claro" y que el hero ya usa en
   .hero__title em y en los ticks de .hero__facts. En oklch la operación es la
   simétrica de la de --red-pieza: mismo tono (33° contra 32,5°), pero bajando
   la luz de --red de 0,596 a 0,485 en vez de subirla a 0,78. No se inventa un
   noveno rojo para esto: el sitio ya tenía el correcto.

   Con la rejilla pasa lo mismo y sale sola: --ink al 16 % sobre --paper resuelve
   a #c6c4c0, que es --line #c9c6be con un pelo de diferencia, o sea el propio
   filete fino del documento. Misma operación que en la lámina —el color del
   sustrato contrario al 16 %—, sustrato invertido, resultado coherente.

   Sobre el LCP, dicho sin adornos: el contenido real está en el HTML desde el
   primer render y no lo toca ningún JavaScript, que es lo que importa para SEO
   y accesibilidad. Pero el titular es el elemento LCP y arranca a opacidad 0,
   así que en la PRIMERA carga de la sesión el LCP se retrasa lo que tarda en
   asomar: 0,26 × 900 ms ≈ 235 ms. Ni cero ni disimulable. Si algún día molesta,
   el mando es el --t0 de .hero__title span y nada más. */

/* Mismo papel que --ex: 0 es la hoja en blanco, 1 el hero asentado. Un solo
   número del que cuelgan las nueve entradas, así que no hay dos animaciones que
   puedan desincronizarse.
   El valor inicial es 1, o sea el estado FINAL. Es deliberado y es la misma
   inversión que en el despiece: sin JavaScript, sin la clase, con la hoja de
   estilos a medias o con movimiento reducido, lo que se ve es el hero entero.
   La animación es lo que hay que encender, no lo que hay que apagar. */
@property --intro {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

/* En reposo no existen. No es sólo por la intro: el visitante que vuelve tiene
   que ver EXACTAMENTE el hero de siempre, sin una cota roja de recuerdo encima
   del titular. Primera visita y segunda acaban en el mismo sitio. */
.hero__plano,
.hero__cota { opacity: 0; }

.hero__plano {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  color: var(--ink);
}

.hero__cota {
  position: absolute;
  left: 0;
  right: 0;
  /* Vive en el margen inferior de .hero__telemetry, que va de 24 px en el móvil
     más estrecho a 48 en el escritorio. La separación se estrecha con él por una
     razón concreta: la telemetría se cierra con un filete negro a todo lo ancho,
     y en 320 px con una separación fija quedaban dos rayas horizontales casi
     pegadas y sin jerarquía. Al encoger, la cota siempre queda más cerca del
     titular que del filete, que es lo que dice a quién está midiendo. */
  bottom: calc(100% + clamp(0.2rem, 1vw, 0.5rem));
  height: 14px;
  /* No intercepta nada. A opacidad 0 un elemento sigue recibiendo clics, y esto
     es una banda de 14 px de ancho de página que se queda ahí para siempre. */
  pointer-events: none;
  /* Los dos topes de la cota. Van en CSS y no en el SVG porque el SVG se estira
     en x con preserveAspectRatio="none" y un trazo vertical saldría de 0,32 px
     a 320 de ancho y de 1,2 a 1200. Un borde no se estira: 1 px siempre.
     Con box-sizing:border-box el lienzo queda entre los dos topes, que es donde
     tiene que empezar y acabar la regla.
     Y 1 px exacto, no 1,4: el navegador redondea el ancho de borde a píxeles de
     pantalla enteros, así que 1,4 salía a 1 y dejaba los topes más finos que la
     regla. Con los dos a 1 es el mismo filete de la rejilla y el mismo del resto
     del documento, que es lo que debe ser una línea de cota. */
  border-inline: 1px solid;
  color: var(--red-ink);
}

.intro-hero .hero { animation: hero-intro 0.9s linear forwards; }

@keyframes hero-intro {
  from { --intro: 0; }
  to { --intro: 1; }
}

/* La capa de plano se retira sobre el último 28 % del recorrido, cuando el
   contenido ya está puesto. Sin esto habría que decidir si la cuadrícula se
   queda para siempre —y entonces el hero de la primera visita no es el de las
   demás— o si la quita un segundo temporizador, que es justo lo que este
   archivo lleva evitando. */
.intro-hero .hero__plano,
.intro-hero .hero__cota { opacity: clamp(0, (1 - var(--intro)) / 0.28, 1); }

/* Del 6 % al 28 %: la regla se traza de izquierda a derecha entre los dos topes
   y termina justo cuando entra el titular. */
.intro-hero .hero__cota path {
  stroke-dasharray: 1;
  stroke-dashoffset: calc(1 - clamp(0, (var(--intro) - 0.06) / 0.22, 1));
}

/* Cada bloque entra con su propio retardo --t0 y una rampa del 16 % del
   recorrido. Sólo opacidad y translateY, así que el desplazamiento del diseño
   es cero: nada de esto reserva ni libera espacio, todo ocupa desde el principio
   el sitio que va a ocupar.
   Y nada de esto bloquea nada. Un elemento a opacidad 0 sigue recibiendo clics:
   quien llegue al botón de "Pedir cita" antes de que termine la animación puede
   pulsarlo igual. No hay pointer-events:none, ni visibility, ni display en toda
   la secuencia, precisamente por eso.
   El titular NO entra en esta lista: entran sus líneas. La cota cuelga del h1 y
   heredaría su opacidad, y entonces el trazo no podría dibujarse antes que el
   texto, que es el orden que pide toda la secuencia. */
.intro-hero .hero__telemetry,
.intro-hero .hero__title span,
.intro-hero .hero__title em,
.intro-hero .hero__lead,
.intro-hero .hero__media,
.intro-hero .hero__promises,
.intro-hero .hero .btn-row,
.intro-hero .hero__facts,
.intro-hero .hero::after {
  --p: clamp(0, (var(--intro) - var(--t0)) / 0.16, 1);
  opacity: var(--p);
  transform: translateY(calc((1 - var(--p)) * 8px));
}

.intro-hero .hero__telemetry { --t0: 0.20; }
.intro-hero .hero__title span { --t0: 0.26; }
.intro-hero .hero__title em { --t0: 0.34; }
.intro-hero .hero__lead { --t0: 0.40; }
.intro-hero .hero__media { --t0: 0.44; }
.intro-hero .hero__promises { --t0: 0.50; }
.intro-hero .hero .btn-row { --t0: 0.58; }
.intro-hero .hero__facts { --t0: 0.66; }
/* El muestrario de pintura del borde inferior, que también forma parte de la
   hoja y no del contenido. Se desliza hacia arriba desde fuera del recorte. */
.intro-hero .hero::after { --t0: 0.66; }

/* Movimiento reducido: la intro se salta entera, y hay dos redes independientes
   para ello. La primera es el script de la portada, que con la preferencia
   activa ni siquiera pone la clase. La segunda es la regla del final de este
   archivo, que apaga esta animación: sin ella el mando vuelve a su valor
   inicial, 1, y el hero sale completo y quieto sin un solo fotograma en blanco. */

/* ---------- secuencia del motor ----------
   Mismo reparto de papeles que el despiece y que la intro del hero: JavaScript
   pone UN número —--seq, de 0 a 1— y todo lo que se mueve cuelga de él en CSS.
   Aquí ese número no lo produce una animación sino la rueda del ratón, pero el
   contrato es el mismo, así que la opacidad del texto no necesita ni una línea
   de JS ni un temporizador que pueda irse de fase con la hoja de estilos.

   El valor inicial vuelve a ser el estado FINAL, 1, por la misma razón de
   siempre: sin JS, sin @property o con movimiento reducido lo que se ve es la
   foto del motor con su texto puesto, que es un resultado correcto. Lo que hay
   que encender es el barrido, no apagarlo. */
@property --seq {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

.motor {
  background: #060606;
  border-block: var(--rule) solid var(--ink);
  color: var(--paper-2);
}

/* La vía es el recorrido de scroll, y su ALTO es la duración de la toma. 320svh
   dan 220 de recorrido útil sobre 72 fotogramas: unos 3 svh por fotograma, o
   sea que un gesto de rueda normal avanza dos o tres y el movimiento se lee
   continuo. Con 200svh se pasaba a fotograma por gesto y se veía a saltos; con
   500 había que insistir tanto que parecía que la página se había atascado.
   Se mide en svh y no en vh porque en el móvil la barra del navegador aparece y
   desaparece: con vh el recorrido cambiaría de largo a mitad de la toma. */
.motor__via { height: 320svh; }

.motor__fijo {
  position: sticky;
  /* Justo debajo de la cabecera, que también es pegajosa. Si no se descuenta,
     la cabecera tapa la franja de arriba del fotograma durante toda la toma. */
  top: var(--header-h);
  height: calc(100svh - var(--header-h));
  overflow: hidden;
  display: grid;
  /* La fila y la columna se declaran a la fuerza, y el minmax(0,...) no es
     adorno: sin esto la rejilla no tenía plantilla y creaba una fila implícita
     de tamaño automático, o sea del alto del contenido más alto. El contenido
     más alto es el fotograma, y su `height: 100%` contra una fila automática no
     resuelve a nada: se cae a `auto` y la imagen reclama su alto intrínseco, el
     de su proporción 16:9. En una ventana más ancha que 16:9 esa fila salía más
     alta que la caja (1057 px de fila dentro de 832 de caja), y como el texto
     va anclado al final de la FILA, se iba 169 px por debajo del borde y se lo
     comía el overflow: hidden. Con la fila atada al alto de la caja, el 100%
     de la imagen ya tiene contra qué medirse y el texto acaba donde debe.
     El minmax(0, 1fr) en lugar de 1fr a secas porque el mínimo automático de
     una pista sigue siendo el del contenido, así que 1fr sola volvería a
     dejarse estirar por la imagen. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}

/* Los cuatro se apilan en la misma celda de la rejilla, sin position:absolute:
   así el alto lo fija el contenedor y no hay que sincronizar nada.
   Ojo con el <picture>: genera caja propia, así que el elemento de rejilla es
   ÉL y no su <img>. Colocando el img —que es lo que parece natural— el picture
   se quedaba suelto en el flujo y la foto caía debajo de la toma en vez de
   encima. Se coloca el picture y se estira el img dentro. */
.motor__plano,
.motor__fijo > picture,
.motor__lienzo,
.motor__copia { grid-area: 1 / 1; }

.motor__plano,
.motor__poster,
.motor__lienzo { width: 100%; height: 100%; }

.motor__plano { color: var(--paper-2); pointer-events: none; }

/* `cover` y no `contain`: la toma tiene que llenar la ventana sea cual sea su
   proporción. Lo que se pierde por los lados es suelo de taller y pared, nunca
   el coche, que va centrado en el encuadre de los 144 fotogramas. */
.motor__poster,
.motor__lienzo { display: block; object-fit: cover; }

/* Con el barrido en marcha la foto se quita y queda la cuadrícula sola, que es
   el estado de carga. Hay que quitarla: la foto es el ÚLTIMO fotograma, así que
   dejarla debajo mientras cargan los primeros enseñaría el final de la toma
   antes que el principio.
   No cuesta una descarga de más. El <picture> pide exactamente el mismo archivo
   que la secuencia —el corte del <source> en 899 px y el del JS en 900 px son
   el mismo corte, a propósito—, así que el navegador sirve el fotograma 72 de
   su caché cuando le toca el turno. Medido: 72 archivos únicos y 72 peticiones.
   (Llegué a poner display:none creyendo que ahorraba los ~50 KB de la foto. No
   ahorra nada; se queda porque el motivo de arriba sí es cierto.) */
.motor.is-viva picture { display: none; }

/* El canvas nace invisible y lo enciende el JS al pintar el primer fotograma.
   Un canvas sin pintar es un rectángulo transparente, así que sin esto lo que
   se vería durante la carga sería la cuadrícula y de repente el fotograma
   encima sin transición. */
.motor__lienzo { opacity: 0; transition: opacity 0.3s linear; }
.motor.is-lista .motor__lienzo { opacity: 1; }

.motor__copia {
  align-self: end;
  justify-self: start;
  z-index: 2;
  max-width: min(38rem, 82vw);
  margin: clamp(1rem, 4vw, 3rem);
  padding: clamp(1rem, 2.5vw, 1.6rem) clamp(1.1rem, 3vw, 2rem);
  background: #060606;
  /* El filete rojo del borde de ataque, como en las planchas del despiece. */
  border-left: 3px solid var(--red);
  /* Entra en el primer 6-18 % del recorrido y ya no se va. Misma forma de rampa
     que --p en la intro del hero.
     Llegué a ponerle también una salida al final, y estaba mal: el estado
     inicial de --seq es 1 —el final— justamente para que sin JS se vea el
     resultado bueno, así que una rampa que se apague en 1 dejaba la portada sin
     JavaScript con la foto del motor y el texto invisible encima. La rampa solo
     puede tener un extremo, y tiene que ser el de entrada. */
  --vis: clamp(0, (var(--seq) - 0.06) / 0.12, 1);
  opacity: var(--vis);
  transform: translateY(calc((1 - var(--vis)) * 10px));
}

.motor__copia .kicker { color: #ff8a6b; margin-bottom: 0.5rem; }

.motor__frase {
  margin: 0;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
  font-size: clamp(1.4rem, 3.4vw, 2.4rem);
}

/* El botón flotante de WhatsApp es fijo y aterriza en esta misma esquina. Por
   debajo de ~820 px de ancho la plancha crece hasta alcanzarlo y le tapaba el
   último renglón —medido en un móvil de 375: se solapaban 105 x 46 px, se comía
   la palabra "tiene"—. Por encima de ese ancho la plancha topa antes con su
   propio max-width y no llega, así que no hay nada que apartar y no se aparta.
   El hueco es el alto del botón más su separación al borde por arriba y por
   abajo, con los dos números tomados de :root. */
@media (max-width: 820px) {
  .motor__copia { margin-bottom: calc(var(--wa-h) + var(--wa-gap) * 2); }
}

/* Movimiento reducido: no hay barrido, no hay vía larga y no hay canvas. Queda
   la foto del motor con su texto, quieta, ocupando lo que ocupa una sección
   normal. Esto NO depende de que el JS haga lo correcto —también se sale por su
   cuenta, ver site.js—: son dos redes independientes para la misma preferencia.
   El texto no hace falta forzarlo a visible aquí: como el JS no llega a tocar
   --seq, se queda en su valor inicial 1 y la rampa de --vis ya da 1. */
@media (prefers-reduced-motion: reduce) {
  .motor__via { height: auto; }
  .motor__fijo { position: static; height: min(70svh, 60vw); }
  .motor__lienzo { display: none; }
}

/* ---------- estrellas ---------- */
.stars { display: inline-flex; gap: 2px; color: var(--red); }
.stars svg { width: 1rem; height: 1rem; }
.stars--lg svg { width: 1.25rem; height: 1.25rem; }

/* ---------- datos de confianza ----------
   Hoja de características. Los filetes que separan las celdas no son bordes:
   son el fondo negro del contenedor asomando por un hueco de rejilla de 2px.
   Es la técnica que pide el sistema y da filetes perfectos, sin dobles bordes
   en los encuentros ni medios píxeles al redondear. */
.stats { background: var(--paper); border-bottom: var(--rule) solid var(--line-strong); }

.stats > .container { padding-block: clamp(1.5rem, 3vw, 2.5rem); }

.stats ul {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--rule);
  background: var(--ink);
  border: var(--rule) solid var(--ink);
  counter-reset: dato;
}

.stats li {
  background: var(--paper-2);
  padding: 1.5rem clamp(0.9rem, 1.5vw, 1.4rem) 1.4rem;
  counter-increment: dato;
  position: relative;
}

/* Índice de celda arriba a la derecha. Es numeración de tabla, no un dato
   inventado: cuenta las celdas que hay, ni más ni menos. */
.stats li::before {
  content: counter(dato, decimal-leading-zero);
  position: absolute;
  top: 0.55rem;
  right: 0.6rem;
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  /* Gris de --muted y no el gris de filete: un lector de pantalla lee este
     contador igual que cualquier otro texto, así que tiene que pasar el 4,5:1
     como todo lo demás (aquí da 5,98:1). Un índice "decorativo" ilegible es un
     fallo de accesibilidad disfrazado de detalle de estilo. */
  color: var(--muted);
}

/* En una banda de datos la cifra es el dato; el resto es pie de foto.
   El techo no es estético, es geométrico: con siete columnas el contenedor se
   queda en 1280px y a cada dato le tocan ~123px útiles. La cifra más larga es
   "4.000 m²": en Archivo Black a 2,9rem mide 118px. Subir de ahí la desborda
   aunque la ventana sea enorme, porque el contenedor ya no crece. Si algún día
   se añade un dato más largo en content.mjs, este número hay que rebajarlo. */
.stats .num {
  display: flex;
  align-items: baseline;
  gap: 0.15rem;
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: clamp(2.15rem, 3.5vw, 2.9rem);
  letter-spacing: -0.045em;
  line-height: 0.9;
  color: var(--ink);
}

.stats .num span { font-size: 0.4em; letter-spacing: 0.02em; color: var(--red-ink); }

.stats .lbl {
  display: block;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink);
  margin-top: 0.7rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--line-strong);
}

.stats .note { font-family: var(--ff-mono); font-size: 0.6875rem; letter-spacing: 0.04em; color: var(--muted); line-height: 1.5; margin-top: 0.4rem; }

/* ---------- tarjetas de servicio ----------
   La rejilla ya no es un conjunto de tarjetas flotando con espacio entre ellas:
   es UN bloque de tinta negra con ocho ventanas recortadas. Los filetes de 2px
   son el fondo del contenedor asomando por el hueco de la rejilla, no bordes,
   así que ni se duplican en los encuentros ni se parten en medio píxel.
   grid-auto-rows: 1fr iguala TODAS las filas, no solo las tarjetas de una
   misma fila. Sin esto las fichas con descripción más larga hacían su fila 24px
   más alta y las ocho dejaban de medir lo mismo, que es justo lo que el encargo
   prohíbe: las dos destacadas se distinguen por color, nunca por tamaño. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  grid-auto-rows: 1fr;
  gap: var(--rule);
  background: var(--ink);
  border: var(--rule) solid var(--ink);
  counter-reset: ficha;
}

/* Cada ficha es una casilla de un formulario de taller: índice arriba a la
   izquierda, pictograma arriba a la derecha, denominación, descripción y pie
   separado por un filete.
   La jerarquía se juega entera al color y a nada más: las dos especialidades
   —chapa/pintura y taller mecánico— van impresas EN NEGATIVO, tinta plena con
   el texto calado. Dos casillas negras dentro de una parrilla de seis blancas
   no se pueden pasar por alto, y miden exactamente lo mismo que las demás.
   Antes eran dos tarjetas con una franja roja arriba; el problema es que ese
   recurso ya lo tenían las otras seis en gris y la diferencia se leía como una
   variante de color, no como una categoría distinta. */
.card {
  --card-bg: var(--paper-2);
  --card-fg: var(--ink);
  --card-dim: var(--muted);
  --card-accent: var(--red-ink);
  --card-rule: var(--line);
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--card-bg);
  color: var(--card-fg);
  padding: 1.4rem;
  text-decoration: none;
  counter-increment: ficha;
  transition: background-color 0.18s var(--ease-out), color 0.18s var(--ease-out);
}

/* Índice de casilla. Numera lo que hay en pantalla, no inventa una referencia
   de catálogo: el sistema propone cadenas tipo "UNIT / D-01" y aquí eso sería
   fingir un dato. */
.card::before {
  content: counter(ficha, decimal-leading-zero);
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  line-height: 1.75rem;
  color: var(--card-accent);
  margin-bottom: 1.15rem;
}

.card--hl {
  --card-bg: var(--ink);
  --card-fg: var(--paper-2);
  --card-dim: #b6b3ac;
  --card-accent: var(--red);
  --card-rule: #3d3d39;
}

/* El realce no mueve la ficha: en una parrilla soldada por filetes de 2px,
   levantar una casilla rompe la costura con las cuatro vecinas. La casilla se
   entinta de rojo entera, que además es la única vez que el acento ocupa un
   área grande en toda la página. */
@media (hover: hover) and (pointer: fine) {
  .card:hover {
    --card-bg: var(--red-btn);
    --card-fg: #fff;
    --card-dim: #fff;
    --card-accent: #fff;
    --card-rule: #fff;
  }

  .card:hover .arrow { transform: translateX(3px); }
}

/* El teclado conserva el mismo realce: el foco no depende de tener ratón. */
.card:focus-visible {
  --card-bg: var(--red-btn);
  --card-fg: #fff;
  --card-dim: #fff;
  --card-accent: #fff;
  --card-rule: #fff;
}

.card:focus-visible .arrow { transform: translateX(3px); }

/* El pictograma se alinea con el índice, no con el titular: los dos ocupan la
   misma banda de 1,75rem en lo alto de la casilla. */
.card__icon {
  position: absolute;
  top: 1.4rem;
  right: 1.4rem;
  width: 1.75rem;
  height: 1.75rem;
  color: var(--card-accent);
}

.card h3 { font-size: clamp(1.05rem, 1.5vw, 1.25rem); margin-bottom: 0.6rem; }

.card p { color: var(--card-dim); font-size: 0.9375rem; line-height: 1.55; }

.card__foot {
  margin-top: auto;
  padding-top: 1rem;
  border-top: var(--rule) solid var(--card-rule);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* La etiqueta es un rótulo impreso, no una pastilla: una caja con borde dentro
   de una casilla con filete son dos cajas para un solo dato. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--ff-mono);
  font-weight: 400;
  font-size: 0.6875rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--card-dim);
}

.tag::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  background: currentcolor;
  flex: none;
}

/* En las dos especialidades la etiqueta sube al acento: sobre tinta plena el
   rojo de marca da 4,55:1, así que aquí sí puede ir el rojo vivo y no el
   oscuro que hace falta sobre papel. */
.tag--hl { color: var(--card-accent); font-weight: 600; }

.card .arrow {
  display: inline-flex;
  font-family: var(--ff-mono);
  font-weight: 600;
  color: var(--card-accent);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-size: 0.6875rem;
  transition: transform 0.18s var(--ease-out);
}

.card .arrow::after { content: " >>>" / ""; }

/* ---------- tarjeta de artículo del blog ----------
   Misma familia que .card -tipografía de etiqueta y de flecha, mismo cambio de
   tinta al pasar el ratón- y por eso reutiliza los MISMOS nombres de variable
   (--card-bg, --card-fg...) en vez de inventar otros: así .tag, que ya lee
   --card-dim, se pinta bien aquí sin tocar su regla, y el hover se resuelve
   igual que en .card, cambiando variables y no propiedades.
   Lo que no comparte es el contador de ficha: ese numera un catálogo cerrado
   de servicios, y aquí no hay catálogo, hay fecha de publicación, que es el
   dato que de verdad orienta en un blog. */
.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
  gap: var(--rule);
  background: var(--ink);
  border: var(--rule) solid var(--ink);
}

.post-card {
  --card-bg: var(--paper-2);
  --card-fg: var(--ink);
  --card-dim: var(--muted);
  --card-accent: var(--red-ink);
  --card-rule: var(--line);
  display: flex;
  flex-direction: column;
  background: var(--card-bg);
  color: var(--card-fg);
  text-decoration: none;
  transition: background-color 0.18s var(--ease-out), color 0.18s var(--ease-out);
}

.post-card__img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; display: block; }

.post-card__body { display: flex; flex-direction: column; flex: 1; gap: 0.6rem; padding: 1.4rem; }

.post-card h3 { font-size: clamp(1.05rem, 1.5vw, 1.25rem); margin: 0; }

.post-card p { color: var(--card-dim); font-size: 0.9375rem; line-height: 1.55; margin: 0; }

.post-card__foot {
  margin-top: auto;
  padding-top: 1rem;
  border-top: var(--rule) solid var(--card-rule);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.post-card__foot time {
  font-family: var(--ff-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--card-dim);
}

.post-card .arrow {
  display: inline-flex;
  font-family: var(--ff-mono);
  font-weight: 600;
  color: var(--card-accent);
  letter-spacing: 0.13em;
  text-transform: uppercase;
  font-size: 0.6875rem;
  transition: transform 0.18s var(--ease-out);
}

.post-card .arrow::after { content: " >>>" / ""; }

@media (hover: hover) and (pointer: fine) {
  .post-card:hover {
    --card-bg: var(--red-btn);
    --card-fg: #fff;
    --card-dim: #fff;
    --card-accent: #fff;
  }

  .post-card:hover .arrow { transform: translateX(3px); }
}

.post-card:focus-visible {
  --card-bg: var(--red-btn);
  --card-fg: #fff;
  --card-dim: #fff;
  --card-accent: #fff;
}

.post-card:focus-visible .arrow { transform: translateX(3px); }

/* ---------- bloques de dos columnas ---------- */
.split {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3.5rem);
  align-items: center;
}

@media (min-width: 900px) {
  .split { grid-template-columns: 1fr 1fr; }
  .split--wide-left { grid-template-columns: 1.15fr 0.85fr; }
}

.split figure { margin: 0; position: relative; }

.split figure img { border-radius: 0; width: 100%; object-fit: cover; }

/* .frame ES el <figure>, no lo contiene: el filete va aquí directamente. */
.frame {
  position: relative;
  border: var(--rule) solid var(--line-strong);
}

.frame .swatches { position: absolute; inset: 0 0 auto 0; z-index: 1; }

/* Recuadro de nota técnica: filete completo y pestaña roja gruesa a la
   izquierda, como el margen coloreado de un apartado en un manual. */
.panel {
  background: var(--paper-2);
  border: var(--rule) solid var(--line-strong);
  border-left: 8px solid var(--red);
  border-radius: 0;
  padding: clamp(1.25rem, 2.5vw, 2rem);
}

.panel h3 { margin-bottom: 0.7rem; }

.panel p + p { margin-top: 0.85rem; }

/* ---------- despiece del vehículo ----------
   La única pieza del sitio que se mira en vez de leerse. Ocurre en tres tiempos,
   y son los del taller: se traza la línea (chapa), entra el rojo (pintura) y el
   coche se abre en las piezas que de verdad se desmontan (despiece). El orden no
   es estético; es el parte de trabajo.

   Cómo funciona sin depender de JavaScript: el estado por defecto de esta hoja
   es el despiece TERMINADO Y ABIERTO. Si no hay JS, o si el usuario pide menos
   movimiento, eso es exactamente lo que se ve —el diagrama completo, con sus
   cinco llamadas— y no falta nada. El JS solo añade .is-armed, que cierra el
   coche y esconde los trazos, y después .is-drawn al entrar en pantalla. Al
   revés (cerrar por CSS y abrir por JS) un fallo de script dejaría un coche
   mudo en mitad de la portada.

   Se anima stroke-dashoffset, que no es transform ni opacity. Es la excepción
   consciente a la regla del proyecto: repinta, pero no recalcula la maquetación,
   y es la única forma real de dibujar un trazo. */

/* --ex es el único mando de todo esto: 0 es el coche montado, 1 el coche
   abierto. La traslación de cada pieza, la escala del encuadre, las líneas de
   referencia, el motor y las llamadas se derivan de él, así que no hay dos
   animaciones que puedan desincronizarse ni un temporizador en JavaScript que
   pueda irse de fase.

   Hay que registrarlo con @property o el navegador lo trata como una cadena de
   texto y no lo interpola: el coche saltaría de montado a abierto de golpe.
   Donde no exista @property salta, que es feo un instante pero no rompe nada. */
@property --ex {
  syntax: '<number>';
  inherits: true;
  initial-value: 1;
}

.dp-plate {
  margin: clamp(1.75rem, 4vw, 2.75rem) 0 0;
  border: var(--rule) solid #3d3d39;
  background: #060606;
  padding: clamp(0.5rem, 2vw, 1.25rem);
}

.dp-svg {
  display: block;
  width: 100%;
  height: auto;
  color: var(--paper-2);
}

/* En una pantalla baja, el que cede alto es el dibujo, no la leyenda: la
   petición es ver la lámina y su índice a la vez, sin desplazar la página, y el
   dibujo es lo único elástico que hay aquí.
   Lo que se descuenta del alto de la ventana es todo lo que acompaña al SVG:
   la cabecera pegajosa (70) por arriba, y por abajo el pie del recuadro con su
   botón (57), el filete y el relleno de la lámina (44) y la leyenda, que mide
   44 px por fila más los filetes. De ahí las dos cifras: a cuatro columnas son
   dos filas (98 -> 269, redondeado a 290) y a dos, cuatro (186 -> 357,
   redondeado a 375). Los cortes son los mismos que los de las columnas, porque
   es la misma pregunta.
   El tope solo entra en juego por debajo de unos 940 px de ventana: en un
   monitor normal el dibujo mide exactamente lo que medía. Y cuando entra no
   deforma nada: `meet` encoge el dibujo y lo centra, y la retícula del fondo
   —que se pinta muy por fuera del viewBox— rellena el lienzo de los lados. */
@media (min-width: 1000px) {
  .dp-svg { max-height: calc(100svh - 375px); }
}

@media (min-width: 1060px) {
  .dp-svg { max-height: calc(100svh - 290px); }
}

.dp-bg { color: var(--paper-2); }
.dp-thin { stroke-width: 1.4; }
.dp-lunas { color: #8f8c86; }
.dp-paint { fill: var(--red); }

/* --dx/--dy son números sin unidad en coordenadas del dibujo. Multiplicados por
   --ex dan el recorrido de esa pieza en cada instante. */
.dp-piece {
  transform: translate(calc(var(--dx, 0) * var(--ex) * 1px), calc(var(--dy, 0) * var(--ex) * 1px));
}

/* Lo que se desmonta se pone al rojo; lo que se queda, no. El casco sigue en
   blanco y esa es toda la lectura: si algo ha cambiado de color, sale del coche.

   El selector no es `.dp-ink`, y no por capricho. Tres razones, las tres
   comprobadas sobre el markup y no supuestas:
     · el casco TAMBIÉN lleva .dp-ink y .dp-cut, así que la clase sola tintaría
       lo único que no debe moverse;
     · las lunas no llevan .dp-ink ni .dp-cut —su grupo va sin clase—, así que
       la clase sola dejaría fuera una de las ocho piezas;
     · .dp-hit no tiene stroke propio, y darle color lo haría VISIBLE, que es
       justo lo contrario de lo que es: la zona de clic.
   Lo que sí distingue exactamente a los quince grupos que hay que tintar es que
   declaran stroke="currentcolor" y viven dentro de una pieza. Los envoltorios
   de .dp-paint no declaran trazo, así que quedan fuera solos y el relleno no se
   toca. Las llamadas viven fuera de .dp-part, así que tampoco entran.

   El color va por el mismo mando que todo lo demás: a --ex 0 la mezcla es 0 %
   acento, o sea currentcolor, o sea EXACTAMENTE el color de hoy —incluido el
   gris propio de las lunas, que arrancan de su tono y no del blanco—. A --ex 1
   es el acento entero. En medio interpola en oklab, que es lo que evita que el
   camino del blanco al rojo pase por un rosa sucio.
   Sin color-mix la declaración entera se cae y manda el atributo currentcolor
   del SVG: el despiece se queda como estaba, que es un respaldo correcto. */
.dp-part .dp-piece > g[stroke='currentcolor'] {
  stroke: color-mix(in oklab, var(--red-pieza) calc(var(--ex) * 100%), currentcolor);
}

/* El encuadre se aleja mientras el coche se abre. Sin esto hay que elegir entre
   un coche diminuto perdido en el lienzo cuando está montado o un despiece
   cortado por los bordes cuando está abierto. El origen es el centro del
   vehículo, no el del lienzo, para que al escalar el coche no derive.
   El origen sale de despejar dónde tiene que caer el coche cerrado: el centro
   del vehículo, (520, 252), tiene que aterrizar en el centro del viewBox, que
   con el lienzo actual (-200 -170 1420 800) es (510, 230).
     ox + (520 - ox) * 1,45 = 510  ->  ox = 542
     oy + (252 - oy) * 1,45 = 230  ->  oy = 301
   Si se toca la escala o el viewBox hay que rehacer esas dos cuentas, no mover
   el punto a ojo. Al pasar el lienzo de 630 a 800 de alto para meter las piezas
   de dentro, la Y bajó de 490 a 301; la X no se movió porque el ancho es la
   misma. Las mismas dos cuentas están duplicadas en tools/preview-lamina.mjs,
   que es lo único capaz de decir si el resultado se ve bien.

   Abierto se queda en 0,8 y no en 1. La rejilla nueva reparte las nueve piezas
   en dos bandas con el mismo hueco a los dos lados del eje, y esa figura mide
   más que el lienzo: con 1 el portón y el condensador se salían por los lados.
   Con 0,8 la banda visible pasa a ser x -385,5..1389,5 e y -287,75..712,25, que
   es contra lo que están calculadas las columnas en tools/dp-layout.mjs. Si se
   cambia este 0,65 hay que cambiar la banda allí y volver a pasar
   tools/z-despiece.mjs. */
.dp-world {
  transform-box: view-box;
  transform-origin: 542px 301px;
  transform: scale(calc(1.45 - 0.65 * var(--ex)));
}

/* El hueco que deja cada pieza. Aparece según se abre el coche y nunca llega a
   competir con el trazo bueno: al 20% es una marca de lápiz, no una línea. No
   recibe puntero para que el fantasma del portón no robe el clic al portón. */
.dp-ghost {
  opacity: calc(var(--ex) * 0.2);
  pointer-events: none;
}

/* Con el coche montado hay suelo, como debe ser. Al abrirse, las cuatro piezas
   de la banda de abajo —las dos ruedas entre ellas— acaban por debajo de esa
   línea: un suelo ahí las dejaría enterradas y además metería una horizontal
   larga cruzando todas las líneas de referencia. Así que el suelo se va con el
   coche, en los dos repartos. */
.dp-ground { opacity: calc(1 - var(--ex)); }

/* Las líneas de referencia se dibujan solas conforme la pieza se aleja: mismo
   mando, cero JavaScript, imposible que vayan a destiempo. Van al 55% porque su
   trabajo es atar la pieza a su hueco, no competir con el trazo de la chapa.

   El color es el rojo de marca aclarado, no --red: sobre la plancha (#060606) el
   --red al 55% da 2,07:1 y se hunde; #ff8a6b da 3,37:1, que es el mismo rojo que
   ya se usa en .section--dark .kicker y en el pie por esta misma razón. No se
   busca más luz que el gris anterior (5,77:1) —tendría menos— sino separarlas por
   TONO del trazo blanco de la chapa: es la convención del plano industrial, la
   acotación en rojo sobre el dibujo en blanco. Al heredar por currentcolor los
   dos juegos (--h y --v) y las 9 guías de cada uno, ninguna se queda fuera. */
.dp-leads path {
  stroke: #ff8a6b;
  stroke-dashoffset: calc(1 - var(--ex));
  opacity: calc(var(--ex) * 0.55);
}

/* Hay dos juegos de guías porque el reparto de las piezas cambia en móvil y el
   vector de una línea va dentro de su trazado. Solo se ve uno de los dos. */
.dp-leads--v { display: none; }

/* Lo que no existe con la chapa puesta: el motor, debajo del capó, y el
   condensador, detrás del paragolpes. Va en el grupo interior y no en la parte
   para no pelearse con el atenuado del hover: las dos opacidades se multiplican
   en vez de pisarse.
   Los asientos NO están aquí y es a propósito: sí se ven desde fuera, por la
   luna. Lo que los tapa de cintura para abajo es la propia chapa de la puerta,
   porque van pintados antes que el casco. Eso no es una regla de CSS, es orden
   de pintado, y por eso funciona igual a medio camino. */
.dp-motor .dp-piece,
.dp-clima .dp-piece { opacity: var(--ex); }

/* Un elemento a opacidad 0 sigue recibiendo el puntero. Las dos piezas se
   dibujan más grandes que su hueco y sobresalen de la carrocería con el coche
   montado; sin esto, pasar el ratón por ese aire apagaría todo el dibujo para
   señalar algo que no se ve. Se apaga en los dos estados cerrados: antes de que
   se abra solo y cuando el usuario lo ha vuelto a montar. */
.despiece.is-armed:not(.is-drawn) .dp-motor,
.despiece.is-armed:not(.is-drawn) .dp-clima,
.despiece.is-closed .dp-motor,
.despiece.is-closed .dp-clima { pointer-events: none; }

/* CADA PIEZA ES UN ENLACE A SU FICHA, y por eso hay que devolverle al <a> el
   color que traía. El selector de tipo `a` del principio del archivo alcanza
   también al <a> de SVG —en CSS no hay espacios de nombres— y un valor
   declarado gana siempre a uno heredado, así que sin este `inherit` el enlace
   valdría --red-ink y con él TODO lo que dentro de la pieza dice currentcolor.
   Medido sobre el dibujo, quitando la clase a una copia del portón: el trazo a
   media apertura pasa de oklab(0.91 ...) a oklab(0.57 ...), o sea de casi
   blanco a un rojo apagado, sobre una plancha que es negra. Las lunas se
   librarían solas porque su grupo declara su propio color, que es justo lo que
   demuestra que lo que se rompe es la herencia y no otra cosa.
   A --ex 1 no se notaría: ahí la mezcla ya es 100 % acento y currentcolor no
   pinta. El destrozo vive entero en el camino, que es donde más se mira.

   El :hover va con nombre y apellidos porque a:hover pesa más que .dp-link y si
   no volvería a pisarlo, esta vez con la tinta.

   El subrayado es cinturón, no tirantes: comprobado que Chrome no subraya el
   <a> de SVG —los <text> de las pastillas salen sin decoración con clase y sin
   ella—, pero es una declaración de cuatro caracteres contra un motor que
   decida lo contrario, y aquí caería sobre rótulos que sí saben pintarla. */
.dp-link,
.dp-link:hover { color: inherit; text-decoration: none; }

/* El dedo, solo cuando el destino existe de verdad. Con el coche montado el clic
   no navega —lo detiene site.js, y allí está el porqué—: monta y desmonta. Son
   los dos mismos estados cerrados que apagan el motor y el condensador aquí
   arriba, y el mismo motivo de fondo: con la chapa puesta, las ocho piezas están
   amontonadas en el mismo sitio y no se puede señalar ninguna.
   El único hueco es el arranque: entre que el JS arma el dibujo y que el coche
   empieza a abrirse solo hay dos segundos y medio de trazado con la clase ya
   puesta, y ahí el cursor promete un enlace que todavía no se puede usar. Se
   deja así porque taparlo obligaría a que el CSS supiera qué hora es. */
.dp-link { cursor: pointer; }

.despiece.is-armed:not(.is-drawn) .dp-link,
.despiece.is-closed .dp-link { cursor: default; }

/* Señalar una pieza apaga las demás. Es la convención de cualquier plano de
   despiece y aquí además responde a la pregunta del cliente, que nunca es «qué
   hacéis» sino «qué le hacéis a ESTO».

   Con las demás se va también la pintura del casco. El casco no se desmonta y
   por eso no es una .dp-part, pero sus puertas son el mayor bloque de rojo del
   dibujo: dejarlas encendidas mientras se apagan las ocho piezas convertía al
   único elemento que NO se puede pedir en el más llamativo de la lámina. Se
   apaga solo el relleno, no el trazo: el coche se queda en línea —que es lo que
   ata cada pieza a su sitio— y el color queda para lo señalado. */
.dp-part,
.dp-static .dp-paint { transition: opacity 0.2s var(--ease-out); }

@media (hover: hover) and (pointer: fine) {
  /* Apaga cuando hay una pieza señalada, no cuando el ratón entra en la lámina.
     Esto era `.dp-svg:hover .dp-part`, y el dibujo es casi todo aire: cruzar la
     plancha camino de otra cosa, o pararse en el hueco entre dos piezas,
     bastaba para apagar las ocho sin haber señalado ninguna. Con `:has()` la
     condición es la que se quería desde el principio —hay una pieza debajo del
     puntero— y el `:not(:hover)` deja fuera a la señalada sin necesitar una
     segunda regla que la vuelva a encender.

     Va sola, sin coma, separada de la de la leyenda. Un navegador sin `:has()`
     tira la lista ENTERA, así que juntas se llevaría por delante también el
     atenuado desde la leyenda, que no lo necesita. Separadas, allí donde no
     haya `:has()` lo único que se pierde es el atenuado por ratón, y lo que
     queda es el dibujo entero encendido: el estado normal. */
  .dp-svg:has(.dp-part:hover) .dp-part:not(:hover),
  .dp-svg:has(.dp-part:hover) .dp-static .dp-paint { opacity: 0.22; }

  .despiece.has-on .dp-part,
  .despiece.has-on .dp-static .dp-paint { opacity: 0.22; }
  .despiece.has-on .dp-part.is-on { opacity: 1; }

  /* Y la señalada sube un escalón de luz, que es la otra mitad del gesto: las
     demás se apagan y ésta se enciende.
     No se repite aquí la fórmula del trazo. Se cambia el color al que ya apunta
     y el color-mix de más arriba se recalcula solo, así que sigue atado a --ex
     igual que todo lo demás: con el coche montado no tiñe nada —la mezcla es 0 %
     acento— y con el coche abierto llega a este acento en vez de al normal. Un
     hover a medio despiece interpola, no salta. */
  .dp-svg .dp-part:hover,
  .despiece.has-on .dp-part.is-on { --red-pieza: var(--red-pieza-on); }
}

/* Las llamadas solo existen donde se pueden leer. El dibujo mide 1420 unidades
   de ancho, así que el número, que son 20 unidades, sale a 12,1 px reales con la
   ventana justo en 1000 px (medido: lámina de 861 px, 0,6063 px por unidad) y se
   queda en 8 px a 700. Ese es el umbral, no una cifra redonda.

   La franja que queda sin números es la de 480 a 999 px: ahí sigue el reparto
   horizontal, el dibujo va a lo ancho y no hay hueco. Por debajo de 480 la
   lámina se pone vertical, el encuadre se acerca y ahí los números vuelven
   —abajo del todo, en su propia media query—, porque en un móvil la leyenda
   queda a un scroll de distancia y no ata nada. El coche se traza, se pinta y se
   abre igual en todos los tamaños: lo que desaparece es el rótulo, no la
   información. */
.dp-tags { display: none; }

@media (min-width: 1000px) {
  .dp-tags { display: inline; }
}

/* EL DESPIECE EN VERTICAL.

   En un móvil el ancho es justo lo que no sobra: a 375 px de ventana la lámina
   va a 0,26 px por unidad y el coche entero mide 143 px de alto. Repartir las
   piezas a lo ancho ahí no es un despiece, es una miniatura. Así que en móvil la
   caja se hace vertical (2:3), el encuadre se queda fijo en 1,45 en lugar de
   alejarse, y las piezas salen hacia arriba y hacia abajo con los vectores de
   móvil. La pieza más grande pasa de 63 a 105 px.

   Las cuentas, para que se puedan rehacer: con la caja a 2:3 y el viewBox a
   1420x800, `meet` escala por el ancho y sobra alto. La banda visible en
   coordenadas del dibujo, ya con el 1,45 aplicado, va de -482 a 987 en vertical
   y de 30 a 1010 en horizontal, y el reparto de ui.mjs cabe dentro con margen.
   Esa banda no cambia si crece el alto del viewBox: el relleno que deja `meet`
   crece exactamente lo mismo que baja el origen de la escala. Lo que sí la
   cambia es la proporción de la caja o el 1,45; si se toca cualquiera de los dos
   hay que rehacer la tabla de celdas que hay en ui.mjs, no basta con mover un
   vector.

   El corte está en 480 px y no más arriba porque la altura de la caja es 1,5
   veces el ancho: a 800 px de ventana pediría 1200 px de alto y no cabría en
   ninguna pantalla. La condición de alto mínimo es para el móvil tumbado, donde
   la caja vertical tampoco entra y vuelve el reparto horizontal. */
@media (max-width: 479px) and (min-height: 600px) {
  /* De borde a borde: el margen del contenedor son 32 px de los 375, y en una
     lámina que ya va justa de tamaño eso es un 9% de dibujo. */
  .dp-plate {
    margin-inline: calc(-1 * var(--gutter));
    border-inline-width: 0;
    padding: 0.5rem 0;
  }

  .dp-svg { aspect-ratio: 2 / 3; }
  .dp-world { transform: scale(1.45); }

  .dp-piece {
    transform: translate(calc(var(--mx, 0) * var(--ex) * 1px), calc(var(--my, 0) * var(--ex) * 1px));
  }

  .dp-leads--h { display: none; }
  .dp-leads--v { display: inline; }

  /* El número vuelve, el rótulo no. Con la lámina vertical el reparto es una
     rejilla de dos por cuatro y hay sitio encima de cada pieza para una
     pastilla, que es lo que ata el dibujo con la lista de servicios de abajo;
     sin él, ocho piezas volando no dicen a qué se refiere cada una. El rótulo
     largo sigue sin caber: "AIRE ACONDICIONADO" son 261 unidades y la banda
     visible mide 980 de ancho, así que se saldría o taparía media lámina.

     El 1,6 no es decorativo: a 430 px de ventana la escala es 0,44 px por
     unidad, y el número de 20 unidades se queda en 8,8 px, ilegible. Escalado
     mide 14 px y la pastilla 26,6, que a 38 unidades originales son 60,8 en
     coordenadas del dibujo; las celdas de ui.mjs están calculadas con ese
     tamaño y dejan 10 unidades de aire entre pastilla y pieza. El grupo de
     dentro existe solo para esto: escalar aquí no toca la traslación que coloca
     la pastilla, y con fill-box el centro del escalado es el de la propia
     pastilla y no el del lienzo. */
  .dp-tags { display: inline; }
  .dp-tag__l { display: none; }

  .dp-tag__b {
    transform: scale(1.6);
    transform-box: fill-box;
    transform-origin: center;
  }

}

/* Las llamadas aparecen con el recorrido: con el coche montado no hay nada que
   llamar, las cinco pastillas estarían amontonadas encima de la misma chapa. */
.dp-tag { opacity: var(--ex); }

/* La pastilla cae encima de la chapa, y la chapa acaba roja. Rojo sobre rojo no
   se lee, así que es tinta con filete claro: funciona igual sobre la retícula
   oscura que sobre la carrocería pintada. 15,9:1 el número sobre la pastilla. */
.dp-tag rect {
  fill: var(--ink);
  stroke: var(--paper-2);
  stroke-width: 1.4;
}

.dp-tag text {
  fill: var(--paper-2);
  font-family: var(--ff-mono);
  font-weight: 600;
  letter-spacing: 0.06em;
}

.dp-tag__n { font-size: 20px; }
.dp-tag__l { font-size: 22px; letter-spacing: 0.08em; }
.dp-tag.is-on rect { fill: var(--red-btn); }

/* Estado armado: lo que el JS cierra justo antes de animar. */
.despiece.is-armed { --ex: 0; }
.despiece.is-armed .dp-draw { stroke-dasharray: 1; stroke-dashoffset: 1; }
.despiece.is-armed .dp-paint { transform: scaleX(0); transform-box: fill-box; transform-origin: left center; }

.despiece.is-armed.is-drawn .dp-draw {
  animation: dp-trazo var(--t, 1s) var(--ease-out) var(--d, 0s) forwards;
}

/* 1,85 s no es un número redondo por capricho: el último trazo termina en 1,8 s
   y la pistola no entra hasta que la chapa está cerrada. Y el coche no se abre
   hasta que la pintura ha secado en pantalla (1,85 + 0,62 = 2,47 s): despiezar
   un coche a medio pintar contaría el proceso al revés. */
.despiece.is-armed.is-drawn .dp-paint { animation: dp-pintar 0.62s var(--ease-out) 1.85s forwards; }

/* La apertura de entrada es una ANIMACIÓN, no una transición, y la diferencia
   importa: la entrada necesita 2,55 s de espera para dejar terminar la pintura,
   pero el botón no puede heredar esa espera o volver a abrir el coche tardaría
   dos segundos y medio en arrancar. Una vez el usuario toca algo, .is-touched
   cancela la animación y a partir de ahí manda la transición, que es inmediata.
   Las dos reglas tienen la misma especificidad a propósito: gana la de abajo. */
.despiece.is-armed.is-drawn {
  animation: dp-abrir 1.15s var(--ease-out) 2.55s forwards;
}

.despiece.is-armed.is-touched {
  animation: none;
  --ex: 1;
  transition: --ex 0.7s var(--ease-out);
}

.despiece.is-armed.is-touched.is-closed { --ex: 0; }

@keyframes dp-trazo { to { stroke-dashoffset: 0; } }
@keyframes dp-pintar { to { transform: scaleX(1); } }
@keyframes dp-abrir { from { --ex: 0; } to { --ex: 1; } }

.dp-hint {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem 1rem;
  margin-top: clamp(0.6rem, 1.5vw, 1rem);
  font-size: 0.8125rem;
  color: #b6b3ac;
}

/* El botón solo aparece si hay JS para atenderlo: .is-armed lo pone el script.
   Un botón muerto en una portada es peor que no tenerlo. */
.despiece:not(.is-armed) .dp-hint { display: none; }

.dp-toggle {
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.6rem 1rem;
  border: var(--rule) solid var(--paper-2);
  background: transparent;
  color: var(--paper-2);
  cursor: pointer;
  transition: background-color 0.18s var(--ease-out), color 0.18s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .dp-toggle:hover { background: var(--paper-2); color: var(--ink); }
}

.dp-toggle:focus-visible { background: var(--paper-2); color: var(--ink); }

/* La leyenda es la que convierte el dibujo en algo útil: cada pieza lleva a su
   ficha real, y las cifras que cita son las del taller. Es además la única vía
   de navegación —el SVG va como imagen— así que aquí no puede faltar nada de lo
   que el dibujo insinúa. */
/* Columnas fijas y no `auto-fit`. Con ocho fichas, `minmax(180px, 1fr)` reparte
   seis arriba y dos abajo en un contenedor de 1200: la última fila queda coja y
   la retícula deja de leerse como retícula. Ocho se divide bien en 1, 2 y 4, así
   que son esos tres escalones y ninguno más. */
.dp-legend {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--rule);
  background: #3d3d39;
  border: var(--rule) solid #3d3d39;
  margin-top: var(--rule);
  list-style: none;
}

@media (min-width: 560px) {
  .dp-legend { grid-template-columns: repeat(2, 1fr); }
}

/* 1060 y no 1000: el cuarto escalón no lo decide el ancho de la ventana sino si
   el rótulo más largo cabe en su casilla de una tirada. Por debajo de 1060 la
   casilla baja de 240 px y "AIRE ACONDICIONADO" ya no entra ni al cuerpo mínimo,
   así que ahí siguen mandando dos columnas anchas, que se leen mejor que cuatro
   partidas. */
@media (min-width: 1060px) {
  .dp-legend { grid-template-columns: repeat(4, 1fr); }
}

/* Una fila es una línea: número a la izquierda y servicio al lado, como el
   índice de una lámina. En columna ocupaba 124 px por fila y la lista entera
   254; así son 48 y 98, y el dibujo y su índice caben juntos en la pantalla sin
   desplazar la página. La `baseline` alinea la cifra con la mayúscula del
   rótulo, no con su caja, que es lo que hace que la columna de números se lea
   como una columna. */
.dp-legend a {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  height: 100%;
  /* 13 px de relleno vertical y no 12: con el cuerpo mínimo la fila mide justo
     44 px, que es el dedo. A una línea por fila esto es un objetivo táctil que
     va al límite, así que se fija por cuenta y no a ojo. */
  min-height: 2.75rem;
  padding: 0.8125rem 0.9rem;
  background: var(--ink);
  text-decoration: none;
  color: var(--paper-2);
  transition: background-color 0.18s var(--ease-out), color 0.18s var(--ease-out);
}

.dp-legend b {
  flex: none;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  /* El rojo de marca sobre tinta da 4,52:1: pasa el AA por dos centésimas, y a
     11 px eso no es margen, es suerte. Este es el mismo rojo aclarado que ya usa
     el sitio para los kickers sobre fondo oscuro (8,58:1), así que además de
     legible es consistente. */
  color: #ff8a6b;
  transition: color 0.18s var(--ease-out);
}

/* El cuerpo se encoge con la ventana para que ningún rótulo parta en dos: una
   casilla de dos líneas estira su fila entera y rompe la retícula.
   El listón lo pone "AIRE ACONDICIONADO", que mide 12,2 px de ancho por cada
   píxel de cuerpo (medido en pantalla, no estimado). A cuatro columnas la
   casilla deja 177 px de rótulo en el peor caso y 243 en el mejor, así que el
   suelo de 14 px pide 171 y el techo de 16 px pide 195: entran los dos. */
.dp-legend__name {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: clamp(0.875rem, 1.25vw, 1rem);
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: -0.02em;
}

@media (hover: hover) and (pointer: fine) {
  .dp-legend a:hover { background: var(--red-btn); color: #fff; }
  .dp-legend a:hover b { color: #fff; }
}

.dp-legend a:focus-visible { background: var(--red-btn); color: #fff; }
.dp-legend a:focus-visible b { color: #fff; }

.panel--dark {
  background: var(--ink);
  border-color: var(--ink);
  border-left-color: var(--red);
  color: var(--paper-2);
}

.panel--dark p { color: #c4c2bc; }

.note-legal { font-family: var(--ff-mono); font-size: 0.6875rem; letter-spacing: 0.04em; line-height: 1.6; color: var(--muted); }

/* ---------- listas ---------- */
/* La marca de verificación deja de ser un "check" de plantilla y pasa a ser una
   cruz de suma: es el signo que se usa en un impreso para marcar una casilla y
   además ya lo tenemos dibujado en las cruces de registro. */
.ticks { display: grid; gap: 0.7rem; }

.ticks li {
  display: grid;
  grid-template-columns: 1.1rem 1fr;
  gap: 0.75rem;
  align-items: start;
  line-height: 1.5;
}

.ticks li::before {
  content: "";
  width: 1.1rem;
  height: 1.1rem;
  margin-top: 0.35rem;
  background:
    linear-gradient(var(--red), var(--red)) center / 100% 3px no-repeat,
    linear-gradient(var(--red), var(--red)) center / 3px 100% no-repeat;
}

.ticks--cols { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 0.7rem 1.5rem; }

.section--dark .ticks li::before {
  background:
    linear-gradient(var(--red), var(--red)) center / 100% 3px no-repeat,
    linear-gradient(var(--red), var(--red)) center / 3px 100% no-repeat;
}

/* Las píldoras de 999px eran el elemento más "web genérica" que quedaba.
   Ahora son rótulos rectangulares en monoespaciada, como las marcas troqueladas
   en el lomo de una pieza. */
.chips { display: flex; flex-wrap: wrap; gap: var(--rule); }

.chips li {
  border: var(--rule) solid var(--line-strong);
  background: var(--paper-2);
  border-radius: 0;
  padding: 0.3rem 0.7rem;
  font-family: var(--ff-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

:is(.section--dark, .cta-final) .chips li { background: var(--ink); border-color: #4a4a45; color: var(--paper-2); }

/* ---------- proceso ---------- */
/* El número del paso pasa de 1,8rem a un bloque de hasta 4,5rem: en un
   diagrama de proceso industrial el ordinal es lo primero que se lee desde
   lejos, no un adorno delante del título. */
.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: clamp(1rem, 2vw, 1.75rem);
  counter-reset: step;
}

.steps li {
  counter-increment: step;
  border-top: var(--rule) solid var(--line-strong);
  padding-top: 0.9rem;
}

.steps li::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: clamp(3rem, 5vw, 4.5rem);
  line-height: 0.85;
  letter-spacing: -0.05em;
  color: var(--red);
  margin-bottom: 0.7rem;
}

.steps h3 { font-size: 1.05rem; margin-bottom: 0.4rem; }
.steps p { color: var(--muted); font-size: 0.9375rem; }

.section--dark .steps li { border-top-color: #4a4a45; }
.section--dark .steps li::before { color: var(--red); }
.section--dark .steps p { color: #c4c2bc; }

/* ---------- reseñas ---------- */
.reviews-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  margin-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.aggregate {
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--ink);
  color: var(--paper-2);
  border-radius: 0;
  padding: 1rem 1.25rem;
}

.aggregate .score {
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: 3.25rem;
  line-height: 0.85;
  letter-spacing: -0.05em;
}

.aggregate .stars { color: var(--red); }

.aggregate small {
  display: block;
  font-family: var(--ff-mono);
  color: #b6b3ac;
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 0.45rem;
}

.review {
  background: var(--paper-2);
  border: var(--rule) solid var(--line-strong);
  border-radius: 0;
  padding: 1.5rem 1.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.review blockquote { font-size: 1.0625rem; line-height: 1.6; color: var(--ink-soft); }

.review figcaption {
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

/* La inicial del cliente pasa de círculo a placa cuadrada: es el último radio
   del sistema y con un 50% se leía como avatar de red social. */
.review .avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0;
  background: var(--ink);
  color: var(--paper-2);
  display: grid;
  place-items: center;
  font-family: var(--ff-display);
  font-weight: 400;
  font-size: 1.05rem;
  flex: none;
}

.review .who { font-family: var(--ff-mono); font-weight: 600; font-size: 0.8125rem; letter-spacing: 0.06em; text-transform: uppercase; line-height: 1.3; }
.review .who small { display: block; font-weight: 400; color: var(--muted); font-size: 0.625rem; letter-spacing: 0.1em; margin-top: 0.2rem; }

/* ---------- mapa ---------- */
.map {
  border: var(--rule) solid var(--line-strong);
  border-radius: 0;
  background: var(--paper-2);
  overflow: hidden;
  min-width: 0;
}

.map__stage {
  position: relative;
  /* width:100% es obligatorio: sin él, min-height + aspect-ratio calculan el ancho (260*16/9 = 462px)
     y el mapa se desborda en móvil. */
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: 260px;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 1.5rem;
  /* Papel milimetrado. Antes debajo de la cuadrícula había un degradado en
     diagonal que le daba un brillo de "mockup"; ahora es papel plano y las
     únicas líneas son la retícula de 44px, que es lo que hace que se lea como
     un plano de situación y no como una imagen de relleno. */
  background:
    repeating-linear-gradient(90deg, #cfccc4 0 1px, transparent 1px 44px),
    repeating-linear-gradient(0deg, #cfccc4 0 1px, transparent 1px 44px),
    var(--paper-2);
}

.map__stage iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.map__placeholder { max-width: 40ch; }
.map__placeholder p { color: var(--ink-soft); font-size: 0.875rem; line-height: 1.6; margin: 0.75rem 0 1.25rem; }

.map__pin { width: 2.25rem; height: 2.25rem; color: var(--red-btn); margin-inline: auto; }

.map__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1.5rem;
  padding: 1rem 1.25rem;
  border-top: var(--rule) solid var(--line-strong);
  font-family: var(--ff-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.7;
}

.map__foot address { font-style: normal; }
.map__foot strong { font-weight: 600; color: var(--ink); }

/* ---------- FAQ ----------
   Índice numerado, no una pila de acordeones separados: las preguntas van
   soldadas por un filete común, como los apartados de un pliego. La pregunta se
   queda en Barlow y en caja normal a propósito —una frase larga en mayúsculas
   monoespaciadas a 13px no hay quien la lea, y el sistema está para que el
   documento parezca técnico, no para que no se entienda. */
.faq {
  display: grid;
  max-width: 76ch;
  border: var(--rule) solid var(--line-strong);
  counter-reset: pregunta;
}

.faq details {
  background: var(--paper-2);
  border-radius: 0;
  counter-increment: pregunta;
}

.faq details + details { border-top: var(--rule) solid var(--line-strong); }

.faq details[open] { background: var(--surface); }

.faq summary {
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  padding: 1rem 1.25rem;
  cursor: pointer;
  font-family: var(--ff-text);
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.4;
  list-style: none;
}

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

.faq summary::before {
  content: counter(pregunta, decimal-leading-zero);
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  color: var(--red-ink);
  flex: none;
}

/* El signo no es una flecha que gira: es un "+" que se convierte en "−",
   que es cómo se marca un apartado plegado en un impreso. Se dibuja con dos
   barras de fondo y al abrir se encoge SOLO la vertical (background-size), así
   que la horizontal ni se mueve ni parpadea. Rotar el conjunto no serviría:
   un "+" girado 90° sigue siendo un "+". */
.faq summary::after {
  content: "";
  width: 0.9rem;
  height: 0.9rem;
  flex: none;
  margin-left: auto;
  align-self: center;
  background-image:
    linear-gradient(var(--red-btn), var(--red-btn)),
    linear-gradient(var(--red-btn), var(--red-btn));
  background-repeat: no-repeat;
  background-position: center, center;
  background-size: 100% 2px, 2px 100%;
  transition: background-size 0.2s var(--ease-in-out);
}

.faq details[open] summary::after { background-size: 100% 2px, 2px 0; }

.faq .answer { padding: 0 1.25rem 1.25rem 3.35rem; color: var(--ink-soft); }

/* ---------- formularios ----------
   Impreso a rellenar: rótulos en monoespaciada, casillas con filete completo y
   fondo de papel. Un campo con esquina redondeada y sombra interior delata la
   plantilla; uno rectangular sobre papel parece una hoja de recepción. */
.form-card {
  background: var(--paper-2);
  border: var(--rule) solid var(--line-strong);
  border-radius: 0;
  padding: clamp(1.25rem, 3vw, 2.25rem);
  box-shadow: none;
}

.form-grid { display: grid; gap: 1.2rem; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }

.field { display: grid; gap: 0.4rem; }
.field--wide { grid-column: 1 / -1; }

.field label {
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.field .req { color: var(--red-ink); }

.field .hint { font-family: var(--ff-mono); font-size: 0.625rem; letter-spacing: 0.06em; color: var(--muted); }

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.7rem 0.8rem;
  background: var(--surface);
  border: var(--rule) solid var(--line-strong);
  border-radius: 0;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.field textarea { min-height: 130px; resize: vertical; }

.field input:focus,
.field select:focus,
.field textarea:focus { background: var(--surface); border-color: var(--red-btn); }

.field [data-error] { font-family: var(--ff-mono); font-size: 0.6875rem; letter-spacing: 0.06em; color: var(--red-ink); font-weight: 600; }

.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--red-ink); background: #fbeeeb; }

.check {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.7rem;
  align-items: start;
  font-size: 0.9375rem;
  color: var(--ink-soft);
}

.check input { width: 1.15rem; height: 1.15rem; margin-top: 0.25rem; accent-color: var(--red-btn); }

.form-foot {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: 0.35rem;
}

.form-foot .note-legal { flex: 1 1 240px; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-status {
  grid-column: 1 / -1;
  border-radius: 0;
  padding: 0.9rem 1.1rem;
  font-family: var(--ff-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  line-height: 1.6;
  border: var(--rule) solid;
  border-left-width: 8px;
}

/* Los tres estados se distinguen por el color del filete izquierdo y por el
   texto, no por un fondo teñido: sobre papel un fondo verde pálido se lee como
   una mancha, y encima obligaba a bajar el contraste del texto. */
.form-status[hidden] { display: none; }
.form-status[data-kind="ok"] { background: var(--surface); border-color: var(--line-strong); border-left-color: #17663f; color: #14503a; }
.form-status[data-kind="error"] { background: var(--surface); border-color: var(--line-strong); border-left-color: var(--red); color: #8c2a16; }
.form-status[data-kind="info"] { background: var(--surface); border-color: var(--line-strong); border-left-color: var(--sw-4); color: #253a52; }

/* ---------- migas ---------- */
.breadcrumb {
  padding-block: 0.75rem;
  border-bottom: var(--rule) solid var(--line-strong);
  background: var(--paper-2);
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.breadcrumb ol { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; color: var(--muted); }
.breadcrumb li { display: flex; gap: 0.5rem; align-items: center; }
.breadcrumb li + li::before { content: "/" / ""; color: var(--red-ink); }
.breadcrumb a { color: var(--muted); padding-block: 0.25rem; text-decoration: none; }
.breadcrumb a:hover { color: var(--ink); text-decoration: underline; }
.breadcrumb [aria-current] { color: var(--ink); font-weight: 600; }

/* ---------- páginas de contenido ----------
   La cabecera interior era otra caja oscura. Ahora es papel con un filete
   inferior de 2px y el titular a tamaño de portada: la página interior es el
   mismo documento, no otra plantilla. */
.page-hero {
  background: var(--paper);
  color: var(--ink);
  padding-block: clamp(2rem, 4.5vw, 3.5rem);
  border-bottom: var(--rule) solid var(--line-strong);
  position: relative;
}

.page-hero::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 8px;
  background: linear-gradient(90deg, var(--sw-1) 0 30%, var(--sw-2) 30% 44%, var(--sw-3) 44% 58%, var(--sw-4) 58% 72%, var(--sw-5) 72% 86%, var(--sw-6) 86%);
}

/* Los titulares interiores no llegan al tamaño de la portada: son frases largas
   con topónimos ("Chapa y pintura de automóviles en Madrid") y a 100px ocuparían
   cuatro líneas de pantalla. 4rem con una medida de 18 caracteres los deja en
   dos o tres, que es lo que aguanta un titular en Archivo Black. */
.page-hero h1 { font-size: clamp(2.25rem, 5.5vw, 4rem); max-width: 18ch; }
.page-hero .lead { color: var(--ink-soft); margin-top: 1.25rem; }
.page-hero__grid { display: grid; gap: clamp(1.5rem, 3vw, 3rem); align-items: center; }

@media (min-width: 900px) {
  .page-hero__grid { grid-template-columns: 1.1fr 0.9fr; }
}

.prose { max-width: 68ch; }
.prose > * + * { margin-top: 1.15rem; }
.prose h2 { margin-top: 2.5rem; }
.prose h3 { margin-top: 1.75rem; }
.prose p { color: var(--ink-soft); }

.prose ul:not(.ticks):not(.chips) { display: grid; gap: 0.6rem; }

.prose ul:not(.ticks):not(.chips) li { padding-left: 1.35rem; position: relative; }

.prose ul:not(.ticks):not(.chips) li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.62em;
  width: 0.5rem;
  height: 0.5rem;
  background: var(--red);
}

.layout-aside { display: grid; gap: clamp(1.75rem, 4vw, 3rem); }

@media (min-width: 980px) {
  .layout-aside { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
  .layout-aside .aside-sticky { position: sticky; top: calc(var(--header-h) + 1.5rem); }
}

.aside-card {
  background: var(--paper-2);
  border: var(--rule) solid var(--line-strong);
  border-radius: 0;
  padding: 1.35rem;
}

.aside-card + .aside-card { margin-top: 1rem; }

.aside-card h3 { font-size: 1.1rem; margin-bottom: 0.9rem; }
.aside-card .swatches { margin: -1.35rem -1.35rem 1.1rem; width: auto; height: 8px; }
.aside-card dl { display: grid; gap: 0.7rem; font-size: 0.9375rem; }
.aside-card dt { font-family: var(--ff-mono); font-weight: 600; font-size: 0.625rem; letter-spacing: 0.14em; text-transform: uppercase; }
.aside-card dd { margin: 0; color: var(--ink-soft); }

/* ---------- CTA final ----------
   Tinta plena. Es el único bloque negro grande de la página junto al pie, y va
   justo antes de él: el documento termina con dos áreas impresas seguidas, que
   es como se cierra un folleto industrial. */
.cta-final { background: var(--ink); color: var(--paper-2); border-top-color: var(--ink); position: relative; }
.cta-final .swatches { position: absolute; inset: 0 0 auto 0; height: 10px; }
.cta-final .kicker { color: #ff8a6b; }
.cta-final p { color: #c4c2bc; }

.cta-grid { display: grid; gap: clamp(1.5rem, 3vw, 3rem); align-items: center; }

@media (min-width: 900px) { .cta-grid { grid-template-columns: 1.1fr 0.9fr; } }

/* Los tres contactos son una tabla soldada por filetes, no tres tarjetas
   sueltas: en negativo, el hueco de 2px del papel hace de línea. */
.contact-list {
  display: grid;
  gap: var(--rule);
  background: var(--paper-2);
  border: var(--rule) solid var(--paper-2);
}

.contact-list a {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  padding: 1rem 1.15rem;
  border-radius: 0;
  color: var(--paper-2);
  text-decoration: none;
  background: var(--ink);
  transition: background-color 0.15s ease, color 0.15s ease;
}

.contact-list a:hover { background: var(--paper-2); color: var(--ink); }
.contact-list a:hover svg { color: var(--red-btn); }
.contact-list svg { width: 1.4rem; height: 1.4rem; flex: none; color: var(--red); transition: color 0.15s ease; }
.contact-list b { display: block; font-family: var(--ff-mono); font-weight: 600; font-size: 1rem; letter-spacing: 0.04em; }
.contact-list small { display: block; font-family: var(--ff-mono); font-size: 0.625rem; letter-spacing: 0.12em; text-transform: uppercase; color: #b6b3ac; margin-top: 0.25rem; }
.contact-list a:hover small { color: var(--muted); }

/* ---------- footer ---------- */
.site-footer { background: var(--ink); color: #b6b3ac; font-size: 0.9375rem; }
.site-footer .swatches { height: 10px; }

.footer-grid {
  display: grid;
  gap: clamp(1.75rem, 3vw, 2.5rem);
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
}

@media (min-width: 760px) { .footer-grid { grid-template-columns: 1.3fr 1fr 1fr; } }
@media (min-width: 1080px) { .footer-grid { grid-template-columns: 1.4fr 1fr 1fr 1.1fr; } }

.site-footer h3 {
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper-2);
  padding-bottom: 0.7rem;
  border-bottom: 1px solid #3d3d39;
  margin-bottom: 1rem;
}

.site-footer a { color: #ddd9d2; text-decoration: none; }
.site-footer a:hover { color: #fff; text-decoration: underline; }
.site-footer ul { display: grid; gap: 0.55rem; }
.site-footer .logo { color: var(--paper-2); margin-bottom: 1rem; }
.site-footer .logo__name span { color: #ff8a6b; }
.site-footer .logo__sub { color: #97948e; }
.site-footer address { font-style: normal; line-height: 1.7; }

/* Gris cálido, no azulado: el sustrato es papel, y un gris frío sobre tinta
   carbón delata que el color viene de otro sistema. 6,55:1 sobre #0a0a0a. */
.site-footer .dim { color: #97948e; }

.legal-bar {
  border-top: var(--rule) solid #3d3d39;
  padding-block: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
  font-family: var(--ff-mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.7;
  color: #97948e;
}

.legal-bar ul { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }

/* ---------- WhatsApp flotante + cookies ---------- */
.wa-float {
  /* Cuánto sube el botón para no quedar tapado por la barra de cookies.
     Se controla con una variable para poder combinarlo con el realce del
     ratón y con la pulsación sin que un transform pise al otro. */
  --wa-shift: 0px;
  position: fixed;
  right: var(--wa-gap);
  bottom: var(--wa-gap);
  /* El alto no es decorativo: es el hueco que otros tienen que dejarle (ver
     --wa-h en :root). Va como min-height y no como height para que un zoom de
     texto grande pueda estirarlo en vez de recortarlo. */
  min-height: var(--wa-h);
  z-index: 80;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  background: var(--wa);
  color: var(--wa-ink);
  padding: 0.7rem 1.1rem 0.7rem 0.85rem;
  /* La píldora de 999px con sombra difusa era el último resto de interfaz
     genérica que quedaba flotando sobre el documento. Ahora es una placa
     rectangular con filete negro y segunda tinta desplazada, igual que los
     botones: si está encima de todo, que al menos sea del mismo sistema. */
  border-radius: 0;
  border: var(--rule) solid var(--ink);
  box-shadow: 5px 5px 0 var(--ink);
  text-decoration: none;
  font-family: var(--ff-mono);
  font-weight: 600;
  font-size: 0.8125rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  /* Se aparta con translateY, no subiendo el bottom: animar bottom recoloca el
     elemento en cada fotograma; translateY no toca el layout. */
  transform: translateY(var(--wa-shift));
  transition: transform 0.24s var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .wa-float:hover { color: var(--wa-ink); transform: translateY(calc(var(--wa-shift) - 2px)); }
}

.wa-float:active { transform: translateY(var(--wa-shift)) scale(0.97); transition-duration: 0.1s; }

.wa-float svg { width: 1.5rem; height: 1.5rem; }

body[data-cookie-open="true"] .wa-float { --wa-shift: -190px; }

@media (min-width: 700px) {
  body[data-cookie-open="true"] .wa-float { --wa-shift: -130px; }
}

.cookie-bar {
  position: fixed;
  inset: auto 0 0 0;
  z-index: 95;
  background: var(--paper-2);
  /* Doble filete: rojo grueso arriba y negro debajo. Una sombra difusa hacia
     arriba no separaba nada que no separe ya un filete de 6px, y el sistema no
     admite desenfoques. */
  border-top: 6px solid var(--red);
  box-shadow: 0 -2px 0 var(--ink);
  padding: 1.1rem 0 1.25rem;
}

.cookie-bar[hidden] { display: none; }

.cookie-bar__inner {
  display: grid;
  gap: 1rem;
  align-items: center;
}

@media (min-width: 860px) { .cookie-bar__inner { grid-template-columns: 1fr auto; } }

.cookie-bar p { font-size: 0.875rem; line-height: 1.6; color: var(--ink-soft); max-width: 76ch; }
.cookie-bar h2 { font-size: 1.15rem; letter-spacing: -0.03em; margin-bottom: 0.45rem; }
.cookie-bar .btn-row { margin-top: 0; }

/* ---------- utilidades ---------- */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.muted { color: var(--muted); }
.center { text-align: center; }
.mt-1 { margin-top: 0.75rem; }
.mt-2 { margin-top: 1.5rem; }
.mt-3 { margin-top: 2.25rem; }
.maxw { max-width: 62ch; }

/* ---------- movimiento reducido ---------- */
/* "Menos movimiento" no es "ningún cambio". Quien activa esta preferencia suele
   marearse con los desplazamientos, no con que un borde cambie de color: las
   transiciones de color y opacidad se conservan porque explican qué está
   pasando. Lo que desaparece es el movimiento por el movimiento.
   Este bloque va al final del archivo a propósito: gana por orden de aparición
   a las reglas anteriores, que tienen la misma especificidad. */
@media (prefers-reduced-motion: reduce) {
  /* Desplazamientos puramente decorativos: fuera. */
  .btn:hover,
  .card:hover .arrow,
  .card:focus-visible .arrow,
  .link-more:hover::after,
  .nav-toggle:active,
  .site-menu { transform: none; }

  /* La segunda tinta desplazada del botón SE QUEDA aunque el botón no se mueva.
     Aparecer no es desplazarse: quien activa esta preferencia se marea con el
     movimiento, no con que surja una sombra dura. Y sin ella el botón se
     quedaría sin ninguna señal de que el ratón está encima. */

  /* El botón seguía necesitando acusar la pulsación, así que la señal pasa de
     movimiento a oscurecimiento. */
  .btn:active { transform: none; box-shadow: 0 0 0 var(--btn-plate); filter: brightness(0.9); }

  .wa-float:hover { transform: translateY(var(--wa-shift)); }
  .wa-float:active { transform: translateY(var(--wa-shift)); filter: brightness(0.92); }

  /* Estos transforms sí se mantienen porque son estado, no adorno: el aspa del
     menú y la posición del botón de WhatsApp cuando esquiva la barra de
     cookies. Lo que se elimina es la animación: cambian de golpe. */
  .skip-link,
  .wa-float,
  .faq summary::after,
  .nav-toggle__box i { transition-duration: 0.001ms; }

  /* .card no entra en la lista de arriba: su realce ya no era un movimiento,
     es un cambio de tinta de la casilla entera, que es justo la clase de
     transición que conviene conservar. Igual el menú, que sigue apareciendo
     con un fundido pero sin deslizar. */

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
  }

  /* La intro del hero no se acorta: se apaga. Acortarla a 0,001 ms como todo lo
     demás deja pasar un fotograma con el mando a 0, que en el resto del sitio no
     se ve pero aquí es la portada entera en blanco. Sin animación, el mando se
     queda en su valor inicial —1, el hero asentado— y no hay nada que ver. */
  .intro-hero .hero { animation: none; }
}
