/* ============================================================
   Dental Studio — chrome compartido y secciones
   ------------------------------------------------------------
   Lo usan index.html, equipo/index.html y 404.html. Los legales
   NO: tienen su propio chrome (.lg-*) y no se tocan.

   Los TOKENS no están acá: van inline en el <head> de cada
   página (críticos para el FCP). Este archivo solo los consume.
   Fuente de verdad de los tokens: tokens/*.css.

   ⚠️ Al editar este archivo hay que subir su ?v= en las páginas
   que lo enlazan, EN EL MISMO CAMBIO (README-NUEVA-WEB §6): el
   HTML va sin caché pero el CSS se cachea una hora.

   La marca es monocroma, rectilínea y plana. Sin gradientes, sin
   pills, sin blur, sin sombras salvo la de tarjeta.
   ============================================================ */

/* ---------------- Base ---------------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { overflow-x: clip; }   /* clip, no hidden: hidden rompe position:sticky */
html { scroll-behavior: smooth; }
body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: var(--font-brand);
  font-size: var(--text-base);
  font-weight: var(--weight-body);
  line-height: var(--leading-normal);
  color: var(--text-primary);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; }
img { max-width: 100%; }
:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

.container { width: min(var(--container-max), 100%); margin-inline: auto; padding-inline: var(--container-pad); }

/* Label de marca: mayúscula, tracking .18em. El recurso jerárquico más usado del
   manual — reemplaza a los iconos y a los colores, que la marca no tiene. */
.label {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: var(--weight-body-strong);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-secondary);
}
.rule { width: 100%; height: 1px; border: 0; background: var(--border-strong); margin: 0; }

/* ---------------- El filete de sección ----------------
   El gesto del escáner —el filete que se traza de izquierda a derecha— vivía
   una sola vez, en el hero, en los primeros 500 ms. Después la página se
   congelaba. Ahora lo repite cada sección al entrar en viewport, que es lo que
   le da ritmo al scroll sin sumar un solo elemento nuevo de diseño.

   No lleva JS: se cuelga del `data-visible` que ya pone el IntersectionObserver
   de los reveals. Y sin JS queda dibujado, porque el estado final es el default.

   ⚠️ El CTA NO lo lleva a propósito: es el cierre, no otra sección para
   escanear, y ya tiene su propio filete separando los datos de contacto. */
/* ⚠️ Ancho COMPLETO, no acotado. Estuvo en `max-width: 420px` y en la FAQ quedaba
   pegado (hueco de 0px) al borde superior del primer ítem del acordeón: los dos
   filetes se leían como UNA sola línea que cambiaba de peso a la mitad. Entero, el
   gesto se lee como lo que es — y de paso el trazo dura más, que era la idea. */
.rule--seccion { margin-top: var(--space-5); }
/* Y en la FAQ, aire para que no se solape con el borde del primer ítem. */
.s-faq .rule--seccion { margin-bottom: var(--space-5); }
.js .reveal .rule--seccion {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 560ms var(--ease-brand);
  /* Arranca cuando el texto de arriba ya entró: primero se lee, después se
     subraya. Al revés se lee como una barra de carga. */
  transition-delay: calc(var(--reveal-delay, 0ms) + 120ms);
}
.js .reveal[data-visible="1"] .rule--seccion { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) {
  .js .reveal .rule--seccion { transform: none; transition: none; }
}

/* Display grande en LIGHT: el énfasis de la marca viene de engordar a tamaño
   chico, no de agrandar. (Manual, "Tipografía institucional".) */
.display {
  margin: 0;
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-wide);
  color: var(--text-primary);
  text-wrap: balance;
}
.display--xl { font-size: clamp(32px, 6.4vw, var(--text-display)); }
.display--lg { font-size: clamp(28px, 4.6vw, var(--text-3xl)); }
.display--md { font-size: clamp(24px, 3.4vw, var(--text-xl)); }
.lead { margin: 0; max-width: 56ch; font-size: var(--text-lg); line-height: var(--leading-relaxed); text-wrap: pretty; }

/* ⚠️ MARCADOR DE DATO PENDIENTE DEL CLIENTE.
   Mismo criterio que en los legales: negro pleno, imposible de pasar por alto.
   La marca no tiene color, así que grita con contraste, no con matiz.
   NO DEBE QUEDAR NINGUNO AL PUBLICAR (README del repo, §7). */
.todo {
  background: var(--black);
  color: var(--white);
  padding: 1px 6px;
  font-weight: var(--weight-body-strong);
  font-size: .9em;
}
.todo-bloque {
  margin: var(--space-4) 0;
  padding: var(--space-4) var(--space-5);
  border-left: 3px solid var(--black);
  background: var(--gray-100);
  font-size: var(--text-sm);
}
.todo-bloque p { margin: 0; }
.todo-bloque p + p { margin-top: var(--space-2); }

/* ---------------- Botones ----------------
   Variante `primary` del design system: sólido gris oscuro, mayúscula,
   tracking .18em. Hover: un paso más oscuro. Sin escalados en el press. */
.btn {
  display: inline-block;
  font-family: inherit;
  font-size: var(--text-xs);
  font-weight: var(--weight-body-strong);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1;
  padding: 16px 32px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--duration-fast) var(--ease-brand),
              color var(--duration-fast) var(--ease-brand),
              border-color var(--duration-fast) var(--ease-brand),
              transform 120ms var(--ease-brand);
}
/* ---------------- Respuesta al toque ----------------
   ⚠️ Hasta el 19/8 no había un solo `:active` en toda la hoja: toda la respuesta
   a la interacción era `:hover`, o sea **nada en mobile**, que es donde cae la
   mayoría del tráfico. El press de 1px es corto a propósito — no es una
   animación, es acuse de recibo.

   ⚠️ En iOS Safari el `:active` NO se aplica salvo que el elemento tenga
   `cursor: pointer` o un listener táctil. Por eso los `summary` lo llevan. */
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--gray-dark); color: var(--gray-light); border: 1px solid var(--gray-dark); }
.btn--primary:hover { background: var(--black); border-color: var(--black); }
.btn--secondary { background: transparent; color: var(--gray-dark); border: 1px solid var(--gray-dark); }
.btn--secondary:hover { background: var(--gray-dark); color: var(--gray-light); }
/* Sobre banda oscura se invierten los roles. */
.on-dark .btn--primary { background: var(--gray-light); color: var(--gray-dark); border-color: var(--gray-light); }
.on-dark .btn--primary:hover { background: var(--white); border-color: var(--white); }
.on-dark .btn--secondary { color: var(--gray-light); border-color: var(--border-on-inverse); }
.on-dark .btn--secondary:hover { background: var(--gray-light); color: var(--gray-dark); border-color: var(--gray-light); }
.btn[disabled] { opacity: .5; cursor: progress; }
.acciones { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; }

/* ============================================================
   TEXTURA DE FONDOS — regla de sistema
   ------------------------------------------------------------
   El sitio tiene DOS gráficos de marca y cada uno tiene su puesto:

   · **Trama** (el patrón poligonal del manual) = TEXTURA DE FONDO.
     Es la única que se usa de fondo, y se usa en las dos bandas:
     `--trama-light` (líneas blancas) sobre gris oscuro y
     `--trama-dark` (líneas casi negras) sobre gris claro. MISMO
     tile y MISMA escala en las dos, para que se lea como una sola
     superficie con dos temperaturas.

   · **Malla de escaneo** (scan-molares) = PROTAGONISTA DEL HERO.
     No se usa de fondo en ninguna sección. Tuvo un eco de marca de
     agua en servicios y FAQ y se quitó el 2026-08-16: dos gráficos
     compitiendo por el mismo trabajo confunden el sistema, y la
     malla pierde fuerza si aparece en todos lados.

   En ambos casos la intensidad busca el mismo punto: se descubre si
   la señalás, no se ve si no la buscás.
   ============================================================ */
.banda-textura { position: relative; }
.banda-textura::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--trama-dark);     /* líneas oscuras sobre el gris claro */
  background-size: var(--trama-size) var(--trama-size);
  background-repeat: repeat;
  /* .055 deja el trazo en ~#D9D9D9 sobre el #E3E3E3: un delta de 11 en 8 bits.
     Sobre fondo claro la textura se lee más que sobre el oscuro con la misma
     opacidad, así que va más baja que la de la banda inversa para que las dos
     se perciban igual de calladas. */
  opacity: var(--textura, .055);
  pointer-events: none;
}
.banda-textura > * { position: relative; }

/* ---------------- Banda inversa ----------------
   La misma trama, en su versión clara sobre el gris oscuro. */
.banda-inversa {
  position: relative;
  background: var(--surface-inverse);
  color: var(--text-inverse);
  overflow: hidden;          /* contiene la trama en su propia sección */
}
.banda-inversa::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--trama-light);
  background-size: var(--trama-size) var(--trama-size);
  background-repeat: repeat;
  /* .07, no .12: sobre el gris oscuro la trama va DETRÁS de texto blanco, y a
     .12 el trazo subía a ~#4F4E4E (delta 24 sobre el fondo) — suficiente para
     picar el ojo al leer. A .07 el delta baja a ~14, del orden del que tiene la
     banda clara. El pico de la luz que pasa vuelve a rozar el .12 de antes, así
     que la intensidad vieja sigue existiendo: ahora es el máximo, no el suelo. */
  opacity: var(--textura-inversa, .07);
}
.banda-inversa > * { position: relative; }

/* ============================================================
   FONDOS VIVOS — el texturado SE DIBUJA al entrar la banda
   ------------------------------------------------------------
   Cuarta versión. Las tres anteriores partían del mismo error: la
   textura ya estaba puesta y algo le pasaba por encima. Primero
   una nube grande (se leía como cambio de ambiente), después una
   barra angosta (se leía, pero apenas), después una ventana
   enmascarada que la revelaba. El problema no era la intensidad:
   **sobre un fondo que ya tiene textura, agregarle un poco más no
   es un evento**.

   Ahora la textura NO ESTÁ hasta que el escáner pasa, y además se
   DIBUJA: el módulo de marca en trazos, en mosaico, cada tile
   trazándose con `stroke-dasharray` como la arcada del hero. Es
   literalmente el mismo gesto, y por eso se lee como una sola idea.

   Ganó un A/B contra la versión de máscara (cerrado el 19/8):
   Lighthouse móvil, mediana de 5 en local, **91 contra 90 · TBT 0
   contra 11 ms · mismo LCP y mismo heap**. Cuesta +2 KB y +536
   nodos de DOM. O sea que salió gratis, y el mecanismo perdedor se
   borró entero — incluido el interruptor `data-fondo`.

   ⚠️ Si el módulo no llega (red caída, 2,5 s de espera agotados) NO
   hay animación de respaldo: se quitan las capas y vuelve la
   textura estática del `::before`, que es el estado por defecto de
   siempre y el que ven quienes no tienen JS.

   ⚠️ El dashoffset va en el <use>, no en los <path>. Es la clave de
   que esto sea barato: `stroke-dasharray` y `stroke-dashoffset` son
   propiedades HEREDADAS, así que puestas en el <use> bajan a los 16
   paths clonados. 130 tiles en una banda son 130 animaciones, no
   2.080.

   ⚠️ Y `pathLength="1"` va en los paths del asset. Los 16 segmentos
   del módulo miden cosas muy distintas; sin normalizar, los cortos
   terminan primero y el tile se dibuja a los saltos.

   Dirección: en las bandas claras el trazo avanza de izquierda a
   derecha y de arriba a abajo; en las oscuras, de derecha a
   izquierda y de arriba a abajo. **No se espeja nada** — el retardo
   de cada tile se calcula al revés y listo. Espejar el contenedor
   espejaría el módulo dibujado.
   ============================================================ */
.tex-js::before { display: none; }        /* la capa dibujada reemplaza al ::before */

.banda-textura > .tex,
.banda-inversa > .tex {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.tex__svg {
  /* Perillas del efecto. Viven aca y no en tokens/: son del componente, no de
     la marca.

     ⚠️ La proporcion entre las dos ES EL ANCHO DEL FRENTE. Con 1500 de tile
     sobre 2600 de barrido hay en todo momento mas de media banda a medio
     dibujar, y ese solape es lo que hace que el borde se lea como un degradado
     y no como una linea. Si se acorta --trazo-dur, el frente se endurece.

     El barrido se alargo dos veces (1250 -> 1800 -> 2600): el pedido fue verlo
     mas lento de costado, no que cada trazo tardara mas. */
  --trazo-dur: 1500ms;       /* lo que tarda UN tile en dibujarse */
  --trazo-barrido: 2600ms;   /* lo que tarda el frente en cruzar la banda */
  position: absolute;
  top: 0; left: 0;
  overflow: visible;
  opacity: var(--textura, .075);
  color: var(--gray-dark);
}
.banda-inversa .tex__svg { opacity: var(--textura-inversa, .095); color: var(--white); }

.js .tex--trazo[data-rev="listo"] .tex__svg use { stroke-dasharray: 1; stroke-dashoffset: 1; }
.js .tex--trazo[data-rev="va"] .tex__svg use {
  stroke-dasharray: 1;
  /* `both` y no `forwards`: durante el retardo, forwards deja el valor normal
     —o sea el tile YA dibujado— y se ve todo el mosaico aparecer de golpe
     antes de empezar. Es el mismo error que ya se cazo en el hero. */
  animation: trama-traza var(--trazo-dur, 1500ms) linear both;
  animation-delay: calc(var(--o, 0) * var(--trazo-barrido, 2600ms));
}
@keyframes trama-traza {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

/* ⭐ EL FADE. La trama se llena poco mas de media banda y se apaga; NO cubre
   todo el ancho. Es lo que hace que la B se lea como la A, y lo que faltaba en
   la primera version: sin esto la banda queda texturada de punta a punta.

   Los cortes estan MEDIDOS de la variante A en reposo, no calculados: alli la
   mascara vive sobre un elemento de 300% de ancho y desplazado, asi que sus
   porcentajes no son los de la banda. En coordenadas de banda da opaca hasta el
   42%, media al 54% y apagada al 66%. Eso es lo que se copia.

   Va en `.tex` y no en `.tex__svg` porque `.tex` mide exactamente la banda; el
   SVG es mas ancho (se teje con una columna de sobra) y los porcentajes saldrian
   corridos. */
.js .tex--trazo {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 42%, transparent 66%);
          mask-image: linear-gradient(to right, #000 0, #000 42%, transparent 66%);
}
.js .banda-inversa > .tex--trazo {
  -webkit-mask-image: linear-gradient(to left, #000 0, #000 42%, transparent 66%);
          mask-image: linear-gradient(to left, #000 0, #000 42%, transparent 66%);
}

/* Terminado. Sin dasharray, o sea dibujo pleno: es lo que permite volver a
   tejer el mosaico cuando la banda cambia de alto sin que el trazo rebobine. */
.js .tex--trazo[data-rev="hecho"] .tex__svg use {
  stroke-dasharray: none;
  stroke-dashoffset: 0;
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .tex--trazo[data-rev] .tex__svg use {
    animation: none !important;
    stroke-dasharray: none !important;
    stroke-dashoffset: 0 !important;
  }
}

.banda-inversa .label { color: var(--text-on-dark-muted); }
.banda-inversa .display { color: var(--text-on-dark-strong); }

/* ---------------- Navbar ----------------
   Se inyecta desde site.js: una sola fuente de verdad para todas las páginas.
   No emite headings (a11y): el outline de encabezados es de la página. */
/* ---------------- Sello de version (solo fuera de produccion) ----------------
   Utillaje, no marca: sirve para saber de un vistazo si lo que se ve es el
   ultimo deploy o una copia cacheada. Lo inyecta site.js y solo cuando el
   hostname NO es dentalstudio.ar, asi el mismo archivo va a las dos ramas y la
   invariante de que main y testing difieren solo en .htaccess/robots/sitemap
   sigue en pie.

   Se dibuja deliberadamente feo y chico: tiene que leerse como andamio y no
   como parte del sitio, para que nadie lo confunda con un elemento de diseno. */
.nav__sello {
  /* ⚠️ FUERA DEL FLUJO. Puesto en linea engordaba la barra 4px, y como la altura
     de la barra se reserva con el hueco #site-nav, eso corria el hero entero y
     en 1024x768 dejaba los botones bajo el pliegue. Un andamio no puede mover
     la maqueta que viene a diagnosticar. */
  /* ⚠️ ESQUINA INFERIOR, NO EL CENTRO DE LA BARRA. Hasta el 27/8 iba en
     `left: 50%` y el menu tambien esta centrado: en desktop el recuadro caia
     ENCIMA de «Experiencia» y «Preguntas» y los tapaba. Se vio en el preview del
     27/8 y lo confirmaron dos herramientas por separado. Abajo a la izquierda no
     hay nada con lo que competir a ningun ancho, y sigue siendo un andamio
     visible de un vistazo. Lo vigila `verificar_sello.py`, que ahora falla si
     solapa cualquier cosa del chrome. */
  position: fixed;
  left: 8px;
  bottom: 8px;
  padding: 2px 6px;
  background: var(--surface-page);
  z-index: 60;
  border: 1px dashed var(--border-subtle);
  border-radius: 2px;
  font-size: 11px;
  line-height: 1.2;
  letter-spacing: .04em;
  font-variant-numeric: tabular-nums;
  color: var(--text-secondary);
  white-space: nowrap;
  user-select: all;              /* para poder copiarlo de un doble clic */
}
@media (max-width: 899px) { .nav__sello { display: none; } }

/* La barra queda fija y su altura se RESERVA con el hueco #site-nav.
   ⚠️ Esto no es un capricho: con la barra en el flujo, compactarla al scrollear
   le quita altura y empuja hacia arriba todo lo que tiene debajo — un salto de
   layout de 0.094 de CLS, medido. Con el hueco reservado, la barra encoge por
   encima del contenido y no mueve nada.
   La reserva la afina site.js midiendo la barra real; el valor del CSS existe
   para que no haya salto mientras el JS no corrió (y para que funcione sin JS). */
/* ⚠️ El valor vive en :root y NO en #site-nav, porque tiene DOS consumidores:
   el hueco que reserva la barra y el `min-height` del hero, que descuenta ese
   mismo alto para quedarse con la primera pantalla justa. Estuvieron escritos a
   mano en los dos sitios (77 y 81 repetidos) con un comentario pidiendo que
   quien tocara uno tocara el otro; eso es exactamente el tipo de acuerdo que se
   rompe callado. Ahora es un token y no hay nada que sincronizar.
   El `style` que site.js escribe sobre #site-nav con la medida real sigue
   ganando para el hueco, que es lo que se quiere: gana el elemento sobre :root. */
:root { --nav-reserva: 77px; }
@media (min-width: 960px) { :root { --nav-reserva: 81px; } }
#site-nav { height: var(--nav-reserva); }

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  border-bottom: var(--border-hairline);
  background: var(--surface-page);
  transition: background var(--duration-base) var(--ease-brand),
              box-shadow var(--duration-base) var(--ease-brand);
}
.nav.is-compacta { background: var(--white); box-shadow: 0 1px 12px rgba(46, 36, 30, .10); }
/* `nowrap` no es cosmético: hace que la altura de la barra sea DETERMINISTA.
   Con wrap, mientras la webfont no cargó los enlaces miden más ancho, la barra
   se parte en dos líneas y su alto no coincide con el hueco reservado — con el
   contenido metiéndose debajo de la barra hasta que la fuente llega. */
.nav__inner {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: nowrap;
  gap: var(--space-5);
  padding-block: var(--space-4);
  transition: padding-block var(--duration-base) var(--ease-brand);
}
.nav__links a { white-space: nowrap; }
.nav.is-compacta .nav__inner { padding-block: var(--space-2); }
.nav__brand img { transition: width var(--duration-base) var(--ease-brand); }
.nav.is-compacta .nav__brand img { width: 150px; }

/* Todo lo que se enlaza por ancla tiene que despejar la navbar pegajosa. */
[id] { scroll-margin-top: var(--nav-alto, 80px); }
.nav__brand { display: inline-block; line-height: 0; flex: none; }
.nav__brand img { width: 180px; height: auto; display: block; }
.nav__links { display: none; align-items: center; gap: var(--space-6); }
.nav__links a {
  font-size: var(--text-xs);
  font-weight: var(--weight-body-strong);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text-secondary);
  padding-block: var(--space-2);
  border-bottom: 1px solid transparent;
  transition: color var(--duration-fast) var(--ease-brand), border-color var(--duration-fast) var(--ease-brand);
}
.nav__links a:hover { color: var(--text-primary); }
.nav__links a[aria-current="page"] { color: var(--text-primary); border-bottom-color: var(--border-strong); }
.nav__cta { display: none; }

/* Hamburguesa por debajo de 820px */
.nav__toggle {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 10px;
  background: transparent; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav__toggle span { display: block; height: 1px; background: var(--gray-dark); transition: transform var(--duration-base) var(--ease-brand), opacity var(--duration-fast) var(--ease-brand); }
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.nav__panel { display: none; border-top: var(--border-hairline); padding-block: var(--space-4) var(--space-5); }
.nav__panel[data-abierto="1"] { display: block; }
.nav__panel a {
  display: block; padding-block: var(--space-3);
  font-size: var(--text-sm); font-weight: var(--weight-body-strong);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  text-decoration: none; color: var(--text-primary);
  border-bottom: var(--border-hairline);
}
.nav__panel .btn { margin-top: var(--space-5); }

/* 960 y no 820: a 820 el logo + los cuatro enlaces + el CTA no entran en una
   línea y la barra se partía en dos, pasando de 77 a 103px de alto. */
@media (min-width: 960px) {
  .nav__links, .nav__cta { display: flex; }
  .nav__toggle, .nav__panel { display: none !important; }
  .nav__brand img { width: 200px; }
  .nav__links { gap: var(--space-5); }
}

/* ---------------- Footer ---------------- */
.ft { margin-top: auto; border-top: var(--border-hairline); padding-block: var(--space-7) var(--space-6); }
.ft__grid { display: grid; gap: var(--space-6); }
@media (min-width: 720px) { .ft__grid { grid-template-columns: 1.4fr 1fr 1fr; gap: var(--space-7); } }
.ft__brand img { width: 190px; height: auto; display: block; }
.ft__claim { margin: var(--space-4) 0 0; max-width: 34ch; font-size: var(--text-sm); color: var(--text-secondary); }
/* Los títulos de columna van como <p>, NO como <h4>: el chrome no debe ensuciar
   el outline de encabezados de la página (README-NUEVA-WEB §5.4). */
.ft__title { margin: 0 0 var(--space-3); font-size: var(--text-xs); font-weight: var(--weight-body-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-primary); line-height: var(--leading-normal); }
.ft__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.ft__list a, .ft__list span { font-size: var(--text-sm); color: var(--text-secondary); text-decoration: none; }
.ft__list a:hover { color: var(--text-primary); text-decoration: underline; text-underline-offset: 4px; }
.ft__legal { display: flex; flex-wrap: wrap; gap: var(--space-4) var(--space-6); align-items: center; justify-content: space-between; margin-top: var(--space-7); padding-top: var(--space-5); border-top: var(--border-hairline); }
.ft__legal p { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); }
.ft__legal nav { display: flex; flex-wrap: wrap; gap: var(--space-5); }
.ft__legal a { font-size: var(--text-sm); color: var(--text-secondary); text-decoration: none; }
.ft__legal a:hover { color: var(--text-primary); text-decoration: underline; text-underline-offset: 4px; }
/* El credito de autoria cuelga del copyright, no es una tercera columna: con
   tres elementos el `space-between` deja el del medio flotando. */
.ft__meta { display: grid; gap: var(--space-2); }
/* Subrayado permanente: `.ft__legal a` va sin decoracion y del mismo color que
   el texto, asi que dentro de una frase "FixRiver" no se leeria como enlace. */
.ft__cred a { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

/* ---------------- Reveal al entrar en viewport ----------------
   Motion callado: solo opacidad y desplazamiento corto, 250ms.
   ⚠️ NUNCA sobre el hero: el elemento LCP no puede arrancar en opacity 0
   esperando al JS (README-NUEVA-WEB §6).

   Sin JS el contenido se ve SIEMPRE: por eso el estado oculto cuelga de `.js`,
   una clase que pone un script inline del <head> antes del primer render. Al
   revés (ocultar por defecto y mostrar con JS) el contenido desaparecería si el
   JS falla, y parpadearía mientras carga.

   Dentro de una grilla los hermanos entran ESCALONADOS (--reveal-delay lo pone
   site.js según la posición): que aparezca todo de golpe se lee como un salto,
   y escalonado se lee como que la página se está armando.

   ⚠️ ESTE COMENTARIO ESTUVO ROTO DEL 16/8 AL 25/8 y se llevo por delante la
   regla de abajo. Al pasar el CSS a un archivo externo (`df54b8d`) quedaron dos
   cierres de comentario de mas, uno tras "§6)" y otro tras "mientras carga": el
   parser cerraba el comentario en el primero, trataba el resto del texto como un
   selector y en la recuperacion de error se tragaba `.js .reveal { opacity: 0 }`
   entera. La pagina no lo aparentaba porque el estado FINAL es el correcto: los
   .reveal se veian siempre y la entrada de secciones simplemente no ocurria.

   ⚠️ Y OJO AL ARREGLARLO: los comentarios CSS NO ANIDAN, asi que escribir el
   simbolo de cierre aqui dentro lo cierra en ese punto. Al documentar esto se
   volvio a romper por eso mismo. Por eso arriba se nombra en palabras.
   Lo vigila `verificar_css.py`. */
.js .reveal {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--duration-base) var(--ease-brand),
              transform var(--duration-base) var(--ease-brand);
  transition-delay: var(--reveal-delay, 0ms);
}
.js .reveal[data-visible="1"] { opacity: 1; transform: none; }

/* ---------------- Titulares por líneas ----------------
   Lo arma y lo desarma site.js: acá solo está cómo se ven mientras dura. Las
   líneas son `inline-block` porque un `<span>` inline no acepta `transform`
   — y por eso mismo el JS las desarma al terminar, para que el h2 vuelva a ser
   texto que refluye normal si cambia el ancho. */
h2.display[data-lineas] .linea {
  display: inline-block;
  animation: entra-arriba 420ms var(--ease-brand) both;
  animation-delay: calc(var(--reveal-delay, 0ms) + var(--l, 0) * 60ms);
}
@media (prefers-reduced-motion: reduce) {
  h2.display[data-lineas] .linea { animation: none; opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   SECCIÓN · HERO  (.s-hero)
   ------------------------------------------------------------
   ⚠️ El h1 es el elemento LCP. Nada del hero puede depender del JS
   para ser visible: el texto se pinta ya en su sitio y la malla se
   ve completa aunque el script nunca corra. Las animaciones solo
   AÑADEN, nunca condicionan.
   ============================================================ */
/* ⚠️ HERO SOLO TIPOGRAFICO desde el 27/8 (orden de Juan). Hasta hoy el hero
   era una columna de texto al 53 % y una arcada dental trazada en el 40 %
   restante, y casi todo lo de aqui abajo existia para que las dos cosas
   entraran juntas: anchos que tenian que sumar menos de 100, el kicker
   escondido en movil, la bajada recortada a 16px, la segunda frase oculta por
   debajo de 390px y el CTA empujado a la derecha. Se fue el grafico y se fue
   todo eso: lo que quedaba era una lista de concesiones a un vecino que ya no
   esta. Ahora el hero pesa por tamano y aire. */
.s-hero { position: relative; padding-block: var(--space-8) var(--space-9); overflow: hidden; }

/* ---------------- El hero se queda con la primera pantalla ----------------
   En desktop el hero medía lo que medía su contenido y en 1080p asomaba el
   arranque de "Servicios" por abajo: la primera pantalla mostraba dos cosas y
   ninguna entera. Ahora reserva el alto completo menos la barra.

   ⚠️ AHORA TAMBIEN EN MOVIL, y ese es el cambio del 27/8. Antes empezaba en
   900px: en vertical el alto estaba calculado al milimetro para que entrara la
   arcada, y meterle un minimo ahi habria movido todo. Sin arcada el problema se
   dio vuelta — el hero movil pasó a medir lo que miden cuatro parrafos y la
   seccion siguiente le comia media pantalla. Reservar el alto y CENTRAR es lo
   que evita el hueco muerto que dejo el grafico: el texto no se queda arriba
   con el vacio debajo, se reparte.

   El alto se descuenta con `--nav-reserva`, que es el MISMO token que usa el
   hueco de la barra. Antes eran dos numeros escritos a mano.

   `svh` y no `vh`: en movil `vh` cuenta la barra del navegador como si no
   existiera. El fallback en `vh` va primero por si `svh` no esta soportado. */
.s-hero {
  min-height: calc(100vh - var(--nav-reserva));
  min-height: calc(100svh - var(--nav-reserva));
  /* ⚠️ GRID y no FLEX, y sin tocarle el ancho al contenedor. Con `flex` hay
     que darle `width: 100%` al hijo para que ocupe la fila, y eso le pisa el
     `width: min(var(--container-max), 100%)` de `.container`: el hero se iba a
     ancho completo y quedaba pegado al borde izquierdo. En grid el item se
     estira solo hasta la columna y conserva su propio ancho maximo y su
     `margin-inline: auto`. */
  display: grid;
  align-content: center;
}

/* Pantallas BAJAS (1024x768, portátiles viejos, ventanas a media altura, y en
   vertical los moviles chicos apaisados). Ahí el contenido del hero mide más que
   la pantalla y el `min-height` no puede hacer nada: lo que sobra es aire, así
   que se saca aire. Sin esto el hero se pasaba 90px y el CTA quedaba debajo del
   pliegue — que es justo lo que el cambio venía a evitar. */
@media (max-height: 820px) {
  .s-hero { padding-block: var(--space-5) var(--space-5); }
  .s-hero__rule { margin-block: var(--space-4); }
  .s-hero__lead { margin-bottom: var(--space-5); }
  .s-hero__titulo { margin-top: var(--space-4); }
}
.s-hero__inner { position: relative; }

/* ---------------- El tamano, que es de lo que vive ahora ----------------
   ⚠️ El titular sube de `.display--xl` (clamp 32-64) a clamp 38-84. Se escribe
   AQUI y no tocando `.display--xl` porque esa clase la comparte el h1 de
   /equipo/, que no es un hero de portada y no tiene por que crecer con este.
   El piso movil pasa de 32 a 38px: es la mitad del trabajo de "que el hero siga
   siendo el hero" en vertical, donde ya no hay dibujo que lo acompane. */
.s-hero .s-hero__titulo {
  margin-top: var(--space-5);
  max-width: 18ch;
  /* ⚠️ El termino en vw es 10.2 y no 7.4 POR LOS MOVILES, no por el escritorio.
     Con 7.4 el titular se quedaba clavado en el piso de 38px en todos los
     telefonos (7.4vw de 430px son 32, o sea por debajo del piso), asi que un
     iPhone 15+ con 375px de aire sobrante mostraba exactamente el mismo tamano
     que un SE que no tiene ni 50. Con 10.2 el titular crece donde hay sitio y se
     queda en el piso donde no lo hay, que es todo lo que habia que resolver. */
  /* ⚠️ Y el termino lleva un `min()` con vh: el titular tiene que caber a lo
     ALTO, no solo a lo ancho. Sin el, una ventana de 1024x600 pedia 96px (por
     ancho) dentro de un hero de 523 utiles y el CTA se iba 15px bajo el
     pliegue; un movil apaisado pedia 86px con 313 utiles. El ancho dice cuanto
     cabe por linea y el alto cuantas lineas caben: hacen falta los dos. */
  font-size: clamp(38px, min(10.2vw, 13vh), 96px);
}
.s-hero__rule { margin-block: var(--space-6); max-width: 520px; }
/* La bajada crece solo de 900px para arriba. En vertical se queda en los 20px
   de `.lead`, que ya es el maximo comodo para una linea de 327px de ancho:
   subirla ahi no agranda el hero, lo desborda. */
@media (min-width: 900px) { .s-hero__lead { font-size: clamp(20px, 1.7vw, 24px); } }

/* El CTA tambien crece, y no es cosmetico: al lado de un titular de 96px, un
   boton de 12px y 46 de alto se lee como una nota al pie. Solo de 900px para
   arriba -- en vertical el boton ya esta bien proporcionado y en las pantallas
   bajas no hay alto que gastar. El objetivo tactil solo mejora: 46px -> 54. */
@media (min-width: 900px) {
  .s-hero .btn { font-size: var(--text-sm); padding: 20px 40px; }
}

/* ---- Dos botones solo donde entran los dos ----
   ⚠️ LAS DOS CONDICIONES HACEN FALTA, y cada una dice una cosa distinta.
   Por ANCHO: debajo de 620px los dos botones (241 y 180 mas el hueco) no entran
   en una fila y envuelven, o sea que el segundo cuesta una fila entera (62px).
   Por ALTO: esa fila solo sobra si hay alto para pagarla. Medido con los dos
   puestos: iPhone 14 (844 de alto) queda con 77px de margen y un SE (667) se
   iba 53px POR DEBAJO del pliegue, que es justo lo que el hero de pantalla
   completa viene a evitar.
   Con una sola de las dos condiciones se pierde el boton donde si entra: a
   844x390 (movil apaisado) los dos van en una fila y sobra sitio, y a 1024x600
   tambien. Por eso 619 y 760 y no un breakpoint redondo: son la medida del
   contenido, no una convencion.
   Lo que se pierde es el CTA secundario, no el destino: «Servicios» esta en el
   menu y la seccion es la siguiente de la pagina. */
@media (max-width: 619px) and (max-height: 760px) {
  .s-hero .acciones .btn--secondary { display: none; }
}

/* ---- Pantallas BAJAS de verdad: se recorta la bajada ----
   ⚠️ LA CONDICION ES EL ALTO, NO EL ANCHO, y ese es el cambio del 27/8. Antes
   la segunda frase se ocultaba por debajo de 390px de ANCHO, y el motivo era la
   arcada: cada linea de texto le comia alto al dibujo. Sin dibujo, el ancho ya
   no dice nada del problema; lo que aprieta es el alto disponible. Medido: a
   375x667 sobran 48px y entra entera, y a 320x568 el CTA se iba 123px por
   debajo del pliegue. Un movil apaisado (844x390) tiene 313px utiles y es el
   mismo caso con un ancho de sobra.
   La segunda mitad es la promesa de experiencia: la parte linda, y la
   prescindible cuando hay que elegir. El texto completo sigue en el HTML y lo
   ven todos los demas. El kicker tambien sale: ya esta dicho en el h1. */
@media (max-height: 600px) {
  .s-hero__lead .lead__cola { display: none; }
  .s-hero .label { display: none; }
  .s-hero .s-hero__titulo { margin-top: 0; }
}

/* ---- Y el caso limite: el movil APAISADO ----
   844x390 deja 313px utiles bajo la barra, y ahi no sobra nada: con los aires
   de `max-height: 820px` el CTA se iba 16px por debajo del pliegue. Se aprieta
   el ritmo una vuelta mas. No se toca la escala tipografica -- el titular ya lo
   limita el termino `13vh` de su clamp, que es el que corresponde cuando lo que
   falta es alto. */
@media (max-height: 450px) {
  .s-hero { padding-block: var(--space-4) var(--space-4); }
  .s-hero__rule { margin-block: var(--space-3); }
  .s-hero__lead { margin-bottom: var(--space-3); }
}
.s-hero__lead { margin-bottom: var(--space-7); }

/* La columna de texto ya no compite con nada, asi que no lleva ancho propio: la
   medida la ponen el titular (18ch) y `.lead` (56ch), que es donde tiene que
   estar. El `width: min(53%, 610px)` que habia era la mitad izquierda que le
   quedaba libre al dibujo. */

/* ---------------- Entrada escalonada del texto del hero ----------------
   ⚠️ EL ESTADO INICIAL CUELGA DE `.js`, NUNCA DE UN ATRIBUTO QUE PONGA site.js.
   Hasta el 25/8 colgaba de `[data-entrada]`, que `arrancarHero()` escribe
   DESPUES de `load`. El resultado, medido en red lenta: el hero se pintaba
   entero a los 3116ms, se veia 1471ms, y al llegar `load` caia de golpe a
   opacity 0 para entrar animado. Se veia como un parpadeo brusco.
   `.js` la pone un script inline del <head> antes del primer render, asi que
   ahora las piezas nacen ocultas y NUNCA pasan de visible a invisible. La
   animacion arranca sola con el primer paint: no hay JS en el camino.

   ⚠️ EL h1 NO SE ANIMA. Es el elemento LCP y no puede arrancar en opacity 0
   (README §3): con esta misma tecnica, animarlo retrasaria el LCP el delay mas
   la duracion. Se pinta y se queda; lo que entra es lo de alrededor.

   Los retardos van escritos aca, no los inyecta el JS con `--entra-delay`:
   las piezas son fijas y asi la entrada no depende de que corra un script. */
.js .s-hero .entra:not(.s-hero__titulo) {
  animation: entra-arriba 420ms var(--ease-brand) both;
}
/* ⚠️ Los retardos repiten `.entra` a proposito: la regla de arriba lleva un
   `:not()` y sin el segundo selector de clase le gana en especificidad, asi que
   la shorthand `animation` (que resetea el delay a 0) se imponia y las tres
   piezas entraban a la vez. Medido: label, lead y acciones subian en el mismo
   fotograma. */
.js .s-hero .label.entra { animation-delay: 0ms; }
.js .s-hero .s-hero__lead.entra { animation-delay: 240ms; }
.js .s-hero .acciones.entra { animation-delay: 320ms; }
@keyframes entra-arriba {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
/* El separador se dibuja de izquierda a derecha. */
.js .s-hero .s-hero__rule {
  transform-origin: left center;
  animation: rule-traza 520ms var(--ease-brand) both;
  animation-delay: 160ms;
}
@keyframes rule-traza {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ⚠️ prefers-reduced-motion: TODO estático, y el contenido en su estado final.
   No basta con quitar la animación: hay que anular el estado de partida, o el
   texto quedaría invisible y la malla sin dibujar. */
@media (prefers-reduced-motion: reduce) {
  .js .s-hero .entra,
  .js .s-hero .s-hero__rule {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

/* ============================================================
   SECCIÓN · SERVICIOS  (.s-svc)
   Tarjetas planas, marco hairline, esquinas rectas. Sin iconos: el
   design system no trae set propio y prefiere tipografía y filetes.
   ============================================================ */
.s-svc { position: relative; overflow: hidden; padding-block: var(--space-8) var(--space-9); }


.s-svc__intro { margin-bottom: var(--space-7); }
/* Sin rótulo encima (se quitaron el 19/8), el título arranca la sección. */
.s-svc__titulo { margin-top: 0; }

/* ⚖️ REGLA DE EQUIDAD (documento de contenido, 16/8 — manda sobre todo lo demás).
   Los cuatro tratamientos principales usan EL MISMO componente, el mismo tamaño y
   el mismo patrón de interacción. Ninguno se destaca sobre los otros: el
   consultorio tiene cuatro profesionales y cada uno tira para su especialidad.

   Por eso, además:
     · lista vertical y no grilla — expandir una celda de grilla reacomoda las
       vecinas y rompe la simetría;
     · sin numerar las tarjetas — un 01/02/03 introduce jerarquía donde no la hay;
     · misma estructura interna en las cuatro, con el hueco 🔴 visible donde
       todavía falta contenido. La equidad es de diseño Y de contenido. */
.svc-lista { display: grid; gap: var(--space-4); }

.svc {
  background: var(--surface-card);
  border: var(--border-hairline);
  transition: border-color var(--duration-fast) var(--ease-brand),
              transform var(--duration-fast) var(--ease-brand),
              box-shadow var(--duration-fast) var(--ease-brand);
  scroll-margin-top: var(--nav-alto, 80px);
}
.svc:hover, .svc[open] { border-color: var(--border-strong); }
/* Hover: elevación corta y sombra CÁLIDA (nunca gris azulado). */
.svc:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
.svc[open]:hover { transform: none; }   /* abierto no levita: se movería el contenido bajo el cursor */
/* Press: la tarjeta baja al plano. Vale abierta y cerrada, porque en mobile es
   la única señal de que el toque llegó. */
.svc__cab:active { transform: translateY(1px); }
.svc__cab { cursor: pointer; transition: transform 120ms var(--ease-brand); }
.svc:active { box-shadow: none; }

/* Altura animada del desplegable. La abre y cierra site.js con la Web Animations
   API, porque <details> no interpola su altura por sí solo. Si el JS falla, el
   toggle nativo sigue funcionando: solo que sin transición. */
.svc__cuerpo { overflow: hidden; }

/* Los ítems de la checklist y de los pasos entran escalonados al desplegar.
   El retardo lo pone site.js como --i (índice del hermano). */
/* Los bloques del cuerpo entran escalonados. Cubre a las CUATRO tarjetas: hasta
   el 19/8 el escalonado solo existía para checklists, y solo ATM tiene una — en
   las otras tres se animaba la altura y el contenido aparecía de golpe. */
.svc[open] .svc__cuerpo > * {
  animation: entra-arriba 240ms var(--ease-brand) both;
  animation-delay: calc(var(--i, 0) * 70ms);
}
.svc[open] .checklist li,
.svc[open] .pasos li {
  animation: entra-arriba 260ms var(--ease-brand) both;
  animation-delay: calc(var(--i, 0) * 50ms);
}

.svc__cab {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
}
.svc__cab::-webkit-details-marker { display: none; }
.svc__texto { display: block; }
.svc__nombre { display: block; font-size: var(--text-lg); font-weight: var(--weight-title); line-height: var(--leading-tight); color: var(--text-primary); }
.svc__resumen { display: block; margin-top: var(--space-2); max-width: 68ch; color: var(--text-primary); }

/* El signo se dibuja con dos filetes, no con un icono: el grosor replica el
   trazo del isotipo, y el design system no trae set de iconos propio. */
.svc__mas {
  flex: none;
  width: 16px; height: 16px;
  margin-top: 6px;
  background:
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 16px 1px no-repeat,
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 1px 16px no-repeat;
  transition: transform var(--duration-base) var(--ease-brand);
}
/* Abierto: el "+" gira a "×". Los dos filetes se mantienen — si se oculta uno,
   queda una diagonal suelta que no significa nada. */
.svc[open] .svc__mas { transform: rotate(45deg); }
.svc__cab:active .svc__mas { transform: scale(.86); }
.svc[open] .svc__cab:active .svc__mas { transform: rotate(45deg) scale(.86); }

.svc__cuerpo { padding: 0 var(--space-6) var(--space-6); border-top: var(--border-hairline); margin-top: var(--space-2); }
.svc__bloque { margin-top: var(--space-5); }
.svc__bloque .label { margin-bottom: var(--space-3); }
.svc__bloque p { margin: 0 0 var(--space-3); max-width: 68ch; }
.svc__bloque p:last-child { margin-bottom: 0; }
.svc__cuerpo .acciones { margin-top: var(--space-6); }

/* Checklist de síntomas: la gente no busca el nombre del tratamiento, busca lo
   que le pasa. El tilde se dibuja con bordes, sin icono. */
.checklist { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); max-width: 68ch; }
.checklist li { position: relative; padding-left: var(--space-6); }
.checklist li::before {
  content: "";
  position: absolute;
  left: 2px; top: .45em;
  width: 10px; height: 5px;
  border-left: 1px solid var(--gray-dark);
  border-bottom: 1px solid var(--gray-dark);
  transform: rotate(-45deg);
}
.pasos { margin: 0; padding-left: var(--space-6); display: grid; gap: var(--space-3); max-width: 68ch; }
.pasos li::marker { font-weight: var(--weight-body-strong); }
/* ⚠️ Es un <span>, NO un enlace, desde el 27/8 (D-34): las tres fichas apuntaban
   a /equipo/#marcos y #lazaro, y esa pagina se delinkeo entera. Por eso aqui no
   queda NADA que insinue un click: el `border-bottom` que tenia era el subrayado
   del enlace y un subrayado sobre texto que no lleva a ningun lado es peor que no
   tener afordancia, porque promete una pagina y no pasa nada. La senal de
   autoridad se conserva por el contenido, no por el enlace: nombre completo y
   matricula (regla de equidad, CLAUDE.md §2). Al reponer /equipo/, esto vuelve a
   ser <a> y vuelve el borde: ver README §7. */
.svc__quien { font-weight: var(--weight-body-strong); color: var(--text-primary); }

.svc-menores { margin-top: var(--space-4); display: grid; gap: var(--space-4); }
@media (min-width: 760px) { .svc-menores { grid-template-columns: repeat(3, 1fr); } }
.svc-min { background: transparent; border-top: 1px solid var(--border-strong); padding-top: var(--space-4); }
.svc-min__nombre { margin: 0 0 var(--space-2); font-size: var(--text-base); font-weight: var(--weight-subtitle); color: var(--text-primary); }
.svc-min p { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); }

/* ============================================================
   SECCIÓN · EXPERIENCIA  (.s-exp)   — banda inversa
   ============================================================ */
.s-exp { padding-block: var(--space-9); }
.s-exp__titulo { margin-top: 0; }
/* El CTA de la banda de experiencia: es la sección que los diferencia, así que
   ahí el visitante tiene que poder decidir sin volver arriba. */
.s-exp__acciones { margin-top: var(--space-6); }
.s-exp__lead { margin-top: var(--space-6); color: var(--text-inverse); }
.s-exp .todo-bloque { background: rgba(255,255,255,.06); border-left-color: var(--gray-light); color: var(--text-inverse); }

/* ============================================================
   SECCIÓN · EQUIPO (teaser)  (.s-equipo)
   ============================================================ */
.s-equipo { padding-block: var(--space-8) var(--space-9); }
/* La banda arena, PRESTADA mientras la galeria este oculta (30/8, go-live sin
   las fotos del cliente). `--surface-arena` es la unica superficie con
   temperatura del sitio y su duena natural es `.s-galeria`; con la galeria
   fuera, el ritmo claro / OSCURA / claro / ARENA / claro / OSCURA se quedaba
   sin su unica pausa calida. La clase la pone `armar_home.py` con el MISMO
   flag que oculta la galeria, asi que se va sola cuando lleguen las fotos y
   la arena vuelva a su sitio. No ponerla a mano en el HTML. */
.s-equipo--arena { background: var(--surface-arena); }
.s-equipo__titulo { margin-top: var(--space-4); }
/* El aire de abajo se lo lleva la invitación, que va después: si lo conserva el
   lead, la invitación queda pegada a las fichas y con un hueco enorme arriba. */
.s-equipo__lead { margin-top: var(--space-4); margin-bottom: 0; }
/* La invitación de Salo (19/8). Va en el CUERPO y con la misma voz que la bajada,
   no dentro del botón ni convertida en un reclamo: el subtítulo del hero abre la
   promesa de que ir al dentista puede ser otra cosa, y esta frase la demuestra.
   Son pareja, así que el tono las conecta. */
.s-equipo__invita { margin-top: var(--space-4); margin-bottom: var(--space-7); color: var(--text-primary); }
/* Cuatro profesionales, cuatro tarjetas idénticas: la regla de equidad también
   vale acá. Nada de mostrar solo a dos o tres en el teaser. */
.equipo-grid { display: grid; gap: var(--space-5); }
@media (min-width: 560px) { .equipo-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .equipo-grid { grid-template-columns: repeat(4, 1fr); } }

.persona { background: var(--surface-card); border: var(--border-hairline); padding: var(--space-5); }
/* `height: auto` es obligatorio: sin él, el atributo height="1000" del <img>
   cuenta como altura definida y `aspect-ratio` no se aplica — la foto sale a
   tamaño natural y estira la fila entera de la grilla. */
.persona__foto { display: block; width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; background: var(--gray-100); }
.persona__nombre { margin: var(--space-4) 0 var(--space-1); font-size: var(--text-lg); font-weight: var(--weight-title); line-height: var(--leading-tight); }
.persona__rol { margin: 0; font-size: var(--text-sm); color: var(--text-secondary); }
.persona__mp { margin: var(--space-3) 0 0; font-size: var(--text-xs); font-weight: var(--weight-body-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-primary); }

/* Hueco de imagen: rotulado con la medida y el encuadre que hace falta.
   Es a propósito que se vea como un hueco — el preview funciona de checklist. */
.foto-hueco {
  position: relative;
  display: grid;
  place-items: center;
  /* `place-items` centra cada hijo DENTRO de su fila, pero las filas implicitas
     siguen estirandose para llenar el alto del `aspect-ratio`: el rotulo se iba
     arriba y la medida abajo, separados por medio hueco. `align-content` agrupa
     las dos filas en el centro y deja que mande el `gap`. */
  align-content: center;
  gap: var(--space-2);
  border: 1px dashed var(--border-strong);
  background: var(--gray-100);
  padding: var(--space-5);
  text-align: center;
  /* ⚠️ CANDADO. Un item de grilla arranca con `min-width: auto`, o sea que su
     min-content puede EMPUJAR el track y ganarle a `1fr`. Con etiquetas en
     versalita y `--tracking-label` (.18em) el min-content de estos huecos daba
     247px contra una columna de 163: los tracks dejaban de caber, `1fr` no
     repartia nada y la grilla entraba en un estado inconsistente (media 711px
     de alto con las filas sumando 929). De ahi salian los DOS defectos del 25/8
     en movil: la segunda columna cortada a filo por el `overflow-x: clip` del
     body, y la ultima foto invadiendo la FAQ 122px por debajo de su seccion.
     Con `min-width: 0` el `1fr` vuelve a mandar en cualquier ancho. */
  min-width: 0;
}
/* La otra mitad del candado: sin esto el texto cabria fuera del recuadro en vez
   de envolver, porque `place-items: center` dimensiona a los hijos a fit-content
   y los saca del estirado. `anywhere` porque una sola palabra larga en versalita
   con tracking (AUTORIZACION) ya no entra en una columna angosta. */
.foto-hueco strong,
.foto-hueco span { max-width: 100%; overflow-wrap: anywhere; }
.foto-hueco span { font-size: var(--text-xs); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-secondary); }
.foto-hueco strong { font-size: var(--text-xs); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-primary); }
.foto-hueco--retrato { aspect-ratio: 4 / 5; }
.foto-hueco--paisaje { aspect-ratio: 3 / 2; }
.foto-hueco--cuadrada { aspect-ratio: 1 / 1; }

/* ============================================================
   SECCIÓN · GALERÍA  (.s-galeria)
   ============================================================ */
/* Fondo arena: la galería es la única superficie con temperatura del sitio, para
   que el ritmo claro → oscuro → claro no se vuelva monótono. El texto sigue
   siendo gris oscuro y da 8.8:1 contra este fondo. */
.s-galeria { background: var(--surface-arena); padding-block: var(--space-8) var(--space-9); }
.s-galeria__titulo { margin-top: var(--space-4); }
.s-galeria__lead { margin-top: var(--space-4); margin-bottom: var(--space-7); }
/* MOSAICO MIXTO. En movil los planos GENERALES van solos a ancho completo y los
   planos CERRADOS de a dos. El criterio no es una lista de fotos, es el encuadre
   que cada hueco ya declara: un 3:2 es un plano general de ambiente (recepcion,
   sala, equipamiento) y a 163px no muestra nada; un 1:1 es un plano cerrado
   (un detalle, una sonrisa) y se lee igual de pequeno, porque el sujeto llena
   el cuadro. Asi la galeria cae de 2648px de alto a ~1074 sin perder las fotos
   que necesitan tamano.
   Si alguna foto concreta pide lo contrario, se cambia SU proporcion, que es lo
   que de verdad manda: la regla se mantiene sola cuando entren fotos nuevas. */
.galeria-grid { display: grid; gap: var(--space-4); grid-template-columns: repeat(2, 1fr); }
.galeria-grid > .foto-hueco--paisaje { grid-column: 1 / -1; }
/* Desde 560 una columna de 248px ya sostiene un plano general y el mosaico deja
   de hacer falta; medido a 700px con el span puesto, los paisajes se iban a
   652x435 y la galeria a 2406px de alto, que es peor que el problema. */
@media (min-width: 560px) {
  .galeria-grid > .foto-hueco--paisaje { grid-column: auto; }
}
@media (min-width: 760px) { .galeria-grid { grid-template-columns: repeat(3, 1fr); } }
/* En la columna angosta el rotulo pide su sitio: AUTORIZACION mide 113px en
   versalita con tracking .18em, y con 24px de padding a cada lado solo quedan
   107 de hueco util a 375px. Con 16 entra sin partir la palabra. */
@media (max-width: 559px) {
  .galeria-grid .foto-hueco { padding: var(--space-4); }
}

/* ============================================================
   SECCIÓN · CASOS  (.s-casos)   — antes y después
   ------------------------------------------------------------
   Nació el 27/8 al partir la galería en dos: arriba el sitio, acá los casos
   clínicos. Reutiliza `.galeria-grid` a propósito — mismo mosaico mixto y mismo
   candado de `min-width` — para no tener dos mecánicas de grilla que mantener.
   ⚠️ Va en superficie CLARA, no en arena: dos bandas arena seguidas se leen como
   una sola y se perdería el corte entre "dónde te atendemos" y "qué resultados".
   ============================================================ */
.s-casos { padding-block: var(--space-8) var(--space-9); }
.s-casos__titulo { margin-top: var(--space-4); }
.s-casos__lead { margin-top: var(--space-4); margin-bottom: var(--space-7); }

/* ============================================================
   SECCIÓN · FAQ  (.s-faq)
   Acordeón NATIVO <details>/<summary>: accesible sin JS, y track.js
   puede leer `open` en capture (refleja el estado ANTERIOR al toggle).
   ============================================================ */
/* La FAQ lleva el mismo eco, todavía más bajo: es la segunda aparición y no
   Segunda aparición de la textura, un punto más baja que en servicios: no tiene
   que llamar la atención dos veces. */
.s-faq { position: relative; overflow: hidden; padding-block: var(--space-8) var(--space-9); --textura: .065; }
.s-faq__titulo { margin-top: var(--space-4); }
.faq { max-width: 820px; border-top: 1px solid var(--border-strong); }
.faq details { border-bottom: var(--border-hairline); }
.faq summary {
  list-style: none;
  cursor: pointer;
  padding-block: var(--space-5);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  font-size: var(--text-base);
  font-weight: var(--weight-subtitle);
  color: var(--text-primary);
  transition: color var(--duration-fast) var(--ease-brand);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--black); }
.faq summary { transition: color var(--duration-fast) var(--ease-brand), transform 120ms var(--ease-brand); }
.faq summary:active { transform: translateX(2px); }
/* El signo se dibuja con dos filetes, no con un icono: el grosor replica
   el trazo del isotipo. */
.faq summary::after {
  content: "";
  flex: none;
  width: 14px; height: 14px;
  margin-top: 6px;
  background:
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 14px 1px no-repeat,
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 1px 14px no-repeat;
  transition: transform var(--duration-base) var(--ease-brand);
}
.faq details[open] summary::after { transform: rotate(45deg); }
.faq__cuerpo { padding-bottom: var(--space-5); max-width: 70ch; color: var(--text-primary); }
.faq__cuerpo p { margin: 0 0 var(--space-3); }
.faq__cuerpo p:last-child { margin-bottom: 0; }
.faq__cuerpo ul { margin: 0 0 var(--space-3); padding-left: var(--space-5); }
.faq__cuerpo li { margin-bottom: var(--space-2); }

/* ============================================================
   SECCIÓN · CTA FINAL  (.s-cta)   — banda inversa
   ============================================================ */
.s-cta { position: relative; padding-block: var(--space-9); }

.s-cta__titulo { margin-top: var(--space-5); max-width: 20ch; }
.s-cta__acciones { margin-top: var(--space-7); }
.s-cta__datos { margin-top: var(--space-8); display: grid; gap: var(--space-5); border-top: 1px solid var(--border-on-inverse); padding-top: var(--space-6); }
@media (min-width: 720px) { .s-cta__datos { grid-template-columns: repeat(3, 1fr); gap: var(--space-6); } }
.s-cta__dato .label { margin-bottom: var(--space-2); }
.s-cta__dato a, .s-cta__dato span { display: block; font-size: var(--text-lg); color: var(--text-inverse); text-decoration: none; }
.s-cta__dato a:hover { color: var(--text-on-dark-strong); text-decoration: underline; text-underline-offset: 4px; }
.s-cta__dato small { display: block; margin-top: var(--space-1); font-size: var(--text-sm); color: var(--text-on-dark-muted); }
.s-cta .todo-bloque { background: rgba(255,255,255,.06); border-left-color: var(--gray-light); color: var(--text-inverse); }

/* ---------------- Mapa: embed directo ----------------
   ⚠️ AQUI HUBO UNA FACHADA (superficie de marca + el iframe al pulsar). La
   retiro Juan el 28/8 por FORMA, no por arquitectura: «realmente horrible y no
   se muestra el mapa». Va el embed de Google tal cual, y el aspecto lo pone
   Google. Consecuencia de sistema que conviene tener escrita: **esta es la
   unica superficie del sitio donde el color entra**, y es deliberado — un mapa
   distingue calles, agua y parques POR COLOR, y la identidad monocroma manda en
   lo que dibujamos nosotros, no dentro de una herramienta ajena embebida.

   ⚠️ NADA DE `filter: grayscale()` para «integrarlo»: lo volveria mas de marca
   y menos util, que es justo al reves de para lo que esta.

   El alto va por `aspect-ratio` y no por el `height="450"` del snippet: con alto
   fijo, el mapa se estira o se aplasta segun el ancho del contenedor. El
   atributo se queda en el HTML igualmente, porque reserva la proporcion antes
   de que el iframe pinte y ahorra el salto de layout. */
.mapa { margin-top: var(--space-7); }
.mapa__marco {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--border-on-inverse);
}
/* Mas cuadrado en vertical: a 342px de ancho, un 16:9 deja 192px de alto y el
   mapa no se lee. 4:3 da 257 y entra el entorno de la clinica. */
@media (max-width: 619px) { .mapa__marco { aspect-ratio: 4 / 3; } }

/* El pie: un enlace de texto a la ficha del negocio en Google, que es lo que
   sirve para pedir indicaciones desde el movil. NO es el respaldo del iframe
   —el iframe es HTML y no depende de JS— sino la otra cosa que la gente quiere
   de un mapa: llegar. */
.mapa__pie { margin: var(--space-3) 0 0; font-size: var(--text-sm); }
.mapa__pie a {
  color: var(--text-inverse);
  text-decoration: none;
  border-bottom: 1px solid var(--border-on-inverse);
  padding-bottom: 2px;
  transition: border-color var(--duration-fast) var(--ease-brand),
              color var(--duration-fast) var(--ease-brand);
}
.mapa__pie a:hover { color: var(--text-on-dark-strong); border-bottom-color: var(--text-on-dark-strong); }

/* ============================================================
   SECCIÓN · FORMULARIO  (.s-form)
   Tarjeta blanca plana, marco hairline, esquinas rectas.
   El envío y el anti-spam están en el IIFE del final de index.html.
   ============================================================ */
.s-form { padding-block: var(--space-8) var(--space-9); }
.form-card { background: var(--surface-card); border: var(--border-hairline); padding: var(--space-6); }
@media (min-width: 720px) { .form-card { padding: var(--space-7); } }
.form-card__title {
  margin: var(--space-4) 0;
  font-size: clamp(24px, 3.4vw, var(--text-xl));
  font-weight: var(--weight-display);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-wide);
  color: var(--text-primary);
}
.form-card__lead { margin: 0 0 var(--space-6); max-width: 56ch; }

.form-grid { display: grid; gap: var(--space-5); }
@media (min-width: 640px) {
  .form-grid { grid-template-columns: 1fr 1fr; }
  .form-field--full { grid-column: 1 / -1; }
}
.form-field { display: block; }
.form-field > .label { display: block; margin-bottom: var(--space-2); }
.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-body);
  color: var(--text-primary);
  background: var(--white);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  outline: none;
  transition: border-color var(--duration-fast) var(--ease-brand);
}
.form-field textarea { resize: vertical; min-height: 120px; line-height: var(--leading-normal); }
.form-field input:hover, .form-field select:hover, .form-field textarea:hover { border-color: var(--gray-400); }
.form-field input:focus, .form-field select:focus, .form-field textarea:focus { border-color: var(--border-strong); }
/* Error: la marca no tiene rojo. El estado se marca con negro y peso. */
.form-field[data-error="1"] input,
.form-field[data-error="1"] select,
.form-field[data-error="1"] textarea { border-color: var(--black); }
.form-field__error { display: none; margin: var(--space-2) 0 0; font-size: var(--text-xs); font-weight: var(--weight-body-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--black); }
.form-field[data-error="1"] .form-field__error { display: block; }

.form-consent { display: flex; gap: var(--space-3); align-items: flex-start; font-size: var(--text-sm); color: var(--text-secondary); }
.form-consent input { width: 16px; height: 16px; flex: none; margin-top: 3px; accent-color: var(--gray-dark); }
.form-consent a { color: var(--text-primary); }
.form-actions { display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; margin-top: var(--space-6); }

/* Trampa anti-bot: fuera de pantalla pero NO display:none — muchos bots
   saltean lo que está oculto con display y llenan el resto. Una persona no
   puede verlo ni tabularlo; si viene con texto, el envío se descarta. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-error { margin-top: var(--space-5); padding: var(--space-5); border-left: 3px solid var(--border-strong); background: var(--gray-100); }
.form-error p { margin: 0; font-size: var(--text-sm); line-height: var(--leading-normal); }
.form-error p + p { margin-top: var(--space-3); }
.form-ok__lead { margin: 0 0 var(--space-6); max-width: 56ch; }
[hidden] { display: none !important; }

/* ============================================================
   PÁGINA · /equipo/  (.s-equipo-full)
   ============================================================ */
.s-equipo-full { padding-block: var(--space-8) var(--space-9); }
.s-equipo-full__intro { max-width: 60ch; margin-top: var(--space-5); }
/* Las cuatro fichas comparten estructura y tamaño: regla de equidad. El orden
   lo decide el cliente; mientras tanto es alfabético, que es el más neutral. */
.ficha { display: grid; gap: var(--space-6); padding-block: var(--space-8); border-top: 1px solid var(--border-strong); scroll-margin-top: var(--space-5); }
/* Columna de foto acotada: con la ficha colapsada el texto es corto, y una foto
   grande dejaba un hueco muerto al lado que hacía la página más larga sin sumar
   nada. */
@media (min-width: 820px) { .ficha { grid-template-columns: 240px 1fr; gap: var(--space-7); } }
.ficha__foto { display: block; width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; background: var(--gray-100); }
.ficha__nombre { margin: 0; font-size: clamp(26px, 3.4vw, var(--text-2xl)); font-weight: var(--weight-display); letter-spacing: var(--tracking-wide); line-height: var(--leading-tight); }
.ficha__rol { margin: var(--space-3) 0 0; font-size: var(--text-lg); color: var(--text-secondary); }
/* La matrícula ES la señal de autoridad de esta página: visible, no escondida. */
.ficha__mp { display: inline-block; margin-top: var(--space-4); padding: 8px 14px; border: var(--border-frame); font-size: var(--text-xs); font-weight: var(--weight-body-strong); letter-spacing: var(--tracking-label); text-transform: uppercase; }
/* "Ver más": la ficha muestra de entrada foto + nombre + especialidad + matrícula,
   y el resto (qué hace, formación, línea humana, horarios) se despliega. Con cuatro
   profesionales, todo abierto convierte la página en un scroll interminable y se
   pierde justo lo que importa: poder comparar de un vistazo quién es quién.
   Mismo componente y mismo patrón para los cuatro — regla de equidad. */
.ficha__toggle { margin-top: var(--space-6); }
.ficha__vermas {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-xs);
  font-weight: var(--weight-body-strong);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-primary);
  padding-bottom: var(--space-2);
  border-bottom: 1px solid var(--border-strong);
  transition: color var(--duration-fast) var(--ease-brand);
}
.ficha__vermas { transition: transform 120ms var(--ease-brand); }
.ficha__vermas:active { transform: translateY(1px); }
.ficha__vermas::-webkit-details-marker { display: none; }
.ficha__vermas:hover { color: var(--black); }
.ficha__vermas::after {
  content: "";
  width: 12px; height: 12px;
  background:
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 12px 1px no-repeat,
    linear-gradient(var(--gray-dark), var(--gray-dark)) center / 1px 12px no-repeat;
  transition: transform var(--duration-base) var(--ease-brand);
}
.ficha__toggle[open] .ficha__vermas::after { transform: rotate(45deg); }
/* El texto cambia por CSS para no duplicar markup ni depender del JS. */
.ficha__vermas .abrir { display: inline; }
.ficha__vermas .cerrar { display: none; }
.ficha__toggle[open] .ficha__vermas .abrir { display: none; }
.ficha__toggle[open] .ficha__vermas .cerrar { display: inline; }

.ficha__bloque { margin-top: var(--space-6); }
.ficha__bloque .label { margin-bottom: var(--space-3); }
.ficha__bloque p { margin: 0 0 var(--space-3); max-width: 62ch; }
.ficha__bloque p:last-child { margin-bottom: 0; }
/* ⚠️ Aqui vivia `.horarios` (grilla dia/horario), borrada el 27/8 por muerta.
   NO reponerla: el horario va en una linea (`.s-cta__dato`) y los horarios POR
   PROFESIONAL no se publican (CLAUDE.md §7). Detalle en README §3. */

/* ============================================================
   PÁGINA · 404
   ============================================================ */
.s-404 { flex: 1; display: flex; align-items: center; padding-block: var(--space-8) var(--space-9); }
.s-404__titulo { margin-top: var(--space-5); }
.s-404__rule { margin-block: var(--space-6); max-width: 520px; }
.s-404__lead { margin-bottom: var(--space-7); }
