/* ==========================================================================
   CABAÑAS DE HUGO — Design tokens y sistema visual
   Nivel Básica — Bellino Webs

   Sitios del research (recapturados con playwright-cli a 1440px en la
   ronda 6; las capturas de la ronda 3 no habían sobrevivido, así que las
   referencias de abajo salen de mirar los sitios de nuevo, no de memoria):
     A. Eastwind Hotels (eastwindhotels.com)
     B. Postcard Cabins / Marriott (postcardcabins.com)
     C. Correntoso Lake & River Hotel (correntoso.com) — Neuquén, AR
     D. Juvet Landskapshotell (juvet.com) — Noruega
     E. Awasi (awasi.com) — AR/CL
     F. Dunton River Camp (duntondestinations.com)
     G. Linear (linear.app) — otro rubro, calidad tipográfica

   Research de la ronda 31 (lodges de PESCA reales, medidos con playwright-cli
   sobre el render, no de memoria — ver el bloque de la ronda 31 más abajo):
     H. Pirá Lodge (piralodge.com) — Iberá, Corrientes, AR. Dorado.
     I. Nervous Waters (nervouswaters.com) — AR/CL/MX/BS/US.
     J. Crystal Creek Lodge (crystalcreeklodge.com) — Alaska.
     K. Untamed Angling (untamedangling.com) — Amazonas.

   ==========================================================================
   RONDA 31 — LA PALETA COMPLETA. DE LOS COLORES DEL MONTE A LOS DEL RÍO.
   ==========================================================================
   Pedido: "rediseño completo del sistema de color, no una iteración sobre la
   paleta actual". Lo que sigue es la derivación entera y el chequeo letra por
   letra contra los 3 anti-defaults, que es la condición no negociable.

   EL DIAGNÓSTICO, Y ES MEDIBLE
   ----------------------------
   El sitio se llama "Cabañas frente al río Paraná" y estaba pintado con los
   colores del MONTE. Peor: estaba pintado con dos familias de hue que no
   tenían relación entre sí. Medido sobre los tokens que había:

       banda clara  --color-bg    #EEF1EC   hue  96°  (verde frío)
       banda oscura --color-dark  #16231D   hue 154°  (verde carbón)
       tarjeta      --color-water #24353D   hue 199°  (agua, ronda 28)
       tinta s/osc. --color-on-dark #EDE4D3 hue  37°  (crema cálido)

   Cuatro superficies, cuatro familias distintas: 96 / 154 / 199 / 37. Entre
   la banda clara y la oscura hay 58° de distancia, o sea que NO son los dos
   extremos de una rampa — son dos colores distintos que conviven. Eso es
   exactamente lo que la skill `redesign-existing-projects` marca como
   defecto: "una sección oscura que rompe una página clara se lee como un
   copiar-pegar; o se compromete el sitio entero o se sostiene una sola
   familia tonal". Acá el sitio venía haciendo lo primero sin decidirlo.

   DE DÓNDE SALE EL HUE NUEVO (no se eligió: ya estaba medido)
   ----------------------------------------------------------
   La ronda 28 había medido el agua de la foto real de la pileta promediando
   147 píxeles que cumplían "azul y verde por encima del rojo":

       PROMEDIO RONDA 28 .......... #4B7689  HSL(199°, 29%, 42%)

   y lo usó para UNA tarjeta. La ronda 31 remuestreó con el mismo método
   sobre el agua en sombra del atardecer (13.685 píxeles, mismo filtro):

       AGUA EN SOMBRA ............. #365C68  HSL(195°, 32%, 31%)

   Dos mediciones independientes, dos fotos distintas, 4° de diferencia. Y el
   perfil vertical de la foto nueva del hero (banda por banda, 4% de alto,
   sobre el 75% central del cuadro) da el río a hue 209-227 y el cielo a 206.
   El lugar entero mide entre 195° y 209°. El sistema se planta en 200°, que
   es la mediana de eso — no es una preferencia, es dónde cae el promedio.

   LA RAMPA NUEVA — UNA SOLA FAMILIA, DOS EXTREMOS
   -----------------------------------------------
       --color-bg           #F0F3F4  HSL(195°, 15%, 95%)
       --color-bg-alt       #E5E9EB  HSL(200°, 13%, 91%)
       --color-border       #D0D8DC  HSL(200°, 15%, 84%)
       --color-on-dark      #ECF1F3  HSL(197°, 23%, 94%)
       --color-on-dark-soft #B3C0C7  HSL(201°, 15%, 74%)
       --color-ink-soft     #44565F  HSL(200°, 17%, 32%)
       --color-water        #29434C  HSL(195°, 30%, 23%)
       --color-dark-lift    #23373E  HSL(196°, 28%, 19%)
       --color-dark         #18292F  HSL(196°, 32%, 14%)
       --color-ink          #17242B  HSL(201°, 30%, 13%)
       --color-darker       #0F1B1F  HSL(195°, 35%,  9%)

   Once escalones, hue 195-201, o sea 6° de dispersión total contra los 162°
   de antes. La saturación sube al bajar la luz (15% arriba, 35% abajo), que
   es cómo se comporta el agua real y lo que evita que el extremo oscuro se
   lea como gris de oficina.

   CONTRASTES, TODOS MEDIDOS (ninguno estimado):
       --color-ink        / --color-bg ......... 14,23:1
       --color-ink-soft   / --color-bg .........  6,87:1
       --color-wood       / --color-bg .........  7,01:1
       --color-on-dark    / --color-dark ....... 13,20:1
       --color-on-dark    / --color-darker ..... 15,41:1
       --color-on-dark    / --color-dark-lift .. 10,93:1
       --color-on-dark    / --color-water ......  9,21:1
       --color-on-dark-soft / --color-dark .....  8,08:1
       --color-on-dark-soft / --color-water ....  5,64:1
       --color-wood-soft  / --color-dark .......  5,99:1
       --color-wa-ink     / --color-wa .........  8,52:1
       --color-wa         / --color-dark .......  7,58:1

   QUÉ NO CAMBIÓ, Y POR QUÉ NO ES PEREZA
   -------------------------------------
   · LA MADERA (#6C4B2F). Es el promedio ponderado de los píxeles de madera
     de SIETE fotos del sitio, medido en la ronda 12. Ninguna medición que se
     pueda hacer en una pasada le gana a esa. Lo que sí cambió es su TRABAJO:
     antes competía con --color-on-dark, que también era cálido (hue 37°),
     y por eso el acento no se leía como acento. Ahora la madera es lo único
     cálido de la página. Y la relación mejoró: 28° contra 200° son 172° de
     separación, o sea complementarios reales; contra los 96° del fondo viejo
     eran 68°, que es una relación turbia. La regla dura sigue intacta: LA
     MADERA NO PINTA CONTROLES.
   · EL VERDE DE WHATSAPP (#25D366). Se evaluó reemplazarlo, como pide el
     brief, y la conclusión es que la paleta nueva lo MEJORA en vez de
     obligarlo a irse. El número:
         ANTES  --color-dark 152° vs --color-wa 142° = 10° de separación
         AHORA  --color-dark 196° vs --color-wa 142° = 54° de separación
     O sea: el sitio viejo escondía su color de acción adentro de su propia
     familia de hue. Un botón verde sobre una superficie verde a 10° de
     distancia es un botón camuflado. A 54° el verde es inequívoco y sigue
     siendo el único color que en este sitio significa "acá se hace clic".
     Cambiarlo, además, rompería la afordancia del canal por el que este
     negocio convierte. Se conserva, y ahora con una razón medida.
   · --color-star (#B8860B) y --color-wa-ink (#0B1F14). El dorado mide 2,92:1
     sobre el fondo claro nuevo, por debajo del 3:1 que pide un objeto
     gráfico — pero NO se usa nunca ahí: las 36 estrellas del sitio llevan
     todas la clase `.stars--on-dark` (verificado una por una en los 4 HTML y
     en crearEstrellas() de main.js) y miden 3,22:1 sobre --color-water. Se
     deja anotado en vez de "arreglado": oscurecerlo para un caso que no
     existe lo rompería en los casos que sí existen (a L=36% baja a 2,92:1
     sobre el agua). --color-wa-ink pertenece al verde, no a la rampa.
   · LOS COLORES DE ESTADO DEL CALENDARIO (--av-*). No se tocan. Son los
     únicos colores del sitio que CODIFICAN INFORMACIÓN (libre / consultar /
     ocupado), no marca. Teñirlos hacia el hue del río para que "combinen"
     le sacaría al semáforo lo único que lo hace un semáforo. El brief
     además protege la lógica del calendario.

   QUÉ SE BORRÓ
   ------------
   --color-water-soft (#72AECA) se elimina. Estaba SIN USO desde la ronda 30
   (su único trabajo eran las comillas decorativas de la cita, que se fueron
   con el formato de tarjeta de Google). La ronda 30 lo dejó declarado para
   no perder su medición; ahora esa medición quedó obsoleta junto con la
   paleta que la contenía, así que el token se va de verdad.

   UN BUG QUE APARECIÓ DE PASO, Y NO ERA DE COLOR
   ----------------------------------------------
   Revisando el tratamiento de imagen de las fichas se encontró que en las
   TRES, por debajo de 620px, la galería de fotos medía 1575px de ancho dentro
   de una pantalla de 375: el riel no deslizaba y se veía UNA sola foto de
   cinco, con el texto "Deslizá para ver las 5" debajo. Estaba tapado porque
   el <html> lleva `overflow-x: clip`, así que el desborde se recortaba en
   silencio en vez de producir scroll horizontal. La causa es un
   `align-items: start` que significa una cosa en la grilla de desktop y otra
   distinta cuando el mismo elemento pasa a flex en columna en mobile. El
   diagnóstico completo y la medición están en el `@media (max-width: 999px)`
   de `.cabin-layout`. Es anterior a esta ronda; se arregla acá porque esta
   ronda tenía que mirar las fotos de las fichas.

   ==========================================================================
   CHEQUEO LETRA POR LETRA CONTRA LOS 3 ANTI-DEFAULTS (ronda 31)
   ==========================================================================
   Se corre sobre la paleta NUEVA ENTERA, no sobre lo que se agregó.

   #1  CREMA CÁLIDO + SERIF DE ALTO CONTRASTE + TERRACOTA
       · CREMA: la banda prohibida vive en hue 30-50° con saturación 25-35%.
         Las cuatro superficies claras del sistema, medidas:
             --color-bg      HSL(195°, 15%, 95%) → 145° fuera de la banda
             --color-bg-alt  HSL(200°, 13%, 91%) → 150° fuera
             --color-border  HSL(200°, 15%, 84%) → 150° fuera
             --color-on-dark HSL(197°, 23%, 94%) → 147° fuera
         Y las cuatro quedan por debajo del piso de saturación (13-23%
         contra 25%). El fondo viejo estaba a 46° de la banda; el nuevo está
         a 145°. Esta ronda TRIPLICA el margen del único anti-default que
         este proyecto rozó una vez (ronda 5). NO MATCHEA.
         Dato de control: la skill ui-ux-pro-max, consultada esta ronda,
         propone para "Hotel/Hospitality" exactamente "Warm neutrals + Gold
         (#D4AF37)" y para "Travel/Tourism" un naranja #EA580C = HSL(21°,
         90%, 48%). O sea que la respuesta de catálogo para este rubro cae
         justo adentro del anti-default #1. Por eso la paleta se DERIVA de
         fotos y no se busca en una tabla. Queda anotado como hallazgo.
       · SERIF: cero. Siguen siendo Archivo + Source Sans 3, dos sans, que
         además es el máximo de 2 familias que fija el brief. NO MATCHEA.
       · TERRACOTA: no se agregó ninguna. La madera sigue en hue 28°, sigue
         siendo un marrón oscuro (L 30%) y no un terracota de acento
         (L 45-55%, sat 50%+), y sigue sin pintar un solo control.
         NO MATCHEA.
       0 de 3. NO MATCHEA.

   #2  NEGRO PURO + VERDE ÁCIDO / BERMELLÓN
       · NEGRO PURO: --color-darker mide HSL(195°, 35%, 9%). No es #000 ni
         un gris neutro: es agua profunda, con 35% de saturación, que es la
         saturación MÁS ALTA de toda la rampa. Un negro puro tiene
         saturación 0 por definición. NO MATCHEA.
       · VERDE ÁCIDO: el único verde saturado del sitio sigue siendo el de
         WhatsApp, HSL(142°, 70%, 49%), que es el verde de una marca
         registrada y no un lima ácido (los ácidos viven en 60-90° con
         luminosidad por encima del 60%). Y esta ronda lo ALEJA del resto de
         la paleta en vez de acercarlo (10° → 54°). NO MATCHEA.
       · BERMELLÓN: no hay rojo en el :root. El único rojo del archivo
         renderizado sigue siendo el arco de la G de Google (#EA4335),
         encerrado adentro del glifo, con el chequeo completo de la ronda 30
         más abajo — que sigue valiendo palabra por palabra. NO MATCHEA.
       0 de 3. NO MATCHEA.

   #3  DIARIO: HAIRLINES DENSAS + RADIO CERO + COLUMNAS APRETADAS
       · HAIRLINES: esta ronda no agrega ninguna. La jerarquía de la ficha
         de cabaña se resuelve con ESCALA, PESO y COLOR (ver el bloque de la
         ficha más abajo), que era justamente la alternativa a "meter más
         líneas". El conteo queda igual que como lo dejó la ronda 30.
       · RADIO CERO: nada en cero. El hero nuevo va a sangre, o sea que ni
         siquiera tiene borde donde poner un radio; las tarjetas siguen en
         --radius-lg (16px) y el avatar sigue siendo un círculo.
       · COLUMNAS APRETADAS: la ronda va en la dirección contraria. El hero
         pasa de dos columnas a UNA imagen a sangre, y la ficha de cabaña
         SUBE el tamaño del contenido primario. Menos densidad, no más.
       NO MATCHEA.

   Ninguno de los tres matchea, y el margen del #1 —el único que este
   proyecto rozó alguna vez— pasa de 46° a 145°.

   ==========================================================================
   RONDA 29 — PROFUNDIDAD DE FONDO SIN UN SOLO COLOR NUEVO.
   ==========================================================================
   Pedido: "los fondos se sienten planos y monótonos, buscá profundidad real".
   Medido antes de tocar nada, sobre el DOM de producción: el sitio tenía
   0 gradientes, 0 texturas y 0 filtros de ruido. La queja era literal, no
   una impresión — cada sección era UN color plano y nada más.

   QUÉ DIJO EL RESEARCH, Y POR QUÉ NO SE PUSO GRANO
   ------------------------------------------------
   Se volvió a medir el fondo de los 8 sitios de referencia con playwright,
   contando capas sobre el render (no leyendo su CSS de memoria):

     Eastwind ......... 0 gradientes · 0 texturas · 0 ruido
     Postcard Cabins .. 7 gradientes · 0 texturas · 0 ruido
     Juvet ............ 1 gradiente  · 0 texturas · 0 ruido
     Correntoso ....... 0 gradientes · 1 "textura" (es una FOTO 2048x1536)
     Awasi ............ 0 gradientes · 0 texturas · 0 ruido
     Dunton ........... 0 gradientes · 0 texturas · 0 ruido (fotos de fondo)
     Jägerhof ......... 0 gradientes · 0 texturas · 0 ruido
     Linear .......... 32 gradientes · 1 textura SVG · 0 ruido

   NINGUNO de los ocho usa <feTurbulence> ni grano. Cero de ocho. Dos de las
   skills cargadas esta ronda proponen "subtle noise / film-grain overlay"
   como respuesta refleja a un fondo plano; el research de ESTA categoría
   dice que no, así que no se puso. La profundidad en hotelería premium sale
   de la FOTOGRAFÍA y de capas de LUZ, no de textura.

   De dónde sale entonces cada decisión:
   · LINEAR (G) es el único que resuelve profundidad en CSS puro, y lo hace
     con gradientes radiales apilados a alfa bajísima. Medido literal en su
     :root renderizado:
         radial-gradient(52.53% 57.5% at 50% 100%, rgba(8,9,10,0) 0%,
                         rgba(8,9,10,0.5) 100%)
         sobre linear-gradient(rgb(8,9,10) 10%, rgb(208,214,224) 100%)
       y sus "glows": rgba(255,255,255,0.04).
     Cuatro centésimas. Ése es el número que hace que se lea como LUZ y no
     como una mancha. Las capas de acá usan 0.04-0.055 por eso.
   · POSTCARD CABINS (B) aporta el scrim con MESETA en vez de fundido lineal
     (0.62 -> 0.54 -> 0): la oscuridad se sostiene y recién después suelta.
     Es la forma de las paradas de --seam-dark/--seam-light de abajo.
   · JÄGERHOF sirve de CONTRAEJEMPLO y por eso se capturó: sus bloques miden
     900/900/958/906/906/906/906/906 — casi todos iguales. Es el sitio más
     monótono de los ocho y el que más se lee a plantilla. Confirma que la
     monotonía es tanto de RITMO como de color, así que esta ronda además
     acorta el equipamiento y elimina una sección entera.

   NO ENTRA NINGÚN COLOR NUEVO AL :root. Todas las capas nuevas son
   transparencias de tokens QUE YA EXISTÍAN, más blanco puro para el realce:
       rgba(236,241,243,α) = --color-on-dark   (realce sobre oscuro)
       rgba(15,27,31,α)    = --color-darker    (asiento sobre oscuro)
       rgba(23,36,43,α)    = --color-ink       (asiento sobre claro)
       rgba(255,255,255,α) = blanco            (realce sobre claro)
   Es decir: no se suma hue, se suma LUZ. Aun así el chequeo se corre entero,
   porque la regla del proyecto es de fondo nuevo, no sólo de color nuevo.

   ⚠️ LA MADERA NO SE USA PARA TEÑIR FONDOS, Y LA RAZÓN ES UNA MEDICIÓN QUE
   SALIÓ AL REVÉS DE LO ESPERADO. El primer intento de esta ronda teñía las
   secciones claras con --color-wood a 4,5%, que era el reflejo obvio
   ("calidez de cabaña"). Compuesto de verdad sobre #EEF1EC da:
       wood 4,5% sobre --color-bg .... #E8EAE3  HSL(74°, 12%, 90%)
   El hue se corre de 96° a 74°: veintidós grados HACIA la banda del crema
   prohibido (30-50°), dejando apenas 24° de margen en vez de los 46 que
   tenía el fondo pelado. No llega a matchear —la saturación queda en 12%
   contra el 25-35% que la banda exige— pero achica el margen del único
   anti-default que este proyecto ya rozó una vez (ronda 5). Con tinta pasa
   lo contrario:
       ink 5% sobre --color-bg ....... #E4E7E2  HSL(100°, 10%, 90%)
   El hue se ALEJA (96° -> 100°) y la saturación baja. Así que las capas de
   las secciones claras van con TINTA y con BLANCO, no con madera. Beneficio
   lateral: la madera se queda con sus dos trabajos declarados (rotular y
   separar) y no empieza a pintar superficie, que sería una tercera tarea
   que nadie autorizó.

   Las otras composiciones, medidas igual:
       blanco 55% sobre --color-bg ... #F7F9F6  realce neutro, sin drift
       on-dark 5% sobre --color-dark . #212D26  HSL(147°, 15%, 15%)
       on-dark 5% sobre --color-water  #2E3E45  HSL(198°, 20%, 22%)
   El realce sobre la banda oscura mueve el hue UN grado (154° -> 147°) y
   sube la luz 4 puntos: es exactamente el efecto buscado —que la superficie
   tenga un lado iluminado— sin convertirla en otro color.

   ==========================================================================
   RONDA 30 — LOS 4 COLORES DE LA G DE GOOGLE. CHEQUEO APARTE.
   ==========================================================================
   La tarjeta de reseña pasa al formato de una reseña real de Google y suma
   el logotipo de la G. Eso mete cuatro colores que este sitio no tenía:

       rojo     #EA4335  HSL(  5°, 81%, 56%)
       azul     #4285F4  HSL(217°, 89%, 61%)
       amarillo #FBBC05  HSL( 45°, 97%, 50%)
       verde    #34A853  HSL(136°, 53%, 43%)

   NO ENTRAN AL :root Y NO SON TOKENS. Viven adentro del <svg> de la G, en
   los atributos fill, y en ningún otro lado del sitio. No se pueden reusar
   por accidente porque no tienen nombre.

   #1  CREMA CÁLIDO — el amarillo cae DENTRO de la banda de hue (45°, la
       banda prohibida es 30-50) y fuera de las otras dos: sat 97% contra
       25-35%, luz 50% contra 84-97. 1 de 3. Y el crema del anti-default es
       un FONDO casi blanco; esto es un arco de 24px que no pinta superficie.
       NO MATCHEA.

   ⚠️ #2  BERMELLÓN — ACÁ HAY UN HALLAZGO REAL Y SE ANOTA COMO TAL.
       El rojo #EA4335 mide HSL(5°, 81%, 56%): cae DENTRO de la banda del
       bermellón (hue 5-10 con saturación alta) y queda a 7° de la terracota
       del anti-default #1. Medido, no estimado. Es el único color que este
       proyecto haya incorporado que aterriza adentro de una banda prohibida.

       Por qué entra igual, y qué lo contiene:
       · No es un color de la paleta: es un fragmento de una marca registrada
         ajena, reproducido literal para atribuir. La G existe para decir de
         dónde viene la reseña, no para decorar.
       · No pinta ninguna superficie, ningún control y ningún texto. Ocupa
         0,14% de la tarjeta a 1440 y 0,15% a 375 (arco rojo contra la caja
         de la tarjeta, calculado sobre las medidas reales de la caja).
       · No es clickeable. El verde de WhatsApp sigue siendo el único color
         que en este sitio significa "acá se hace clic", sin excepción nueva.
       · Y NO SE PUEDE ARREGLAR RECOLOREANDO: retocar el rojo de un logotipo
         ajeno para que pase un chequeo interno es uso indebido de marca.
         La opción real era ponerlo tal cual o no ponerlo.
       La mitigación es de CONTENCIÓN, no de sustitución: el color queda
       encerrado en el glifo. Si algún día alguien lo copia al :root o lo usa
       para pintar otra cosa, ahí sí matchea y hay que sacarlo.

   #3  DIARIO — esta ronda BORRA una hairline (la del pie de la tarjeta, que
       había puesto la ronda 29) y no agrega ninguna. El sitio queda en 12
       contra 13. La tarjeta mantiene --radius-lg y el avatar es un círculo,
       así que sigue sin haber radio cero. NO MATCHEA.

   CONTRASTE DE LA G: azul 3,07:1, amarillo 6,41:1 y verde 3,58:1 sobre la
   superficie iluminada de la tarjeta; el rojo mide 2,79:1 y no llega a 3.
   WCAG 1.4.11 exime explícitamente a los logotipos del mínimo de contraste,
   y además la G se lee por su forma completa, no por un arco suelto. Se deja
   anotado en vez de "arreglado" por lo mismo de arriba: no se recolorea.

   ==========================================================================
   CHEQUEO LETRA POR LETRA CONTRA LOS 3 ANTI-DEFAULTS (ronda 29)
   --------------------------------------------------
   #1  CREMA CÁLIDO + SERIF DE ALTO CONTRASTE + TERRACOTA
       · Fondo: sigue siendo #EEF1EC = HSL(96°, 15%, 94%), verde frío. Las
         dos capas que se le apoyan lo alejan del crema en vez de acercarlo,
         medido y no estimado:
             blanco 55% ... #F7F9F6  realce neutro, sin corrimiento de hue
             ink 5% ....... #E4E7E2  HSL(100°, 10%, 90%)
         El extremo más "cálido" que puede alcanzar una sección clara de
         este sitio es ahora hue 100°, es decir CUATRO GRADOS MÁS VERDE que
         el fondo pelado, con la saturación bajando de 15% a 10%. El crema
         prohibido vive en hue 30-50 / sat 25-35%: quedan 50° de margen por
         hue y 15 puntos por saturación, más que antes de esta ronda.
         (Ver arriba por qué se descartó teñir con madera, que era el camino
         obvio y el que achicaba ese margen a 24°.)
         Dato de control del research: Jägerhof mide HSL(35°, 30%, 88%), o
         sea que aterriza dead center en la banda prohibida. Ese es el
         aspecto que estas capas NO pueden producir. NO MATCHEA.
       · Serif: cero. Siguen siendo Archivo + Source Sans 3, dos sans.
         NO MATCHEA.
       · Terracota: no se agregó. La madera sigue en hue 28 y sigue sin
         pintar controles. NO MATCHEA.
       0 de 3 ingredientes nuevos. NO MATCHEA.

   #2  NEGRO + VERDE ÁCIDO / BERMELLÓN
       · Las superficies oscuras siguen siendo #16231D HSL(154°,22%,11%) y
         #0E1712: verde-carbón, no negro. Las capas de luz que se les suman
         son --color-on-dark (#EDE4D3, hue 37° desaturado) a 4-5,5%: sobre
         #16231D dan #212D26 = HSL(147°, 15%, 15%), medido. El hue se mueve
         7 grados dentro de la misma familia verde y la luz sube 4 puntos:
         la superficie gana un lado iluminado y no cambia de color. Sobre
         la tarjeta de reseña (--color-water) da #2E3E45 = HSL(198°, 20%,
         22%), o sea que el agua tampoco cambia de hue (199° -> 198°).
         No aparece ningún verde ácido nuevo.
       · El verde de WhatsApp HSL(142°,70%,49%) sigue siendo el único color
         que significa "acá se hace clic". Se revisó uno por uno lo que esta
         ronda agrega: ninguna capa de fondo es clickeable, y los dos
         botones nuevos de #contacto (Instagram y Facebook) van con relleno
         NEUTRO —--color-on-dark sobre texto --color-dark, 12,88:1— y no con
         los violetas y azules de esas marcas, que es la misma decisión que
         ya tomó la ronda 15 para el bloque #redes. Sumar dos saturados
         ajenos al lado del verde le sacaría al verde lo único que lo hace
         significar algo.
       · Bermellón: no hay rojo en el archivo. NO MATCHEA.

   #3  DIARIO: HAIRLINES + RADIO CERO + COLUMNAS DENSAS
       · Éste es el que hay que mirar de verdad esta ronda, porque el
         rediseño del equipamiento pone 4 ítems en fila y el de la tarjeta
         de reseña suma una hairline. Se recuenta entero:
         hairlines del sitio = 3 (ficha de datos) + 6 (ficha técnica de
         cabaña) + 3 (equipamiento, que BAJA de 4 a 3 al pasar a fila) + 1
         (pie de la tarjeta de reseña, nueva). Total 13 contra 16 antes:
         esta ronda deja MENOS hairlines de las que había, no más. Todas
         siguen separando datos que existen por separado.
       · Radios: nada en cero. El anillo nuevo de la tarjeta de reseña usa
         el mismo --radius-lg del que ya tenía.
       · Columnas densas: los 4 ítems en fila tienen 48px de canaleta y
         mucho aire; no es una retícula de diario. Y la página total se
         ACORTA (se elimina una sección entera), así que la densidad baja.
       NO MATCHEA.

   Ninguno de los tres matchea. El fondo nuevo entra por la puerta más
   angosta posible: sin hue nuevo, sólo luz, y con el número (0.04) tomado
   de una medición y no de una preferencia.

   ==========================================================================
   RONDA 28 — SEGUNDO COLOR SATURADO, MEDIDO, NO ELEGIDO A OJO.
   ==========================================================================
   El cliente reabrió expresamente la regla de "un solo color saturado" para
   esta ronda: el carrusel de reseñas (tarjeta --color-dark-lift sobre banda
   --color-dark, los dos verde-carbón) se leía como una sola mancha — "todo
   se mezcla". La ronda 27 ya había resuelto el problema anterior (cajas
   blancas sobre negro); este es el problema que esa solución dejó atrás.

   La solución NO es aflojar la regla dura de acción (el verde de WhatsApp
   sigue siendo el único color que dice "acá se hace clic" en todo el
   sitio) sino sumar un SEGUNDO acento, con el mismo método que ya se usó
   para la madera: medido de una foto real del lugar, no elegido de un
   catálogo, y con un trabajo declarado y acotado.

   Fuente: la propia foto de la sección (proof__figure,
   lugar-pileta-atardecer-960.webp) — la pileta con la gente adentro, en la
   franja de agua iluminada. Muestreo sobre el render (no hay decodificador
   WebP disponible en este entorno para leer el archivo fuente directo, así
   que se muestreó el PNG ya decodificado por el navegador — mismo pixel,
   un paso más): 147 puntos que cumplen "azul y verde por encima del rojo
   en al menos 25-40 unidades", promediados.

     PROMEDIO MEDIDO ............ #4B7689  HSL(199°, 29%, 42%)

   De ahí se derivan dos tokens por el mismo procedimiento que ya usan
   --color-wood / --color-wood-soft (mismo hue, dos bandas de luminosidad
   distintas según dónde se posan):
     --color-water      #24353D  HSL(199°, 26%, 19%) — bajado a la banda de
                         luminosidad de las superficies oscuras del sitio
                         (--color-dark 11% / --color-dark-lift 15%), para
                         que la tarjeta siga leyéndose como SUPERFICIE y no
                         como objeto flotante encima de la banda.
     --color-water-soft #72AECA  HSL(199°, 45%, 62%) — subido a banda de
                         acento legible sobre fondo oscuro, para el detalle
                         decorativo (la comilla grande de la cita).

   EL AGUA HACE UN SOLO TRABAJO, declarado y nada más: es la SUPERFICIE de
   la tarjeta de reseña y el acento de su comilla decorativa. No pinta
   ningún control, no reemplaza a la madera en sus dos trabajos (rotular y
   separar), no aparece en ninguna otra sección. Un color, un lugar.

   CHEQUEO LETRA POR LETRA CONTRA LOS 3 ANTI-DEFAULTS (ver el chequeo
   completo de la ronda 12 más abajo — este repite el mismo método):

   #1  CREMA CÁLIDO + SERIF DE ALTO CONTRASTE + TERRACOTA
       El agua no es terracota ni ningún pariente: hue 199° (azul-cian frío)
       contra hue 12° (terracota rechazada, ronda 6) o 28° (madera). No hay
       fondo crema en la sección donde vive (sigue siendo --color-dark,
       verde-carbón) y no hay serif en el archivo. 0 de 3 ingredientes.
       NO MATCHEA.
   #2  NEGRO + VERDE ÁCIDO / BERMELLÓN
       El agua no es verde ácido: HSL(142°,70%,49%) es el verde de WhatsApp,
       hue 142; el agua está en hue 199, del otro lado de la rueda, familia
       azul-cian. Tampoco es bermellón (no hay rojo). Y la regla de USO se
       mantiene sin excepción nueva: la tarjeta de reseña es clickeable
       (abre la reseña completa) igual que ya lo era con --color-dark-lift,
       así que el agua no le suma afordancia de control a nada que no la
       tuviera ya — la tarjeta y el cursor la crean, no el color, mismo
       argumento que sostiene el rótulo de madera en la ronda 12. Ninguna
       flecha, botón, link de texto, estado de calendario o foco cambia de
       color por esto. NO MATCHEA.
   #3  DIARIO: HAIRLINES + RADIO CERO + COLUMNAS DENSAS
       No se tocó ningún radio (la tarjeta sigue en --radius-lg) ni se sumó
       ninguna hairline nueva. NO MATCHEA.
   Ninguno de los tres matchea, letra por letra. El agua entra por la misma
   puerta que la madera en la ronda 12 y queda con la misma disciplina: un
   color medido, con un trabajo, en un lugar.

   ==========================================================================
   RONDA 15 — RE-CHEQUEO. La paleta tampoco cambió esta ronda. Lo que se
   rehízo es la composición del hero (el sello dejó de ser una tarjeta sobre
   la foto y el mazo de cabañas pasó a ser una repisa alineada a la grilla),
   la ficha técnica de cada cabaña y el bloque de redes. Ninguno de los tres
   toca color.
   Repaso de la regla de madera después de los cambios — todo lo que quedó
   pintado con --color-wood o --color-wood-soft es rótulo o hairline, nada es
   un control: rótulo de localidad del hero, capacidad sobre cada foto de la
   repisa, el "HAY MÁS" de la cuarta celda, rótulos de la ficha de datos,
   íconos del equipamiento del home, íconos y etiquetas de la ficha técnica,
   numeradores de los tres pasos de la tarjeta de reserva, rótulos de columna
   del pie, y las hairlines (repisa, cuarta celda, filas de la ficha).
   Las dos únicas que viven DENTRO de un <a> —la capacidad y el "HAY MÁS"—
   son la excepción ya declarada en la ronda 12: rótulo dentro de una tarjeta
   que es link, no el texto del link.
   Lo nuevo que suma color: nada. El bloque de redes se rehizo a propósito
   SIN los colores de marca de Instagram y Facebook (violeta y azul). Dos
   cuadraditos saturados ajenos a la paleta, al lado del verde de WhatsApp,
   le sacan al verde lo único que lo hace significar algo. Las dos placas van
   en tinta.

   ==========================================================================
   RONDA 14 — RE-CHEQUEO. La paleta NO cambió: los tres veredictos de abajo
   siguen valiendo tal cual están escritos. Lo que cambió es el HERO, que
   pasó a estar partido en desktop, y eso movió una cuenta de la lista:
     · El rótulo de localidad ya no cae sobre la foto en ningún ancho, así
       que perdió la plaquita que lo protegía. Sobre --color-darker mide
       7,44:1 sin nada abajo (antes, sobre el cielo de la foto, 3,37:1).
     · El texto del hero dejó de necesitar el degradado de 6 paradas: apoya
       en superficie sólida y mide 16,4:1 por construcción.
   Y la auditoría de uso de la madera encontró UNA fuga, ya corregida: los
   tres atajos de la sección de fechas (ronda 13) eran links pintados con
   --color-wood, y la regla dura declarada más abajo dice que la madera no
   toca elementos interactivos. Pasaron a --color-ink-soft. Es la segunda
   vez que pasa lo mismo, así que queda anotado al lado de la regla.
   Repaso de dónde vive la madera hoy, todos rótulos o líneas, ninguno
   clickeable: rótulo del hero, datos del predio del hero, rótulos de la
   ficha de datos, íconos del equipamiento, numeradores de los tres pasos
   de la ficha, rótulos de columna del pie, capacidad sobre cada foto del
   mazo, y las hairlines.

   ==========================================================================
   CHEQUEO LETRA POR LETRA CONTRA LOS 3 ANTI-DEFAULTS — RONDA 12
   ==========================================================================
   Vuelve un acento MARRÓN/MADERA, que la ronda 6 había sacado. El pedido
   del cliente trae con él la corrección que faltaba: el anti-default #1
   prohíbe una COMBINACIÓN de tres ingredientes, no cada ingrediente por
   separado. La ronda 6 lo aplicó como si prohibiera el marrón solo, y con
   eso se llevó puesto el único color que el lugar tiene de verdad.

   El marrón de esta ronda además NO es una elección de humor: es una
   MEDICIÓN. Se muestrearon 7 fotos del sitio en canvas, promediando sólo
   los píxeles en banda madera (hue 14-46, sat 18-70%, luz 14-55%):

     hero-cabana ................ #63513F  HSL(30, 22%, 32%)
     cabana-6-exterior .......... #665541  HSL(32, 22%, 33%)
     lugar-quincho .............. #7D5836  HSL(29, 40%, 35%)   29,0% del cuadro
     quincho-mesa-larga ......... #7A5B47  HSL(24, 26%, 38%)
     cabana-2-4-interior ........ #6A482B  HSL(28, 42%, 29%)   25,2% del cuadro
     cabana-6-escalera-comedor .. #794D32  HSL(23, 42%, 34%)   18,2% del cuadro
     cabana-5-living ............ #554027  HSL(33, 37%, 24%)   26,4% del cuadro
     ──────────────────────────────────────────────────────────────────
     PROMEDIO PONDERADO ......... #6C4B2F  HSL(28, 39%, 30%)

   Los 7 hues caen entre 23 y 33: 10 grados de dispersión. No es "un
   marrón", es la madera de UN lugar, y el token es su promedio exacto.

   #1  CREMA CÁLIDO + SERIF DE ALTO CONTRASTE + TERRACOTA
       · Fondo: #EEF1EC = HSL(96°, 15%, 94%). (El header de la ronda 6
         decía 103°/12%; recalculado ahora da 96°/15% — mismo color, la
         cuenta vieja estaba redondeada de más. El argumento no cambia.)
         El hue 96 es VERDE. El crema prohibido vive en hue 30-50 con
         saturación 25-35%. Fuera de la banda por hue (46° de distancia
         al borde más cercano) y por saturación (15% contra 25-35%).
         SIGUE SIN MATCHEAR — y no se toca en esta ronda.
       · Display: ARCHIVO. Cuerpo: SOURCE SANS 3. (La ronda 12 decía
         Alegreya Sans / Public Sans; las cambió la 13 y este renglón se
         había quedado viejo.) Dos sans-serifs: cero remates en todo el
         archivo, cero familias serif cargadas.
         SIGUE SIN MATCHEAR.
       · Terracota: acá está el ingrediente que vuelve, así que se mide
         en vez de argumentarse.
         La terracota rechazada en la ronda 6 era #7B4133 = HSL(12°, 41%,
         34%). Hue 12 es rojo-anaranjado: barro cocido, óxido.
         La madera de esta ronda es #6C4B2F = HSL(28°, 39%, 30%). Hue 28
         es marrón: nogal, umber. Son 16° de separación, que es
         justamente el ancho que separa "rojo" de "marrón" a esta
         saturación.
         Pero la distancia de hue sola no lo absuelve, y no es el
         argumento. El argumento es que de los TRES ingredientes hay UNO
         solo: el fondo es verde frío y la tipografía es sans. Un acento
         marrón sobre fondo verde y tipografía de palo seco no
         reconstruye "crema + serif + terracota"; le falta el fondo y le
         falta la letra, que son los dos que hacen el clima.
         NO MATCHEA: 1 de 3, y el que está, medido de las fotos.

   #2  NEGRO + VERDE ÁCIDO / BERMELLÓN
       · #16231D no es negro: es verde-carbón, HSL(154°, 22%, 11%).
       · #25D366 es el verde OFICIAL de WhatsApp. Es afordancia de canal,
         no elección estética: marca lo que abre la conversación y nada
         más lo usa. No es neón ácido (HSL 142°, 70%, 49%).
       · No hay bermellón en ningún lado.
       · Lo que SÍ cambia en esta ronda y hay que declarar: el verde deja
         de ser el único color saturado de la página. Medido, la acción
         sigue ganando por lejos — WhatsApp HSL(142, 70%, 49%) contra
         madera HSL(28, 39%, 30%): 31 puntos más de saturación y 19 más
         de luz. Y la regla dura es de USO, no de color: LA MADERA NO
         TOCA NINGÚN ELEMENTO INTERACTIVO. Ni un botón, ni un link, ni un
         estado del calendario. Vive en rótulos y en hairlines, que son
         las dos cosas que nadie intenta clickear.
       NO matchea.

   #3  DIARIO: HAIRLINES + RADIO CERO + COLUMNAS DENSAS
       · Éste es el que la madera roza, porque suma hairlines. Se
         recuenta: las hairlines del sitio son 3 (ficha de datos), 3
         (inventario del predio), 6 (ficha técnica de cabaña) y 4
         (equipamiento). Todas separan datos que EXISTEN por separado,
         ninguna decora ni arma retícula.
       · Radios de 8 a 24px en todo, ninguno en cero.
       · Fotos grandes y mucho aire, cero columnas de diario.
       NO matchea.

   ==========================================================================
   PALETA — 3 roles + un acento de madera que no es de acción
   ==========================================================================
   Un fondo frío, una tinta, una superficie oscura, UN color saturado
   reservado a la acción (verde WhatsApp) y UN acento de madera que sólo
   rotula y separa.
   Nota para el cliente: vuelve el marrón que él pidió en la ronda 5 y
   que la ronda 6 sacó. La madera está ahora en los dos lados: en las
   fotos, donde es real, y en la interfaz, atada por medición a esas
   mismas fotos.

   TIPOGRAFÍA — 2 familias, tope respetado (cambiada en la ronda 13)
     ARCHIVO (display, variable wdth 100-125 / wght 500-900) — grotesca de
       Omnibus-Type, fundición ARGENTINA (Buenos Aires). Se mantiene la
       razón local que tenía Alegreya Sans, que también era argentina: para
       un destino de Entre Ríos la letra sale de acá, no de un catálogo.
       Lo que cambia es el registro: Archivo tiene EJE DE ANCHO real, así
       que el titular se compone ENSANCHADO (wdth 112) en vez de sólo
       engordado. Un display ancho y pesado es lo que separa una portada de
       hotel de un h1 de plantilla, y no se consigue con font-weight.
     SOURCE SANS 3 (cuerpo, variable 400-700) — humanista de Paul D. Hunt,
       con la altura de x alta y las aperturas grandes que hacen falta en
       una pantalla chica con señal floja, que es el contexto real de este
       comprador. Reemplaza a Public Sans, que hacía bien ese trabajo pero
       era otra grotesca: al lado de Archivo se leían como la misma letra
       en dos tamaños.

     EJE DE CONTRASTE, que es lo que la regla pide: grotesca ANCHA de
     display contra humanista de texto. Contrastan por esqueleto (una es
     de tensión vertical y cerrada, la otra caligráfica y abierta) y
     además por ANCHO, que el par viejo no tenía. Ningún serif: el
     anti-default #1 ya tiene un ingrediente puesto (la madera) y sumarle
     el serif lo dejaría en 2 de 3.

   POR QUÉ NO SE FUE A UN SERIF, que era el reflejo obvio para "más
     estético" en hotelería: la categoría entera lo hace (Awasi, Correntoso
     y Dunton River Camp los tres usan serif de display). Copiarlo es la
     segunda capa del reflejo, no una decisión. Un grotesco ancho sobre
     fondo verde frío no se parece a ninguno de los tres.

   CONTRASTE WCAG (luminancia relativa)
     --color-ink sobre --color-bg .............. 12.94:1
     --color-ink-soft sobre --color-bg ..........  7.26:1
     --color-on-dark sobre --color-dark ......... 12.88:1
     --color-on-dark-soft sobre --color-dark ....  7.57:1
     #0B1F14 sobre #25D366 (botón WhatsApp) ......  8.68:1
       ⚠️ Corregido en la ronda 29. Acá decía 10.4:1 desde que se escribió
       la tabla, y está mal: recalculado con la fórmula de luminancia
       relativa de WCAG da 8,68:1 (L del verde 0,4796, L de la tinta
       0,0110). Sigue pasando AA de sobra y no cambia ninguna decisión —el
       par se eligió porque blanco sobre ese verde da 2,1:1 y reprueba—,
       pero el número estaba inflado y esta tabla sólo sirve si es exacta.
     --color-wood sobre --color-bg ..............  6.86:1
     --color-wood sobre --color-bg-alt ..........  6.35:1
     --color-wood-soft sobre --color-dark .......  6.48:1
     --color-wood-soft sobre --color-dark-lift ..  5.76:1
   Los cuatro valores de madera pasan AA para texto chico (4.5:1), que es
   el tamaño en el que efectivamente se usa. No hay ningún uso de madera
   que dependa del umbral flojo de 3:1.

   CONTRASTE SOBRE LAS SUPERFICIES NUEVAS DE LA RONDA 29
     Una capa de luz sobre un fondo claro lo ACLARA y le baja el contraste
     al texto oscuro; una sobre fondo oscuro lo aclara y se lo baja al texto
     claro. Así que no alcanza con medir contra el color base de la sección:
     abajo va el PEOR CASO de cada capa, o sea el punto donde el realce o el
     asiento están al 100% de su alfa.
       ink sobre claro+realce (#F7F9F6) .......... 13.92:1
       ink sobre claro+asiento (#E4E7E2) ......... 11.82:1
       ink-soft sobre claro+asiento ..............  6.63:1
       ink-soft sobre bg-alt+asiento (#DDE2DA) ...  6.30:1
       wood sobre claro+asiento ..................  6.27:1
       on-dark sobre oscuro+realce (#222E27) ..... 11.23:1
       on-dark-soft sobre oscuro+realce ..........  6.60:1
       blanco sobre oscuro+realce ................ 14.17:1
       on-dark sobre oscuro+asiento (#121C17) .... 13.78:1
       wood-soft sobre oscuro+realce .............  5.65:1
       on-dark sobre tarjeta+realce (#2F3F45) ....  8.71:1
       on-dark-soft sobre tarjeta+realce .........  5.12:1
       water-soft sobre tarjeta+realce ...........  4.51:1
       blanco sobre tarjeta+realce ............... 10.99:1
       btn--channel: dark sobre on-dark .......... 12.88:1
     Los 16 pasan AA para texto chico. El peor es la comilla decorativa de
     la cita (4,51:1), que por ser decorativa ni siquiera estaría obligada
     al umbral, y lo pasa igual. Ninguna capa de luz empuja a nada por
     debajo de 4,5.
   ========================================================================== */

/* Las fuentes ya NO se piden desde acá: el @import encadenaba dos viajes
   en serie (bajar esta hoja → parsearla → recién ahí pedir las fuentes).
   Ahora van con <link rel="stylesheet"> + preconnect en el <head> de cada
   página, en paralelo con este archivo. */

:root {
  /* --- Neutros — RAMPA DEL RÍO (ronda 31). Ver la derivación en el header.
         Una sola familia de hue (195-201°) de punta a punta: lo que antes
         eran DOS familias sin relación (claro 96° verde / oscuro 154° verde)
         ahora son los dos extremos de la misma rampa. --- */
  --color-bg: #f0f3f4;        /* HSL(195, 15%, 95%) */
  --color-bg-alt: #e5e9eb;    /* HSL(200, 13%, 91%) */
  --color-ink: #17242b;       /* HSL(201, 30%, 13%) — 14,23:1 sobre --color-bg */
  --color-ink-soft: #44565f;  /* HSL(200, 17%, 32%) —  6,87:1 sobre --color-bg */
  --color-border: #d0d8dc;    /* HSL(200, 15%, 84%) */

  /* --- Superficie oscura (panel del hero y CTA final: son un par).
         Mismo hue que la banda clara: el extremo profundo de la rampa, no
         un color aparte. --- */
  --color-dark: #18292f;      /* HSL(196, 32%, 14%) */
  --color-darker: #0f1b1f;    /* HSL(195, 35%,  9%) */
  /* Cuarto escalón de la MISMA rampa oscura, no un color nuevo:
     HSL(196°, 28%, 19%) contra el 196°/32%/14% de --color-dark. Existe por
     la misma razón por la que --color-border es el tercer escalón de la
     rampa clara: para que una superficie apoyada sobre la banda oscura se
     lea COMO superficie y no como un matiz. Se usa en la luz del hero.
     Contraste verificado sobre él (medido, ronda 31):
     --color-on-dark 10,93:1. */
  --color-dark-lift: #23373e;
  /* ⚠️ RONDA 31 — LA TINTA SOBRE OSCURO DEJÓ DE SER CÁLIDA, Y ES UNA
     DECISIÓN DE SISTEMA, NO UN AJUSTE. Era #EDE4D3, un crema hue 37°. El
     problema no era el color en sí sino que había DOS cosas cálidas sobre
     la banda oscura —esta tinta y la madera— y por eso la madera no se
     leía como acento: competía con el fondo de texto. Ahora la tinta es el
     extremo claro de la rampa del río y la madera queda como lo ÚNICO
     cálido de la página, que es lo que un acento tiene que ser.
     13,20:1 sobre --color-dark · 15,41:1 sobre --color-darker. */
  --color-on-dark: #ecf1f3;      /* HSL(197, 23%, 94%) */
  --color-on-dark-soft: #b3c0c7; /* HSL(201, 15%, 74%) — 8,08:1 sobre --color-dark */

  /* --- ACENTO SECUNDARIO: MADERA. Ver el chequeo anti-default arriba.
         #6C4B2F es el promedio ponderado medido de los píxeles de madera
         de 7 fotos del sitio, no un marrón de catálogo.

         REGLA DE USO, y es dura: LA MADERA NO PINTA CONTROLES.
         Ni botones, ni links de texto, ni estados del calendario, ni
         focus rings. El verde de WhatsApp sigue siendo el único color de
         acción del sitio.

         Una precisión que la ronda 12 tuvo que hacer al construir el hero
         nuevo, porque la primera redacción decía "nada interactivo" y el
         propio código la desmentía: lo que la regla protege es que ningún
         color además del verde se lea como "acá se hace clic". Un RÓTULO
         de madera adentro de una tarjeta que entera es un link (la
         capacidad debajo de la foto de cada cabaña) no crea esa
         afordancia — la crean la tarjeta y la foto, y el rótulo sigue
         haciendo de rótulo. Un LINK DE TEXTO en madera sí la crearía, y
         por eso el "Dejanos tu reseña" del sello va en tinta y no en
         madera. La prueba es de control, no de elemento interactivo.

         La madera hace dos trabajos y ninguno más:
           1. ROTULAR  — las etiquetas que nombran una fuente o un campo
                         (GOOGLE / FACEBOOK / CINCO ESTRELLAS, Camas,
                         Climatización, el rótulo del mapa, la localidad
                         del hero). Son rótulos que ya existían: esto los
                         pinta, no agrega ninguno. El sitio sigue SIN
                         eyebrow decorativo arriba de cada sección — esa
                         decisión de la ronda 6 no se reabre.
           2. SEPARAR  — las hairlines que dividen datos reales.
         --color-wood      va sobre superficie CLARA.
         --color-wood-soft va sobre superficie OSCURA (mismo hue, 29 vs
                           28; sube la luz para pasar contraste). Se
                           distingue de --color-on-dark-soft #BFAF95 por
                           saturación (48% contra 25%): uno es acento, el
                           otro es tinta apagada, y a simple vista se ve.
         Las dos hairlines son transparencias del acento, no colores
         nuevos: sobre claro tiñen hacia la madera, sobre oscuro tiñen
         hacia el reflejo cálido de la misma madera. --- */
  --color-wood: #6c4b2f;
  --color-wood-soft: #cb9a6b;
  --color-wood-line: rgba(108, 75, 47, 0.28);
  --color-wood-line-dark: rgba(203, 154, 107, 0.3);

  /* --- AGUA — segundo acento medido, ronda 28. Ver el chequeo anti-default
         completo arriba en el header. Mismo método que la madera (promedio
         de píxeles de una foto real, acá la pileta de proof__figure), y la
         misma disciplina de uso: UN trabajo, UN lugar.
         --color-water es la superficie de la tarjeta de reseña (reemplaza
         a --color-dark-lift ahí y sólo ahí). Ninguno de los dos pinta un
         control: la regla que protege al verde de WhatsApp como único color
         de acción no tiene una excepción nueva acá.

         ⚠️ --color-water-soft QUEDÓ SIN USO EN LA RONDA 30. Su único trabajo
         era pintar las comillas tipográficas decorativas de la cita, y esas
         se fueron cuando la tarjeta pasó al formato de reseña de Google, que
         muestra el texto pelado. Se deja declarado y NO se borra por una
         razón concreta: el valor está medido (5,22:1 sobre --color-water) y
         justificado en la ronda 28 junto con --color-water, del que es el
         par claro. Borrarlo perdería esa medición; el que lo vuelva a
         necesitar tiene acá el número y no tiene que medirlo de nuevo. --- */
  /* ⚠️ RONDA 31 — EL AGUA DEJÓ DE SER UN ACENTO Y PASÓ A SER EL SISTEMA.
     Este token existía desde la ronda 28 para resolver un problema puntual
     ("la tarjeta de reseña verde sobre banda verde se lee como una sola
     mancha"). La ronda 31 se dio cuenta de que la medición de la ronda 28
     —#4B7689, HSL(199°, 29%, 42%), promediada sobre 147 píxeles de la foto
     de la pileta— era la respuesta correcta a una pregunta más grande: el
     sitio se llama "Cabañas frente al río Paraná" y estaba pintado con los
     colores del monte, no del río. Así que el hue del agua se promovió a
     hue del sistema entero (ver el header) y este token quedó como lo que
     siempre debió ser: UN ESCALÓN MÁS de la rampa, el que usa la tarjeta de
     reseña. Sigue haciendo un solo trabajo y sigue sin pintar controles.
     9,21:1 con --color-on-dark · 5,64:1 con --color-on-dark-soft. */
  --color-water: #29434c;  /* HSL(195, 30%, 23%) */

  /* --- LUZ — capas de profundidad (ronda 29). NO SON COLORES: son alfas de
         tokens que ya existen. Ver el chequeo completo en el header.

         Hay UNA dirección de luz en todo el sitio y es ARRIBA-DERECHA. Se
         eligió una sola y se respeta en las 7 secciones porque el defecto
         que estas capas vienen a corregir es "todo plano", y una luz que
         cambia de lado por sección no produce profundidad: produce ruido.
         El par realce/asiento es siempre el mismo gesto —se ilumina un
         vértice y se asienta el opuesto—; lo único que varía por sección es
         CUÁNTO y CUÁN ABIERTO, para que dos secciones seguidas no se lean
         calcadas.

         El 0.04-0.055 no es una preferencia: es el número medido en los
         "glows" de Linear (rgba(255,255,255,0.04)), el único de los 8 sitios
         del research que resuelve profundidad en CSS puro. Por encima de
         ~0.08 la capa deja de leerse como luz y se lee como mancha. --- */
  /* Ronda 31: siguen siendo alfas de tokens que ya existen — lo único que
     cambió es CUÁLES, porque los tokens de abajo cambiaron de hue. La
     mecánica (una sola dirección de luz, 0.04-0.055, nada de grano) no se
     tocó: sigue valiendo la medición de Linear de la ronda 29. */
  --lift-dark: rgba(236, 241, 243, 0.055);  /* = --color-on-dark, realce sobre banda oscura */
  --sink-dark: rgba(15, 27, 31, 0.55);      /* = --color-darker, asiento sobre banda oscura */
  --lift-light: rgba(255, 255, 255, 0.55);  /* realce sobre banda clara */
  --sink-light: rgba(23, 36, 43, 0.05);     /* = --color-ink, asiento sobre banda clara */

  /* --- Estrella de la calificación de Google. Ochre real (HSL 43°), no
         terracota (HSL 12°): es la estrella de una calificación pública,
         igual que la pinta Google. Único uso en todo el sitio. --- */
  --color-star: #b8860b;

  /* --- Principal de ACCIÓN: verde WhatsApp.
         No es una elección estética: es la afordancia del canal por el que
         se convierte. Todo lo que escribe a Sandra y Paulo es de este
         color, y nada más lo usa. Es el ÚNICO color que en este sitio
         significa "acá se hace clic" — el agua (ronda 28) suma riqueza
         cromática a una superficie, no compite por ese significado, así
         que la acción sigue siendo literalmente lo más fuerte que hay en
         pantalla. --- */
  --color-wa: #25d366;
  --color-wa-dark: #1eb85a;
  --color-wa-ink: #0b1f14;

  /* --- Tipografía: vuelve Alegreya Sans al display.
     La ronda 5 la había cambiado por Bitter (slab serif) para que los
     números pesaran más. El diagnóstico estaba bien, la solución no: el
     peso se consigue con Alegreya Sans BLACK (900) + cifras tabulares +
     tracking cerrado, sin meter un serif en un sitio que tenía prohibido
     el combo crema + serif + terracota desde el arranque.
     Alegreya Sans es de Huerta Tipográfica, fundición argentina: razón
     local real para un destino argentino, no una elección de catálogo. --- */
  --font-display: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-body: 'Source Sans 3', system-ui, -apple-system, 'Segoe UI', sans-serif;
  /* El ancho del display, en un solo lugar. 112% y no el 125% máximo del
     eje: pasado de ahí la ñ y la á se estiran de más, y este sitio las
     tiene las dos en el H1 ("Cabañas frente al río Paraná").
     Se maneja con `font-stretch` y no con `font-variation-settings`: la
     propiedad estándar hereda, participa de la cascada y, si la variable
     no cargara, el navegador elige la instancia más cercana en vez de
     quedarse sin aplicar nada. */
  --wdth-display: 112%;

  --text-xs: 0.8125rem;
  --text-sm: 0.9375rem;
  --text-base: 1rem;
  --text-md: 1.125rem;
  --text-lg: 1.375rem;
  --text-xl: 1.75rem;
  --text-2xl: clamp(1.9rem, 4.5vw, 2.75rem);
  --text-3xl: clamp(2.1rem, 5.5vw, 3.25rem);
  /* Techo 3.75rem = 60px. `impeccable` topea el display en 6rem; acá se
     queda bien por debajo a propósito: el hero nuevo es partido, el título
     vive en una columna de ~42vw y un clamp más alto lo desbordaría. */
  --text-hero: clamp(2.25rem, 4.2vw, 3.75rem);
  --text-capacity: clamp(3rem, 12vw, 4.5rem);

  /* --- Espaciado: múltiplos de 8 estrictos --- */
  --space-1: 8px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 32px;
  --space-5: 40px;
  --space-6: 48px;
  --space-7: 64px;
  --space-8: 96px;

  /* --- Forma: un solo sistema de radios.
         Bajó de 24px a 16px en cards. `impeccable` topea las cards en
         12-16px y marca 24px+ como tell; HANDOFF ya lo tenía anotado como
         tensión conocida sin resolver. Esta pasada lo resuelve. --- */
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 16px;
  --radius-pill: 999px;

  /* Sombras tintadas al fondo (nunca negro puro) y por debajo de 16px de
     blur, para no caer en el par prohibido borde-1px + sombra difusa. */
  --shadow-card: 0 2px 12px rgba(15, 27, 31, 0.12);
  --shadow-card-hover: 0 8px 24px rgba(15, 27, 31, 0.18);
  --shadow-float: 0 6px 20px rgba(15, 27, 31, 0.28);

  /* ==========================================================================
     MOVIMIENTO — UN SOLO VOCABULARIO, COMPARTIDO POR CSS Y POR GSAP
     ==========================================================================
     El problema que resuelve (ronda 7): el sitio tenía CUATRO curvas
     conviviendo sin saberlo — cubic-bezier(0.16,1,0.3,1) en CSS,
     `power3.out` en los reveals, `power2.out` en los contadores y
     `ease:'none'` en el puente — y las duraciones reales (700, 900, 1000,
     1400ms) no salían de ningún token. Cada sección se movía bien SOLA y
     ninguna se movía como la de al lado. Eso es lo que se sentía como
     "animaciones sueltas" en vez de un sitio con criterio.

     De dónde salen estos dos valores (research de esta ronda, medido con
     playwright-cli sobre el :root real de cada sitio, no de memoria):
     Linear (G) publica su escala de easings completa en :root, y dos de
     sus tokens son EXACTAMENTE estos:
        --ease-out-quint:     cubic-bezier(.23, 1, .32, 1)
        --ease-in-out-quart:  cubic-bezier(.77, 0, .175, 1)
     Son, letra por letra, las dos curvas que recomienda emil-design-eng.
     Dos fuentes independientes que convergen en el mismo par: se adoptan.
     La vieja (0.16,1,0.3,1) es expo-out, que frena más de golpe; quint
     frena más largo y va con el registro tranquilo del sitio.

     js/main.js LEE estos dos tokens del CSS y los convierte en eases de
     GSAP (registerEase). No hay una copia de los números en JS: si se
     cambia acá, cambia el sitio entero, timeline de scroll incluida.

     La escala de duración es de 6 escalones y NO se inventan valores
     fuera de ella. Presión y hover por debajo de 300ms (regla de
     emil-design-eng); las fotos por encima, porque son ambientales y no
     bloquean ninguna decisión. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-press: 120ms;
  --dur-fast: 160ms;
  --dur-base: 240ms;
  --dur-slow: 400ms;
  --dur-reveal: 640ms;
  --dur-photo: 800ms;

  /* --- Capas: escala semántica, nunca valores sueltos --- */
  --z-base: 1;
  --z-raised: 2;
  --z-float: 90;
  --z-header: 100;
  --z-skip: 200;

  --container: 1180px;

  /* Alto real del header, medido con playwright-cli (81px, estable en los
     cuatro anchos probados: 320 a 1280). Se usa para el scroll-margin-top
     de acá abajo — no hay otro lugar del CSS que necesite este número. */
  --header-h: 81px;
}

/* ==========================================================================
   Reset mínimo
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; overflow-x: clip; }
/* ==========================================================================
   BUG REAL, NO DE ESTILO: ningún destino de ancla tenía scroll-margin-top.
   El header es sticky y siempre ocupa los primeros 81px del viewport — con
   `scroll-behavior: smooth` y sin este margen, saltar a `#disponibilidad`
   (menú → "Disponibilidad"), `#el-lugar`, `#cabanas`, `#como-llegar` o
   `#contacto` deja el título de la sección TAPADO a la mitad por el header,
   porque el navegador alinea el borde superior de la sección con el borde
   superior del viewport, no con el borde inferior del header.
   Medido con playwright-cli tocando el link real del menú (no con
   scrollTo a mano): "¿Hay lugar en tus fechas?" quedaba con la mitad de
   arriba de la primera línea detrás del header, en las cuatro páginas —
   toda ancla del sitio comparte el mismo header sticky.
   `[id]` y no una lista de selectores: cualquier destino de ancla futuro
   queda cubierto solo, sin acordarse de agregarlo acá. */
[id] { scroll-margin-top: calc(var(--header-h) + var(--space-3)); }
body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--color-ink);
  background: var(--color-bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul { margin: 0; padding: 0; list-style: none; }
h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 800;
  /* El eje de ancho es el motivo por el que se cambió a Archivo: un
     titular ANCHO y pesado es lo que hace que una portada se lea como
     portada. Con font-weight solo se consigue grosor, no presencia. */
  font-stretch: var(--wdth-display);
  line-height: 1.08;
  letter-spacing: -0.015em;
  text-wrap: balance;
}
/* Las cifras grandes son el OTRO momento display del sitio (el 4,7 del
   sello, la capacidad de cada cabaña) y llevan el mismo ancho que los
   titulares. Los tres números de la banda de confianza YA NO están acá desde
   la ronda 34: pasaron de tarjeta a chip y el chip es texto de lectura, no
   una cifra grande — ver ".proof__chip-text" más abajo. El resto de lo que
   usa la display —la cita del carrusel, el inventario, el nombre de cada
   ítem de equipamiento— se queda en ancho normal: son textos para LEER, y
   una grotesca ensanchada a tamaño de lectura cansa. */
.unit__capacity,
.page-hero__capacity { font-stretch: var(--wdth-display); }

p { margin: 0; text-wrap: pretty; }
blockquote { margin: 0; }
button { font: inherit; cursor: pointer; background: none; border: none; }

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-3);
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-ink);
  color: #fff;
  padding: var(--space-2) var(--space-3);
  z-index: var(--z-skip);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--color-ink);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
.section--dark a:focus-visible,
.hero a:focus-visible { outline-color: var(--color-wa); }

/* ==========================================================================
   Tipografía de sección — 3 niveles reales de jerarquía.
   SIN EYEBROW. Las 7 etiquetas en versalitas que había arriba de cada
   sección se eliminaron en esta pasada: `impeccable` y `design-taste`
   coinciden en que un eyebrow constante es andamiaje, no voz, y el
   research lo confirma — Eastwind (A) titula "SETTLE IN, VENTURE OUT" sin
   etiqueta arriba, y Postcard Cabins (B) no pone eyebrow en ningún H2 de
   su home. El único rótulo que sobrevive es el del mapa, porque ahí
   rotula un dato real (la localidad), no decora.
   ========================================================================== */
.section-title {
  font-size: var(--text-2xl);
  color: var(--color-ink);
  max-width: 24ch;
}
.section-title--xl { font-size: var(--text-3xl); }
.section-title--sm { font-size: var(--text-xl); max-width: 30ch; }
.section-lede {
  font-size: var(--text-md);
  color: var(--color-ink-soft);
  max-width: 62ch;
  margin-top: var(--space-3);
}
.section { padding-block: var(--space-8); }
.section--tight { padding-block: var(--space-7); }
/* Ronda 29 — el CTA final queda parado sobre la parte iluminada de su
   propia banda: el realce baja al centro-derecha en vez de pegarse al
   vértice, porque acá el contenido está centrado y no alineado a un lado. */
.section--dark.final-cta {
  background-color: var(--color-dark);
  background-image:
    radial-gradient(64% 76% at 74% 12%, var(--lift-dark) 0%, transparent 64%),
    radial-gradient(90% 80% at 10% 100%, var(--sink-dark) 0%, transparent 70%);
}
.section--dark {
  background: var(--color-dark);
  color: var(--color-on-dark);
}
.section--dark .section-title { color: #fff; }
.section--dark .section-lede { color: var(--color-on-dark-soft); }
.section--alt { background: var(--color-bg-alt); }

/* ==========================================================================
   PROFUNDIDAD DE FONDO — ronda 29. El hero NO participa de este sistema.
   ==========================================================================
   Medido antes: el sitio tenía 0 gradientes en 8.342px de página. Cada
   sección era un color plano, y con 4 secciones claras seguidas del mismo
   #EEF1EC el scroll no daba ninguna señal de estar avanzando.

   Cada sección recibe DOS radiales: un realce en el vértice superior
   derecho y un asiento en el inferior izquierdo. Es una sola idea repetida
   con parámetros distintos, no siete efectos.

   POR QUÉ RADIALES Y NO UN LINEAR: un linear-gradient de arriba a abajo
   sobre una sección de 600px es indistinguible de un color plano cuando la
   sección entra en pantalla de a 900px por vez —se ve un solo tramo de la
   rampa—. Un radial anclado a un vértice tiene un centro y un borde
   DENTRO del cuadro visible, así que se lee aunque la sección esté cortada
   por el viewport. Es lo que hace Linear con `at 50% 100%`.

   POR QUÉ background-image Y NO PSEUDO-ELEMENTOS: varias de estas secciones
   ya usan ::before/::after (el puente, la franja de fotos, la tarjeta de
   reseña) y varias tienen `isolation: isolate`. Pintar en background-image
   no toca el z-index de nadie, no crea contexto de apilamiento nuevo y no
   suma un nodo por sección. Cero JS y cero DOM.

   PERFORMANCE: son gradientes estáticos, sin animación y sin backdrop-
   filter. No repintan al scrollear y no promueven capa. La regla de
   `gsap-performance` sobre no poner will-change "por las dudas" aplica acá:
   estas capas no animan, así que no llevan nada.
   ========================================================================== */
.section--depth {
  background-color: var(--color-bg);
  background-image:
    radial-gradient(78% 62% at 88% 4%, var(--lift-light) 0%, transparent 64%),
    radial-gradient(70% 58% at 6% 100%, var(--sink-light) 0%, transparent 60%);
}
/* Variación 2: el realce entra más abierto y más bajo. La usa la sección
   que va PEGADA a otra clara, para que el corte entre las dos se note. */
.section--depth-b {
  background-color: var(--color-bg);
  background-image:
    radial-gradient(96% 74% at 78% 16%, var(--lift-light) 0%, transparent 70%),
    radial-gradient(58% 70% at 0% 88%, var(--sink-light) 0%, transparent 56%);
}
/* Sobre --color-bg-alt, que ya es un escalón más oscuro: el asiento se
   suaviza para no cerrar la sección de más. */
.section--depth-alt {
  background-color: var(--color-bg-alt);
  background-image:
    radial-gradient(84% 64% at 90% 0%, var(--lift-light) 0%, transparent 66%),
    radial-gradient(64% 56% at 4% 100%, rgba(23, 36, 43, 0.035) 0%, transparent 58%);
}
/* Banda oscura. Acá el realce es el gesto que importa (es lo que Linear
   hace con sus glows al 4%) y el asiento hunde el vértice opuesto para que
   la banda tenga volumen en vez de ser un rectángulo. */
.section--depth-dark {
  background-color: var(--color-dark);
  background-image:
    radial-gradient(72% 58% at 86% 6%, var(--lift-dark) 0%, transparent 62%),
    radial-gradient(80% 72% at 4% 104%, var(--sink-dark) 0%, transparent 66%);
}
/* Variación oscura para el cierre de la página: el realce baja al centro
   para que el CTA quede parado sobre la parte iluminada. */
.section--depth-dark-b {
  background-color: var(--color-dark);
  background-image:
    radial-gradient(64% 76% at 74% 12%, var(--lift-dark) 0%, transparent 64%),
    radial-gradient(90% 80% at 10% 100%, var(--sink-dark) 0%, transparent 70%);
}

/* inline-block y no inline: sobre un <span> inline el navegador ignora
   transform y filter, así que sin esto la palabra no se mueve ni se
   desenfoca — sólo se le nota la opacidad.
   `.split-chars__char` son las letras que arma SplitText en el H1 del hero
   (ronda 19). SplitText ya les escribe `display: inline-block` él mismo;
   está declarado igual acá para que la regla viva en el CSS y no dependa
   de que el plugin haya cargado. */
.split-words__word,
.split-chars__char { display: inline-block; }

/* ==========================================================================
   Botones — jerarquía de UNA acción primaria.
   Research: Postcard Cabins (B) y Dunton (F) tienen UN solo botón sólido
   por pantalla; Dunton lo pone saturado arriba a la derecha y es el
   elemento más fuerte de la página. El hero viejo tenía dos botones de
   peso casi igual (teal sólido + outline blanco) compitiendo entre sí.
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: 48px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-sm);
  font-weight: 700;
  font-size: var(--text-sm);
  border: 2px solid transparent;
  white-space: nowrap;
  /* ------------------------------------------------------------------
     LEVANTADA Y PRESIÓN, COMPUESTAS A MANO.
     El bug que arregla: antes eran dos reglas que escribían `transform`
     ENTERA, con la misma especificidad (0,2,0) —
        .btn:active { transform: scale(0.97) }
        .btn:hover  { transform: translateY(-2px) }
     — y la de hover estaba después, así que ganaba siempre. EN DESKTOP EL
     :active NUNCA SE APLICABA: el botón no bajaba al apretarlo, que es
     justo lo que el comentario de al lado decía que hacía.
     Medido con playwright-cli antes de tocar nada: con el mouse encima y
     el botón apretado, el transform seguía en matrix(1,0,0,1,0,-2).

     Se probó resolverlo con dos custom properties compuestas en un solo
     transform (--btn-lift / --btn-press). NO SIRVE, y quedó comprobado en
     el navegador: con `transition: transform` declarada, cambiar una
     custom property SIN REGISTRAR que alimenta el transform no actualiza
     el valor computado — la variable pasaba a -2px y el transform se
     quedaba en identidad; quitando la transición sí se movía. Se
     arreglaría con @property, pero eso ataría la respuesta al toque de un
     botón de conversión al soporte de una feature relativamente nueva.
     Por eso los tres estados se escriben completos y la especificidad
     hace el trabajo: :hover:active (0,3,0) le gana a los dos.
     ------------------------------------------------------------------ */
  transform: translateY(0) scale(1);
  transition: transform var(--dur-press) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
/* emil-design-eng: todo lo apretable baja de escala al presionarse. */
.btn:active { transform: translateY(0) scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .btn:hover { transform: translateY(-2px) scale(1); }
  .btn:hover:active { transform: translateY(-2px) scale(0.97); }
}

/* Envoltorio del imán (reactbits/Magnet). Sólo existe para recibir el
   transform de GSAP sin tocar el del botón, que tiene su propio sistema de
   levantada y presión. Sin JS es un inline-flex y no hace nada. */
.magnet { display: inline-flex; }

/* Acción primaria. Texto casi negro sobre el verde: blanco sobre #25D366
   da 2.1:1 y reprueba AA; #0B1F14 da 8.68:1 (ver la corrección del número
   en la tabla de contrastes del header). */
.btn--whatsapp { background: var(--color-wa); color: var(--color-wa-ink); }
.btn--whatsapp:hover { background: var(--color-wa-dark); box-shadow: var(--shadow-card-hover); }

.btn--teal { background: var(--color-ink); color: #fff; }
.btn--teal:hover { background: var(--color-ink); box-shadow: var(--shadow-card-hover); }

.btn--outline { background: transparent; border-color: currentColor; color: inherit; }
.btn--outline:hover { background: rgba(255, 255, 255, 0.12); }

/* ==========================================================================
   BOTÓN DE CANAL (ronda 29) — Instagram y Facebook en #contacto.
   ==========================================================================
   Mismo tamaño, misma forma y misma fila que el verde de WhatsApp: eso es
   el "mismo peso" que se pidió. Lo que NO comparte es el color, y no es un
   descuido: el verde de este sitio significa "acá se abre la conversación
   que reserva". Si Instagram y Facebook llegan pintados de violeta y azul
   —sus colores de marca— pasan a haber tres saturados peleando y el verde
   deja de significar nada. Es la misma decisión que la ronda 15 ya tomó
   para el bloque #redes, aplicada acá.

   Relleno de tinta clara sobre la banda oscura: --color-on-dark de fondo con
   texto --color-dark da 12,88:1, el mismo par invertido que ya usa el sitio.
   Presente y clickeable, sin competirle en COLOR al que convierte.
   ========================================================================== */
.btn--channel { background: var(--color-on-dark); color: var(--color-dark); }
.btn--channel:hover { background: #fff; box-shadow: var(--shadow-card-hover); }
.btn--channel .icon { fill: currentColor; }

/* La fila de los tres. `auto-fit` con un mínimo de 232px: a 1440 entran los
   tres en línea, después caen a dos y en el teléfono a uno, sin una sola
   media query.
   ⚠️ EL 232 NO ES UN NÚMERO LINDO, ES UNA MEDICIÓN. El primer intento usó
   minmax(200px, 1fr) con max-width 40rem, y medido daba celdas de 203px con
   el botón de WhatsApp adentro midiendo 250: "Escribir por WhatsApp" lleva
   `white-space: nowrap` (heredado de .btn) y se desbordaba de su columna.
   El mínimo tiene que ser mayor que el ancho intrínseco del botón más
   ancho, no un redondeo. Con 232 de mínimo y 52rem de tope, a 1440 las
   celdas dan 266px y el de 250 entra con aire.
   No se resolvió acortando la etiqueta a "WhatsApp": el verbo es lo que
   hace que el botón que convierte diga qué pasa al tocarlo, y perderlo para
   emparejar tres anchos habría sido emparejar hacia abajo. */
.channels {
  list-style: none;
  margin: var(--space-5) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: var(--space-2);
  max-width: 52rem;
  margin-inline: auto;
}
.channels li { display: flex; }
.channels .magnet { flex: 1; }
.channels__btn { width: 100%; }

/* ==========================================================================
   BOTÓN DE RESEÑA — "¿Ya viniste? Dejá tu reseña"
   ==========================================================================
   El cliente: "el botón de reseña es muy simple, conseguir reseñas es tan
   importante como conseguir consultas". Era un link subrayado de 13px; ahora
   tiene cuerpo de botón.

   POR QUÉ NO ES VERDE. El verde de este sitio marca UNA cosa: lo que
   convierte huéspedes. Un segundo botón verde a 40px del CTA de WhatsApp
   compite con él y le roba clicks a la reserva. Y la madera no toca
   controles, por la regla de la cabecera de este archivo. Queda contorno
   sobre tinta clara: presente y clickeable, claramente por debajo del verde.

   POR QUÉ EL COPY EMPIEZA CON UNA PREGUNTA. El que mira el hero es, casi
   siempre, alguien que TODAVÍA NO VINO — no puede dejar una reseña y un
   botón que se la pide lo confunde. "¿Ya viniste?" filtra en tres palabras:
   el que no vino sigue de largo sin sentir que se perdió algo, y el que sí
   vino se da por aludido. Un botón que se auto-descarta para el 90% del
   tráfico no molesta a ese 90%.

   EL ÚNICO COLOR ES LA ESTRELLA, Y SÓLO AL PASAR EL CURSOR. Va del mismo
   gris apagado del contorno al --color-star de las cinco estrellas de al
   lado: el botón "se prende" y el gesto dice literalmente lo que hace.
   Sin sombra: `impeccable` marca contorno + sombra ancha en el mismo
   elemento como el tell de tarjeta fantasma. Contorno solo.
   ========================================================================== */
.btn--review {
  min-height: 46px;
  padding-inline: var(--space-3);
  /* text-sm y no text-xs: a 13px el botón se leía como una nota al pie con
     un recuadro alrededor. 15px lo pone en la familia de los botones sin
     acercarlo al verde, que está a 16px y 56px de alto. */
  font-size: var(--text-sm);
  background: transparent;
  color: var(--color-on-dark);
  border-color: rgba(236, 241, 243, 0.42);
}
.btn--review .btn__star {
  font-size: 1.2em;
  line-height: 1;
  /* En reposo la estrella tiene que LEERSE como estrella, no como una
     mancha: a 0.45 de alpha se perdía contra el contorno. Apagada pero
     nítida, para que encenderse sea un cambio de color y no una aparición. */
  color: rgba(236, 241, 243, 0.62);
  transition: color var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .btn--review:hover {
    background: rgba(236, 241, 243, 0.07);
    border-color: rgba(236, 241, 243, 0.72);
    box-shadow: none; /* el hover genérico de .btn no aplica acá */
  }
  .btn--review:hover .btn__star {
    color: var(--color-star);
    transform: scale(1.18);
  }
}
/* En teclado la estrella también se prende: si el estado de foco no muestra
   lo mismo que el hover, el que navega con Tab ve un botón muerto. */
.btn--review:focus-visible { border-color: rgba(236, 241, 243, 0.72); }
.btn--review:focus-visible .btn__star { color: var(--color-star); }
@media (prefers-reduced-motion: reduce) {
  .btn--review .btn__star { transition: color var(--dur-base) var(--ease-out); }
  .btn--review:hover .btn__star { transform: none; }
}

.btn--lg { min-height: 56px; font-size: var(--text-base); padding-inline: var(--space-4); }
.btn-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.icon { width: 1.15em; height: 1.15em; flex-shrink: 0; fill: currentColor; }

/* El `display:none` que trae [hidden] del navegador es la regla MÁS DÉBIL
   que existe, así que cualquier clase de autor que fije un display se lo
   come. `.btn-row` de acá arriba es exactamente ese caso: js/main.js apaga
   el fallback del calendario con `fallback.hidden = true` y, sin esta
   línea, el bloque de WhatsApp seguiría a la vista DUPLICADO debajo del
   calendario. Es la única regla !important del archivo y existe para que
   `hidden` signifique oculto en todos los casos, no en la mayoría. */
[hidden] { display: none !important; }

/* ==========================================================================
   FLECHA DE AVANCE — la micro-interacción compartida del sitio.
   ==========================================================================
   Todos los enlaces que llevan hacia adelante terminan en una "→" que es
   `<span aria-hidden="true">`. Antes esa flecha era un carácter muerto: el
   enlace cambiaba de color y la flecha se quedaba quieta. Ahora TODOS se
   mueven los mismos 3px, en la misma duración y con la misma curva, estén
   en el hero, en una tarjeta de cabaña o en un pie de sección.
   Ése es el sentido de "conexión": no una animación distinta por sección,
   sino que el mismo gesto signifique lo mismo en toda la página.
   Va sobre `span` y no sobre pseudo-elemento porque el carácter ya existe
   en el HTML y sacarlo de ahí sería contenido creado por CSS. */
.unit__more span,
.cabins__more span { display: inline-block; transition: transform var(--dur-fast) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .unit__more:hover span,
  .cabins__more:hover span { transform: translateX(3px); }
}

/* ==========================================================================
   Header — barra sólida POR ENCIMA del hero, no superpuesta.
   Research: Eastwind (A), Postcard Cabins (B) y Juvet (D) coinciden en lo
   mismo: el header vive en su propia banda de color sólido y la foto
   empieza abajo. Así la foto no tiene que cargar con la legibilidad del
   nav, que era el otro motivo por el que el hero viejo necesitaba scrim.
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
  /* El header era idéntico apoyado sobre el hero que flotando sobre el
     contenido: nada decía que había dejado de estar en su lugar y pasado
     a estar ENCIMA de otra cosa. Al despegarse gana una sombra corta.
     El alto no se toca nunca, porque animar el alto de un sticky corre
     todo el documento y mueve los triggers de abajo. La transición
     completa (con el transform de la ronda 26) está unas líneas más
     abajo, junto a `.is-away`. */
}
.site-header.is-stuck {
  border-bottom-color: transparent;
  box-shadow: 0 1px 0 rgba(15, 27, 31, 0.06), 0 6px 20px rgba(15, 27, 31, 0.10);
}
/* ==========================================================================
   RONDA 26 — EL HEADER SE APARTA AL BAJAR (SÓLO MOBILE)
   ==========================================================================
   El porqué completo, con la medición del defecto que arregla y la
   corrección al diagnóstico de "z-index o scroll-margin", está en
   wireHeaderState() de js/main.js. Acá sólo el cómo.
   La clase la pone y la saca el JS; si el JS no corre, el header se queda
   fijo como siempre y no se pierde nada: es una mejora, no una muleta.
   Se anima `transform` y nada más — ni el alto ni la posición—, así que no
   corre el documento ni mueve un solo ScrollTrigger de los de abajo.
   ========================================================================== */
.site-header { transition: box-shadow var(--dur-base) var(--ease-out),
                           border-color var(--dur-base) var(--ease-out),
                           transform var(--dur-base) var(--ease-out); }
@media (max-width: 999px) {
  .site-header.is-away { transform: translateY(-100%); }
}
/* La preferencia pide que no haya MOVIMIENTO, no que el visitante se quede
   sin los 81px de pantalla: se sigue apartando, de golpe. */
@media (prefers-reduced-motion: reduce) {
  .site-header { transition: none; }
}
.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-2);
  gap: var(--space-3);
  min-height: 64px;
}
/* Sin logo (confirmado con el cliente): el nombre en tipografía es la
   única marca que existe. */
.brand {
  display: inline-flex;
  align-items: center;
  /* flex colapsa el nodo de texto en blanco entre "Cabañas" y el <span>,
     así que el espacio hay que reponerlo con gap. */
  gap: 0.28em;
  min-height: 44px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-lg);
  color: var(--color-ink);
  letter-spacing: -0.02em;
}
.brand span { color: var(--color-ink-soft); }

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
}
.nav-toggle__bar,
.nav-toggle__bar::before,
.nav-toggle__bar::after {
  content: '';
  display: block;
  width: 20px;
  height: 2px;
  background: var(--color-ink);
  position: relative;
  transition: transform var(--dur-base) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}
.nav-toggle__bar::before { position: absolute; top: -6px; }
.nav-toggle__bar::after { position: absolute; top: 6px; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar::after { transform: translateY(-6px) rotate(-45deg); }

/* ==========================================================================
   EL MENÚ DE MOBILE — el panel FLOTA, no engorda la barra (ronda 21)
   ==========================================================================
   La ronda 19 le puso al panel un fundido de 8px desde arriba y lo dejó
   como TERCER ÍTEM de `.site-header__bar`, que es un flex row sin wrap. En
   escritorio angosto no se nota; en un teléfono de verdad rompía tres cosas
   a la vez, medidas en WebKit iOS a 393x659 con toque real:

     1. El nav entraba a la MISMA FILA que la marca y el botón. La marca se
        partía en dos renglones ("Cabañas de / Hugo"), el hamburguesa
        quedaba apretado en el medio y los cinco destinos caían en una
        columna angosta contra el borde derecho, alineados con nada.
     2. La barra pasaba de 81px a 318px de alto — el 48% de la pantalla.
     3. Y como el header es `sticky`, ese crecimiento es CONTENIDO NUEVO EN
        EL FLUJO: el documento creció 237px y todo lo que estabas leyendo
        SALTÓ 237px hacia abajo en un solo frame, ANTES de que empezara el
        fundido. O sea que el gesto más repetido del sitio empezaba con un
        tirón y recién después venía la animación cuidada.

   El arreglo es de posición, no de timing: el panel sale del flujo
   (`position: absolute` colgado de `top: 100%` del header, que ya es
   `sticky` y por lo tanto su bloque contenedor). La barra no cambia de
   alto, el documento no cambia de alto, y no hay nada que saltar. El panel
   se apoya ENCIMA del contenido, así que ahora sí necesita superficie
   propia: fondo, la línea de arriba que ya tenía y una sombra corta que lo
   despega de lo que tapa.

   El movimiento de la ronda 19 se conserva tal cual —baja 8px y se funde
   desde arriba, 240ms al abrir y 160ms al cerrar— porque la intención era
   correcta; lo que estaba mal era el lugar desde donde se movía.

   `allow-discrete` es lo que hace que `display` espere el fundido en vez de
   cortarlo. Donde no esté soportado, abre y cierra de golpe.
   Arriba de 1000px no se toca nada: ahí el nav es una fila siempre visible
   y no tiene estado abierto/cerrado que animar.
   ========================================================================== */
.main-nav {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  display: none;
  flex-direction: column;
  gap: var(--space-1);
  /* Los destinos quedan alineados con la marca a cualquier ancho: abajo de
     --container el panel toma el mismo padding que `.container`, y arriba
     le suma el margen que el contenedor centrado deja a los costados. */
  padding-block: var(--space-2) var(--space-3);
  padding-inline: max(var(--space-3), calc((100% - var(--container)) / 2 + var(--space-3)));
  background: var(--color-bg);
  border-top: 1px solid var(--color-border);
  box-shadow: 0 12px 24px rgba(15, 27, 31, 0.14);
  /* Un teléfono apaisado deja ~390px de alto y el panel mide 285: entra,
     pero sin margen. Los 88px son la barra (81) más aire; si el visitante
     agrandó la tipografía del sistema, el panel scrollea en vez de
     desbordar la pantalla. */
  max-height: calc(100svh - 88px);
  overflow-y: auto;
  overscroll-behavior: contain;
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              display var(--dur-fast) allow-discrete;
}
.main-nav.is-open {
  display: flex;
  opacity: 1;
  transform: translateY(0);
  /* ⚠️ EL FUNDIDO ES MÁS CORTO QUE EL DESPLAZAMIENTO, y no es un capricho
     de curva. Desde que el panel flota, lo que hay atrás se VE mientras el
     panel es translúcido: a mitad de un fundido de 240ms quedaban los cinco
     destinos y el contenido de la página legibles a la vez, uno encima del
     otro (capturado). Con la superficie opaca en 120ms ese cruce
     prácticamente no existe, y el movimiento —los 8px de bajada, que son
     los que cuentan de dónde sale el panel— sigue durando los 240ms de
     siempre. Cerrar sigue siendo más rápido: al abrir el visitante está
     leyendo, al cerrar ya eligió. */
  transition: opacity 120ms var(--ease-out),
              transform var(--dur-base) var(--ease-out),
              display var(--dur-base) allow-discrete;
}
@starting-style {
  .main-nav.is-open { opacity: 0; transform: translateY(-8px); }
}
@media (prefers-reduced-motion: reduce) {
  /* Se va el desplazamiento, queda el fundido: el panel sigue sin aparecer
     de golpe, pero no se mueve. */
  .main-nav,
  .main-nav.is-open { transform: none; }
  .main-nav {
    transition: opacity var(--dur-fast) var(--ease-out),
                display var(--dur-fast) allow-discrete;
  }
  @starting-style {
    .main-nav.is-open { opacity: 0; transform: none; }
  }
}
/* 44px de alto mínimo en los destinos de navegación (ui-ux-pro-max §2 /
   Apple HIG). Antes medían 40px. */
.main-nav a {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding-inline: var(--space-1);
  font-weight: 600;
  font-size: var(--text-sm);
  border-radius: var(--radius-sm);
  transition: color var(--dur-fast) var(--ease-out);
}
.main-nav a:hover { color: var(--color-ink); }
/* Página actual señalada — faltaba por completo en las fichas de cabaña. */
.main-nav a[aria-current="page"] { color: var(--color-ink); text-decoration: underline; text-underline-offset: 4px; }
.header-cta { display: none; }

/* ⚠️ EL NAV DE ESCRITORIO ARRANCA EN 1000px, NO EN 900. Defecto real medido
   esta ronda: entre 900 y 1000 el header entraba en modo escritorio pero no
   había lugar para marca + 5 secciones + botón de WhatsApp, así que se
   partían en dos líneas TODOS —"Cabañas / de Hugo", "El / lugar", "Cómo /
   llegar"— y la barra pasaba de 80px a 102px de alto. Es lo primero que se
   ve del sitio en una notebook chica o en una tablet apaisada.
   Se corrige por los dos lados: el hamburguesa se queda hasta 1000px, que
   es donde los 5 destinos entran de verdad, y la separación baja de 32 a
   24px, que es el escalón de al lado de la misma escala. El resto del
   layout de escritorio (hero partido, etiqueta del botón flotante) sigue
   cambiando en 900: no dependen del ancho del nav. */
@media (min-width: 1000px) {
  .nav-toggle { display: none; }
  .main-nav {
    /* ⚠️ Y acá vuelve a ser un ítem de la barra. Sin `position: static` el
       nav de escritorio seguiría colgado abajo del header como el panel de
       mobile. Lo mismo con la piel: fondo, sombra, borde y max-height son
       del PANEL, no del nav. */
    position: static;
    display: flex;
    flex-direction: row;
    background: none;
    box-shadow: none;
    border-top: none;
    max-height: none;
    overflow: visible;
    padding: 0;
    gap: var(--space-3);
    /* ⚠️ Estas tres líneas NO son decorativas: cancelan el estado cerrado
       del menú de mobile. La regla base ahora arranca en opacity 0 y
       desplazada (es el panel plegado), así que sin esto el nav de
       escritorio —que nunca recibe .is-open, porque acá no hay hamburguesa—
       heredaría ese estado y los cinco destinos quedarían INVISIBLES en
       toda notebook. Verificado en el navegador antes de dejarlo. */
    opacity: 1;
    transform: none;
    transition: none;
  }
  .header-cta { display: inline-flex; }
}

/* Siempre visible, en todos los tamaños: la conversión del sitio entero es
   este mensaje. En desktop se ensancha y muestra la palabra, porque un
   círculo verde suelto no dice qué hace. */
.whatsapp-float {
  position: fixed;
  right: var(--space-3);
  bottom: var(--space-3);
  z-index: var(--z-float);
  min-width: 56px;
  height: 56px;
  padding-inline: var(--space-2);
  /* ⚠️ `gap: 0` y la separación vive en el padding de la etiqueta. Con un
     gap del flex, la etiqueta plegada (max-width: 0) igual dejaba los 8px
     del hueco y el círculo de mobile pasaba de 56 a 64px: un óvalo. Con el
     aire adentro de la etiqueta, colapsar la etiqueta colapsa también su
     aire y el círculo mide exactamente 56. */
  gap: 0;
  font-weight: 700;
  font-size: var(--text-sm);
  border-radius: var(--radius-pill);
  background: var(--color-wa);
  color: var(--color-wa-ink);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-float);
  /* Mismos tres estados escritos completos que .btn, por el mismo motivo
     técnico. Acá además hacía falta por otra razón: es el único elemento
     que está en pantalla TODO el tiempo y era el único apretable del
     sitio sin ninguna respuesta al hover (verificado: con el mouse
     encima devolvía transform:none). Es el botón por el que convierte el
     sitio entero; que no reaccione lo hace leer como una calcomanía
     pegada a la pantalla, no como un control. */
  transform: translateY(0) scale(1);
  transition: transform var(--dur-press) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out),
              visibility var(--dur-base) var(--ease-out);
}
.whatsapp-float:active { transform: translateY(0) scale(0.94); }
/* Se esconde mientras el CTA del hero está en pantalla. Son el MISMO botón
   y el mismo mensaje: mostrar los dos juntos no agrega una acción, agrega
   un choque. En mobile además se pisaban literalmente — medido a 375, el
   círculo verde caía encima del borde derecho de "Consultar
   disponibilidad", que es el momento exacto en que el sitio convierte.
   Lo enciende un IntersectionObserver (ver js/main.js). Si el JS no corre,
   la clase nunca se aplica y el flotante queda visible: el peor caso es el
   comportamiento viejo, nunca un botón que no está. */
.whatsapp-float.is-tucked {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(8px) scale(0.96);
}
@media (hover: hover) and (pointer: fine) {
  .whatsapp-float:hover { transform: translateY(-2px) scale(1); box-shadow: 0 10px 28px rgba(15, 27, 31, 0.34); }
  .whatsapp-float:hover:active { transform: translateY(-2px) scale(0.94); }
}
.whatsapp-float .icon { width: 24px; height: 24px; }
/* En mobile el flotante es un círculo mudo a propósito: no hay lugar para
   una palabra y el ícono de WhatsApp ya dice adónde va. Plegada con
   `max-width: 0` en vez de `display: none` para que pueda ABRIRSE cuando
   hay fechas elegidas (ver más abajo): `display` no interpola. */
.whatsapp-float__label {
  max-width: 0;
  /* ⚠️ El aire va en el padding de la etiqueta Y ARRANCA EN 0. Con
     `box-sizing: border-box`, `max-width: 0` NO puede comerse el padding:
     el ancho usado nunca baja de padding + borde, así que un padding fijo
     de 10px dejaba el círculo de mobile en 64px de ancho por 56 de alto —
     un óvalo. Medido en el navegador, no deducido. */
  padding-left: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
}
@media (min-width: 900px) {
  .whatsapp-float { padding-inline: var(--space-3); }
  .whatsapp-float__label { max-width: 22ch; padding-left: 10px; opacity: 1; }
}

/* ==========================================================================
   EL FLOTANTE CON LAS FECHAS ELEGIDAS (ronda 21)
   ==========================================================================
   Con al menos una fecha marcada en el calendario, el flotante deja de ser
   un círculo y dice el período corto ("Consultar 21–25 ago"). No es adorno:
   en ese momento del scroll la lectura "Elegiste 5 días…" y el botón de la
   tarjeta están abajo del pliegue (medido: 304px abajo), así que ESTA
   etiqueta es la única señal en pantalla de que el toque llegó a destino y
   de que el mensaje ya está escrito.

   `max-width` en la etiqueta y no `width` en el botón: el flotante es
   `position: fixed`, así que su caja no reflowea nada de la página, y
   animando el hijo el ícono se queda quieto mientras la píldora crece hacia
   la izquierda —lejos del pulgar— en vez de saltar de tamaño.
   ========================================================================== */
.whatsapp-float__label {
  transition: max-width var(--dur-base) var(--ease-out),
              padding-left var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
}
.whatsapp-float.has-fechas {
  padding-inline: var(--space-2) var(--space-3);
}
.whatsapp-float.has-fechas .whatsapp-float__label {
  /* Tope generoso a propósito: `max-width` sólo recorta, y el caso más
     largo ("Consultar 28 ago–3 sep") tiene que entrar sin que se le corte
     el mes. */
  max-width: 22ch;
  padding-left: 10px;
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  /* Sin animar el ancho: la etiqueta está o no está. Sigue siendo la misma
     información en el mismo lugar. */
  .whatsapp-float__label { transition: none; }
}

/* ==========================================================================
   HERO — VENTANA AL RÍO (ronda 12)
   ==========================================================================
   Reemplaza al díptico de dos placas iguales de las rondas 6-11. El díptico
   nació de un dato real —ninguna de las 36 fotos tiene cabaña y río en el
   mismo plano— pero resolvía ese problema poniendo las dos mitades a pesar
   lo mismo, y así ninguna llegaba a leerse.

   Ahora hay jerarquía, y es la del negocio: el río a sangre (es lo que no se
   puede copiar) y las cabañas apoyadas encima como mazo (es lo que se
   alquila).

   MOBILE-FIRST, Y NO ES UNA FORMALIDAD. A 375 la foto NO es fondo: es un
   bloque arriba y el texto se apoya sobre superficie sólida. Con la foto de
   fondo, el bloque de texto ocupa tanto del alto que el titular y el rótulo
   caen sobre el CIELO — medido rgb(114,176,216), 1,06:1 en el rótulo y
   2,92:1 en el titular. Ahí no hay degradado que alcance: taparlo pide
   oscurecer media foto, y oscurecer media foto es no tener foto.
   Desde 900px el texto vuelve sobre la foto, que es donde hay lugar.
   ========================================================================== */
.hero {
  position: relative;
  display: grid;
  /* minmax(0, 1fr) y no el `auto` implícito: un item de grid trae
     min-width:auto, así que la columna no puede achicarse por debajo del
     min-content del item más ancho. Medido a 320px, eso empujaba el hero
     a 359px y toda la página quedaba con scroll horizontal. El 0 del
     minmax le da permiso a la columna para achicarse. */
  grid-template-columns: minmax(0, 1fr);
  background: var(--color-darker);
  isolation: isolate;
}
/* 36svh, no 46. Medido a 375×812: con la foto en 46svh (374px) el botón de
   WhatsApp terminaba en y=875, o sea 63px POR DEBAJO del pliegue — el
   visitante tenía que scrollear para encontrar la única acción de la
   página. Con 36svh entra entero arriba del pliegue en 375 y en 390, que
   es donde está la mayoría del tráfico de este sitio. El min-height evita
   que en un teléfono apaisado la foto quede como una banda sin contenido. */
/* Ronda 31: 42svh, no 36. El recorte de mobile es vertical (820x899) y a
   36svh la caja quedaba en relación 1,28 contra el 0,91 de la fuente, así que
   `cover` se comía el 29% del ancho — justo el poste de la galería, que es lo
   que mete la cabaña adentro del cuadro. A 42svh la caja baja a ~1,10 y con
   `object-position: 22%` el poste entra entero. Sigue por debajo del 46svh
   que la ronda 14 midió como el techo a partir del cual el botón de WhatsApp
   se caía abajo del pliegue en 375 y 390. */
/* Ronda 32 había pisado esto a 50% 0% para encuadrar la foto de Google Maps
   (recorte cuadrado 819x819, marca de atribución pegada al borde de arriba).
   Ronda 33: vuelve la foto propia (hero-rio-sm, 760x832) y con ella su
   propio valor — el poste de la galería entra entero en 22% de X. */
.hero__bg img { display: block; width: 100%; height: 42svh; min-height: 260px; object-fit: cover; object-position: 22% 55%; }
/* Degradado corto al pie de la foto para que la costura entre foto y
   superficie no sea un corte seco. En mobile el texto va DEBAJO de la foto
   (ver el bloque de desktop para por qué ahí sí puede ir encima), así que
   este velo no protege texto: sólo cose la foto con la superficie. */
.hero__bg { position: relative; }
.hero__bg::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 96px;
  background: linear-gradient(to top, var(--color-darker) 0%, rgba(15, 27, 31, 0) 100%);
}
.hero__inner {
  position: relative;
  padding-top: var(--space-4);
  padding-bottom: var(--space-5);
  color: var(--color-on-dark);
}

/* Rótulo de localidad, no eyebrow decorativo: dice DÓNDE queda el lugar, que
   es el dato que un visitante frío necesita antes que ninguno. Va en madera
   (ronda 12) por eso mismo: es información, y la madera es el color de los
   rótulos.
   RONDA 14 — SE LE CAYÓ LA PLAQUITA. La plaquita existía por una sola razón:
   en desktop este texto de 13px caía sobre la foto y medía 3,37:1. Con el
   hero partido el rótulo ya no toca la foto en ningún ancho, así que sobre
   la superficie oscura del sitio mide 7,44:1 sin nada abajo. Queda el
   guioncito de madera, que hace el trabajo que hacía la caja —marcar que
   esto es un rótulo y no la primera línea del titular— con mucho menos
   ruido. Una caja que ya no protege nada es decoración. */
.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  width: max-content;
  max-width: 100%;
  justify-self: start;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-wood-soft);
  margin-bottom: var(--space-1);
}
.hero__eyebrow::before {
  content: '';
  width: 24px;
  height: 2px;
  flex: none;
  background: var(--color-wood-soft);
}
/* El H1 dejó de ser el nombre de la marca. Un visitante frío no sabe qué es
   "Cabañas de Hugo"; sí entiende "cabañas frente al río Paraná".
   Research: ni Eastwind ("MOUNTAIN GETAWAY") ni Postcard Cabins ("Adventure
   by day, comfort by night.") usan su marca como H1. */
/* El bloque de texto es UNA unidad, no cuatro piezas sueltas. Los tres
   saltos internos son 8 / 16 / 24: el rótulo casi pegado al titular
   (nombran lo mismo), la bajada un escalón más lejos y el CTA el más
   separado, porque es lo único que cambia de tarea. Antes los tres saltos
   eran iguales y el bloque se leía como una lista. */
.hero__title {
  font-size: var(--text-hero);
  font-weight: 900;
  /* Archivo a 900 y ensanchada ya trae mucho peso; -0.02em alcanza y deja
     respirar la ñ. El piso de la regla es -0.04em. */
  letter-spacing: -0.02em;
  line-height: 0.98;
  color: #fff;
  max-width: 14ch;
  text-wrap: balance;
  /* ⚠️ ESTAS DOS LÍNEAS SON DEL SPLIT, NO DE GUSTO (ronda 19).
     SplitText mete cada letra en su propio <span>, y dos letras en dos cajas
     distintas dejan de kernearse entre sí: el navegador ya no ve el par
     "Pa" ni "ra", ve dos elementos sueltos. Sin esto el titular CAMBIA DE
     ANCHO en el momento del split —medido acá: 2px sobre 456, con "Paraná"
     abriéndose— y como arriba hay `text-wrap: balance`, ese cambio de ancho
     puede además reacomodar el corte de línea. O sea: un salto visual en el
     LCP de la página.
     Apagando el kerning desde el principio, el ancho con letras partidas y
     sin partir es EL MISMO, y no hay nada que saltar. El costo es que el
     titular se ve siempre sin kerning; a este cuerpo (clamp hasta 4.5rem) y
     con Archivo, la diferencia no se ve. Es lo que recomienda la propia doc
     de SplitText para este caso exacto. */
  font-kerning: none;
  text-rendering: optimizeSpeed;
}
.hero__lede {
  margin-top: var(--space-2);
  font-size: var(--text-md);
  max-width: 38ch;
  color: var(--color-on-dark);
  /* La bajada arrancaba en la misma tinta que el titular y competía con
     él. Un escalón de opacidad la manda al segundo plano sin cambiarle el
     color: sigue midiendo 12,46:1. */
  opacity: 0.92;
}
.hero__cta { margin-top: var(--space-4); align-items: center; gap: var(--space-2); }
/* `.hero__link` ("Ver las cabañas") se eliminó en la ronda 14 junto con su
   markup: repetía el destino del mazo de abajo y del ítem del menú. */

/* ==========================================================================
   LA CALIFICACIÓN — una LÍNEA debajo del botón, ya no una tarjeta sobre la foto
   ==========================================================================
   Las rondas 12 a 14 la tenían como tarjeta crema flotando con `position:
   absolute` sobre la foto. El cliente la vio y dijo "queda en el medio de la
   imagen de la pileta, no está bueno", y el diagnóstico es de composición:
   la tarjeta no se apoyaba en NINGÚN borde de la foto ni se alineaba con
   ninguna columna de la grilla. Un elemento sólido que flota a media altura
   dentro de una fotografía se lee como calcomanía, por bien dibujado que
   esté. Y encima obligaba a que la foto tuviera cielo vacío arriba para
   hacerle lugar.

   Como línea resuelve tres cosas de una: la foto queda limpia (que era el
   otro pedido de la misma ronda), la prueba queda pegada al botón —que es
   donde aparece la objeción "¿serán serios?"— y el hero se puede recortar
   más cerrado porque ya no hay que reservarle cielo a nadie.

   Sigue sin ser verde ni madera. El verde marca lo que convierte; el link a
   Google no convierte huéspedes. Y la madera no toca controles, por la regla
   declarada en la cabecera de este archivo. Va en tinta clara. */
/* ⚠️ RONDA 17 — PASÓ DE UNA LÍNEA A DOS RENGLONES.
   El link de reseña se convirtió en un botón con contorno, y un botón no
   puede compartir `align-items: baseline` con un texto: no tiene línea base
   que alinear y se cuelga. Ahora el bloque es una columna de dos renglones
   —la prueba arriba, la invitación abajo— y el `baseline` vive adentro de
   `.hero__rating-line`, que es donde sigue haciendo falta. */
.hero__rating {
  margin-top: var(--space-3);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-on-dark-soft);
}
.hero__rating-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px var(--space-2);
}
.hero__rating .stars { position: relative; top: 1px; font-size: var(--text-md); }
.hero__rating-score {
  font-family: var(--font-display);
  font-weight: 900;
  font-stretch: var(--wdth-display);
  font-size: var(--text-xl);
  line-height: 1;
  letter-spacing: -0.02em;
  color: #fff;
  font-variant-numeric: tabular-nums;
}
.hero__rating-text { color: var(--color-on-dark-soft); }
/* Acá vivía `.hero__rating-link`, el link subrayado "Dejanos la tuya →".
   Se fue completo en la ronda 17: lo reemplazó `.btn btn--review`, que es
   un botón de verdad. Sus 6 reglas quedaban sin un solo elemento que las
   usara en las 4 páginas. */

/* ==========================================================================
   LA REPISA DE LAS CABAÑAS — una fila alineada a la grilla del sitio
   ==========================================================================
   Reemplaza al mazo apilado de las rondas 12-14. Aquel vivía con `position:
   absolute` en la esquina inferior derecha de la foto: cerrado dejaba dos
   tarjetas asomando a la izquierda, rotadas -2,2° y -1,1°, y se abría en
   abanico al pasar el cursor. El cliente lo describió como "desentado a la
   izquierda" y es exactamente lo que era: tres fotos rotadas, sin alinear
   con ninguna columna, apoyadas sobre el pasto de una fotografía.

   Lo que se pierde: el abanico, que era bonito. Lo que se gana: las tres
   cabañas están SIEMPRE a la vista y en la misma grilla que todo lo demás,
   con el mismo borde izquierdo que el titular. El abanico existía para
   resolver "no hay lugar" en una esquina; bajando la fila a su propia
   repisa, lugar hay, y esconder dos de tres destinos detrás de un hover
   deja de tener justificación. (Además el hover no existe en un teléfono,
   así que el abanico nunca fue el camino real de la mayoría del tráfico.)

   Cuatro celdas y no tres: la última no es una cabaña y por eso no lleva
   foto. Es el dato que impide que el sitio se lea como "tres cabañas y
   listo" —son 6, más quincho y casa para 10— puesto donde se lo busca: al
   final de la lista de opciones.
   ========================================================================== */
.hero__shelf {
  border-top: 1px solid var(--color-wood-line-dark);
  padding-block: var(--space-3);
}
/* ⚠️ RONDA 26 — EN EL TELÉFONO LA REPISA ES UN RIEL, NO TRES COLUMNAS.
   Medido a 375px antes de tocar nada: cada foto de la repisa medía 98x96px.
   Noventa y ocho píxeles de ancho para "mostrar la cabaña" es un sello, no
   una foto: no se distingue un altillo de un living. La repisa existe para
   ser el índice de las tres cabañas en la primera pantalla, y a ese tamaño
   no cumple ni con eso.
   Abajo de 620px pasa a riel con enganche y tarjetas de 62vw (232px a 375,
   o sea foto de 232x174: +137% de ancho y +81% de alto). Desde 620 vuelve
   a ser la grilla de tres de siempre, sin cambios.
   Mismo patrón y mismo argumento que el selector de capacidad de más abajo;
   ver el bloque largo de `.units`. */
.cabins {
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
  /* A sangre contra el borde de la pantalla, para que la tercera asome. */
  margin-inline: calc(var(--space-3) * -1);
  padding-inline: var(--space-3);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--space-3);
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.cabins::-webkit-scrollbar { display: none; }
/* El envoltorio que anima GSAP. Sin transiciones propias a propósito: la
   levantada del hover vive en `.cabins__item`, un nivel adentro, y las dos
   capas no se pisan. Ver el comentario del markup en index.html. */
.cabins__slot { flex: 0 0 62vw; max-width: 260px; min-width: 0; scroll-snap-align: start; }
.cabins__item {
  position: relative;
  display: block;
  height: 100%;
  min-width: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-dark);
  text-decoration: none;
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .cabins__item:hover,
  .cabins__item:focus-visible { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(8, 14, 11, 0.5); }
}
.cabins__item img { display: block; width: 100%; aspect-ratio: 4 / 3; height: auto; object-fit: cover; object-position: 50% 45%; }

/* ---- Barrido de luz al pasar el cursor. Concepto de reactbits/Glare
   Hover, con dos cambios respecto del original: el barrido es MUCHO más
   tenue (0.18 de alpha contra el blanco casi puro del original) y viaja
   una sola vez en 700ms en vez de quedar atado al mouse. Sobre una foto
   de una cabaña real, un destello fuerte parece un error de render; a
   esta intensidad se lee como luz pasando por una ventana.
   Es una capa hermana con transform, no un background-position animado:
   así el barrido lo compone la GPU y no repinta la foto en cada frame.
   Gateado a dispositivos con cursor real: en una pantalla táctil el
   "hover" queda pegado después del tap y el destello se quedaría fijo. */
@media (hover: hover) and (pointer: fine) {
  .cabins__item::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(105deg, transparent 38%, rgba(255, 255, 255, 0.18) 50%, transparent 62%);
    transform: translateX(-120%);
    transition: transform 700ms var(--ease-out);
  }
  .cabins__item:hover::after,
  .cabins__item:focus-visible::after { transform: translateX(120%); }
}
@media (prefers-reduced-motion: reduce) {
  .cabins__item::after { display: none; }
  .cabins__item { transition: none; }
  .cabins__item:hover { transform: none; }
}

.cabins__cap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 8px var(--space-1);
  background: var(--color-dark);
  color: var(--color-wood-soft);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
.cabins__cap span { transition: transform var(--dur-fast) var(--ease-out); }
.cabins__item:hover .cabins__cap span { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .cabins__cap span { transition: none; }
  .cabins__item:hover .cabins__cap span { transform: none; }
}

/* ⚠️ RONDA 16 — DEJÓ DE SER UNA CELDA DE LA FILA Y PASÓ A SER UNA LÍNEA.
   Como cuarta columna se comía el 25% del ancho de la fila para poner texto
   al lado de tres fotos, y ése era el motivo medible de que las cabañas
   "no se terminaran de ver". Como línea debajo dice exactamente lo mismo,
   sigue siendo un link a contacto, y le devuelve a cada cabaña 107px de
   ancho y 68px de alto.
   Sigue sin caja: con fondo o borde sería una tarjeta más y la primera
   pantalla volvería a leerse como cuatro opciones. Es una nota al pie, y se
   comporta como una nota al pie. */
.cabins__more {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px var(--space-2);
  margin-top: var(--space-2);
  text-decoration: none;
  color: var(--color-on-dark-soft);
}
.cabins__more-title {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-wood-soft);
}
.cabins__more-text { font-size: var(--text-xs); line-height: 1.45; }
.cabins__more-text strong { color: var(--color-on-dark); font-weight: 700; }
.cabins__more-cue {
  display: inline-block;
  margin-left: 2px;
  color: var(--color-on-dark);
  transition: transform var(--dur-fast) var(--ease-out);
}
.cabins__more:hover { color: var(--color-on-dark); }
.cabins__more:hover .cabins__more-cue { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .cabins__more-cue { transition: none; }
  .cabins__more:hover .cabins__more-cue { transform: none; }
}

/* La fila es de TRES en todos los anchos: son tres cabañas. Antes pasaba a
   cuatro columnas en 620 para hacerle lugar a "Hay más", que ya no está
   adentro de la grilla.
   Desde 620 el riel de mobile vuelve a ser esa grilla: hay ancho de sobra
   para que las tres fotos se lean sin deslizar nada. */
@media (min-width: 620px) {
  .cabins {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
  }
  .cabins__slot { flex: none; max-width: none; }
  .cabins__item img { height: 150px; aspect-ratio: auto; }
}
@media (min-width: 1060px) {
  .cabins__item img { height: 200px; }
  .cabins__cap { padding: 10px var(--space-2); font-size: var(--text-xs); }
  .cabins__more { margin-top: var(--space-3); }
  .cabins__more-text { font-size: var(--text-sm); }
}

/* ==========================================================================
   ESTRELLAS — la calificación REAL, dibujada como es.
   ==========================================================================
   Antes el sello llevaba UNA estrella suelta al lado del 4,7, con esta
   justificación: pintar cinco estrellas llenas redondearía hacia arriba un
   dato verificable. El razonamiento era correcto y el problema era la
   solución: una estrella sola no se lee como una calificación, se lee como
   un ícono decorativo, y la prueba más fuerte que tiene el negocio pasaba
   desapercibida en el primer golpe de vista.

   Esto dibuja 4,7 sobre 5 EXACTO. Dos filas de cinco estrellas
   superpuestas: la de abajo apagada, la de arriba en --color-star y
   recortada al ancho justo. El ancho sale de --stars, que se escribe en el
   HTML al lado del número, así el dibujo no puede desincronizarse del dato.
   El porcentaje es exacto pese al letter-spacing: el espaciado se aplica
   por caracter, así que la proporción se conserva.

   Va con role="img" y un aria-label con el valor escrito: un lector de
   pantalla oye "4,7 de 5 estrellas", no diez asteriscos.

   ⚠️ RONDA 30 — AHORA SÍ HAY ESTRELLAS EN CADA RESEÑA, Y ESTE COMENTARIO
   DECÍA LO CONTRARIO. Queda escrito lo que cambió, porque lo que cambió no
   es el criterio sino el DATO de entrada.
   Lo de antes: "el archivo fuente sólo confirma la calificación de 3 de las
   12 publicadas, así que la estrella se dibuja donde es verdad y en ningún
   otro lado". Seguía siendo correcto mientras la planilla fuera un pool
   mezclado de calificaciones desconocidas.
   Lo de ahora: Francisco fijó una política de curación — a la planilla se
   cargan SÓLO reseñas de 5 estrellas. Con esa regla, las 5 estrellas de cada
   tarjeta no son una suposición sobre una fila, son la condición de entrada
   de todas las filas. El criterio "se dibuja donde es verdad" no se movió;
   lo que se movió es qué es verdad.
   El límite de esto está anotado donde se pinta la estrella
   (pintarOpiniones() en js/main.js): si algún día entra una de menos, esto
   no lo va a reflejar solo.
   ========================================================================== */
.stars {
  position: relative;
  display: inline-block;
  line-height: 1;
  letter-spacing: 0.08em;
  white-space: nowrap;
  font-size: var(--text-sm);
}
.stars__base { color: var(--color-border); }
.stars__fill {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--stars, 100%);
  overflow: hidden;
  color: var(--color-star);
}
.stars--on-dark .stars__base { color: rgba(236, 241, 243, 0.28); }

/* --------------------------------------------------------------------------
   DESDE 1060: EL HERO SE PARTE. Texto a la izquierda sobre superficie sólida,
   foto a la derecha, y la repisa de cabañas cruzando abajo.
   --------------------------------------------------------------------------
   POR QUÉ ESTÁ PARTIDO (ronda 14, sigue valiendo). El archivo
   lugar-pileta-rio mide 1440×1000 y la pileta vive al 60% de la altura. Con
   la foto a sangre y el texto encima, el degradado que hacía falta para que
   el blanco pasara AA sobre el agua tenía 0,88-0,97 de opacidad exactamente
   en esa banda: el titular se leía perfecto y la pileta desaparecía. En
   mobile, donde el texto siempre estuvo DEBAJO de la foto, la pileta se veía
   entera y sin un solo degradado. Partido, la foto no lleva nada encima.

   QUÉ CAMBIÓ EN LA RONDA 15, y las tres cosas venían del mismo pedido:
   1. LA FOTO Y EL TEXTO COMPARTEN CELDA DE GRILLA. Antes la foto era
      `inset: 0 0 0 50%` y el alto del hero estaba atado a `40vw` con un
      clamp, para que el panel derecho quedara en una relación parecida a la
      del archivo. Ahora los dos van en `grid-area: 1 / 1` y la foto se
      estira al alto que fija el bloque de texto: el alto lo decide el
      contenido y no una cuenta. Como el panel siempre queda MÁS ANCHO que
      alto respecto de 1,44, el recorte es sólo vertical: la pileta entra
      completa de punta a punta en todo el rango, y de paso se va el cielo
      vacío de arriba, que existía nada más que para hacerle lugar al sello.
   2. LA COSTURA SE CORRIÓ AL 44%. El cliente: "te quedó ocupando mucho el
      texto del lado izquierdo". Con 50/50 el titular a 56px llenaba su
      columna de borde a borde. Ahora el texto se queda con el 42% de la
      grilla interna y el titular baja a 48px: el bloque pesa menos y la foto
      —que es el argumento del lugar— pesa más.
   3. NO HAY NADA APOYADO SOBRE LA FOTO. Ni el sello (ahora es una línea
      debajo del botón) ni el mazo de cabañas (ahora es la repisa de abajo).
      Por eso tampoco hay ya velo al pie: no queda nada que despegar del
      fondo.

   Research: Juvet Landskapshotell resuelve el hero así —superficie sólida
   contra paisaje, sin texto sobre la foto— y aplica acá porque su argumento
   también es el paisaje y no el edificio.
   -------------------------------------------------------------------------- */
@media (min-width: 1060px) {
  .hero { padding-block: 0; }

  /* ⚠️ LA FOTO SIGUE EN LA CELDA DEL TEXTO, PERO YA NO VOTA POR EL ALTO
     (ronda 16). Una celda de grilla mide lo que mide el MÁS ALTO de sus
     ocupantes, así que mientras el <img> estuvo en flujo el alto del hero lo
     terminaba fijando la relación de aspecto de la foto y no el contenido:
     con el recorte nuevo, 675px de placa verde para un bloque de texto de
     380. Exactamente el hueco que marcó el cliente, entrando por otra puerta.
     El arreglo es que el <img> pase a `absolute` dentro de `.hero__bg`: un
     hijo absoluto no aporta alto, así que la caja de la foto queda en cero y
     se estira al alto que fija `.hero__inner`. Cambiar el recorte ya no puede
     mover el alto de la placa nunca más.
     (Y `absolute` sobre `.hero` a secas tampoco servía: `.hero` incluye la
     repisa de las cabañas, así que la foto se metía DEBAJO de las tarjetas.) */
  /* ⚠️ EL ANCHO NO ES 56% (ronda 16). Con `56%` la costura vivía en el 44%
     del VIEWPORT mientras que la columna del texto vive en el 42% de un
     contenedor de 1180px CENTRADO: dos sistemas de coordenadas distintos que
     se separan a medida que la pantalla crece. Medido con un barrido de
     anchos: el borde derecho de la columna avanza a 0,5·vw − 91 y la costura
     a 0,44·vw, así que se cruzan a partir de ~1517px y a 1920 el titular ya
     entraba 4px adentro de la foto.
     `calc(50% + var(--container) * 0.06)` deja la costura clavada al 44% del
     CONTENEDOR en cualquier ancho, o sea en el mismo sistema que la grilla:
     20px de aire constante entre el texto y la foto, de 1060 a 2560. */
  /* ⚠️ RONDA 31 — SE ACABÓ LA COSTURA. LA FOTO VA A SANGRE.
     Todo el aparato de arriba (el ancho al 44% del contenedor, los dos
     sistemas de coordenadas, el barrido de anchos) existía para sostener un
     hero PARTIDO en dos placas. El research de esta ronda sobre lodges de
     pesca reales dice que ninguno hace eso: Pirá Lodge, Nervous Waters y
     Untamed Angling ponen UNA media a sangre al 100% del viewport con el
     texto encima. Así que la costura no se ajusta: se elimina. */
  .hero__bg {
    grid-area: 1 / 1;
    justify-self: stretch;
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
    z-index: -1;
  }
  /* ⚠️ RONDA 32 — PASA DE 62% A 0%, Y EL MOTIVO NO ES DE COMPOSICIÓN SINO DE
     ATRIBUCIÓN.
     La foto nueva es 1091x819 (ratio 1,33) dentro de una caja de 1440x612
     (ratio 2,35): `cover` deja ver apenas el 57% del alto, o sea 464 de 819
     píxeles. Con esa cuenta NO EXISTE un encuadre que muestre las dos marcas
     quemadas a la vez — la tarjeta de atribución vive en y 0-105 y el wordmark
     de Google en y 770-805, y entre las dos hay 805px de foto para una ventana
     de 464. Hay que elegir una.
     Se probaron los cuatro encuadres en el navegador y se miraron (capturas en
     .shots/ronda32/hero-objpos-*.png):
       0%   → se ve la tarjeta "Emiliano Machin · ago 2018". Cielo, arboleda y
              la fila de cabañas enteras. El crédito cae en la franja oscura de
              arriba a la izquierda, ARRIBA del eyebrow: no pisa el H1.
       30%  → la mejor composición de las cuatro, pero deja las DOS marcas
              fuera de cuadro.
       62%  → tampoco muestra ninguna (ventana 220-684).
       100% → muestra el wordmark de Google, pero el cuadro queda dominado por
              el pasto vacío y les corta el techo a las cabañas.
     Se elige 0%. La foto NO es del cliente (ver el comentario del <picture> en
     index.html): la sacó un visitante y la publicó en Google Maps. Recortar
     justo la firma del autor para quedarse con el encuadre más lindo es
     exactamente lo que no hay que hacer, así que el encuadre cede ante el
     crédito. Y 0% no es un mal encuadre: es el segundo mejor de los cuatro.
     En mobile la caja es casi cuadrada y el recorte es de apenas 34-49px, así
     que ahí el crédito entra sin discusión (ver la regla base). */
  .hero__bg img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    min-height: 0;
    /* Ronda 33: vuelve la foto propia (hero-rio, 1440x810) y con ella el
       62% que ya estaba calibrado para ésta antes de que la ronda 32 lo
       pisara con el 0% de la foto de Google Maps (ver el comentario del
       <picture> en index.html). A este ratio de caja (más apaisada que la
       foto) `cover` recorta sólo en vertical: 62% deja el cielo casi afuera
       y conserva el río, la pileta y la franja de pasto donde se apoya el
       texto. */
    object-position: 50% 62%;
  }
  /* ==================================================================
     EL SCRIM, Y POR QUÉ AHORA SÍ SE PUEDE PONER TEXTO SOBRE LA FOTO
     ==================================================================
     La ronda 14 bajó el texto de la foto con un argumento correcto: para que
     el blanco pasara AA sobre la pileta hacía falta un degradado de 0,88-0,97
     de opacidad, y ese degradado caía JUSTO sobre la pileta — o sea que el
     precio de leer el titular era borrar el único objeto que la foto venía a
     mostrar. Ese argumento valía para AQUELLA foto con el texto en AQUEL
     lugar. Con la foto nueva no vale, y no es una opinión: se midió el perfil
     vertical del archivo banda por banda (4% de alto, sobre el 75% central
     del cuadro, muestreado en canvas):

         0-20%   cielo ................ lum 166-204
         24-40%  el Paraná ............ lum 164-181
         43-51%  barranca y baranda ... lum 117-152
         55-63%  LA PILETA ............ lum 117-143
         67-75%  el deck de madera .... lum  89-146
         79-100% pasto en primer plano  lum  96-139

     El sujeto (río arriba, pileta al medio) y el lugar donde va el texto
     (pasto, abajo) están en bandas DISTINTAS. Así que el scrim puede ser
     denso donde está el texto y casi nulo donde está el sujeto. Las paradas
     de abajo salen de ahí, y el perfil de meseta —se sostiene y recién
     después suelta— es el de Postcard Cabins, ya adoptado en la ronda 29.

     Contraste del blanco sobre el pasto compositado, medido sobre las bandas
     reales (#61643E a #889151):
         sin scrim ...... 3,36-6,18:1   (la banda clara reprueba)
         a 0,54 ......... 10,29-13,81:1
         a 0,62 ......... 12,26-15,34:1
     Y lo que el scrim le hace al sujeto, que es la pregunta de la ronda 14:
         la pileta queda a 0,18 → #1EA7DB pasa a #1989B4. Se oscurece un 18%,
         se sigue viendo entera. El río queda a 0,00: intacto.
     Contra el 0,88-0,97 que habría hecho falta antes. Ese es todo el cambio.

     ⚠️ EL SCRIM ES DIRECCIONAL (izquierda), NO VERTICAL, Y ESA CORRECCIÓN
     COSTÓ UNA ITERACIÓN. El primer intento de esta ronda puso las paradas de
     arriba tal cual, en un degradado `to bottom`. No funcionó, y la razón es
     un error de sistema de coordenadas que vale dejar anotado: esas bandas
     están medidas sobre EL ARCHIVO, y un degradado CSS se dibuja sobre LA
     CAJA. Como la caja de desktop es más apaisada que la foto, `cover`
     recorta 25% del alto, así que "79% del archivo" y "79% de la caja" son
     dos alturas distintas y el pasto quedaba en una franja de 170px al pie
     mientras el texto —que mide ~420px— caía sobre la pileta.
     La banda de pasto es el 21% de la foto y no da para sostener el bloque
     entero por más que se estire el hero. Lo que sí es cierto es que el
     texto vive en el TERCIO IZQUIERDO (x 155-660 de 1440) y la pileta en el
     centro-derecha (x 370-1310): se superponen en apenas 290px. Así que el
     scrim va de izquierda a derecha, que es el eje donde el sujeto y el
     texto SÍ están separados. Es el mismo dispositivo de Postcard Cabins
     (texto anclado en el tercio oscuro) que la ronda 29 ya había adoptado
     para otra cosa.
     El segundo degradado, al pie, es corto y suave: no protege texto, cose
     la foto con la repisa de cabañas que viene abajo.

     Va tintado a --color-darker y no a negro puro, como todas las capas de
     este archivo desde la ronda 29. ================================== */
  /* ⚠️ RONDA 32 — EL SCRIM SUBE, Y NO POR GUSTO: CON LA FOTO NUEVA HABÍA DOS
     TEXTOS REPROBANDO AA.
     Todo el análisis de arriba —el perfil banda por banda, el 0,54/0,62— está
     medido sobre la foto ANTERIOR. La foto nueva pone CIELO en el tercio
     izquierdo, que es justo donde vive el texto, así que las mismas paradas
     dan otro resultado. Medido en el navegador componiendo foto + scrim y
     muestreando la caja real de cada elemento:

                        scrim viejo      scrim nuevo     umbral
       eyebrow 13px/700 ... 3,29:1  ✗ ...... 5,02:1  ✓ ... 4,5
       titular 52px/900 ... 4,41:1  ✓ ...... 7,44:1  ✓ ... 3,0 (texto grande)
       lede    18px/400 ... 6,99:1  ✓ ..... 10,93:1  ✓ ... 4,5
       rating  15px/400 ... 3,20:1  ✗ ...... 5,27:1  ✓ ... 4,5

     El eyebrow y la línea de calificación reprobaban. El titular pasaba sólo
     por ser texto grande, con 4,41 contra los 10,29-15,34 que medía antes: no
     es que estuviera bien, es que el umbral es más flojo.

     LO QUE ESTO LE CUESTA AL SUJETO: NADA MEDIBLE. Brillo medio del tercio
     derecho (donde están las cabañas): 129 con el scrim viejo, 127 con el
     nuevo. Dos puntos sobre 255. Es la ventaja de que el degradado sea
     DIRECCIONAL y no vertical: del 76% para la derecha vale 0, así que subir
     la punta izquierda no toca el sujeto. Ese es exactamente el motivo por el
     que la ronda 31 lo hizo direccional, y acá se cobra el dividendo. */
  .hero__bg::after {
    display: block;
    inset: 0;
    height: auto;
    background:
      linear-gradient(100deg,
        rgba(15, 27, 31, 0.97) 0%,
        rgba(15, 27, 31, 0.94) 24%,
        rgba(15, 27, 31, 0.86) 38%,
        rgba(15, 27, 31, 0.64) 50%,
        rgba(15, 27, 31, 0.26) 62%,
        rgba(15, 27, 31, 0) 76%),
      linear-gradient(to top,
        rgba(15, 27, 31, 0.46) 0%,
        rgba(15, 27, 31, 0.12) 24%,
        rgba(15, 27, 31, 0) 48%);
  }

  /* ⚠️ RONDA 16 — EL VACÍO VERDE DE LA IZQUIERDA.
     El cliente: "queda muy raro que haya tanto espacio verde a la izquierda,
     porque hay una parte muy grande para que sólo diga [las cuatro líneas]".
     Tenía razón y se puede medir. A 1440x900 la placa oscura medía 634x560px
     y el bloque de texto 424x370: el 56% de la placa era verde sin nada. Un
     panel que es mayoría vacío no se lee como aire, se lee como un hueco.
     El error NO era de ancho —634px es un ancho razonable para una columna
     de titular— sino de ALTO: `min-height: 26rem` + 48px de padding arriba y
     abajo estiraban la placa muy por encima de lo que su contenido pedía, y
     `align-content: center` repartía el sobrante en dos bandas muertas de
     ~90 y ~100px.
     La corrección es que la altura la fije el contenido, no un número: el
     piso baja a 20rem (que ya no llega a activarse en desktop), el padding a
     32px, y el titular sube de 3rem a 3.5rem para que el bloque ocupe el
     ancho de columna que tiene. Resultado medido: la placa pasa a ~634x470 y
     el bloque a ~424x420, o sea del 44% al 60% de aprovechamiento, con el
     texto más grande y no más chico.
     Lo que NO se toca: la costura al 44% y la regla de que el texto nunca
     pisa la foto. Eso lo resolvió la ronda 14 con contraste estructural
     (16,4:1) y no era lo que el cliente estaba señalando. */
  /* ⚠️ RONDA 31 — EL BLOQUE SE APOYA ABAJO, NO EN EL MEDIO.
     Con la foto a sangre, `align-content: center` dejaba el texto flotando en
     la mitad del cuadro, encima de la pileta, que es exactamente lo que la
     ronda 14 prohibió. `end` lo apoya en la franja de pasto, que es la banda
     que el scrim puede oscurecer sin costo (ver `.hero__bg::after`).
     El alto ya no lo fija un `min-height` de contenido: lo fija el viewport,
     como en las tres referencias del research. El clamp evita los dos
     extremos —un hero enano en una notebook de 1366x768 y uno de 1000px en un
     monitor vertical— sin llegar nunca al 100vh, porque acá abajo hay una
     repisa de cabañas que el cliente pidió expresamente que se vea. */
  .hero__inner {
    grid-area: 1 / 1;
    display: grid;
    grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
    column-gap: var(--space-6);
    align-content: end;
    min-height: clamp(26rem, 68vh, 40rem);
    padding-block: var(--space-6) var(--space-6);
  }
  .hero__eyebrow,
  .hero__title,
  .hero__lede,
  .hero__cta,
  .hero__rating { grid-column: 1; }
  /* 3.5rem = 56px. El techo de `impeccable` es 6rem y el piso de
     letter-spacing -0.04em; a 56px con -0.02em queda muy adentro de los dos.
     max-width 12ch mantiene el corte en dos renglones ("Cabañas frente" /
     "al río Paraná") dentro de la columna de 42fr. */
  .hero__title { font-size: clamp(2.25rem, 3.6vw, 3.5rem); max-width: 12ch; }
  /* ⚠️ RONDA 31 — VUELVE A TENER TOPE, Y AHORA EL MOTIVO ES DE CONTRASTE, NO
     DE MEDIDA. Con el texto apoyado sobre la foto, cada carácter de más hacia
     la derecha es un carácter que se acerca a la zona donde el scrim se
     suelta. Medido con el texto escondido y muestreando el fondo real: sin
     tope, el peor píxel bajo la bajada daba 2,66:1 y reprobaba AA. Con 34ch
     el renglón corta antes de salirse de la zona protegida. Sigue entrando en
     dos renglones, así que la objeción de la ronda 16 (tres renglones cortos
     se leen como columna de diario) tampoco se reabre. */
  .hero__lede { max-width: 34ch; }

  .hero__shelf { grid-row: 2; padding-block: var(--space-4); }
}

/* Fichas de cabaña: hero corto, mismo vocabulario, foto a sangre.
   Acá SÍ va texto sobre foto porque son fotos de interior en 4:3 reales,
   no verticales estiradas, y el bloque de texto es corto. */
.page-hero { position: relative; min-height: 46vh; display: flex; align-items: flex-end; color: #fff; overflow: hidden; }
.page-hero__media { position: absolute; inset: 0; }
/* ⚠️ RONDA 31 — CADA FICHA ENCUADRA SU PROPIA FOTO.
   La banda mide ~3,5:1 y las tres fuentes tienen relaciones distintas (16:9,
   1:1 y una vertical 9:16), así que `cover` centrado recortaba cada una en un
   lugar arbitrario: en la de 5 personas la banda caía sobre la pared y los
   almohadones en vez de la cama, y en la de 6 —que es un plano VERTICAL de la
   cabaña entera— el centro geométrico cae en la baranda del deck y se perdía
   el techo a dos aguas, que es lo único que distingue a esa unidad.
   Un token por ficha, con el centro como valor por defecto. */
.page-hero__media img { width: 100%; height: 100%; object-fit: cover; object-position: var(--hero-focus, 50% 50%); }
.page-hero--dormitorio { --hero-focus: 50% 54%; }
.page-hero--cama { --hero-focus: 50% 62%; }
/* La única fuente vertical del set: se sube el foco para que entren el techo
   y la galería, no el pasto de adelante. */
.page-hero--exterior { --hero-focus: 50% 38%; }
/* ⚠️ RONDA 31 — EL DATO PRIMARIO ERA LO MENOS LEGIBLE DE LA PÁGINA.
   El scrim era un degradado lineal de 0,2 a 0,88: denso abajo, casi nulo
   arriba. El H1 vive abajo y se leía bien; la CIFRA DE CAPACIDAD —que es el
   dato que esta página existe para dar— vive al 58-70% del alto de la banda,
   donde el degradado todavía no había cerrado, y en la ficha de 5 personas
   caía justo sobre una pared de troncos clara. Medido con el texto escondido:
   2,03:1. Es la misma inversión de jerarquía que arregla el bloque de la
   ficha técnica, sólo que expresada en contraste en vez de en tamaño.
   Ahora el degradado cierra ANTES, de modo que ya está denso donde arranca el
   bloque de texto, y deja la parte de arriba de la foto casi limpia, que es
   donde no hay nada escrito. Tintado a --color-darker como todas las capas
   del sitio.

   ⚠️ Y LAS PARADAS VAN EN PÍXELES DESDE ABAJO, NO EN PORCENTAJES. Primer
   intento de esta ronda: porcentajes. Arregló desktop y ROMPIÓ mobile —el
   breadcrumb medía 1,21:1 en la ficha de 5 personas a 375px—, porque un
   porcentaje se calcula sobre el alto de la BANDA (46vh, o sea 414px en
   desktop y 373px en un teléfono) mientras que el bloque de texto mide
   aproximadamente lo mismo en los dos. Al achicarse la banda, el texto se
   come una fracción mayor de su alto y se sale de la zona protegida.
   Anclado en píxeles desde abajo, el degradado cubre el bloque de texto
   —que es lo que tiene que cubrir— sin importar cuánto mida la banda. */
.page-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(to top,
    rgba(15, 27, 31, 0.92) 0,
    rgba(15, 27, 31, 0.88) 140px,
    rgba(15, 27, 31, 0.78) 220px,
    rgba(15, 27, 31, 0.68) 300px,
    rgba(15, 27, 31, 0.38) 380px,
    rgba(15, 27, 31, 0.10) 460px,
    rgba(15, 27, 31, 0.04) 100%);
}
.page-hero__content { position: relative; z-index: var(--z-raised); padding-block: var(--space-6) var(--space-5); width: 100%; }
.page-hero__capacity { font-family: var(--font-display); font-weight: 900; font-size: var(--text-capacity); line-height: 1; color: var(--color-on-dark); }
.page-hero__capacity small { display: block; font-family: var(--font-body); font-weight: 700; font-size: var(--text-sm); letter-spacing: 0.1em; text-transform: uppercase; color: #fff; margin-top: var(--space-1); }
.page-hero__title { font-size: var(--text-2xl); margin-top: var(--space-1); color: #fff; }
/* Ronda 31: pasa de --color-on-dark-soft a --color-on-dark. El breadcrumb es
   lo más alto del bloque de texto, o sea lo que queda más lejos del pie donde
   el scrim es denso, y en la ficha de 5 personas cae sobre una pared de
   troncos clara: medido, 4,07:1 con la tinta apagada. Es un requisito de AA,
   no una decisión de jerarquía — y la jerarquía no se resiente porque lo que
   subordina a este texto es la ESCALA (15px contra los 72px de la cifra de
   capacidad), no su contraste. Endurecer el scrim una tercera vez era la
   alternativa, y habría oscurecido la foto entera para salvar un renglón. */
.breadcrumb { position: relative; z-index: var(--z-raised); font-size: var(--text-sm); font-weight: 600; color: var(--color-on-dark); display: inline-flex; gap: var(--space-1); align-items: center; margin-bottom: var(--space-2); }
.breadcrumb a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }

/* Confianza: la sección quedó sólo con pruebas (números + reseñas).
   El bloque de presentación de los dueños se eliminó a pedido del cliente:
   ese dato vive en el hero y en el CTA final, no ocupa una sección. */


/* ==========================================================================
   CARRUSEL DE RESEÑAS — horizontal, avanza solo, con peek.
   ==========================================================================
   Antes era un spotlight que cruzaba con fade una reseña por vez. El
   cliente pidió: más reseñas, que pasen solas, que se vea que hay más a la
   derecha, y poder hacer clic sobre la propia tarjeta (nadie toca los
   puntitos chiquitos de abajo).

   El ancho de tarjeta es menor al del viewport a propósito: siempre se ve
   el borde de la siguiente. Esa es la señal de que hay más para leer, y es
   lo que hace que el carrusel se entienda sin instrucciones.

   SIN JS: el track queda con scroll horizontal nativo y las 12 se pueden
   arrastrar igual. No hay contenido escondido.
   ========================================================================== */
.revcar { margin-top: var(--space-6); position: relative; }
.revcar__viewport { overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.revcar__viewport::-webkit-scrollbar { display: none; }
.revcar__track {
  display: flex;
  gap: var(--space-3);
  padding-bottom: var(--space-1);
}
/* ==========================================================================
   TARJETA DE RESEÑA — ronda 30: el formato de una reseña real de Google.
   ==========================================================================
   Lo de la ronda 29 (luz, anillo, identidad arriba) SE MANTIENE y está
   documentado más abajo. Lo que cambia esta ronda es la CABECERA: la
   tarjeta pasa a tener la misma anatomía que una reseña de Google Maps,
   porque eso es literalmente lo que es y porque el lector ya sabe leer ese
   formato sin que nadie se lo explique.

       ┌──────────────────────────────────┐
       │  (A)  Alejandra Retman      [G]  │   avatar · nombre · atribución
       │       Hace 5 meses               │
       │  ★★★★★                           │
       │                                  │
       │  Hermoso lugar para unos días…   │   la cita, abajo
       └──────────────────────────────────┘

   SE ARMA EN VANILLA, NO CON UN WIDGET. Nada de Trustindex ni de ningún
   servicio de terceros: son los mismos tres campos que ya venían de la
   planilla (Nombre / Antigüedad / Texto), compuestos acá. Un widget de
   reseñas habría metido un iframe de terceros, su propia tipografía y su
   propio CSS en la única sección que sostiene la prueba social del sitio.

   NO HAY TILDE DE VERIFICADO, Y ES UNA DECISIÓN, NO UN OLVIDO. Las reseñas
   de Google NO llevan tilde celeste: eso lo agregan los widgets tipo
   Trustindex, y es justo su marca de fábrica. La G de cuatro colores sí es
   atribución real —estas reseñas vienen de Google— y con eso alcanza. Poner
   un tilde de "verificado" habría afirmado una verificación que Google no
   muestra, en un sitio cuya regla nº1 es no fingir nada (ver PRODUCT.md,
   "Anti-references"). Es el mismo criterio del sello 4,7/284 del hero:
   se dibuja donde es verdad y en ningún otro lado.

   SE FUE LA HAIRLINE DEL PIE (la que agregó la ronda 29). No la reemplaza
   nada: la fila de estrellas ya separa identidad de cita, y dos separadores
   apilados son ruido. Recuento del anti-default #3: el sitio queda con 12
   hairlines contra 13, o sea MENOS que antes de esta ronda.

   SE FUERON LAS COMILLAS TIPOGRÁFICAS (::before/::after). Google muestra el
   texto pelado, y el trabajo que hacían —decir "esto es un testimonio"— hoy
   lo hacen mejor y con más verdad las cinco estrellas y la G. Consecuencia
   a la vista: --color-water-soft queda sin uso (ver su comentario en :root).

   LA CITA BAJA DE --text-lg A --text-md, y es la ronda 27 al revés. Aquel
   comentario decía, con razón, que sin foto "la tipografía de la cita es lo
   único que sostiene la tarjeta", y por eso la agrandó. Ahora la cabecera
   sostiene: hay avatar de 40px, cinco estrellas y un logo. Con la cita a 22px
   la tarjeta entraba en 4 líneas y cortaba reseñas por la mitad; a 18px entra
   en 6 y el pool real casi no se recorta.
   ==========================================================================
   DE LA RONDA 29, QUE SIGUE VIGENTE:

   1. LUZ. Un radial de --color-on-dark al 5,5% anclado arriba-derecha, la
      MISMA dirección de luz que las secciones (ver "PROFUNDIDAD DE FONDO").
      Compuesto sobre --color-water da #2F3F45 = HSL(196°, 19%, 23%): el hue
      no se mueve (199 -> 196), sube la luz. Es el gesto de Linear (G).

   2. ANILLO DE 1px QUE SE APAGA. No es un borde parejo: es un gradiente que
      arranca en 28% de alfa arriba-izquierda y llega a 0 antes del final,
      de modo que el canto superior "recibe" la luz y el inferior no. La
      técnica es la de Magic Bento de reactbits (leída en vivo con el MCP):
      pintar el gradiente en el ::after y recortarlo al ANILLO con
      mask-composite, en vez de usar border-image. Se adapta a vanilla —el
      original es React— y se le saca el glow que sigue al cursor, que en
      un sitio de cabañas es cosmético y encima costaría un mousemove.
      `pointer-events: none` es obligatorio: la tarjeta entera es clickeable.

   3. LA IDENTIDAD VA ARRIBA, NO ABAJO. Cambio de composición, no de dato.
      Antes el pie iba abajo con `margin-top: auto`, así que en las reseñas
      cortas quedaba un hueco muerto en el medio de la tarjeta. Con la
      identidad arriba, la cita fluye desde el borde y lo que sobra queda al
      pie, que es donde el ojo no lo lee como error. Y en un carrusel
      barajado, donde cada tarjeta es otra persona, saber QUIÉN habla antes
      de leer hace que la cita aterrice.
      Se hace con `order` y NO tocando el HTML: en el markup el <footer>
      tiene que seguir DESPUÉS del texto dentro del <blockquote>, que es lo
      correcto semánticamente y lo que lee un lector de pantalla.
   ========================================================================== */
.revcar__card {
  flex: 0 0 82%;
  position: relative;
  background-color: var(--color-bg-alt);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Sube de 15rem a 17rem (240 -> 272px): la cabecera de la ronda 30 suma
     la fila de estrellas y un avatar de 40 en vez de 32. Con 15rem las
     reseñas largas quedaban con la cita pegada al canto de abajo. */
  min-height: 17rem;
}
.revcar__card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(155deg, var(--ring-strong) 0%, var(--ring-fade) 42%, transparent 72%);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
/* Valores del anillo sobre superficie CLARA (la tarjeta sin JS, fuera de
   .proof). Sobre oscuro los pisa el bloque .proof más abajo. */
.revcar__card { --ring-strong: rgba(23, 36, 43, 0.16); --ring-fade: rgba(23, 36, 43, 0.05); }
/* En el teléfono la tarjeta mide ~280px y 32px de padding a cada lado se
   comían 64: al nombre le quedaban ~124px y "Marcelo Speranza" se partía en
   dos líneas, empujando la cita y comiéndose una línea del clamp. Con 24 el
   nombre entra de una. Los nombres de verdad largos ("Maria Del Carmen
   Mercado") siguen envolviendo, y está bien: antes que cortarle el nombre a
   alguien con "…", que envuelva. */
.revcar__body { padding: var(--space-3); display: flex; flex-direction: column; flex: 1; }
@media (min-width: 700px) { .revcar__body { padding: var(--space-4); } }
.revcar__quote {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--text-md);
  line-height: 1.5;
  color: var(--color-ink);
  /* Las citas reales van de una frase a un párrafo largo: sin tope, una
     fila de tarjetas queda dispareja.

     EL TOPE ES DISTINTO POR ANCHO, Y ESO SE MIDIÓ. En el teléfono la tarjeta
     mide 268px contra los 340 del escritorio, así que el mismo texto ocupa
     más líneas: con 6 líneas fijas se recortaban 11 de las 20 reseñas del
     pool a 375 contra 5 a 1440. Más de la mitad del contenido cortado, y
     justo en el dispositivo principal de este sitio (ver PRODUCT.md).
     En un carrusel horizontal el alto no es gratis del todo: las tarjetas
     son flex items y se estiran todas a la más alta, así que cada línea que
     se le concede a la reseña más larga es hueco al pie de las cortas. Se
     midió la curva entera a 375 antes de elegir (recortadas / alto máximo):
         6 líneas .... 11 de 20 · 324px
         7 líneas ....  8 de 20 · 351px
         8 líneas ....  6 de 20 · 378px   <- elegido
         9 líneas ....  5 de 20 · 405px
        10 líneas ....  2 de 20 · 432px
     8 es donde se aplana: iguala la tasa de recorte del escritorio (6 de 20
     contra 5) y de ahí en más cada línea cuesta 27px de alto en TODAS las
     tarjetas para rescatar una sola reseña. */
  display: -webkit-box;
  -webkit-line-clamp: 8;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (min-width: 700px) { .revcar__quote { -webkit-line-clamp: 6; } }
/* ---- Cabecera de la tarjeta: la anatomía de una reseña de Google ----
   Grid de 3 columnas y 2 filas, y no tres flex anidados, porque hay dos
   ejes reales: la fila del nombre y la fila de las estrellas, que tiene
   que empezar en el borde izquierdo de la tarjeta (como en Google) y no
   indentada bajo el avatar.

       col:   auto        1fr          auto
       fila1  [avatar]    nombre       [G]
              (40px)      antigüedad
       fila2  ★★★★★  (cruza las 3 columnas)

   `order: -1` la sigue subiendo al tope sin tocar el orden del DOM: en el
   markup el <footer> va DESPUÉS de la cita adentro del <blockquote>, que
   es lo correcto semánticamente y lo que lee un lector de pantalla. */
.revcar__by {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  column-gap: var(--space-2);
  row-gap: var(--space-1);
  order: -1;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
}
/* Círculo con la INICIAL del nombre (ronda 30; antes era una silueta
   genérica de persona). Cambia el glifo, no el criterio: sigue sin haber
   foto real de nadie, y ahora lo que se dibuja es un dato REAL de la
   planilla —la primera letra del nombre— en vez de un ícono inventado.
   Es lo que muestra una reseña de Google de verdad.
   El anillo es el mismo círculo de 1px que ya usan las flechas del propio
   carrusel (.revcar__arrow): no es un patrón nuevo.
   aria-hidden porque el nombre de al lado ya dice quién es — sin eso, un
   lector de pantalla leería una "A" suelta antes de "Alejandra Retman". */
.revcar__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  color: var(--color-ink);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1;
  text-transform: uppercase;
  /* La inicial es un glifo suelto: sin esto, las mayúsculas con acento y
     las letras de bajo perfil se sientan distinto en cada tarjeta. */
  font-variant-numeric: tabular-nums;
  user-select: none;
}
.revcar__id { display: flex; flex-direction: column; min-width: 0; }
.revcar__name { display: block; font-weight: 700; color: var(--color-ink); }
.revcar__meta { display: block; color: var(--color-ink-soft); margin-top: 2px; }

/* ---- La G de Google: atribución, no decoración ----
   Es el logotipo oficial de cuatro colores, con sus paths y sus colores
   tal cual — sin recolorear ni deformar, que es lo que pide el uso de una
   marca ajena. Los cuatro colores NO ENTRAN AL :root y no son tokens de
   este sitio: viven adentro de este SVG y en ningún otro lado. Ver el
   chequeo anti-default completo en el header del archivo.
   No es clickeable a propósito: el verde de WhatsApp sigue siendo el único
   color que en este sitio significa "acá se hace clic". El link para ver
   las reseñas en Google está abajo, en .reviews__source, donde se lee. */
.revcar__source {
  display: inline-flex;
  /* 20 y no 24: a 24 la G era lo más fuerte de la tarjeta y le competía la
     atención a las estrellas, que son el dato. La atribución tiene que decir
     de dónde viene la reseña, no gritarlo — es el único elemento saturado
     sobre una superficie deliberadamente apagada. */
  width: 20px;
  height: 20px;
  flex-shrink: 0;
}
.revcar__source svg { width: 100%; height: 100%; display: block; }

/* ---- Las cinco estrellas ----
   Reusa el componente .stars del sello 4,7/284 del hero, con --stars al
   100%. No es sólo economía: si algún día entra una columna de estrellas
   reales a la planilla, un 4 se dibuja poniendo --stars: 80% y no hace
   falta CSS nuevo. Ver el comentario de pintarOpiniones() en js/main.js
   para POR QUÉ hoy van fijas en 5. */
.revcar__stars {
  grid-column: 1 / -1;
  justify-self: start;
  font-size: var(--text-base);
}

@media (min-width: 700px)  { .revcar__card { flex-basis: 46%; } }
@media (min-width: 1080px) { .revcar__card { flex-basis: 31%; } }

/* --- Estado con JS: el viewport deja de scrollear a mano y el track se
       mueve por transform (sin repintar layout). --- */
.revcar.is-live .revcar__viewport { overflow: hidden; }
/* 700ms -> --dur-slow (400ms). No cambia NADA de la arquitectura del
   carrusel (queda intacta, como está pedido): cambia sólo su reloj, para
   que el paso de tarjeta corra al mismo tiempo que el resto de lo que se
   mueve en el sitio. A 700ms el carrusel era lo más lento de la página y
   se sentía de otro sitio. */
.revcar.is-live .revcar__track { transition: transform var(--dur-slow) var(--ease-out); will-change: transform; }
.revcar.is-live .revcar__card { cursor: pointer; }

.revcar__nav { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-3); }
.revcar__arrow {
  width: 48px; height: 48px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-bg);
  color: var(--color-ink);
  font-size: var(--text-md);
  line-height: 1;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.revcar__arrow:hover { background: var(--color-ink); border-color: var(--color-ink); color: #fff; }
.revcar__arrow:active { transform: scale(0.94); }

/* ==========================================================================
   LAS CABAÑAS — una fila por unidad: foto a la izquierda que va pasando,
   info a la derecha, y el botón de consultar abajo de cada una.
   ==========================================================================
   Reescrito para conversión. Antes eran 3 tarjetas iguales que sólo
   llevaban a otra página: para consultar por una cabaña puntual había que
   navegar, leer y recién ahí encontrar el botón. Ahora la acción está en
   la misma fila que la información que la justifica, con el mensaje de
   WhatsApp ya escrito para esa unidad.
   ========================================================================== */
/* Grid de 3 desde el arranque (ronda 24, pedido textual: "me gustaría que
   las cabañas aparezcan los tres cositos, uno al lado de la otra"). Antes
   era 1 columna hasta 900px y recién ahí pasaba a 3: la versión angosta
   quedaba como tres filas foto+specs+botón, una debajo de la otra, y el
   cliente la vio así en su teléfono y pidió el mismo formato de "al lado"
   que ya corre en escritorio. El gap baja a --space-2 acá abajo porque con
   3 columnas y --space-4 (32px) de aire, a 390px quedaban ~70px netos por
   columna; @media (max-width: 899px) recorta también el padding de la
   ficha y el contenido de adentro (ver ese bloque, unas líneas más abajo
   de .unit__actions) para que quepa. */
.units { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-2); margin-top: var(--space-6); }

/* ==========================================================================
   LAS 3 CABAÑAS SON FICHAS CON SUPERFICIE PROPIA. (Ronda 8, pedido directo)
   ==========================================================================
   ⚠️ ESTO REVIERTE UNA DECISIÓN DE LA RONDA 6, a pedido explícito del
   usuario. La ronda 6 las había dejado sin NINGÚN cromo de tarjeta —sin
   caja, sin borde, sin sombra— siguiendo la fila "Romma" de Juvet, donde la
   foto ES la tarjeta. El problema real de eso, dicho por el usuario: las
   tres quedaban "pegadas en un fondo blanco", sin nada que las separara
   entre sí ni del resto de la página, y lo que tenía que leerse como tres
   opciones comparables se leía como una lista suelta de fotos y texto.

   NO SE AGREGÓ NINGÚN COLOR. La rampa neutra del sitio ya tenía tres
   escalones claros —#EEF1EC (--color-bg), #E4E9E1 (--color-bg-alt) y
   #D7DED4 (--color-border)— y la ficha usa el tercero. El primer intento
   usó el segundo y quedó corto: contra el fondo de la página son 4 puntos
   de luminosidad y la ficha se seguía leyendo despegada apenas. El tercer
   escalón son 8 puntos y ahí sí es una superficie, no un matiz.
   --color-border se llama así por dónde nació, pero el valor es
   sencillamente el tercer escalón de esa rampa; acá cumple ese rol.
   La paleta sigue siendo la misma y el chequeo contra los 3 anti-defaults
   de la cabecera de este archivo no se toca.
   Contraste verificado sobre esta superficie: --color-ink-soft da 6,0:1 y
   --color-ink 10,8:1. Los dos pasan AA con margen.
   El borde de la ficha lo da el color y la forma, no una hairline: 1px de
   borde MÁS sombra difusa sobre la misma caja es el patrón "ghost card"
   que `impeccable` prohíbe. Acá no hay ninguno de los dos.
   ========================================================================== */
.unit {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
  transition: background-color var(--dur-base) var(--ease-out);
}

/* ==========================================================================
   LA TARJETA ENTERA ES EL LINK. (Pedido directo del cliente.)
   ==========================================================================
   El problema, dicho por él: "el link de ver todas las fotos es muy chico".
   Tenía razón y era peor de lo que parecía — el destino táctil real de una
   tarjeta de 380px de alto, con foto grande, capacidad en cifra enorme,
   nombre y cinco specs, eran 150px de texto subrayado al pie. Todo lo demás
   era decorado inerte: la foto no llevaba a ningún lado.

   Se resuelve con UN SOLO link (el que ya existía) estirado sobre la caja
   con un pseudo-elemento, no envolviendo la tarjeta en un <a>. Motivo: el
   botón de WhatsApp vive adentro, y un <a> dentro de otro <a> es markup
   inválido y el navegador lo desarma solo. Así hay un único destino en el
   árbol de accesibilidad, con su texto real ("Ver todas las fotos"), y el
   botón verde queda POR ENCIMA del pseudo con su propio z-index: sigue
   siendo un destino separado, que es lo único que el cliente pidió excluir.

   El foco de teclado se dibuja sobre la TARJETA y no sobre el renglón de
   texto, porque el destino ahora es la tarjeta: sin esto, quien navega con
   Tab veía un anillo de 150px sobre un objeto de 380px.
   ========================================================================== */
.unit__more::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  border-radius: var(--radius-lg);
}
/* El botón de consultar se queda por encima del área estirada. Es la única
   excepción que el cliente pidió: "toda la tarjetita menos el coso verde". */
.unit__actions .btn {
  position: relative;
  z-index: var(--z-raised);
}
.unit:has(.unit__more:focus-visible) {
  outline: 3px solid var(--color-ink);
  outline-offset: 3px;
}
/* El anillo ya lo dibuja la tarjeta: dos anillos concéntricos sobre el mismo
   destino se leen como dos destinos. */
.unit .unit__more:focus-visible { outline: none; }
/* Radio interior menor que el exterior: una foto con el mismo radio que su
   caja, y a 16px de distancia del borde, se ve desalineada en las esquinas. */
.unit__media { position: relative; display: grid; min-width: 0; border-radius: var(--radius-md); overflow: hidden; }
/* Las fotos se apilan en la misma celda: el alto lo fija la primera y no
   salta al rotar. Sólo cambia la opacidad. */
.unit__media img {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 4 / 3;
  opacity: 0;
  /* CRUCE CON DESENFOQUE, no crossfade pelado.
     Era `opacity 900ms` a secas: durante casi un segundo se veían DOS
     fotos distintas superpuestas, y el ojo lee eso como dos objetos, no
     como una foto convirtiéndose en otra. Es la transición más vista de
     la página (las 3 tarjetas rotan solas cada 4-5 s), así que era la que
     más pagaba arreglarla.
     Concepto de reactbits/Blur Text (catálogo consultado en vivo esta
     ronda: su reveal no es opacity, es blur(10px) -> blur(5px) -> 0 junto
     con la opacidad) y de emil-design-eng, que llega a lo mismo por otro
     lado: el desenfoque tapa el artefacto del cruce. 6px, bien por debajo
     del techo de 20px que esa misma guía marca por costo en Safari.

     ⚠️ ACÁ HABÍA UN CIERRE DE COMENTARIO DE MÁS (arreglado esta ronda).
     Cerraba el comentario en esta línea, así que las ocho de abajo quedaban
     como texto suelto DENTRO del bloque de declaraciones. El parser de CSS,
     al recuperarse de eso, descarta todo hasta el punto y coma siguiente — y
     ese punto y coma era el que cierra la `transition` de acá abajo.
     Resultado medido con playwright-cli: `.unit__media img` devolvía
     transition-property `all` y duration `0s`, o sea NINGUNA TRANSICIÓN. Las
     fotos de las tarjetas no cruzaban con desenfoque: saltaban de golpe, que
     es exactamente lo que el cliente describió como que las fotos "quedan
     repodridas".
     NO ESCRIBIR LA SECUENCIA DE CIERRE DE COMENTARIO DENTRO DE UN
     COMENTARIO, ni entre comillas ni entre backticks: no hay escape que
     valga, el parser corta igual. Se cayó dos veces en lo mismo.

     EL DESENFOQUE ES TRANSITORIO Y DE UNA SOLA FOTO POR VEZ, y eso no es
     un detalle de estilo: el primer intento lo dejó como estado de reposo
     (todas las fotos apagadas con blur puesto) y eso son 13 imágenes de
     hasta 1440px con un filtro permanente encima. Medido: GSAP entró en
     lag smoothing y su timeline global avanzaba 0,33 s por cada 2,5 s de
     reloj — la entrada del hero corría a un séptimo de velocidad y el
     botón del hero se quedaba a mitad de camino, invisible. Ahora sólo la
     foto que ENTRA lleva blur, y sólo mientras dura el cruce. */
  transition: opacity var(--dur-photo) var(--ease-out),
              filter var(--dur-photo) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}
.unit__media img:first-child { opacity: 1; }
.unit__media.is-live img { opacity: 0; }
.unit__media.is-live img.is-on { opacity: 1; filter: blur(0); }
/* Estado de arranque del cruce, puesto por JS un frame antes de encender
   la foto: sin transición, para que el blur no se anime al ponerse sino
   sólo al resolverse. */
.unit__media img.is-blur { filter: blur(6px); transition: none; }

/* Las 3 tarjetas de cabaña NO TENÍAN NINGÚN HOVER (verificado con
   playwright-cli: con el mouse encima, el transform de la tarjeta y el de
   su foto seguían en `none`). Son los tres objetos que el visitante
   compara para decidir, y eran lo único inerte de la página.
   El acercamiento va en la FOTO y no en la tarjeta: mover la caja
   entera desplazaría el botón de WhatsApp justo cuando el mouse va hacia
   él. Referencia de contención: en los 9 sitios recapturados esta ronda
   ninguno mueve la tarjeta en hover — Under Canvas cambia sólo el color
   del texto (0.6s ease) y Dunton sólo el background-color (0.3s ease),
   medido, no de memoria. Acá se agrega el zoom porque la foto ES la
   tarjeta (no hay caja, ni borde, ni sombra) y sin él no queda nada que
   pueda responder. */
@media (hover: hover) and (pointer: fine) {
  /* Ahora que la ficha tiene superficie, el hover también la responde: SUBE
     un escalón de la rampa, no baja. En una página clara, acercarse al
     color del fondo es acercarse a la luz, y eso se lee como que la ficha
     se adelanta. La caja no se mueve ni se levanta a propósito: correrla
     desplazaría el botón de WhatsApp justo cuando el mouse va hacia él. */
  .unit:hover { background: var(--color-bg-alt); }
  .unit:hover .unit__media img { transform: scale(1.03); }
  /* Ahora que el destino es la tarjeta entera, el enlace tiene que contestar
     cuando el mouse está en cualquier parte de ella, no sólo encima de sus
     150px de texto. Es la misma flecha de 3px del resto del sitio. */
  .unit:hover .unit__more { color: var(--color-ink-soft); }
  .unit:hover .unit__more span { transform: translateX(3px); }
}

.unit__body { padding: var(--space-3) var(--space-1) var(--space-1); display: flex; flex-direction: column; flex: 1; }
.unit__capacity {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2.25rem, 5vw, 2.75rem);
  line-height: 0.9;
  letter-spacing: -0.03em;
  color: var(--color-ink);
}
.unit__capacity span {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-ink-soft);
  display: block;
  margin-top: var(--space-1);
}
.unit__name { margin-top: var(--space-2); font-size: var(--text-xl); color: var(--color-ink); }
@media (min-width: 900px) {
  /* Reserva de dos lineas. Los tres nombres tienen largo distinto y sin esto
     las listas de specs arrancan a alturas distintas. */
  .unit__name { min-height: 2.3em; }
}
.unit__specs { margin-top: var(--space-3); display: grid; gap: var(--space-1); }
.unit__specs li {
  position: relative;
  padding-left: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  line-height: 1.5;
}
/* Marca de lista propia en madera, no un ícono de librería ni una viñeta
   del navegador. */
.unit__specs li::before {
  content: '';
  position: absolute;
  left: 0; top: 0.62em;
  width: 8px; height: 2px;
  background: var(--color-ink);
}

/* ==========================================================================
   FICHA TÉCNICA DE LA CABAÑA — la información con peso propio
   ==========================================================================
   Reemplaza a la lista de guiones en la ficha. El contenido es el MISMO
   —no se inventó un solo dato—, lo que cambia es que cada línea ahora
   declara de qué habla: "Camas", "Climatización", "Baño".
   Por qué importa: la lista de guiones obligaba a leer las seis líneas
   enteras para encontrar si había aire acondicionado. Con la etiqueta al
   frente, la respuesta se encuentra sin leer. Es la diferencia entre una
   descripción y una ficha, y esta página es una ficha.
   El texto sube de --text-sm a --text-base y cada fila se separa con una
   hairline: la información deja de ser un bloque gris al pie de las fotos.
   ========================================================================== */
/* ==========================================================================
   FICHA TÉCNICA DE LA CABAÑA — reescrita en la ronda 15
   ==========================================================================
   Pedido del cliente: "cuando entrás a ver una de las cabañas, mejorá la
   parte donde muestra la información, tipo baño, TV, cocina, climatización.
   Cambiá la manera en que la mostrás."

   Qué había: una tabla de una sola columna, seis renglones etiqueta/valor
   separados por hairlines. Funcionaba, pero se lee EN SERIE: para saber si
   hay wifi había que barrer las seis filas de arriba abajo, porque nada
   distingue una fila de otra más que su texto. Y ocupaba 6 renglones de alto
   en una columna de 680px, con la mitad derecha vacía en las filas cortas
   ("Colchones: Sommier" dejaba 500px en blanco).

   Qué hay ahora, y los tres cambios son del mismo tipo — pasar de leer a
   reconocer:
   · DOS COLUMNAS desde 700px. Seis datos en dos columnas de tres se abarcan
     de un vistazo; en una columna de seis, no. Y se acaba el blanco de las
     filas cortas.
   · UN ÍCONO POR FILA, del mismo set de trazo que los cuatro del
     equipamiento del home. El ícono es el que permite encontrar "cocina" sin
     leer: es un ancla visual, no decoración. Van con aria-hidden porque el
     nombre está escrito al lado.
   · LA ETIQUETA ARRIBA Y EL DATO ABAJO, no en dos columnas. Con etiqueta a
     la izquierda en medida fija, los valores largos se partían en tres
     renglones contra una columna angosta. Apilado, cada celda usa su ancho
     entero.

   Sigue sin ser una grilla de tarjetas: no hay cajas, no hay fondos, no hay
   bordes alrededor. Lo único que separa es la hairline de arriba de cada
   fila, que es el mismo dispositivo que ya usaba la versión anterior.
   ========================================================================== */
/* ==========================================================================
   ⚠️ RONDA 33 — DE "ÍCONO POR RENGLÓN" A DOS PISOS: FRANJA + FICHA.
   ==========================================================================
   Pedido: volver a mirar sitios profesionales de cabañas/lodges y aplicar
   una idea DISTINTA a la actual (ícono-panel al costado de cada dato, la de
   la ronda 32). No es que esa idea estuviera mal — reprodujo bien Postcard
   Cabins — es que el pedido es explícitamente otra cosa.

   LO QUE DICE EL RESEARCH DE ESTA RONDA: los listados de cabañas/lodges que
   convierten (Airbnb, Vrbo y la mayoría de los sitios de alojamiento
   revisados) no muestran seis filas iguales de una: primero una FRANJA DE
   LECTURA RÁPIDA —los mismos rótulos, en píldoras, para escanear en un
   segundo qué tiene la unidad— y recién debajo la ficha completa con el
   detalle real de cada ítem. Es el patrón que la industria llama "quick
   facts": una fila de reconocimiento antes del texto que hay que leer.

   Achicó dos pisos, dos trabajos:
   1. `.unit-quickfacts` — las MISMAS seis etiquetas (Camas, Climatización,
      Cocina, TV, Baño, Exterior — o las que tenga cada cabaña) en píldoras
      con ícono chico. No agrega ni un dato: son los mismos `dt` de la ficha
      de abajo, promovidos a resumen. Mismo componente visual que ya se
      sumó esta ronda a los atajos de WhatsApp (`.dates__shortcuts`), así
      que la píldora con ícono en badge + texto pasa a ser un objeto propio
      del sistema, no una pieza suelta.
   2. `.unit-specs` — la ficha completa se simplifica: sin el ícono de 44px
      por fila (ese trabajo de "encontrar sin leer" ya lo hace la franja de
      arriba), cada fila es sólo etiqueta + dato, más compacta. Sigue sin
      hairlines: la separación es aire, como fijó la ronda 32, y esa parte
      no cambia — lo que cambia es que ya no hace falta el chip repetido
      dos veces (arriba en la franja, al costado en la fila) para el mismo
      dato. */
.unit-quickfacts { margin: var(--space-3) 0 0; display: flex; flex-wrap: wrap; gap: var(--space-2); list-style: none; padding: 0; }
.unit-quickfacts li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 40px;
  padding: 0 var(--space-3) 0 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--color-ink-soft);
}
.unit-quickfacts__icon {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--sink-light);
  color: var(--color-ink-soft);
  flex-shrink: 0;
}
.unit-quickfacts__icon svg {
  display: block;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.unit-specs { margin-top: var(--space-5); display: grid; row-gap: var(--space-4); }
.unit-spec { display: grid; row-gap: 4px; }
.unit-spec__value { margin: 0; }
@media (min-width: 700px) {
  .unit-specs { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-5); row-gap: var(--space-4); }
}
/* La etiqueta de cada fila (Camas, Climatización, Baño…) va en madera y el
   dato en tinta: el ojo baja por la columna de etiquetas hasta la que
   busca sin tener que leer las seis líneas enteras. 6,86:1 sobre el fondo
   claro, así que el color no le come legibilidad a nada. */
/* ==========================================================================
   ⚠️ RONDA 31 — LA JERARQUÍA ESTABA INVERTIDA, Y SE PUEDE MEDIR
   ==========================================================================
   Diagnóstico del cliente: "la información de capacidad y distribución de la
   cabaña que estás viendo se renderiza al mismo tamaño que los enlaces a las
   otras capacidades". Es literal, y medido en el archivo era peor todavía:

       CONTENIDO PRIMARIO            |  NAVEGACIÓN SECUNDARIA
       (de qué es esta página)       |  (irse a otra página)
       .unit__name .......... 28px   |  h2 "Otras capacidades" ...... 28px
       .unit-spec__label .... 15px   |  .other-cabins__card strong .. 15px
       .unit-spec__value .... 16px   |  .other-cabins__card span .... 13px
       color: --color-ink-soft       |  color: --color-ink

   O sea: los dos titulares empataban en 28px, las dos etiquetas empataban en
   15px, y encima el bloque secundario tenía MÁS contraste que el primario
   (tinta plena contra tinta apagada) y más peso visual (tarjetas con borde
   contra filas sin caja). Con todo empatado, lo que desempata es la posición
   y el encajonado — y los dos favorecían a la navegación. La página estaba
   diciendo, en tipografía, que lo importante era irse a otro lado.

   La corrección va por los tres ejes que pide el brief y NO por agregar
   hairlines (eso sería empujar al anti-default #3):
     · ESCALA — el dato primario sube de 16px a 18px (--text-md); la
       navegación secundaria baja su titular de 28px a 18px.
     · PESO   — la etiqueta primaria sube a 800 y el valor a 500; el titular
       secundario baja de 800 (h2 heredado) a 700.
     · COLOR  — el valor primario pasa de --color-ink-soft a --color-ink
       (6,87:1 -> 14,23:1) y la ficha secundaria pasa a tinta apagada. Se
       invierte exactamente el par que estaba al revés.
   Después de esto el primario le saca 10px y 4 puntos de peso al secundario,
   en vez de empatarle.
   ========================================================================== */
.unit-spec__label {
  font-weight: 800;
  font-size: var(--text-sm);
  color: var(--color-wood);
}
.unit-spec__value {
  font-size: var(--text-md);
  font-weight: 500;
  color: var(--color-ink);
  line-height: 1.5;
  text-wrap: pretty;
}
/* El botón se pega al fondo de la tarjeta: las tres columnas tienen listas
   de largo distinto y sin esto los tres botones quedaban a alturas
   distintas, que es lo que hace que una grilla se vea desprolija. */
.unit__actions { margin-top: auto; padding-top: var(--space-4); display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-1); }
.unit__more { font-weight: 700; font-size: var(--text-sm); color: var(--color-ink); text-decoration: underline; text-underline-offset: 4px; display: inline-flex; align-items: center; gap: 0.35em; min-height: 44px; transition: color var(--dur-fast) var(--ease-out); }
.unit__more:hover { color: var(--color-ink-soft); }

/* ==========================================================================
   RONDA 26 — EN MOBILE DEJA DE SER UNA GRILLA DE 3 Y PASA A SER UN RIEL
   ==========================================================================
   ⚠️ ESTO REEMPLAZA AL BLOQUE "MOBILE COMPACTO" DE LA RONDA 24, Y EL MOTIVO
   ESTÁ MEDIDO, NO OPINADO. Aquella ronda metió las 3 tarjetas en 3 columnas
   también en el teléfono (pedido textual del cliente: "que las cabañas
   aparezcan los tres cositos, uno al lado de la otra") y para que entraran
   les recortó todo: la lista de specs desapareció, el botón de WhatsApp se
   volvió un círculo sin etiqueta y el nombre quedó con `overflow-wrap:
   break-word` + `-webkit-line-clamp: 3` dentro de una caja de 82px.
   Medido a 375px con playwright-cli: `.units .unit` = 98px de ancho,
   `.unit__name` = 82px, y el line-clamp DISPARANDO de verdad sobre
   "Matrimonial y altillo con dos camas". En pantalla eso se lee
   "Matrimon-ial", "Dormitori-o, altillo y wifi", "con dos…".
   Un nombre propio partido a mitad de palabra no es un ajuste fino: es el
   sitio diciendo una palabra que no existe.

   LA REGLA DE ANCHO GANA SOBRE EL FORMATO PEDIDO, pero no lo contradice.
   Las tres siguen estando UNA AL LADO DE LA OTRA —que es lo que el cliente
   pidió ver— sólo que la fila ahora se desplaza en vez de comprimirse:
   riel horizontal con enganche, tarjetas de 78vw (292px a 375, tres veces
   lo de antes) y la tercera asomando en el borde derecho, que es lo que
   avisa que hay más. La lista de specs VUELVE completa, el botón verde
   recupera su etiqueta escrita y el nombre se compone entero.

   RESEARCH MOBILE (capturado esta ronda con iPhone 15 en WebKit, no el
   desktop angostado): Postcard Cabins convierte en el teléfono sus grillas
   de escritorio en carruseles de una tarjeta por vez —tanto "What's
   included" como la galería, ésta con la tarjeta del medio entera y las
   vecinas asomando— y Correntoso lleva cada tipo de habitación a un panel
   propio a pantalla completa. Ninguno de los dos achica la grilla de
   escritorio para que entre: le cambian el formato.

   Es además el patrón que este sitio YA TIENE: el carrusel de reseñas es
   un riel horizontal con la misma mecánica (scroll nativo, sin JS para
   moverse). No se agrega vocabulario nuevo, se reusa el propio.
   ========================================================================== */
@media (max-width: 899px) {
  /* ⚠️ TODO ACÁ VA PRECEDIDO DE `.units` A PROPÓSITO. La ficha de cada
     cabaña (`cabana-2-4-personas.html` etc.) reutiliza las MISMAS clases
     `.unit`, `.unit__body`, `.unit__capacity`, `.unit__name` para su propio
     encabezado grande (`<article class="unit unit--sheet">`, sin `.units`
     alrededor). Selector bare (`.unit__name` sin ancestro) alcanza
     cualquier `.unit__name` del DOM, esté donde esté; con `.units
     .unit__name` sólo alcanza a las 3 tarjetas del selector de capacidad
     del home, que es el único lugar con `.units` alrededor. */
  .units {
    display: flex;
    /* A SANGRE. El riel arranca y termina en el borde real de la pantalla:
       le saca al container sus 24px y se los devuelve como padding propio,
       así la primera tarjeta sigue alineada con el titular pero la tercera
       puede asomar por el borde en vez de chocar contra un margen. Sin
       esto, la tarjeta cortada parece un error de maquetación en vez de
       una invitación a deslizar. */
    margin-inline: calc(var(--space-3) * -1);
    padding-inline: var(--space-3);
    overflow-x: auto;
    /* `proximity` y no `mandatory`: con `mandatory` el navegador se queda
       con el gesto y un deslizamiento corto —el que hace alguien que sólo
       quería ver el borde de la siguiente— rebota a la tarjeta anterior.
       `proximity` engancha cuando el dedo ya decidió y no pelea cuando no. */
    scroll-snap-type: x proximity;
    scroll-padding-inline: var(--space-3);
    /* El riel no se puede llevar el scroll vertical de la página cuando el
       dedo llega a la última tarjeta. */
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .units::-webkit-scrollbar { display: none; }
  .units .unit {
    /* 78vw = 292px a 375 y 306 a 390; el tope de 320 evita que en un
       teléfono grande (414) la tarjeta crezca tanto que la siguiente deje
       de asomar. */
    flex: 0 0 78vw;
    max-width: 320px;
    scroll-snap-align: start;
  }
  /* El nombre baja de --text-xl a --text-lg: a 28px "Matrimonial y altillo
     con dos camas" ocupaba cuatro renglones en 260px útiles. A 22px entra
     en dos o tres y sigue siendo el segundo elemento más fuerte de la
     tarjeta, detrás del número de capacidad.
     Nada de `break-word`, nada de `line-clamp`, nada de alto fijo: si un
     nombre necesita un renglón más, lo toma. El alto parejo de las tres
     tarjetas lo da el `stretch` del flex, no un recorte del texto. */
  .units .unit__name { font-size: var(--text-lg); line-height: 1.2; }
  /* El botón vuelve a ser un botón con su texto escrito y ocupa el ancho
     de la tarjeta, que en un teléfono es el destino táctil que corresponde
     a la única acción que convierte. */
  .units .unit__actions { align-items: stretch; }
  .units .unit__actions .btn--whatsapp { width: 100%; }
  .units .unit__more { align-self: flex-start; }
}
/* ==========================================================================
   EL AVISO DE QUE EL RIEL SE DESLIZA
   ==========================================================================
   La tarjeta cortada por el borde derecho ya es la señal estándar de "hay
   más", pero este sitio lo lee gente que organiza un fin de semana para la
   familia, no gente que audita interfaces: una línea que lo diga con
   palabras cuesta un renglón y saca la duda. Es además lo que hace Postcard
   Cabins en el teléfono, sólo que ellos lo resuelven con un botón de flecha
   flotante; acá alcanza el texto, que no necesita JS.
   Sólo existe donde hay riel: donde las tres (o las seis fotos) están a la
   vista al mismo tiempo, una instrucción de deslizar sería falsa, así que
   el CSS la esconde. Los dos cortes son distintos a propósito porque los
   dos rieles vuelven a ser grilla en anchos distintos: el selector de
   capacidad en 900 y la galería de la ficha en 620.
   ========================================================================== */
.units__hint,
.gallery__hint {
  display: none;
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
}
.units__hint::before,
.gallery__hint::before {
  content: '';
  display: inline-block;
  width: 24px;
  height: 2px;
  margin-right: 10px;
  vertical-align: middle;
  background: var(--color-wood);
}
@media (max-width: 899px) { .units__hint { display: block; } }
@media (max-width: 619px) { .gallery__hint { display: block; } }

@media (min-width: 900px) {
  /* ========================================================================
     LAS 3 CABAÑAS COMO TARJETAS, UNA AL LADO DE LA OTRA. (A confirmar con
     el cliente: es el formato que pidió ver antes de decidirlo.)

     QUÉ REEMPLAZA: tres filas foto-izquierda / texto-derecha, una debajo de
     la otra. Eso eran TRES splits foto+texto consecutivos, que es
     exactamente el patrón que la checklist de anti-slop marca como falla
     (máximo dos seguidos). Además obligaba a scrollear tres pantallas para
     comparar capacidades, que es lo único que se compara acá.

     RESEARCH: es la fila "Romma" de Juvet (D), mirada esta ronda: tres
     placas de foto al hilo, misma relación, con el nombre debajo y CERO
     cromo de tarjeta. Sin caja blanca, sin borde, sin sombra. La foto es la
     tarjeta. Lo único que se le suma a Juvet es el botón de WhatsApp por
     unidad, que es la conversión que ganó la ronda 5 y no se resigna.
     ======================================================================== */
  .units { grid-template-columns: repeat(3, 1fr); gap: var(--space-4); margin-top: var(--space-6); }
  /* Relación fija en las tres. Las fotos de cada cabaña mezclan verticales y
     apaisadas; si se deja libre, la vertical más alta define el alto de toda
     la fila. La constante es la caja, el recorte es intencional. */
  .unit__media img { aspect-ratio: 4 / 3; height: 100%; }
}

/* El pie del carrusel: la atribución a la izquierda y el pedido de reseña a
   la derecha. `space-between` y no un margen fijo, así en pantallas angostas
   los dos caen apilados y alineados a la izquierda sin una regla aparte. */
.reviews__foot {
  margin-top: var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
}
.reviews__foot .reviews__source { margin-top: 0; max-width: 54ch; }
.reviews__source { margin-top: var(--space-2); font-size: var(--text-sm); color: var(--color-ink-soft); }
.reviews__source a { color: var(--color-ink); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }

/* `clip` en vez de `hidden`: hidden en el ancestro rompería el
   position:sticky del header. */
html { overflow-x: clip; }

/* ⚠️ RONDA 35 — .hero-followup SE FUE. Vivía acá desde la ronda 34 como la
   línea suelta entre el hero y #equipamiento; en esa ronda 35 la sección
   #confianza entera volvió a ese lugar y la reemplazó (ver el comentario
   en index.html, justo después de cerrarse el hero). */

/* ==========================================================================
   EQUIPAMIENTO ("En estas cabañas") — sin tarjeta, sin ícono genérico.
   ==========================================================================
   El patrón anterior era una lista tipográfica suelta sobre fondo plano.
   El nuevo es el split de Eastwind (A) espejado respecto del hero: foto a
   sangre de un interior real en una mitad, lista en la otra sobre color.
   MOTIVO DE LA FOTO DOMINANTE: la lista de equipamiento es abstracta
   ("cocina equipada" no significa nada); una foto de interior real es la
   prueba de que el estándar existe. Va la de la cabaña de 5 porque es la
   única que muestra cama, cocina y mesa en un solo cuadro.

   Ubicación: el equipamiento va ANTES del listado de cabañas, no después.
   No es una suposición — es el orden real de Postcard Cabins (B), cuyos
   H2 van "What's included at Postcard Cabins" y recién después "Popular
   Postcard Cabins". Contestar una vez el "¿es decente?" deja las fichas
   de cabaña libres para hablar sólo de capacidad, que es como se elige acá.

   También hace de puente visual: ver wireBridgeTransition() en main.js. */
/* ==========================================================================
   ESTA SECCIÓN PASA A LA SUPERFICIE OSCURA. (Pedido directo del cliente.)
   ==========================================================================
   Lo que dijo, señalando esta sección por su título: "esto lo tenés en
   cualquiera también queda muy en el aire, está como todo un fondo blanco
   podrido".

   Tenía razón y el diagnóstico es medible: entre "El lugar" y "Cómo llegar"
   había CINCO secciones seguidas de superficie clara, y los dos únicos tonos
   disponibles ahí —#EEF1EC y #E4E9E1— se llevan 3 puntos de luminosidad. A
   escala de página eso no es ritmo, es una sola mancha pálida de 2.400px de
   alto. La sección del equipamiento caía justo en el medio, sin fondo propio
   (lo pintaban las capas del puente) y sin nada que la sostuviera.

   Ahora es la tercera superficie oscura del sitio, y eso ordena la página
   entera: oscuro (hero + confianza) -> claro (el lugar) -> OSCURO
   (equipamiento) -> claro (cabañas, disponibilidad, cómo llegar, redes) ->
   oscuro (CTA final). Cinco tiempos en vez de dos.

   ⚠️ CON ESTO SE DA DE BAJA EL CRUCE DE FONDO DEL PUENTE (las dos capas
   .bridge__layer en opacity). No es un descuido: el cruce iba de #E4E9E1 a
   #EEF1EC, o sea que animaba 3 puntos de luminosidad durante una pantalla
   entera de scroll —invisible— y encima ahora sería imposible: un fondo que
   pasa de oscuro a claro deja el texto blanco sobre claro a mitad de camino.
   LO QUE SÍ SE CONSERVA del puente es la parte que hacía el trabajo real:
   las 3 tarjetas de cabaña siguen entrando UNA POR UNA atadas al scroll
   (scrub), que sigue siendo el único momento scroll-linked del sitio. El
   borde entre esta sección oscura y "Las cabañas" es ahora una transición de
   verdad, no un degradé que nadie veía.
   ========================================================================== */
.bridge {
  position: relative;
  isolation: isolate;
  /* Ronda 29. El equipamiento es ahora una banda CORTA (ver .kit más abajo),
     así que su realce va bajo y ancho: en 360px de alto, un radial anclado
     al 6% quedaría fuera de cuadro y la banda volvería a leerse plana. */
  background-color: var(--color-dark);
  background-image:
    radial-gradient(58% 116% at 82% 18%, var(--lift-dark) 0%, transparent 68%),
    radial-gradient(70% 120% at 2% 100%, var(--sink-dark) 0%, transparent 72%);
  color: var(--color-on-dark);
}
/* ==========================================================================
   EQUIPAMIENTO — ronda 29: de bloque partido a BANDA CORTA.
   ==========================================================================
   EL PROBLEMA, MEDIDO EN PRODUCCIÓN ANTES DE TOCAR:
     1440 -> 606px de alto · 375 -> 1.147px
   Para decir cuatro cosas: baño, clima, cocina, TV. A 375 el sitio gastaba
   1.147px en cuatro datos contra 1.440px en las tres fichas completas de
   cabaña (foto + specs + botón propio cada una). No es que la sección fuera
   más alta que las cabañas —no lo era, y el pedido decía eso—: es que la
   relación entre lo que ocupa y lo que informa estaba invertida.

   LA REFERENCIA ES POSTCARD CABINS (B), MEDIDA ESTA RONDA, NO RECORDADA:
   su "What's included" resuelve exactamente el mismo trabajo —cuatro cosas
   que vienen con cualquier unidad— en 368px de alto sobre 1.140 de ancho,
   o sea el 4,7% de una página de 7.845px. Nuestro 606 sobre 8.342 era 7,3%,
   y el 1.147 sobre 10.563 de mobile era 10,9%.

   QUÉ SE FUE, Y POR QUÉ SE PUEDE IR: la foto de 32fr. Era lo que fijaba el
   alto de toda la sección (606px de sección con una foto de 606px al lado).
   Su trabajo declarado en la ronda 26 era PROBAR que las cuatro cosas
   existen; ese trabajo lo hacen mejor, y ya lo hacen, las galerías de las
   tres fichas, donde la misma cabaña se ve en 6 fotos en vez de en una.
   Pagar 606px de home por una prueba que ya está a un clic era el negocio
   malo. Se eliminó `.kit__media` del HTML: no queda markup muerto.

   EL RITMO TAMBIÉN ES RESEARCH. Eastwind intercala bandas de 121/186/246px
   entre bloques de 450-550; Awasi, bandas de 73/183/229/276 entre bloques
   de 750-1.270. Jägerhof, el contraejemplo, tiene todo en ~900 y es el que
   se lee a plantilla. Esta sección pasa a ser una de esas bandas cortas.
   ========================================================================== */
.kit {
  /* Deja de ser una grilla de 2 columnas (foto | texto) y pasa a ser el
     ancho del container, igual que el resto de las secciones. La banda
     entera vive dentro de --container, así que su borde izquierdo cae
     sobre la misma línea que el titular de las cabañas de abajo. */
  width: min(100% - var(--space-4), var(--container));
  margin-inline: auto;
  /* --space-6 en el teléfono y --space-7 desde 760. Es una banda corta: con
     64px arriba y abajo, el aire pesaba 128 de los 767 que medía la sección
     a 375, o sea más que dos de sus cuatro ítems. En escritorio la banda es
     ancha y baja, y ahí los 64 sí hacen falta para que no se lea apretada
     contra las secciones vecinas. */
  padding-block: var(--space-6);
}
@media (min-width: 760px) {
  .kit { padding-block: var(--space-7); }
}
.bridge .section-title { color: #fff; }
/* Encabezado en DOS COLUMNAS desde 760: el título a la izquierda y la
   bajada a la derecha, en la misma línea horizontal. Es lo que ahorra la
   primera tanda de píxeles — apilados son 76+44+32 = 152px sólo para
   presentar la banda; al lado, 76. */
.kit__head { display: grid; gap: var(--space-2); }
.kit__intro { color: var(--color-on-dark-soft); max-width: 42ch; }
@media (min-width: 760px) {
  .kit__head {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
    gap: var(--space-6);
    align-items: end;
  }
  .kit__intro { margin-bottom: 4px; }
}

/* ==========================================================================
   LAS CABAÑAS — comparar de un vistazo.
   Las 3 tarjetas SÍ son una grilla pareja, y es la única excepción
   deliberada del sitio: el brief la declara intencional y el trabajo de la
   sección es comparar capacidad, no jerarquizar. Ninguna cabaña es "la
   recomendada" porque el negocio no la tiene.
   Los chips llevan miniatura real: el eyebrow de las tarjetas de Postcard
   Cabins (B) lleva un dato (la ciudad más cercana), no un rótulo decorativo.
   ========================================================================== */
.section-note { margin-top: var(--space-4); font-size: var(--text-sm); color: var(--color-ink-soft); }
.section-note a { color: var(--color-ink); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }


/* ==========================================================================
   Disponibilidad — el fallback ES el contenido real mientras no exista la
   planilla. No es un calendario vacío disfrazado.
   ========================================================================== */
.availability { margin-top: var(--space-4); }
/* Padding chico en mobile: con var(--space-5) a cada lado, el contenedor
   dejaba 247px útiles y el botón no entraba, lo que empujaba el documento
   2px a la derecha. */
.availability-fallback {
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  text-align: center;
}
@media (min-width: 560px) { .availability-fallback { padding: var(--space-5); } }
.availability-fallback p { color: var(--color-ink-soft); max-width: 46ch; margin-inline: auto; }
/* Medido: sin esto el <a> heredaba el color del párrafo, sin subrayado y
   en peso 400 — o sea, indistinguible del texto. Y no es un link
   cualquiera: desde que el calendario vive en las fichas, ESTE es el
   camino que la sección existe para ofrecer. Mismo tratamiento que
   `.section-note a`, que es el link en prosa del resto del sitio. */
.availability-fallback a {
  color: var(--color-ink);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.availability-fallback .btn { margin-top: var(--space-3); }

/* ==========================================================================
   ¿HAY LUGAR EN TUS FECHAS? — selector de fechas del home (ronda 13)
   ==========================================================================
   Dos columnas: a la izquierda por qué escribir y por dónde, a la derecha
   el control. En mobile se apila y el control va SEGUNDO, porque quien
   entra desde el teléfono necesita primero entender qué le van a pedir.

   El control va sobre superficie OSCURA dentro de una sección clara. No es
   decoración: lo separa del resto de la página y lo hace leer como un
   aparato, no como un bloque de contenido. Reusa las mismas custom
   properties que el calendario de la ficha (.availability--dark), así que
   cualquier corrección al componente les llega a los dos.
   ========================================================================== */
.dates { display: grid; gap: var(--space-5); }
.dates__note {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  max-width: 52ch;
}
.dates__note a { color: var(--color-ink); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
.dates__cta { margin-top: var(--space-4); align-items: center; }

/* ==========================================================================
   ⚠️ RONDA 33 — DE TRES LINKS SUBRAYADOS EN FILA A TRES CHIPS.
   ==========================================================================
   Pedido: "mejorá el formato, hoy es texto plano en fila". Siguen siendo
   links —no botones: tres botones más al lado del verde convierten la
   sección en una botonera y ninguno se lee— pero ahora cada uno es su
   propio objeto: borde, radio, ícono propio y más aire, en vez de tres
   palabras subrayadas compitiendo por leerse en la misma línea. */
.dates__shortcuts {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-wood-line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
}
/* ⚠️ EL ÍCONO VA EN TINTA, NUNCA EN MADERA. La regla declarada en la
   cabecera de este archivo dice, textual, "LA MADERA NO TOCA NINGÚN
   ELEMENTO INTERACTIVO. Ni un botón, ni un link". Estos tres son links, así
   que el color vivo del chip —borde, ícono, texto— sale de la rampa de
   tinta (--color-ink-soft, 7,26:1), la misma familia que ya usan los otros
   controles secundarios del sitio. La madera se queda en la hairline de
   arriba, que no se clickea. */
.dates__shortcuts a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 48px;
  padding: 0 var(--space-3) 0 var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
  background: var(--color-bg);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.dates__shortcuts a:hover { color: var(--color-ink); border-color: var(--color-ink-soft); background: var(--color-bg-alt); }
.dates__shortcut-icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--sink-light);
  color: var(--color-ink-soft);
  flex-shrink: 0;
}
.dates__shortcut-icon svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.dates__shortcut-cue { transition: transform var(--dur-fast) var(--ease-out); }
.dates__shortcuts a:hover .dates__shortcut-cue { transform: translateX(3px); }

.dates__picker {
  margin-top: 0;
  background: var(--color-dark);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}
/* Sin JS el contenedor queda vacío. Un bloque oscuro de 300px de alto sin
   nada adentro se lee como algo roto, así que sin contenido no ocupa. */
.dates__picker:empty { display: none; }

/* ==========================================================================
   RONDA 26 — EN MOBILE EL CALENDARIO VA ANTES QUE EL BOTÓN, NO DESPUÉS
   ==========================================================================
   Éste es el caso más claro de todo el sitio de "una composición de
   escritorio aplanada en una columna". Desde 900px la sección son DOS
   columnas: la explicación a la izquierda con su botón, y el calendario a
   la derecha. Lado a lado el orden no existe — se ven las dos cosas de una.
   En el teléfono la misma grilla se apila en el orden del DOM y queda:
     "Marcá cuándo pensás venir y te llega el mensaje ya escrito"
     → [Consultar por WhatsApp]  ← el botón, 337px ANTES del calendario
     → los tres atajos
     → recién ahí el calendario donde había que marcar
   Medido: el botón arranca en y=6883 y el calendario en y=7220.
   O sea que el visitante lee la instrucción, encuentra el botón, lo aprieta
   —está haciendo lo que el texto le pidió— y manda el mensaje SIN NINGUNA
   FECHA ADENTRO, que es exactamente lo único que esta sección existe para
   evitar. La promesa del sitio ("el dato que elegís viaja adentro del
   mensaje") se incumple por orden de apilado.
   Acá el orden se pone a mano y sigue el orden de la tarea: leo qué hago,
   lo hago, y recién entonces tengo el botón que se lleva lo que hice.
   `display: contents` saca al envoltorio del layout sin tocar el HTML —que
   en escritorio tiene que seguir siendo la columna izquierda— y deja
   ordenar los cinco bloques como hermanos. `.dates__intro` no tiene fondo,
   borde ni relleno propios, así que disolver su caja no se lleva nada.
   ========================================================================== */
@media (max-width: 899px) {
  .dates { display: flex; flex-direction: column; gap: 0; }
  .dates__intro { display: contents; }
  .dates .section-title { order: 1; }
  .dates .section-lede { order: 2; }
  .dates__note { order: 3; }
  .dates__picker { order: 4; margin-top: var(--space-4); }
  .dates__cta { order: 5; }
  .dates__shortcuts { order: 6; }
}

@media (min-width: 900px) {
  /* ⚠️ REBALANCEO MEDIDO (ronda 14). Con 1fr / 320-400px y gap de 64, la
     columna de texto terminaba en y=487 y la del calendario en y=606: 119px
     de superficie muerta abajo a la izquierda, que es lo que el cliente vio
     como "está mal ubicado".
     Se arregla por tres lados: el texto cede ancho (0,88fr) así ocupa más
     alto, el calendario se lo queda —de paso las celdas de día crecen de 44
     a 52px, mejor blanco de toque y mejor legibilidad—, y el gap baja de 64
     a 48 porque con las columnas parejas 64 las despegaba.
     Y align-items pasa a `center`. El calendario va a ser SIEMPRE más alto
     que su explicación —tiene cuatro a seis filas de días— así que la
     diferencia no se puede eliminar, sólo repartir: con `start` los 216px
     sobrantes se juntaban todos abajo a la izquierda y se leían como un
     agujero; centrado quedan 108 arriba y 108 abajo y se leen como aire. */
  .dates { grid-template-columns: minmax(0, 0.88fr) minmax(360px, 1fr); gap: var(--space-6); align-items: center; }
}
@media (prefers-reduced-motion: reduce) {
  .dates__shortcuts span { transition: none; }
  .dates__shortcuts a:hover span { transform: none; }
}

/* Botón terciario: la misma caja que .btn pero en contorno de tinta. Existe
   para las acciones que NO son la conversión (llamar, por ejemplo). El
   verde queda reservado a WhatsApp; un segundo botón verde al lado no
   agrega una opción, le saca fuerza al primero. */
.btn--quiet {
  background: transparent;
  border: 1px solid var(--color-ink);
  color: var(--color-ink);
}
.btn--quiet:hover { background: var(--color-ink); color: var(--color-bg); }
/* La misma variante, invertida, para cuando el botón cae sobre superficie
   oscura (la tarjeta de la ficha). Con la versión clara el contorno de tinta
   sobre --color-dark medía 1,3:1 y el botón desaparecía. */
.btn--on-dark { border-color: rgba(236, 241, 243, 0.5); color: var(--color-on-dark); }
.btn--on-dark:hover { background: var(--color-on-dark); color: var(--color-dark); border-color: var(--color-on-dark); }

/* ==========================================================================
   EL CALENDARIO EN SÍ — dos superficies, un solo componente
   ==========================================================================
   El mismo componente aterriza en DOS fondos distintos y eso no es un
   accidente que haya que emparchar: en el home vive sobre la página clara
   y en la ficha vive DENTRO de `.cabin-card`, que es superficie
   oscura. Por eso las reglas de estructura se escriben UNA vez y lo único
   que cambia entre superficies son seis custom properties, redefinidas en
   `.availability--dark`. Sin esto habría dos copias del componente
   divergiendo en la primera corrección que se le haga a una sola.

   ---------------------------------------------------------------------
   POR QUÉ ESTOS TRES COLORES NO ROMPEN LA REGLA DE "UN SOLO SATURADO"
   ---------------------------------------------------------------------
   El sistema declara arriba que el único color saturado de la página es
   el verde de WhatsApp, porque es la afordancia del canal por el que se
   convierte. Verde/ámbar/rojo acá NO son un secundario cromático que
   vuelve por la ventana: son la codificación de un dato de tres estados
   que el cliente ya mantiene con esos mismos colores en su planilla, y
   viven exclusivamente adentro de este componente.
   Lo que los mantiene subordinados al CTA es que están DESATURADOS a
   propósito: el verde "Libre" es #2E6B45 (HSL 143°, 40%, 30%) contra el
   #25D366 de WhatsApp (HSL 142°, 70%, 49%). Mismo hue, la mitad de la
   saturación y 19 puntos menos de luz. Puestos uno al lado del otro —que
   es literalmente lo que pasa, el botón queda pegado al calendario— el
   botón sigue siendo lo más fuerte de la pantalla. Si el "Libre" usara
   el verde de WhatsApp, el calendario le comería la acción al CTA.

   ---------------------------------------------------------------------
   EL COLOR NUNCA ES EL ÚNICO CANAL (WCAG 1.4.1)
   ---------------------------------------------------------------------
   Tres refuerzos independientes, no uno:
     1. La leyenda nombra los tres estados con palabras.
     2. Cada día del calendario lleva MARCA DE FORMA además de color:
        "Ocupado" tachado, "Consultar" con subrayado de puntos, "Libre"
        limpio. Se distinguen en escala de grises y con cualquier tipo de
        daltonismo.
     3. El número del día se pinta con la tinta de máximo contraste de la
        superficie, no con el color del estado. El estado tiñe el FONDO.
        Así el texto nunca queda en el borde del ratio: medido, 11,0:1 en
        el peor caso claro y 7,2:1 en el peor caso oscuro, contra el 4,5:1
        que pide la norma.
   La tira de 30 días del home va aria-hidden a propósito: es un resumen
   visual redundante, y el dato completo está en las dos frases de al
   lado. 30 celdas anunciadas una por una serían 90 anuncios por sección.
   ========================================================================== */
.availability {
  margin-top: var(--space-4);

  /* Superficie clara (home). Fondo tintado por estado + tinta común. */
  --av-surface: var(--color-bg);
  --av-border: var(--color-border);
  --av-ink: var(--color-ink);
  --av-ink-soft: var(--color-ink-soft);
  --av-empty: rgba(23, 36, 43, 0.06);
  --av-hover: rgba(23, 36, 43, 0.12);
  /* La selección invierte: tinta llena con el fondo de página encima.
     Medido 12,94:1, el contraste más alto de todo el componente — es el
     estado que tiene que ganarle a los tres colores de estado. */
  --av-sel: var(--color-ink);
  --av-sel-ink: var(--color-bg);

  --av-libre-bg: #d8e7dc;
  --av-libre-mark: #2e6b45;
  --av-consultar-bg: #f0e4cb;
  --av-consultar-mark: #a8760f;
  --av-ocupado-bg: #ecdcd9;
  --av-ocupado-mark: #9a4a40;
}

/* Superficie oscura (ficha, dentro de `.cabin-card`). Los fondos de
   estado bajan a la rampa oscura y la tinta pasa a --color-on-dark.
   Contrastes medidos del número sobre su celda: Libre 7,2:1 · Consultar
   7,5:1 · Ocupado 9,0:1 · sin dato 6,7:1. */
.availability--dark {
  --av-surface: var(--color-dark-lift);
  --av-border: rgba(236, 241, 243, 0.16);
  --av-ink: var(--color-on-dark);
  --av-ink-soft: var(--color-on-dark-soft);
  --av-empty: rgba(236, 241, 243, 0.06);
  --av-hover: rgba(236, 241, 243, 0.16);
  /* Misma inversión que en claro, al revés: sobre superficie oscura la
     selección es la luz. Medido 12,92:1. */
  --av-sel: var(--color-on-dark);
  --av-sel-ink: var(--color-dark);

  --av-libre-bg: #24513a;
  --av-libre-mark: #57a375;
  --av-consultar-bg: #5c4211;
  --av-consultar-mark: #d2a84e;
  --av-ocupado-bg: #5a2c26;
  --av-ocupado-mark: #c98a80;
}

/* ---------- Home: una fila por cabaña ---------- */
/* ⚠️ ESTO ARREGLA UN CONTRASTE ROTO REAL, medido: 1,70:1.
   Histórico: el calendario vivía dentro de `.cabin-cta-block`, que declaraba
   `.cabin-cta-block p { color: var(--color-on-dark-soft) }`.
   Esa regla es (0,1,1) y le gana por especificidad a todos los párrafos
   del componente, que son (0,1,0) — y encima está escrita después.
   Mientras todo era texto claro sobre fondo oscuro el atropello no se
   notaba (daba 7,57:1 por casualidad, no por diseño). Pero la caja de la
   selección invierte los colores: fondo claro. Ahí el párrafo heredaba
   texto claro sobre fondo claro y el rango elegido quedaba ilegible.
   Se resuelve con especificidad (0,2,0) anclada al contenedor del
   componente, no con !important: el color lo tiene que decidir el
   componente, no el bloque que lo hospeda. */
.availability .avail-cal__sum { color: var(--av-ink); }
.availability .avail-cal__next,
.availability .avail-cal__hint,
.availability .avail-cal__aviso { color: var(--av-ink-soft); }
.availability .avail-sel__range,
.availability .avail-sel__warn { color: inherit; }

/* ---------- Ficha: el calendario elegible ---------- */
.avail-cal__sum { font-size: var(--text-md); font-weight: 600; color: var(--av-ink); }
.avail-cal__next { margin-top: 4px; font-size: var(--text-sm); color: var(--av-ink-soft); }

/* La instrucción va ANTES de la grilla y no después: un calendario que se
   puede tocar no se distingue de uno que sólo se mira hasta que alguien
   lo toca, y nadie toca lo que parece decorativo. */
.avail-cal__hint {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--av-ink-soft);
  text-wrap: pretty;
}

/* La salvedad sobre el dato ("es orientativo y se actualiza a mano"). Vive
   abajo de la leyenda, con el calendario del que habla, y no en la fila de
   botones, donde se metía entre la lectura y la acción. */
.avail-cal__aviso {
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  color: var(--av-ink-soft);
  max-width: 62ch;
  text-align: left;
  text-wrap: pretty;
}

/* Un solo mes visible. Los otros están en el DOM con [hidden], así que la
   selección puede cruzar de mes sin rearmar nada. */
.avail-cal { margin-top: var(--space-2); text-align: left; }

/* Paso de mes. Los dos botones son del mismo tamaño y quedan a los
   costados de la etiqueta: la etiqueta no se mueve al cambiar de mes
   porque los botones nunca cambian de ancho ni desaparecen. */
.avail-cal-nav {
  margin-top: var(--space-3);
  display: grid;
  grid-template-columns: 44px 1fr 44px;
  align-items: center;
  gap: var(--space-1);
}
.avail-cal-nav__label {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-md);
  letter-spacing: -0.01em;
  color: var(--av-ink);
  text-transform: capitalize;
  text-align: center;
}
.avail-cal-nav__btn {
  -webkit-appearance: none;
  appearance: none;
  border: 1px solid var(--av-border);
  background: none;
  color: var(--av-ink);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .avail-cal-nav__btn:hover:not(:disabled) { background: var(--av-hover); }
}
.avail-cal-nav__btn:focus-visible { outline: 3px solid var(--av-ink); outline-offset: 2px; }
/* Deshabilitado, no escondido: si desapareciera en las puntas, la
   etiqueta del mes se correría sola de lugar. */
.avail-cal-nav__btn:disabled { opacity: 0.35; cursor: default; }
@media (prefers-reduced-motion: reduce) { .avail-cal-nav__btn { transition: none; } }

.avail-cal__weekdays,
.avail-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }

.avail-cal__weekdays {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--av-ink-soft);
  text-align: center;
}
.avail-cal__grid { margin-top: 4px; }

/* Cada día es un <button> de verdad, no un div con listener: llega el
   foco, entra Enter y Espacio, y los lectores de pantalla lo anuncian
   como control. Lo único que hace este bloque de reset es sacarle la
   apariencia de botón de sistema, no su comportamiento. */
.avail-day {
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--av-ink);
  background: var(--av-empty);
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.avail-day--libre { background: var(--av-libre-bg); }
/* Las marcas de forma. Son el canal que sobrevive al daltonismo y a una
   captura en escala de grises. Sobreviven también a la selección: un día
   ocupado elegido sigue tachado sobre el fondo claro. */
.avail-day--consultar {
  background: var(--av-consultar-bg);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
.avail-day--ocupado { background: var(--av-ocupado-bg); text-decoration: line-through; }
.avail-day--vacio { color: var(--av-ink-soft); }

/* Foco visible y grueso. Es la única señal que tiene quien navega con
   teclado para saber dónde está parado dentro de una grilla de 60. */
.avail-day:focus-visible {
  outline: 3px solid var(--av-ink);
  outline-offset: 2px;
  z-index: var(--z-raised);
}
@media (hover: hover) and (pointer: fine) {
  .avail-day:hover { background: var(--av-hover); color: var(--av-ink); }
}

/* ---------- Selección ----------
   La selección PISA el color de estado a propósito. Mientras elegís, lo
   que importa es "estos son mis días", y el estado de cada uno lo dice el
   aviso de abajo con palabras —que además es lo que se anuncia por
   aria-live—. El tachado del ocupado sobrevive igual, así que un día
   tomado adentro del rango se sigue distinguiendo sin depender del color.
   Las puntas se redondean sólo del lado de afuera: eso es lo que hace que
   seis celdas sueltas se lean como UN período y no como seis marcas. */
.avail-day.is-sel {
  background: var(--av-sel);
  color: var(--av-sel-ink);
  border-radius: 0;
}
.avail-day.is-sel-ini { border-start-start-radius: var(--radius-sm); border-end-start-radius: var(--radius-sm); }
.avail-day.is-sel-fin { border-start-end-radius: var(--radius-sm); border-end-end-radius: var(--radius-sm); }
.avail-day.is-sel-ini.is-sel-fin { border-radius: var(--radius-sm); }
@media (hover: hover) and (pointer: fine) {
  .avail-day.is-sel:hover { background: var(--av-sel); color: var(--av-sel-ink); }
}

/* ---------- Leyenda ---------- */
.avail-legend {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
  color: var(--av-ink-soft);
}
.avail-legend__item { display: flex; align-items: center; gap: 8px; }
.avail-legend__dot { width: 12px; height: 12px; border-radius: var(--radius-pill); background: var(--av-empty); }
.avail-legend__dot.avail-day--libre { background: var(--av-libre-mark); }
.avail-legend__dot.avail-day--consultar { background: var(--av-consultar-mark); }
.avail-legend__dot.avail-day--ocupado { background: var(--av-ocupado-mark); }

/* ---------- Lectura de la selección ----------
   Le devuelve al visitante, en palabras, exactamente lo que va a decir el
   mensaje de WhatsApp. Nadie manda un mensaje que no vio: esto es la
   confirmación antes de salir del sitio. */
.avail-sel {
  margin-top: var(--space-3);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--av-sel);
  color: var(--av-sel-ink);
  display: grid;
  gap: var(--space-1);
  justify-items: start;
  text-align: left;
}
.avail-sel__range { font-size: var(--text-base); font-weight: 700; }
/* El aviso NO es rojo ni lleva ícono de alerta: no pasó nada malo. Que
   haya días tomados adentro del rango es información, y el copy invita a
   escribir igual. Pintarlo de error contradiría el mensaje. */
.avail-sel__warn { font-size: var(--text-sm); opacity: 0.82; text-wrap: pretty; }
.avail-sel__clear {
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
  min-height: 44px;
}
.avail-sel__clear:focus-visible { outline: 3px solid currentColor; outline-offset: 2px; border-radius: var(--radius-sm); }

/* Región viva: sólo para lectores de pantalla. No se oculta con
   display:none ni con hidden porque eso la sacaría del árbol de
   accesibilidad y dejaría de anunciar, que es su único trabajo. */
.avail-sel__live {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- El CTA que viaja al lado del dato ---------- */
/* El botón no se reemplaza ni se duplica: js/main.js MUEVE el mismo nodo
   <a> del fallback hasta acá, con su href real de wa.me intacto. */
/* ⚠️ UNA SOLA COLUMNA, SIEMPRE, Y ES POR UN BUG MEDIDO.
   Antes esto tenía `@media (min-width: 720px) { grid-template-columns:
   1fr auto }` para poner la nota y el botón lado a lado, y funcionaba
   mientras el calendario ocupaba el ancho completo de la página.
   Desde que vive en una tarjeta de ~380px, deja de funcionar: una media
   query mide el VIEWPORT, no el contenedor. A 1440px de pantalla la regla
   se activaba igual dentro de la tarjeta angosta, la nota quedaba en una
   columna de ~90px partiendo una palabra por renglón, y el botón —que es
   width:100% dentro de la tarjeta— se dibujaba encima del texto.
   Apilado no hay nada que calcular mal: la nota arriba, el botón abajo,
   a cualquier ancho. (Si algún día esto vuelve a un contenedor ancho, lo
   correcto es una container query, no una media query.) */
.avail__cta {
  margin-top: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--av-border);
  display: grid;
  gap: var(--space-2);
}
/* ⚠️ CON FECHAS ELEGIDAS, LA LECTURA Y EL BOTÓN SON UN SOLO BLOQUE (ronda 23).
   El usuario lo reportó así: "el consultar esta cabaña queda aparte". Y
   quedaba: entre la caja que dice "Elegiste 5 días: del 21 al 25 de
   septiembre" y el botón verde había 24px, una línea divisoria, 24px y un
   párrafo de advertencia. Cuatro cosas separando un dato de la acción que
   ese dato habilita.
   La advertencia se mudó arriba (ver `.avail-cal__aviso`) y acá, mientras
   hay selección, se van la línea y el aire: el botón cuelga de la caja a
   8px, que es el escalón chico de la escala. Sin selección la línea vuelve,
   porque ahí sí separa dos cosas distintas (el calendario y las acciones). */
/* ⚠️ SIN CALIFICAR CON `.availability`, y es a propósito: el contenedor que
   recibe la clase NO es el mismo en las dos superficies. En el home
   pintarSelectorFechas() recibe el propio `[data-datepicker]`, que sí es
   `.availability`; en la ficha recibe el `.avail` que wireAvailability()
   crea ADENTRO de `.availability`. Escrito como `.availability.has-seleccion`
   sólo funcionaba en el home — medido: `borderTopWidth` seguía en 1px en la
   ficha. `.avail__cta` no existe en ningún otro componente, así que el
   selector corto no es laxo, es el correcto. */
.has-seleccion .avail__cta {
  margin-top: var(--space-1);
  padding-top: 0;
  border-top: 0;
}

/* ---------- Movimiento ----------
   Acá el movimiento es de RESPUESTA, no de entrada: el único que hay es el
   cambio de color al pasar el mouse y al elegir un día. No hay reveal en
   la grilla, y es a propósito — 60 celdas apareciendo escalonadas retrasan
   el momento en que se puede tocar la primera, y esto es un control, no
   una ilustración.
   Con prefers-reduced-motion el cambio es instantáneo. No se pierde nada:
   el color final es el mismo, sólo llega sin interpolar. */
@media (prefers-reduced-motion: reduce) {
  .avail-day { transition: none; }
}

/* ---------- Tamaño de toque en mobile ----------
   ⚠️ MEDIDO. La tarjeta usa var(--space-3) de padding en mobile en vez de
   var(--space-5): con 40px a cada lado quedaban 247px útiles y las celdas
   daban 31,9px, que para algo que se toca con el dedo es chico. Con 24px
   quedan 279px y suben a ~36px.
   No llegan a los 44px de la guía y no pueden: 7 columnas × 44px son 308px
   más los huecos, más de lo que hay en un teléfono de 375. Es el mismo
   techo que tiene cualquier calendario en mobile —el de iOS ronda los
   40px—, así que se busca el máximo posible y se compensa con la
   separación entre celdas, no se finge que entra. */
@media (max-width: 559px) {
  .avail-cal__grid { gap: 5px; }
}

/* ==========================================================================
   FICHA DE CABAÑA — LA GALERÍA. Reemplaza al pase de fotos automático.
   ==========================================================================
   ⚠️ ESTO DA DE BAJA EL COMPONENTE ROTATIVO EN LAS 3 FICHAS, a pedido
   directo del cliente. Sus palabras: "cuando te metés a ver las fotos de una
   cabaña en específico queda repodrido: se van pasando solas y uno las
   quiere seguir pasando; se pasan al tiempo que vos la vas mirando, no se
   hunde clic".

   Tenía razón y hay una diferencia de propósito detrás. En el home, la foto
   que rota es un ADELANTO: la tarjeta muestra que hay más y nadie fue ahí a
   mirar una foto puntual. En la ficha de cabaña el visitante hizo clic en
   "ver todas las fotos": vino EXPRESAMENTE a mirar, y una foto que se va
   sola mientras la está mirando le saca de la mano lo único que fue a
   buscar. Un carrusel automático es lo correcto para lo primero y lo peor
   posible para lo segundo.

   Ahora las fotos están TODAS a la vista al mismo tiempo, sin nada que se
   mueva solo, y se abren a pantalla completa al hacer clic. Nada acá tiene
   reloj propio: todo lo mueve el visitante.

   Sin JS la grilla es el contenido completo y funciona igual — la única
   diferencia es que las fotos no se agrandan. Antes, sin JS, se veía UNA
   sola foto de las seis: las otras quedaban en opacity:0 esperando un
   intervalo que nunca arrancaba.
   ========================================================================== */
/* Columnas fijas por breakpoint y NO `auto-fit minmax()`: las tres fichas
   tienen 3, 5 y 6 fotos, y con auto-fit el container de 1180px entraba en
   cuatro columnas, así que la ficha de 5 quedaba 4 + 1 huérfana colgando.
   Con tres columnas las tres fichas cierran bien: 3 = una fila justa,
   6 = dos filas justas, 5 = una fila entera más dos, que es como se ve
   cualquier galería y no se lee como un hueco. */
/* ==========================================================================
   GALERÍA — MOSAICO, Y POR QUÉ CAMBIA SEGÚN LA CABAÑA
   ==========================================================================
   ⚠️ ESTO ARREGLA UN HUECO REAL. La grilla anterior era de 3 columnas para
   las tres fichas, pero las cabañas NO tienen la misma cantidad de fotos:
   son 3, 5 y 6. Con 3 columnas fijas, la de 5 fotos dejaba una celda vacía
   abajo a la derecha — un agujero en el medio del contenido, que es lo que
   hace que una ficha se vea sin terminar.
   Así que la forma del mosaico la decide la cantidad de fotos, declarada
   en `data-fotos` (atributo estático, no calculado por JS: la galería
   tiene que verse bien también sin JS):
     3 fotos → 3 col × 2 filas, la primera ocupa 2×2  (4+1+1 = 6 celdas)
     5 fotos → 4 col × 2 filas, la primera ocupa 2×2  (4+4    = 8 celdas)
     6 fotos → 3 col × 2 filas, todas iguales         (6      = 6 celdas)
   Ninguna deja celdas vacías. Y en los tres casos son DOS filas, así que
   la galería nunca vuelve a crecer más que la información de al lado.
   La foto grande es siempre la primera: es la que el copy eligió como
   portada de esa cabaña.
   ========================================================================== */
/* ⚠️ minmax(0, 1fr) Y NO 1fr. Medido: con `repeat(4, 1fr)` las columnas
   salían 105,75px / 105,75px / 211,5px / 211,5px — desiguales—, y la foto
   grande, que ocupa las dos primeras, terminaba MÁS ANGOSTA que las
   chicas. El motivo es que `1fr` significa `minmax(auto, 1fr)`: el mínimo
   `auto` deja que el contenido de cada columna empuje su propio ancho, y
   las columnas con fotos sueltas empujan más que las dos que se reparten
   una foto que las cruza. Con el mínimo en 0 las cuatro son de verdad
   iguales y el mosaico queda como está dibujado arriba. */
/* En mobile NO va una columna. Con 5 fotos a ancho completo eran ~1.200px
   de scroll de puras fotos antes de llegar al primer dato — exactamente el
   desbalance que este bloque vino a arreglar, sólo que en vertical.
   Dos columnas con la primera foto cruzada: la portada sigue siendo
   grande, y las otras entran de a dos. Sin huecos en los tres casos
   (3 fotos → 2 filas · 5 → 3 filas · 6 → 3 filas). */
/* ==========================================================================
   RONDA 26 — EN EL TELÉFONO LA GALERÍA ES UN RIEL, NO UN MOSAICO DE DOS
   ==========================================================================
   Medido a 375 antes de tocarla: las 6 fotos de la ficha de 6 entraban en
   dos columnas de 156x117px cada una. Ciento cincuenta y seis píxeles para
   un interior en penumbra es exactamente el defecto que este mismo bloque
   ya había diagnosticado y arreglado PARA ESCRITORIO en la ronda 14 ("a
   217px no se ve nada de eso"), y que en mobile seguía vivo con la mitad
   de ancho todavía.
   Y es la sección con menos derecho a estar apretada de las cuatro páginas:
   la ficha se abre para MIRAR LAS FOTOS. Todo lo demás de la página puede
   resumirse; ésta no.
   Como riel cada foto pasa a 78vw = 292x219px (+87% de ancho, +250% de
   superficie) y encima la sección se acorta: el mosaico medía tres filas de
   alto, el riel mide una. Se gana tamaño Y se gana scroll, que es lo raro.
   El toque sigue abriendo el visor a pantalla completa, así que el riel no
   reemplaza a nada: es el índice, el visor sigue siendo el destino.
   Research mobile: es lo que hace Postcard Cabins con su galería en el
   teléfono (tarjeta central entera, las vecinas asomando) mientras en
   escritorio la misma galería es una grilla.
   ========================================================================== */
.gallery {
  display: flex;
  gap: var(--space-2);
  margin-inline: calc(var(--space-3) * -1);
  padding-inline: var(--space-3);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--space-3);
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.gallery::-webkit-scrollbar { display: none; }
.gallery > .gallery__item { flex: 0 0 78vw; max-width: 320px; scroll-snap-align: start; }
@media (min-width: 620px) {
  .gallery {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
  }
  .gallery > .gallery__item { flex: none; max-width: none; }
  /* La primera cruza las dos columnas cuando son 3 o 5 fotos, y ahí va en
     16/9 y no en 4/3: a 4/3 ocupando el ancho entero medía 500px de alto y
     se comía la pantalla ella sola. El corte era 760 y bajó a 620 junto con
     el del riel: entre 620 y 759 la primera ya cruzaba (por la regla de
     abajo) pero se quedaba en 4/3, o sea con el defecto que ese ratio
     existía para evitar. */
  .gallery[data-fotos="3"] .gallery__item:first-child,
  .gallery[data-fotos="5"] .gallery__item:first-child {
    grid-column: span 2;
    aspect-ratio: 16 / 9;
  }
}
/* ⚠️ DOS COLUMNAS Y NO TRES desde 760 (ronda 14, pedido del cliente: "bien
   mostradas las imágenes, capaz que un poquito más grande").
   Medido a 1440: con 3 columnas cada foto entraba en 217×163px. Son fotos de
   interiores oscuros, que es donde el detalle importa —qué cama hay, cómo es
   el baño— y a 217px no se ve nada de eso. Con 2 columnas pasan a 333×250:
   +53% de ancho y +135% de superficie.
   Las tres cantidades siguen cerrando sin huecos: 6 fotos → 3 filas de 2;
   5 y 3 → la primera cruza las dos columnas y el resto entra de a dos.
   La primera, cuando cruza, va en 16/9 y no en 4/3: a 4/3 ocupando el ancho
   entero medía 500px de alto y se comía la pantalla ella sola. */
/* El bloque `@media (min-width: 760px)` que vivía acá se fusionó con el de
   620 de arriba en la ronda 26: decía exactamente lo mismo (dos columnas y
   la primera cruzada en 16/9) y con el riel nuevo el corte real pasó a ser
   620. Dos reglas idénticas en dos cortes distintos sólo sirven para que
   una de las dos se olvide la próxima vez. */
/* El alto de fila lo fijan las fotos chicas; la grande se estira sola para
   cubrir las dos filas. Por eso el ratio vive en el ITEM y la imagen sólo
   rellena: así la grande no tiene que saber cuánto mide. */
.gallery__item { position: relative; margin: 0; aspect-ratio: 4 / 3; }
/* La forma de la foto vive en la FOTO y no en el botón, porque el botón
   sólo existe cuando corre JS: si el radio estuviera únicamente en el
   wrapper, la grilla sin JS saldría con las esquinas vivas. */
.gallery__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--radius-md);
  background: var(--color-border);
}
/* height:100% es lo que sostiene el mosaico cuando corre el JS: el botón
   se interpone entre el <figure> —que es quien tiene el aspect-ratio— y la
   foto, así que si no ocupa todo el alto, la foto grande deja de cubrir
   sus dos filas y vuelve a aparecer un hueco. */
.gallery__btn {
  display: block;
  width: 100%;
  height: 100%;
  padding: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-border);
}
/* Dentro del botón el recorte lo hace el wrapper (hace falta para que el
   acercamiento del hover no se salga por las esquinas), así que la foto
   entrega su propio radio para no dibujar dos redondeos superpuestos. */
.gallery__btn img {
  border-radius: 0;
  transition: transform var(--dur-photo) var(--ease-out);
}
/* La lupa aparece recién con JS: sin JS la foto no se agranda, así que
   prometer que sí sería mentirle al cursor. */
.gallery__zoom {
  position: absolute;
  right: var(--space-1);
  bottom: var(--space-1);
  width: 36px;
  height: 36px;
  display: none;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(15, 27, 31, 0.72);
  color: #fff;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.gallery.is-live .gallery__zoom { display: inline-flex; }
.gallery__zoom svg { width: 18px; height: 18px; fill: currentColor; }
@media (hover: hover) and (pointer: fine) {
  .gallery.is-live .gallery__btn:hover img { transform: scale(1.04); }
  .gallery.is-live .gallery__item:hover .gallery__zoom { opacity: 1; }
}
.gallery.is-live .gallery__btn:focus-visible ~ .gallery__zoom { opacity: 1; }

/* --- Visor a pantalla completa. <dialog> nativo: el foco atrapado, el
       Escape y el fondo inerte los resuelve el navegador, no 40 líneas de
       JS que después hay que mantener. --- */
/* ⚠️ BUG REAL, encontrado con playwright-cli tocando "foto siguiente" en la
   ficha de la cabaña de 6 (mobile, 390px): sin `height` propio, el <dialog>
   toma el alto de su CONTENIDO (hasta el tope de max-height), y el
   contenido es la foto — object-fit:contain, así que cada foto define un
   alto distinto según su relación de aspecto. Con la primera foto (vertical)
   el visor se veía "a pantalla completa"; al tocar siguiente, una foto más
   apaisada rendía a media altura y el visor se achicaba a una tarjeta
   flotando en el medio de la pantalla, con la página de atrás asomando
   arriba y abajo — el salto de tamaño entre foto y foto es el defecto, no
   sólo el tamaño chico de una en particular. `height: 94vh` fijo (no sólo
   max-height) más `.lightbox__figure` como flex:1 con la foto centrada
   adentro (ver abajo) hace que el visor mida SIEMPRE lo mismo, sea cual
   sea la relación de aspecto de la foto que esté mostrando. */
.lightbox {
  width: min(96vw, 1400px);
  max-width: none;
  height: 94vh;
  max-height: 94vh;
  padding: 0;
  border: none;
  border-radius: var(--radius-lg);
  background: var(--color-darker);
  color: var(--color-on-dark);
  overflow: hidden;
}
/* ==========================================================================
   ENTRADA DEL VISOR (ronda 19) — el único teletransporte que quedaba
   ==========================================================================
   Hasta acá, tocar una foto de la galería hacía `showModal()` y el visor
   APARECÍA: la caja entera más un fondo negro al 88% que tapa la página, en
   un solo frame, sin nada que explique de dónde salió. Es el caso de manual
   de "preventing a jarring change": lo más violento que hace el sitio.

   Va en CSS y no en GSAP a propósito. Son tres razones, no una preferencia:
   1. `<dialog>` + `showModal()` es del navegador; meter GSAP en el medio
      obliga a sincronizar a mano el top layer y el ::backdrop, que ni
      siquiera es un elemento al que GSAP pueda apuntar.
   2. Una transición de CSS es interrumpible y se re-apunta sola: abrir y
      cerrar rápido no la deja a mitad de camino.
   3. Corre fuera del hilo principal, que es justo lo que hace falta cuando
      al mismo tiempo se está decodificando una foto de 1440px.

   `allow-discrete` es lo que permite que `display` y `overlay` —dos
   propiedades que normalmente saltan— esperen a que termine el fundido; sin
   eso el cierre sería instantáneo. Donde no esté soportado, el visor abre y
   cierra de golpe como hoy: se degrada al comportamiento viejo, no se rompe.

   La escala arranca en 0,96 y NUNCA en 0: nada en el mundo real aparece de
   la nada, y una caja que crece desde cero se lee como un glitch. El origen
   queda en el centro —un modal no cuelga de ningún disparador, aparece en
   el medio de la pantalla— que es la excepción a la regla de que los
   popovers escalan desde su gatillo.
   ========================================================================== */
.lightbox {
  /* Estado CERRADO, y el reloj de SALIDA. Salir más rápido que entrar
     (140 sobre 200 = 70%) es lo que hace que cerrar se sienta obediente en
     vez de lento: al abrir el visitante mira, al cerrar ya decidió. */
  opacity: 0;
  transform: scale(0.96);
  transition: opacity var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              overlay var(--dur-fast) allow-discrete,
              display var(--dur-fast) allow-discrete;
}
.lightbox[open] {
  opacity: 1;
  transform: scale(1);
  transition-duration: var(--dur-base);
  /* Sólo en [open]: el <dialog> cerrado sigue dependiendo del
     `display: none` de la hoja del navegador (`dialog:not([open])`), y
     escribir `display` en el selector sin `[open]` pisaría eso y dejaría el
     visor visible aunque nunca se haya abierto. */
  display: flex;
  flex-direction: column;
}
/* El estado desde el que arranca la primera vez. Sin esto no hay entrada:
   el elemento nace ya abierto y no hay nada entre qué y qué interpolar. */
@starting-style {
  .lightbox[open] { opacity: 0; transform: scale(0.96); }
}

.lightbox::backdrop {
  background: rgba(8, 14, 11, 0.88);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out),
              overlay var(--dur-fast) allow-discrete,
              display var(--dur-fast) allow-discrete;
}
.lightbox[open]::backdrop { opacity: 1; transition-duration: var(--dur-base); }
@starting-style {
  .lightbox[open]::backdrop { opacity: 0; }
}
.lightbox__figure {
  margin: 0;
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.lightbox__figure img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  background: #000;
}
.lightbox__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
}
.lightbox__caption {
  flex: 1;
  min-width: 0;
  font-size: var(--text-sm);
  color: var(--color-on-dark-soft);
  line-height: 1.35;
}
.lightbox__count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: var(--text-sm);
  color: var(--color-on-dark);
  white-space: nowrap;
}
.lightbox__btn {
  width: 48px;
  height: 48px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(236, 241, 243, 0.3);
  color: var(--color-on-dark);
  font-size: var(--text-md);
  line-height: 1;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-press) var(--ease-out);
}
.lightbox__btn:hover { background: rgba(236, 241, 243, 0.14); }
.lightbox__btn:active { transform: scale(0.94); }
.lightbox__close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  background: rgba(8, 14, 11, 0.66);
}
@media (min-width: 700px) {
  .lightbox__figure img { max-height: 78vh; }
}

/* En la ficha, el bloque NO es una tarjeta: es la página. La superficie de
   --color-border existe en el home para separar tres opciones comparables
   entre sí; acá no hay nada de qué separarla. */
.unit--sheet { background: none; padding: 0; }
/* El padding-top de 32px existía porque la ficha venía DESPUÉS de la galería
   y necesitaba despegarse de la última foto. Ahora abre el artículo, así que
   el aire de arriba lo pone la sección y el que hace falta es el de ABAJO,
   contra la galería que ahora va después. */
.unit--sheet .unit__body { padding-inline: 0; padding-top: 0; padding-bottom: var(--space-5); }
.unit--sheet .unit__name { min-height: 0; }

/* ==========================================================================
   ⚠️ RONDA 32 — LA FICHA DE TEXTO SUBE ARRIBA DE LA GALERÍA.
   ==========================================================================
   EL DEFECTO, MEDIDO EN PRODUCCIÓN ANTES DE TOCAR (1440x900, ficha de 6):
       portada .......... y=81    alto 414
       galería .......... y=591   alto 782   <- 6 fotos, 3 filas de 2
       ficha de texto ... y=1397  alto 508
       primera spec ..... y=1568
   O sea: 1.196px de foto seguidos antes del primer dato, y "Camas" cayendo a
   1,74 pantallas del inicio. La relación foto/texto era de 2,35 a 1 y encima
   EN SERIE: primero todas las fotos, después todo el texto. La ronda 14 ya
   había peleado este mismo número (era 3 a 1) sacando la tarjeta del
   calendario a una columna propia; lo que quedó sin resolver es que DENTRO de
   la columna ancha el orden siguiera siendo álbum-y-nota-al-pie.

   LA REFERENCIA, MEDIDA EN VIVO ESTA RONDA (playwright-cli):
     · Under Canvas ("tents") — foto a la izquierda y la tira de specs a la
       derecha, en la misma banda: SLEEPS / BATHROOM / GREAT FOR arriba de la
       descripción. Los datos duros no esperan a que termine la galería.
     · Correntoso ("habitaciones & suites") — igual: las fotos y el renglón de
       Equipamiento comparten fila, el texto nunca cuelga debajo del álbum.
   El principio transferible de las dos es UNO: la información no va después
   de las fotos. La forma lateral, en cambio, NO se transfiere, y se midió
   antes de descartarla: montando la primera foto al lado de las specs, la
   columna ancha de 683px se parte en 299 de foto contra 352 de ficha, las
   specs pierden su segunda columna y pasan de 313 a 598px de alto, la banda
   queda en 761 y la foto en 299x761 — una astilla de aspecto 0,39 sobre
   originales que son 3:4. Total: +387px de página para empeorar la foto.
   Medido con el layout puesto, no estimado.
   Así que se aplica el principio y se descarta la forma: la ficha de texto se
   mueve ANTES de la galería y la galería se queda entera y con su ancho.
   Resultado medido: la primera spec pasa de y=1568 a y=730 y la página no
   crece por el reordenamiento.
   ========================================================================== */

/* ==========================================================================
   FICHA DE CABAÑA — LAS DOS COLUMNAS
   ==========================================================================
   ⚠️ MEDIDO, y el motivo por el que existe este bloque: en la versión
   anterior la galería medía 566px de alto y toda la información de la
   cabaña medía 308px. Sumando el hero, eran 980px de foto contra 308 de
   texto — 3 a 1. La ficha se leía como un álbum con una nota al pie.

   El arreglo no es agrandar la letra: es dejar de apilar. Las fotos y los
   datos pasan a una columna de ~62%, y al lado queda una tarjeta fija con
   el calendario y el botón. Eso hace tres cosas de una: la galería se
   angosta (y por lo tanto se achica de alto), la información deja de
   estar "después" de las fotos para estar al lado, y el CTA queda a la
   vista todo el scroll en vez de aparecer al final.
   Es el patrón de cualquier ficha de alojamiento seria, y no por copiar:
   es lo que pasa cuando el objeto de la página es decidir, no mirar.
   ========================================================================== */
.cabin-layout { display: grid; gap: var(--space-5); align-items: start; }
@media (min-width: 1000px) {
  /* minmax(0,…) en la columna ancha: sin eso, una foto o una palabra larga
     le fija un mínimo intrínseco y la tarjeta se desborda hacia afuera. */
  .cabin-layout { grid-template-columns: minmax(0, 1.7fr) minmax(320px, 1fr); gap: var(--space-6); }
}
/* "Otras capacidades" vive DENTRO de esta columna y no full-width abajo.
   Motivo medido: la tarjeta del calendario es más alta que la ficha, así
   que con las capacidades afuera quedaban ~390px de blanco al costado
   izquierdo. Metiéndolas acá las dos columnas terminan casi juntas y el
   bloque además cae donde tiene sentido: justo después de leer los datos
   de esta cabaña es cuando aparece "¿y si necesito otra?". */
.cabin-layout__main { min-width: 0; }

/* ⚠️ ORDEN EN MOBILE. En una sola columna, el DOM deja la tarjeta DESPUÉS
   de "Otras capacidades", o sea que el visitante se cruza los links a las
   otras dos cabañas ANTES de poder elegir fechas en la que está mirando.
   Eso es mandarlo a otro lado justo cuando estaba por consultar.
   `display: contents` saca al wrapper del layout —sin tocar el HTML, que
   en desktop tiene que seguir siendo dos columnas— y deja ordenar los tres
   bloques como hermanos: ficha, después elegir fechas, y recién al final
   las otras capacidades. */
@media (max-width: 999px) {
  /* ⚠️ BUG REAL ENCONTRADO EN LA RONDA 31, Y NO ES DE ESTILO: EN MOBILE LA
     GALERÍA DE FOTOS MEDÍA 1575px DENTRO DE UNA PANTALLA DE 375.
     `align-items: start` viene de la regla base, donde `.cabin-layout` es una
     GRILLA de dos columnas y ese valor sirve para que la tarjeta lateral no se
     estire hasta el alto de la ficha. Acá abajo el mismo elemento pasa a ser
     un FLEX EN COLUMNA, y ahí `start` cambia de significado: el eje cruzado
     pasa a ser el horizontal, así que los items dejan de estirarse al ancho
     del contenedor y se dimensionan por su contenido.
     Medido en la ficha de 5 personas a 375px, antes de tocar esto:
         .cabin-layout ...... 327px   (correcto)
         article.unit ....... 1526px  (se dimensionaba por su contenido)
         .gallery ........... 1575px  (5 items de 78vw + huecos + padding)
     La galería tiene `overflow-x: auto` para ser un riel deslizable, pero un
     riel sólo desliza si su CAJA es más angosta que su contenido; acá la caja
     crecía junto con el contenido, así que no deslizaba nada. Y como el <html>
     de este sitio lleva `overflow-x: clip`, el desborde no se veía: quedaba
     recortado y mudo. Resultado: en la ficha de 5 fotos se veía UNA y las
     otras cuatro eran inalcanzables, con el texto "Deslizá para ver las 5"
     debajo prometiendo un gesto que no existía. Igual en las tres fichas.
     `stretch` devuelve a los items el ancho del contenedor, la caja del riel
     vuelve a medir 375 y el deslizamiento funciona. */
  .cabin-layout { display: flex; flex-direction: column; align-items: stretch; }
  .cabin-layout__main { display: contents; }
  .unit--sheet { order: 1; }
  .cabin-card { order: 2; }
  .other-cabins { order: 3; }

  /* ⚠️ RONDA 26 — EN EL TELÉFONO LA IDENTIDAD DE LA CABAÑA ESTABA ESCRITA
     DOS VECES, Y ÉSTE ES EL SEGUNDO MOTIVO POR EL QUE EL TÍTULO CORTADO
     MOLESTABA TANTO.
     La portada de la ficha ya dice, en grande y con foto detrás, "5" /
     "Personas" y "Cabaña mono ambiente para 5 personas". Seiscientos
     píxeles más abajo, la ficha volvía a abrir con "5 personas" en cifra
     enorme y "Mono ambiente" en display. En escritorio los dos bloques
     viven en columnas distintas y separadas, y ahí la repetición hace de
     encabezado de la columna de datos; en una sola columna es la misma
     frase dicha de nuevo a una pantalla de distancia.
     Se va la cifra repetida (el dato sigue en la portada y en el <h1>) y
     queda el nombre corto, que es lo único que la portada NO dice: pasa a
     ser el rótulo de la ficha técnica, con la misma hairline de madera que
     separa sus filas. Un rótulo de sección pasando por debajo del header
     se lee como una sección que empieza; un titular de display cortado al
     medio se lee como algo roto. */
  .unit--sheet .unit__capacity { display: none; }
  .unit--sheet .unit__body { padding-top: 0; }
  /* ⚠️ RONDA 32 — SE VA LA REGLA DE ARRIBA DEL NOMBRE. Existía porque la
     ficha caía a mitad de página, después de la galería, y ahí una hairline
     decía "acá empieza otra cosa". Ahora el nombre es lo PRIMERO del artículo,
     apenas debajo de la portada: una línea sobre el primer elemento de una
     sección no separa nada de nada, sólo se lee como un resto. El que separa
     ahora es el borde de la portada, que es una foto a sangre. */
  .unit--sheet .unit__name { font-size: var(--text-lg); }
}
/* Insurance de anclas: `[id]` ya lleva scroll-margin-top desde la ronda 18,
   pero un titular sin id que alguna vez sea destino de un scroll
   programático quedaría igual detrás del header. Los cuatro niveles lo
   llevan puesto y el problema no puede volver por esa puerta. */
h1, h2, h3, h4 { scroll-margin-top: calc(var(--header-h) + var(--space-3)); }

/* La tarjeta hereda la superficie oscura que antes tenía `.cabin-cta-block`
   (que desaparece: esto lo reemplaza). top:96px la deja debajo del header
   fijo. */
.cabin-card {
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--color-dark);
  color: var(--color-on-dark);
}
@media (min-width: 560px) { .cabin-card { padding: var(--space-4); } }
/* ⚠️ DEFECTO REAL, medido esta ronda y anterior a ella: la tarjeta mide 829px
   y en una ventana de 900 el sticky la clava en top:96, así que el botón de
   consultar terminaba en y=925 — 25px POR DEBAJO del borde inferior, y en un
   portátil de 800 quedaba a 125px de distancia. O sea: la única acción de la
   ficha era inalcanzable justo cuando la tarjeta estaba "fija a mano".
   Sumarle texto (que es lo que pidió el cliente) lo empeoraba.
   Se arregla con el patrón de las barras laterales de reserva: la tarjeta
   nunca es más alta que la ventana y hace scroll interna, y la fila de
   botones queda pegada al pie de esa caja. Así el contenido entero es
   alcanzable y la acción está SIEMPRE a la vista, midan lo que midan las dos. */
@media (min-width: 1000px) {
  .cabin-card {
    position: sticky;
    top: 96px;
    max-height: calc(100svh - 96px - var(--space-3));
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Sin relleno abajo: el que da el aire es la barra pegada, y así no
       queda una franja de 32px por debajo de ella donde el contenido que
       scrollea se siga viendo. Medido: la leyenda del calendario asomaba
       justo ahí. */
    padding-bottom: 0;
  }
  /* ⚠️ EL PEGADO VA EN `.avail__cta`, NO EN `.btn-row`. El `.btn-row` del
     HTML es el FALLBACK: cuando el calendario se dibuja, wireAvailability()
     le saca los botones, los mete en `.avail__cta` y lo deja `hidden`. O sea
     que pegar el `.btn-row` no pega nada — es un nodo oculto y de alto cero.
     (Costó un render darse cuenta: el botón que se veía era el mudado.)
     `bottom: 0` y no un negativo: el rectángulo que limita a un sticky es la
     CAJA DE RELLENO del contenedor, así que 0 ya lo deja apoyado con los
     32px de padding de la tarjeta debajo. */
  .cabin-card .avail__cta {
    position: sticky;
    bottom: 0;
    z-index: var(--z-raised);
    margin-inline: calc(var(--space-4) * -1);
    padding: var(--space-2) var(--space-4) var(--space-4);
    /* Degradado corto para que el contenido que pasa por debajo se apague en
       vez de cortarse contra el borde del botón. */
    background: linear-gradient(to top, var(--color-dark) 84%, rgba(22, 35, 29, 0) 100%);
  }
}
.cabin-card__title { color: #fff; font-size: var(--text-lg); }
.cabin-card .btn-row { margin-top: var(--space-3); }
.cabin-card .btn { width: 100%; justify-content: center; }
/* Los dos botones que el calendario mueve a `.avail__cta` van EN FILA: el
   verde se estira y el de llamar se queda con lo justo para su etiqueta.
   `.avail__cta` es una grilla, así que la fila se arma con dos columnas en
   vez de cambiarle el display al bloque, que se comparte con el calendario
   del home. */
.cabin-card .avail__cta { grid-template-columns: minmax(0, 1fr) auto; }
.cabin-card .avail__cta .btn--quiet { width: auto; padding-inline: var(--space-2); }

/* ⚠️ ACÁ VIVÍAN `.cabin-card__lede` Y `.cabin-card__steps` (los pasos
   1/2/3). Se borraron en la ronda 23 junto con su marcado: la bajada decía
   lo mismo que la instrucción que el calendario imprime tres líneas más
   abajo, y los tres pasos los reemplazó lo que el calendario ya sabe decir
   con datos de la planilla (cuántos días con lugar, cuál es la primera
   fecha libre). La promesa que aportaba el paso 2 —"se abre WhatsApp con el
   mensaje ya escrito"— se mudó a esa misma instrucción.
   Se borran las reglas y no se dejan huérfanas: este archivo ya arrastró
   una vez cuatro selectores muertos durante seis rondas. */
.cabin-card__note { margin-top: var(--space-2); font-size: var(--text-xs); color: var(--color-on-dark-soft); line-height: 1.5; }

/* `.cabin-cta-block` se eliminó en la ronda 11: era el bloque oscuro
   centrado al final de la ficha, y lo reemplaza `.cabin-card`, que es el
   mismo material pero fijo al costado en vez de al final. */

/* ⚠️ RONDA 31 — ESTE BLOQUE ES NAVEGACIÓN, NO CONTENIDO, Y AHORA SE VE.
   Ver el bloque de .unit-spec__label para el diagnóstico completo. Acá se
   aplica la otra mitad: el titular deja de competir con el de la cabaña que
   el visitante está mirando, y las tarjetas dejan de tener más contraste que
   la ficha. No se achica la información ni se esconde nada —los dos links
   siguen enteros, con su foto y su descripción, y el target táctil no baja de
   los 44px— sólo deja de gritar más fuerte que el contenido de la página.
   El margen sube de 64 a 96px: separar es lo que convierte dos bloques que
   competían en un bloque y su apéndice. */
.other-cabins { margin-top: var(--space-8); }
/* 18px contra los 28px que heredaba de .section-title--sm. Es el mismo
   tamaño que el dato primario de la ficha, no más grande: un rótulo de
   sección secundaria no puede pesar más que el contenido que la página vino
   a mostrar. */
.other-cabins > .section-title {
  font-size: var(--text-md);
  font-weight: 700;
  font-stretch: normal;
  letter-spacing: 0.01em;
  color: var(--color-ink-soft);
}
/* ⚠️ ESTO ARREGLA UN DESBORDE HORIZONTAL REAL, medido esta ronda en las 3
   fichas a 390px: la grilla estaba fija en 2 columnas en TODOS los anchos,
   así que a 390px cada columna daba 163px pero la tarjeta medía 179,6 —no
   podía achicarse por debajo del ancho de "2 a 4 personas"— y el documento
   se iba 9px a la derecha. Es exactamente la trampa que HANDOFF ya tenía
   anotada dos veces (min-width:auto por default en items de grid). Se
   arregla por los dos lados: una sola columna hasta 520px, y min-width:0
   para que el texto pueda ceder en vez de empujar. */
.other-cabins__grid { display: grid; gap: var(--space-2); margin-top: var(--space-3); grid-template-columns: 1fr; }
@media (min-width: 520px) { .other-cabins__grid { grid-template-columns: repeat(2, 1fr); } }
.other-cabins__card {
  display: flex; align-items: center; gap: var(--space-2);
  min-width: 0;
  padding: var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: border-color var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.other-cabins__card > span { min-width: 0; }
@media (hover: hover) and (pointer: fine) { .other-cabins__card:hover { border-color: var(--color-ink); transform: translateY(-2px); } }
/* 56px y no 64: es una miniatura de navegación, no una foto. Y `object-position`
   al 45% porque las tres fuentes tienen el sujeto por encima del centro
   geométrico — recortadas al medio, la de la cabaña de 6 mostraba pasto. */
.other-cabins__card img { width: 56px; height: 56px; border-radius: var(--radius-sm); object-fit: cover; object-position: 50% 45%; flex-shrink: 0; }
.other-cabins__card strong { display: block; font-size: var(--text-sm); font-weight: 700; color: var(--color-ink-soft); }
.other-cabins__card span { font-size: var(--text-xs); color: var(--color-ink-soft); }
/* En hover sí pasa a tinta plena: cuando el visitante apunta ahí, ESE es el
   contenido que le importa. La jerarquía es de reposo, no de intención. */
@media (hover: hover) and (pointer: fine) {
  .other-cabins__card:hover strong { color: var(--color-ink); }
}

/* ==========================================================================
   Cómo llegar
   ========================================================================== */
.map-wrap {
  position: relative;
  margin-top: var(--space-4);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  /* ⚠️ RONDA 26 — CUADRADO EN MOBILE, APAISADO EN ESCRITORIO.
     Medido a 375: con 16/10 el mapa quedaba en 327x204px. Doscientos cuatro
     píxeles de alto es una FRANJA, no un mapa: entra el pin y una cuadra
     para cada lado, y la pregunta que la sección viene a contestar —dónde
     queda esto respecto del río y del pueblo— no se puede contestar mirando
     eso. Un mapa es el caso más claro de contenido que no se puede
     angostar: si se le saca alto, se le saca información.
     Cuadrado sube a 327x327 (+60% de alto) sin comerse una pantalla entera,
     y de paso la relación 1:1 es la que usa cualquier mapa embebido en un
     teléfono. La ficha de Google que dibuja el propio embed en la esquina
     superior izquierda ahora entra sin tapar el pin. */
  aspect-ratio: 1 / 1;
}
@media (min-width: 700px) {
  .map-wrap { aspect-ratio: 16 / 10; }
}
.map-wrap iframe { width: 100%; height: 100%; border: 0; }
/* Indicaciones. Va como link y no como botón: el botón de esta pantalla es
   WhatsApp, y "cómo llegar" es para el que YA decidió venir. */
.map-dir { margin-top: var(--space-2); font-size: var(--text-sm); }
.map-dir__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  font-weight: 700;
  color: var(--color-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.map-dir__link span { display: inline-block; transition: transform var(--dur-fast) var(--ease-out); }
.map-dir__link:hover span { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .map-dir__link span { transition: none; }
  .map-dir__link:hover span { transform: none; }
}
/* Acá vivía `.map-wrap__label`, el rótulo oscuro sobre la esquina del mapa.
   Se fue en la ronda 18 junto con su markup: desde que el mapa apunta al
   complejo y no al pueblo, Google dibuja su propia ficha en esa esquina y el
   rótulo la tapaba. Ver el comentario en index.html. */

/* ==========================================================================
   CTA final — cierra el par con el panel oscuro del hero.
   `impeccable` marca "una sección oscura suelta en una página clara" como
   accidente. Acá no es suelta: el hero abre con el mismo --color-dark y el
   CTA cierra con él. Es un par deliberado, no un bloque huérfano.
   ========================================================================== */
/* ==========================================================================
   REDES — una franja de fotos a sangre, no dos íconos de 44px.
   ==========================================================================
   Lo que dijo el cliente, textual: "me pediste el Instagram y el Facebook y
   no lo pusiste en ningún lado... quedó abajo todo chiquitito, ¿quién te lo
   va a ver? Ponelo tipo carrusel, que se vayan mostrando las fotos una al
   costado de la otra".

   Los dos íconos seguían existiendo, pero al pie del CTA final, a 44px, sin
   etiqueta y compitiendo con el botón de WhatsApp: era el último lugar de la
   página y el más chico. Ahora las redes tienen sección propia, con las dos
   cuentas escritas con todas las letras y una franja de fotos que cruza el
   ancho entero de la pantalla.

   POR QUÉ LA FRANJA SE MUEVE SOLA Y EL PASE DE LA FICHA NO (que el mismo
   cliente pidió frenar): acá no se REEMPLAZA nada. Ninguna foto se lleva el
   lugar de otra ni desaparece mientras se la mira — la fila entera se
   desplaza y todo lo que salió por un lado se puede traer de vuelta
   arrastrando. Es una vidriera, no un pase de diapositivas. Además se frena
   al pasar el mouse y al enfocar con el teclado.

   Las fotos son del material del complejo, las mismas que se ven en el resto
   del sitio: son de ellos y son reales. La franja NO simula un feed de
   Instagram —no hay likes, ni comentarios, ni fechas inventadas—, porque
   fabricar la interfaz de una red con datos que no existen es exactamente lo
   que no hay que hacer. Es una vidriera de fotos con dos links reales.
   ========================================================================== */
/* Ronda 29: --color-bg-alt ya es un escalón más oscuro que el fondo, así que
   el asiento va más flojo (0.035 en vez de 0.05) para no cerrarla de más. */
.social {
  background-color: var(--color-bg-alt);
  background-image:
    radial-gradient(84% 64% at 90% 0%, var(--lift-light) 0%, transparent 66%),
    radial-gradient(64% 56% at 4% 100%, rgba(23, 36, 43, 0.035) 0%, transparent 58%);
  padding-block: var(--space-7);
  overflow: hidden;
}
/* ==========================================================================
   LAS DOS CUENTAS — lista de canales, no botonera (ronda 15)
   ==========================================================================
   Antes eran dos píldoras del mismo tamaño, una llena y otra en contorno,
   apoyadas abajo a la derecha del titular. El problema no era el estilo: era
   que dos botones idénticos declaran dos acciones equivalentes, y estas dos
   no lo son ni hacen lo mismo. Además ninguna de las dos decía qué hay del
   otro lado, así que la única razón para tocarlas era "es un ícono de una
   red social".

   Ahora es una lista de dos entradas con hairline en el medio: placa con el
   ícono, la cuenta en la display, y una línea que dice qué se encuentra ahí.
   El bloque entero cabe en la columna derecha del titular en desktop y pasa
   a ancho completo abajo del texto en mobile.

   El fondo de la placa NO es el color de cada marca. Un cuadradito violeta de
   Instagram al lado de uno azul de Facebook mete dos colores saturados
   ajenos a la paleta justo al lado del verde de WhatsApp, y el verde deja de
   ser el único color que significa algo. Las dos placas van en tinta.
   ========================================================================== */
.social__head { display: grid; gap: var(--space-4); }
.social__intro { max-width: 46ch; }

.social__accounts { list-style: none; margin: 0; padding: 0; }
.social__accounts li + li { border-top: 1px solid var(--color-border); }
.social__account {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-2);
  min-height: 64px;
  text-decoration: none;
  color: var(--color-ink-soft);
}
.social__badge {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  background: var(--color-ink);
  color: var(--color-bg);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.social__badge svg { width: 22px; height: 22px; fill: currentColor; }
.social__account-body { min-width: 0; display: grid; gap: 2px; }
.social__handle {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
  line-height: 1.1;
  color: var(--color-ink);
  font-variant-numeric: tabular-nums;
  /* El @ de Instagram es largo y en una columna angosta se pasaba del borde.
     Puede partirse en cualquier caracter antes que desbordar. */
  overflow-wrap: anywhere;
}
.social__what { font-size: var(--text-sm); line-height: 1.4; }
.social__cue {
  font-size: var(--text-lg);
  color: var(--color-ink);
  transition: transform var(--dur-fast) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .social__account:hover .social__badge { background: var(--color-darker); }
  .social__account:hover .social__handle { text-decoration: underline; text-underline-offset: 4px; }
  .social__account:hover .social__cue { transform: translateX(4px); }
}
@media (prefers-reduced-motion: reduce) {
  .social__cue { transition: none; }
  .social__account:hover .social__cue { transform: none; }
}
@media (min-width: 900px) {
  /* Titular a la izquierda, canales a la derecha, alineados al pie: el borde
     inferior de la última cuenta y el de la bajada caen en la misma línea, y
     la franja de fotos arranca pareja debajo de los dos. */
  .social__head { grid-template-columns: minmax(0, 1fr) minmax(320px, 0.85fr); gap: var(--space-6); align-items: end; }
}

/* La franja se sale del container a propósito: es lo único del sitio que
   toca los dos bordes de la pantalla, y eso es lo que la hace imposible de
   pasar por alto, que es todo el punto del pedido. */
.social__strip {
  margin-top: var(--space-5);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.social__strip::-webkit-scrollbar { display: none; }
.social__track {
  display: flex;
  width: max-content;
}
/* La separación va en margin-right y NO en `gap`, y no es cosmética: con
   `gap`, el ancho total de la fila duplicada es 20 fotos + 19 separaciones,
   así que el 50% del recorrido NO cae sobre la primera foto del clon sino
   media separación antes. Se veía un salto de 8px en cada vuelta. Con
   margin-right cada foto mide lo mismo con su espacio incluido, el total es
   20 unidades exactas y el 50% cae clavado en el empalme. */
/* Más grandes que antes (eran clamp(150px, 24vw, 240px)) y en 4/5 en vez
   de cuadradas: el pedido era que se vea MÁS de cada foto. El cuadrado
   recorta el río por arriba y por abajo justo donde está el paisaje; el
   vertical suave devuelve esa franja y de paso aumenta el alto de la
   franja entera, que es lo que la hace imposible de saltear. */
.social__track img {
  width: clamp(196px, 30vw, 320px);
  margin-right: var(--space-2);
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-md);
}
/* El modo marquesina lo enciende JS, y sólo después de duplicar la fila:
   sin el clon, la animación llega al final y salta hacia atrás a la vista.
   Sin JS queda una fila arrastrable con scroll horizontal nativo, que sigue
   siendo exactamente "las fotos una al costado de la otra". */
.social__strip.is-live { overflow: hidden; }
.social__strip.is-live .social__track {
  animation: social-marquee 64s linear infinite;
  will-change: transform;
}
.social__strip.is-live:hover .social__track,
.social__strip.is-live:focus-within .social__track { animation-play-state: paused; }
/* La pausa explícita del botón. Va DESPUÉS del hover a propósito: si alguien
   pausó a mano y después pasa el mouse por encima, tiene que seguir pausada;
   y si le da play y saca el mouse, tiene que arrancar. */
.social__strip.is-live.is-paused .social__track { animation-play-state: paused; }

/* ==========================================================================
   EL BOTÓN DE PAUSA (ronda 20)
   ==========================================================================
   Vive en `.social__stripwrap` y no adentro de la franja: la franja es la
   caja que scrollea, así que un hijo posicionado ahí adentro viajaría con
   las fotos o quedaría recortado por el overflow.

   No estrena ni un color ni una forma. Es la MISMA placa que ya usan las dos
   cuentas de arriba (`.social__badge`): 44px, tinta maciza, radio chico, el
   ícono en el color del fondo del sitio. Lo único que se suma es el borde
   claro, y no es decoración: la franja de fotos abajo es impredecible —hay
   un fuego naranja, un río gris, una pared de madera— y sin ese contorno la
   placa oscura desaparece contra una foto oscura.

   ⚠️ ABAJO A LA IZQUIERDA, Y ESO NO ES UNA PREFERENCIA.
   El primer intento fue abajo a la derecha y quedó MAL: ahí vive el botón
   flotante de WhatsApp, que es `position: fixed` y está en esa esquina en
   TODAS las páginas y en todos los scrolls. Los dos se pisaban, y el que
   perdía era el flotante — o sea el CTA que le da de comer al cliente.
   Detectado mirando la captura a 375, no razonándolo: se veía el triángulo
   de play asomando abajo del círculo verde.
   Esa esquina está reservada para el flotante en todo el sitio. Arriba
   tampoco sirve: es el borde donde la franja se encuentra con las dos
   cuentas y una placa ahí compite con ellas. Queda abajo a la izquierda,
   que está libre, fuera del recorrido de lectura y a un dedo.
   ========================================================================== */
.social__stripwrap { position: relative; }
.social__pause {
  position: absolute;
  left: var(--space-2);
  bottom: var(--space-2);
  display: grid;
  place-items: center;
  /* 44px es el piso de área táctil (Apple HIG / ui-ux-pro-max §2), y acá se
     cumple con el tamaño real del botón, sin depender de hitSlop ni de un
     pseudo-elemento invisible. */
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: var(--radius-sm);
  border: 1px solid rgba(236, 241, 243, 0.45);
  background: var(--color-ink);
  color: var(--color-bg);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              transform var(--dur-press) var(--ease-out);
}
.social__pause svg { width: 20px; height: 20px; fill: currentColor; display: block; }
.social__pause:hover { background: var(--color-darker); }
/* Misma presión que `.btn`: el sitio entero responde así al toque. */
.social__pause:active { transform: scale(0.94); }
.social__pause:focus-visible {
  outline: 2px solid var(--color-teal);
  outline-offset: 2px;
}
@keyframes social-marquee {
  /* -50% y no -100%: la fila está duplicada, así que medio recorrido es un
     ciclo completo y el empalme cae en una copia idéntica. */
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

.final-cta { text-align: center; }
.final-cta .section-title { max-width: none; margin-inline: auto; }
.final-cta .section-lede { margin-inline: auto; }
.final-cta .btn-row { justify-content: center; margin-top: var(--space-4); }
.final-cta__reassure { margin-top: var(--space-3); font-size: var(--text-sm); color: var(--color-on-dark-soft); }
/* Una línea que dice que las redes existen y manda a la sección donde de
   verdad están. `.social-row` (los dos íconos de 44px que vivían acá) se
   eliminó junto con su markup: ver la sección .social más arriba. */
/* Acá vivía .final-cta__channels: el renglón "También estamos en Instagram
   y Facebook" cuyo link medía 21px y apuntaba a #redes, no a las redes. Lo
   reemplaza la fila .channels (ver el bloque BOTÓN DE CANAL más arriba).
   Se elimina y no se deja apagado: no queda ningún elemento que lo use. */

/* ==========================================================================
   PIE — tres columnas sobre la superficie más oscura del sitio.
   ========================================================================== */
.site-footer {
  background: var(--color-darker);
  color: var(--color-on-dark-soft);
  padding-block: var(--space-6) var(--space-4);
  font-size: var(--text-sm);
}
.site-footer a { color: var(--color-on-dark); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: rgba(236, 241, 243, 0.35); }
.site-footer a:hover { color: #fff; text-decoration-color: currentColor; }

.site-footer__grid { display: grid; gap: var(--space-5); }
/* El nombre va en la display, en el mismo peso que el logotipo del header:
   es el único lugar de la página donde la marca se firma. */
.site-footer__name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: var(--text-lg);
  letter-spacing: -0.02em;
  color: #fff;
}
.site-footer__where { margin-top: 6px; max-width: 34ch; line-height: 1.5; }
.site-footer__proof { margin-top: var(--space-2); display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-1) 10px; }
.site-footer__label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-wood-soft);
}
.site-footer__col ul { list-style: none; margin: var(--space-2) 0 0; padding: 0; display: grid; gap: 2px; }
/* 40px de alto de línea y no 44: son links de pie, van uno abajo del otro y
   con 44 la columna se estiraba 16px más que la del texto de al lado. 40 con
   el espacio de la lista alcanza para el objetivo de toque real. */
.site-footer__col li a { display: inline-flex; align-items: center; min-height: 40px; }
.site-footer__who { margin-top: var(--space-2); max-width: 30ch; line-height: 1.5; }

.site-footer__base {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-wood-line-dark);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
}
.site-footer__base a { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; }

@media (min-width: 720px) {
  /* La marca pesa más que las dos columnas de links: es la que lleva el
     nombre, la dirección y la calificación. */
  .site-footer__grid { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-6); }
}

/* ==========================================================================
   Movimiento — GSAP + ScrollTrigger lo maneja desde js/main.js.
   La CSS NO esconde contenido real por default: si el JS falla o el CDN
   tarda, todo está visible.
   ========================================================================== */
/* Las dos capas .bridge__layer (cruce de fondo bg-alt -> bg en opacity) se
   dieron de baja junto con el markup: ver el bloque del equipamiento más
   arriba para el motivo. El scrub sigue existiendo y sigue revelando las 3
   tarjetas de cabaña una por una; lo que ya no hace es animar un fondo. */

/* ==========================================================================
   PREFERS-REDUCED-MOTION
   ==========================================================================
   El bloque anterior protegía `.cabin-card`, `.cabin-card__photo img`,
   `.cabin-card__glow` y `.cabins-grid`: LOS CUATRO SELECTORES ESTABAN
   MUERTOS desde que la ronda 5 borró ese componente (verificado en el DOM
   real con playwright-cli: 0 elementos cada uno). Mientras tanto, lo que
   sí se mueve hoy — el cruce con desenfoque de las fotos de cabaña, el
   zoom de la tarjeta, la flecha de avance — no estaba cubierto por nada.

   Criterio: movimiento MENOR, no interfaz muerta (es lo que pide la regla
   `reduced-motion` de ui-ux-pro-max y lo mismo que dice emil-design-eng).
   Se apaga todo lo que desplaza, escala o desenfoca. Se DEJAN vivas las
   transiciones de color, de opacidad y la sombra del header: ayudan a
   entender qué está pasando y no son movimiento.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Los botones dejan de levantarse y de hundirse. El cambio de color de
     fondo y la sombra SÍ se conservan: son la respuesta al toque, no
     movimiento, y sin ellos el botón no contesta nada. */
  .btn, .btn:hover, .btn:active, .btn:hover:active,
  .whatsapp-float, .whatsapp-float:hover, .whatsapp-float:active, .whatsapp-float:hover:active {
    transform: none !important;
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
  }

  .nav-toggle__bar,
  .nav-toggle__bar::before,
  .nav-toggle__bar::after { transition: none !important; }

  /* Fotos de cabaña: el cruce sigue existiendo (hace falta para entender
     que cambió la foto) pero pasa a ser sólo opacidad, sin desenfoque.
     El auto-avance ni siquiera arranca: wireUnitSlides() sale antes. */
  .unit__media img,
  .unit:hover .unit__media img {
    transform: none !important;
    filter: none !important;
  }

  .hero__link span, .unit__more span,
  .hero__link:hover span, .unit__more:hover span {
    transform: none !important;
    transition: none !important;
  }

  .other-cabins__card, .other-cabins__card:hover {
    transform: none !important;
    transition: border-color var(--dur-fast) var(--ease-out);
  }

  /* Cinturón de seguridad del hero: aunque el JS no llegue a correr o
     falle a mitad, nada del hero queda desplazado ni transparente. El
     ScrollTrigger de las placas viejas ya no existe (el díptico se fue en
     la ronda 12), pero la entrada por timeline sí, y esto la neutraliza. */
  .hero__bg img,
  .hero__inner > *,
  .cabins,
  .cabins__slot,
  .cabins__more { transform: none !important; opacity: 1 !important; }

  /* La franja de redes deja de correr sola y vuelve a ser lo que es por
     debajo: una fila de fotos que se arrastra con el dedo. No se esconde
     ninguna — el clon duplicado sigue ahí y se puede scrollear entero. */
  .social__strip.is-live { overflow-x: auto; }
  .social__strip.is-live .social__track { animation: none; }
  /* El botón de pausa acá no debería existir siquiera —wireSocialStrip()
     sale antes de crearlo—, pero la regla queda igual como cinturón: si en
     el futuro alguien lo mueve al HTML estático, un control para pausar algo
     que ya está quieto no puede quedar a la vista prometiendo algo falso. */
  .social__pause { display: none; }

  /* La galería de la ficha no tiene ningún movimiento automático que
     apagar (por eso se rehizo); queda sólo el acercamiento del hover. */
  .gallery__btn img { transform: none !important; transition: none !important; }
  .lightbox__btn { transition: background var(--dur-fast) var(--ease-out); }
  .lightbox__btn:active { transform: none; }

  /* El visor NO deja de fundirse: reduced-motion es menos movimiento, no
     cero animación, y aparecer de golpe con un fondo negro al 88% encima es
     justo el tipo de cambio brusco del que hay que protegerse. Se va la
     ESCALA (que es el movimiento) y queda la opacidad, más corta. */
  .lightbox,
  .lightbox[open] { transform: none; }
  .lightbox {
    transition: opacity var(--dur-fast) var(--ease-out),
                overlay var(--dur-fast) allow-discrete,
                display var(--dur-fast) allow-discrete;
  }
  @starting-style {
    .lightbox[open] { opacity: 0; transform: none; }
  }
}

/* ==========================================================================
   CONFIANZA — banda oscura continua.
   ==========================================================================
   El problema anterior: números enormes flotando sobre fondo claro, con
   mucho aire muerto entre ellos y sin hilo que los uniera. Se resuelve
   atando TODA la prueba (titular, ficha de datos y carrusel) a una sola
   superficie oscura a sangre. Tratamiento tomado de Eastwind, que apoya
   todo su contenido sobre paneles de color sólido y no deja blanco muerto.
   La banda además cierra el sistema oscuro del sitio: panel del hero,
   esta banda, y el CTA final.
   ========================================================================== */
.proof {
  /* Ronda 29: deja de ser un rectángulo verde plano. Realce arriba-derecha
     (donde está la foto de la pileta, así la luz de la capa coincide con la
     luz de la foto) y asiento abajo-izquierda, debajo del carrusel. */
  background-color: var(--color-dark);
  background-image:
    radial-gradient(72% 58% at 86% 6%, var(--lift-dark) 0%, transparent 62%),
    radial-gradient(80% 72% at 4% 104%, var(--sink-dark) 0%, transparent 66%);
  color: var(--color-on-dark);
  padding-block: var(--space-8);
}
.proof__head { display: grid; gap: var(--space-4); }
.proof__intro { max-width: 46ch; }
.proof__title { font-size: var(--text-3xl); color: #fff; }
/* El archivo del atardecer es vertical (960×1730, relación 0,55). Recortarlo
   a apaisado dejaría afuera o las palmeras o la pileta, que son las dos cosas
   que hacen la foto. Va en 4/5 —vertical, pero mucho menos que el original—
   con el foco al 46% de la altura, que es donde caen las palmeras, el río y
   la gente en el agua. Arriba se va cielo gris y abajo se va pasto vacío. */
.proof__figure {
  margin: 0;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-darker);
}
/* ⚠️ EL RECORTE ES LA MITAD DEL TRABAJO ACÁ. El archivo mide 960×1730 y en él
   la pileta con la gente ocupa apenas del 50% al 55% del alto: todo lo demás
   es cielo gris arriba y pasto vacío abajo. Un 4/5 centrado —que fue el
   primer intento— muestra el 69% del alto y deja a las dos personas del
   tamaño de una moneda dentro de un rectángulo de pasto.
   Este recorte es de 16/10 apoyado al 41%, que es el centro exacto de la
   franja que importa: palmeras, río y pileta. Muestra el 34% del alto del
   archivo y descarta el resto. La foto no cambió; cambió qué parte se mira. */
.proof__figure img { display: block; width: 100%; height: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: 50% 41%; }
@media (min-width: 900px) {
  /* La foto pesa menos que el texto (0,86) y se alinea al pie del bloque:
     así el borde inferior de la foto y el de la bajada caen en la misma
     línea y la hairline de la ficha de datos arranca pareja debajo de los
     dos. Con align-items:start la foto colgaba 60px por debajo. */
  .proof__head { grid-template-columns: minmax(0, 1fr) minmax(0, 0.86fr); gap: var(--space-6); align-items: end; }
}

/* ==========================================================================
   ⚠️ RONDA 34 — LA FICHA DE DATOS PASA DE TARJETA A CHIP.
   ==========================================================================
   Pedido textual: "cambia de formato y ubicación esto", señalando las 3
   tarjetas de la ronda 33. Reemplaza esa ronda entera (línea→tarjeta) por
   un tercer formato: la misma píldora que ya usa `.dates__shortcuts` en
   #disponibilidad (ronda 33 también), no un componente nuevo. La diferencia
   con ese otro uso es que estos chips NO son links — son datos, no
   controles — así que no llevan hover, cursor ni flecha de "ir a".
   Los tres números y sus tres fuentes son EXACTAMENTE los mismos
   (data-count-to intacto); lo que se pierde a propósito es la nota
   descriptiva de la ronda 33 ("Promedio sobre...", "Recomienda el lugar,
   sobre..."): en una píldora de una línea no entra, y el número + la
   palabra que lo explica ya alcanzan.
   También se muda: de después del carrusel a la cabecera de la sección,
   junto a la foto (dentro de `.proof__intro`, debajo de la bajada). */
.proof__chips {
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
}
.proof__chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px;
  padding: 0 var(--space-3) 0 var(--space-2);
  border: 1px solid rgba(236, 241, 243, 0.2);
  border-radius: var(--radius-pill);
  background: rgba(236, 241, 243, 0.06);
}
/* El badge de marca. Mismo criterio que en la ronda 33: el de Google queda
   blanco para que su logo de cuatro colores se lea tal cual se ve en
   cualquier lado; Facebook y la estrella se quedan en la tinta traslúcida
   que ya usa el avatar de las reseñas (`.proof .revcar__avatar`), y el
   dorado sólido sólo en la tercera, que es la única celda cuyo dato ES una
   calificación por estrellas. */
.proof__chip-icon {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(236, 241, 243, 0.12);
  color: var(--color-on-dark);
  flex-shrink: 0;
}
.proof__chip-icon svg { width: 15px; height: 15px; display: block; }
.proof__chip-icon--google { background: #fff; }
.proof__chip-icon--star { background: rgba(184, 134, 11, 0.18); color: var(--color-star); font-size: 13px; line-height: 1; }
.proof__chip-text {
  font-size: var(--text-sm);
  color: var(--color-on-dark-soft);
  line-height: 1.3;
}
.proof__chip-text strong { color: #fff; font-weight: 700; }

/* ==========================================================================
   LAS TARJETAS DE RESEÑA VUELVEN A SER BLANCAS Y SIMPLES. (Pedido del
   cliente, ronda 34.) ESTO REVIERTE A PROPÓSITO LAS RONDAS 27-30 DE ABAJO.
   ==========================================================================
   Historial completo, para que no se "corrija" de vuelta sin leer esto:
   en su momento el cliente dijo que la sección de opiniones "está en
   blanco" (cajas #EEF1EC sobre la banda oscura, ronda 27) y por eso las
   rondas 28-30 les dieron una superficie propia --color-water + texto
   claro, para que dejaran de leerse como cajas blancas de plantilla.
   Ronda 34 pide explícitamente lo contrario — tarjeta blanca, simple — y
   además que el carrusel vaya ANTES de la ficha de datos, no después (ver
   el orden del HTML en #confianza). No hay contradicción que resolver:
   es una decisión de diseño nueva del cliente, no un despiste.
   Con la tarjeta blanca no hace falta remedir contraste: son los mismos
   tokens (--color-ink, --color-ink-soft, --color-border) que ya usa el
   componente .revcar__card fuera de esta sección, ya auditados ahí. */
.proof .revcar { margin-top: var(--space-5); }
.proof .revcar__card {
  background-color: #fff;
  background-image: none;
  --ring-strong: rgba(23, 36, 43, 0.14);
  --ring-fade: rgba(23, 36, 43, 0.04);
  min-height: 0;
}
.proof .revcar__body { padding: var(--space-3); }
.proof .revcar__quote {
  color: var(--color-ink);
  font-size: var(--text-sm);
  -webkit-line-clamp: 4;
}
@media (min-width: 700px) { .proof .revcar__quote { -webkit-line-clamp: 3; } }
.proof .revcar__avatar { width: 32px; height: 32px; font-size: var(--text-sm); border-color: var(--color-border); color: var(--color-ink); }
.proof .revcar__name { color: var(--color-ink); }
.proof .revcar__meta { color: var(--color-ink-soft); }
.proof .reviews__source { color: var(--color-on-dark-soft); margin-top: var(--space-3); }
.proof .reviews__source a { color: #fff; }
.proof .revcar__arrow { background: #fff; border-color: rgba(23,36,43,0.14); color: var(--color-ink); }
.proof .revcar__arrow:hover { background: var(--color-ink); border-color: var(--color-ink); color: #fff; }

/* ==========================================================================
   MOVIMIENTO DEL CARRUSEL — profundidad entre tarjetas (idea de Card Swap
   / Bounce Cards de reactbits, consultado en vivo, adaptado a vanilla
   porque el sitio no usa React).
   ==========================================================================
   La tarjeta en foco va a escala 1 y opacidad plena; las de al lado bajan
   a 0.94 y se apagan. Al deslizar, el peso visual viaja de una a otra, y
   eso es lo que hacía falta: antes el track se movía pero nada cambiaba.

   RONDA 27 — el barrido de luz y el parallax que vivían acá eran sobre la
   foto de cada tarjeta (.revcar__photo). Sin foto por tarjeta, esos dos
   efectos no tienen superficie donde correr: se sacaron en vez de dejarlos
   de adorno apuntando a un elemento que ya no existe. La profundidad
   entre tarjetas de abajo no dependía de la foto y sigue igual.

   Todo con transform y opacity. No corre con reduced-motion. */
.revcar.is-live .revcar__card {
  transition: transform var(--dur-slow) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
  transform: scale(0.94);
  opacity: 0.45;
}
.revcar.is-live .revcar__card.is-focus { transform: scale(1); opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .revcar.is-live .revcar__card { transform: none; opacity: 1; transition: none; }
  .revcar.is-live .revcar__track { transition: none; }
}

/* ==========================================================================
   EL QUINCHO — texto en medida angosta + bloque de foto que pesa más.
   ==========================================================================
   Composición tomada de Dunton River Camp (F): el texto se queda en una
   columna angosta alineada a la izquierda y la foto ocupa un bloque más
   grande al lado, sin centrar nada y sin marco.
   El texto va PRIMERO en el DOM: es la sección que responde una pregunta,
   y la respuesta se lee antes que la prueba. En mobile queda en ese orden.

   La foto NO se estira más allá de 960px de ancho real: la fuente
   (CabanasDeHugo_4.jpg recortada) no da para más, y agrandarla la deja
   blanda. Por eso la columna se topa en 52vw y no ocupa el container entero.
   ========================================================================== */
/* ⚠️ RONDA 29 — .place__grid / .place__body / .place__lede / .place__note /
   .place__frame SE ELIMINARON. Eran el layout de la sección del quincho
   (#el-lugar), que esta ronda dio de baja entera por pedido. Se borran en
   vez de dejarse: 0 elementos en el DOM de las 4 páginas los usaban después
   de sacar la sección, y CSS huérfano es exactamente lo que produjo el bug
   de la "regla zombi" de la ronda 12 (un @media del hero viejo sobrevivió
   2000 líneas y rompió el sello en escritorio).
   .inventory NO se toca y sigue vivo: el inventario del predio se mudó a
   #cabanas, que es el dato que el pedido exigía preservar. */

/* Inventario del predio. Tres cosas que EXISTEN, separadas por hairlines.
   Es el mismo dispositivo estructural que la ficha de datos de la banda de
   confianza, y encodea algo real: son las tres unidades distintas del
   predio, no tres features elegidas para llenar. */
.inventory {
  margin: var(--space-4) 0 0;
  display: grid;
  gap: var(--space-1);
  border-top: 1px solid var(--color-wood-line);
  padding-top: var(--space-3);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-md);
  line-height: 1.2;
  color: var(--color-ink);
}
/* En mobile van apilados. Con flex-wrap, al pasar a dos filas el separador
   vertical del primer item de la segunda fila quedaba colgando solo, que se
   leia como una raya suelta. La fila con separadores empieza recien cuando
   los tres entran de verdad. */
@media (min-width: 560px) {
  .inventory { display: flex; flex-wrap: nowrap; gap: 0; }
  .inventory li { padding-right: var(--space-3); }
  .inventory li + li {
    padding-left: var(--space-3);
    border-left: 1px solid var(--color-wood-line);
  }
}
.inventory strong { font-weight: 900; font-variant-numeric: tabular-nums; }

/* ==========================================================================
   EQUIPAMIENTO — jerarquía real dentro de cada ítem.
   Antes el nombre y el detalle competían los dos en cuerpo chico, y la
   información que más se consulta del sitio pasaba desapercibida. Ahora el
   nombre es display grande y el detalle es texto legible de verdad, con el
   dato que resuelve la duda en negrita.
   ========================================================================== */
/* ==========================================================================
   LOS 4 ÍTEMS — RONDA 32: LA FOTO REEMPLAZA AL ÍCONO.
   ==========================================================================
   QUÉ HABÍA (ronda 29): 4 columnas de ícono de 26px + nombre + detalle,
   separadas por 3 hairlines verticales de madera. Sobre la banda oscura eso
   es exactamente lo que el cliente describió: "texto plano con un ícono chico
   sobre un fondo sólido de color, se lee como un documento de Word con
   fondo". Y tenía razón por una causa concreta: el ícono y su etiqueta iban
   DEL MISMO COLOR (--color-wood-soft), así que el glifo no aportaba un canal
   nuevo — repetía la palabra que tenía al lado en forma de dibujo.

   RESEARCH EN VIVO DE ESTA RONDA (playwright-cli, 5 sitios):
     · Postcard Cabins / Marriott Outdoor Collection — "Ways to enjoy your
       stay": 3 tarjetas con FOTO REAL del interior arriba (~355x200) y el
       texto abajo sobre superficie sólida. En la foto del dormitorio se ve el
       equipo de aire; en la de la cocina, la bacha y la pava. Cuando SÍ usan
       ícono (la fila "Queen bed / Dog friendly / Campfire / Kitchenette") el
       ícono no es chico: mide ~90px y tiene panel propio de 2/3 de la
       tarjeta. En ningún caso hay un glifo de 26px al lado de un renglón.
     · Eastwind (Windham, "Property amenities") — cero íconos: panel de texto
       + foto real del sauna, solapados.
     · Under Canvas ("tents") y Correntoso ("habitaciones") — la foto y el
       dato comparten banda horizontal; el dato nunca cuelga debajo.
     · Collective Retreats — superpone texto a la foto SÓLO para nombres de
       destino, nunca para un dato.
   Conclusión medida y aplicada: 0 de 5 ponen el dato de amenity encima de la
   foto. El patrón que le da peso a esta información es FOTO REAL + TEXTO
   SOBRE SUPERFICIE SÓLIDA. Por eso acá la foto va arriba y el texto abajo
   sobre la banda oscura: se gana peso visual sin tocar un solo contraste de
   los ya medidos.

   SE VAN LAS 3 HAIRLINES. Con foto, cada ítem ya tiene su propio borde
   visual: la línea sería un segundo separador para lo mismo. Bajar de 3 a 0
   además aleja al sitio del anti-default #3 (diario = hairlines), que era el
   único de los tres que la madera rozaba.

   EL ALTO NO CRECE, Y ESA ERA LA CONDICIÓN. La ronda 29 bajó esta banda de
   606 a 384px a 1440 y de 1.147 a 687 a 375, con la referencia de Postcard
   (368px). Meter cuatro fotos sin devolver la banda a 1.100px se resuelve
   con el formato, no con el ancho: en el teléfono los 4 ítems dejan de ser
   una columna apilada y pasan a RIEL con peek, que es el mismo dispositivo
   que la galería de la ficha ya usa abajo de 620 y el que Postcard usa en
   mobile. Cuatro tarjetas de 78vw en un riel miden lo que UNA apilada.
   ========================================================================== */
.kit__list {
  margin-top: var(--space-5);
  /* MOBILE = RIEL. Los márgenes negativos sangran el riel hasta el borde de
     la pantalla para que la tarjeta que asoma se corte contra el vidrio y no
     contra el padding: sin eso el peek se lee como un error de alineación.
     ⚠️ EL NÚMERO ES 16 Y NO 24 COMO EN .gallery, Y NO ES UN DESCUIDO. La
     galería vive dentro de .container, que separa con `padding-inline: 24px`,
     así que -24 cancela exactamente ese padding. Esta banda NO usa container:
     .kit se angosta con `width: min(100% - var(--space-4), …)`, o sea 16px de
     aire a cada lado por margen automático. Copiar el -24 dejaría el riel 8px
     más ancho que la pantalla a cada lado, y como <html> lleva
     `overflow-x: clip` el desborde no se vería: quedaría recortado y mudo,
     que es exactamente el bug de galería de 1575px que se corrigió en la
     ronda 31. Se sangra lo que hay: 16. */
  display: flex;
  gap: var(--space-2);
  margin-inline: calc(var(--space-2) * -1);
  padding-inline: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--space-2);
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.kit__list::-webkit-scrollbar { display: none; }
.kit__list > li { flex: 0 0 78vw; max-width: 320px; scroll-snap-align: start; }
/* ⚠️ POR QUÉ ESTE RIEL NO LLEVA tabindex="0" Y EL DE LA GALERÍA NO LO NECESITA.
   Un contenedor scrolleable sin hijos focusables no se puede recorrer con
   teclado en navegadores viejos, y la técnica documentada es hacerlo focusable
   a mano. Acá se decidió NO hacerlo, y el motivo es que el costo cae del lado
   equivocado: el riel sólo existe abajo de 1000px, y de 1000 para arriba —que
   es donde vive el usuario de teclado— la lista es una grilla sin scroll, así
   que el tabindex sería una parada de tabulación permanente hacia un elemento
   que no hace nada. Chrome 127+ y Firefox 136+ ya hacen focusables los
   scrollers sin hijos focusables, que es la solución correcta y no cuesta
   nada. Y el contenido no depende del gesto en ningún caso: los cuatro <li>
   están completos en el DOM, así que un lector de pantalla los lee los cuatro
   sin scrollear. La galería sí es focusable, pero no por una regla: es que el
   JS le mete un <button> por foto. */
/* La tarjeta NO es una caja: no lleva fondo, ni borde, ni sombra. La foto ya
   dibuja su propio rectángulo y el texto se apoya en la banda. Encajonarla
   sumaría el borde+sombra que la checklist marca como "ghost card" y además
   convertiría cuatro datos en cuatro objetos flotando sobre otro objeto. */
.kit__shot {
  margin: 0 0 var(--space-2);
  border-radius: var(--radius-md);
  overflow: hidden;
  /* El asiento de la banda oscura pasa por debajo de la foto: sin él, una
     foto de interior en penumbra se funde con el fondo y pierde el borde. */
  background: var(--sink-dark);
}
.kit__shot img {
  display: block;
  width: 100%;
  /* ⚠️ `height: auto` NO ES REDUNDANTE ACÁ, Y COSTÓ UNA MEDICIÓN AVERIGUARLO.
     El reset de este archivo es `img { max-width: 100%; display: block }`: no
     trae el `height: auto` que traen casi todos los resets. Sin él, el
     atributo height="640" del HTML queda como alto ESPECIFICADO, y
     `aspect-ratio` sólo actúa cuando una de las dos medidas es `auto` — así
     que la propiedad se ignoraba en silencio.
     Medido antes de agregarlo: la foto salía 265x640 en vez de 265x199 y la
     banda entera pasaba de 384 a 998px de alto. Los atributos width/height se
     dejan con las medidas REALES del archivo (son verticales de verdad); el
     que manda para el layout es el aspect-ratio de acá abajo, que viaja en una
     hoja render-blocking, así que no hay salto de layout. */
  height: auto;
  /* 4:3 y no el 3:4 de los originales: cuatro fotos verticales en fila
     devolverían la banda a ~700px de alto, que es justo lo que la ronda 29
     recortó. El recorte se ancla por foto con --shot-pos (ver el HTML),
     porque el objeto que prueba el dato no está en el centro de todas: el
     ventilador vive en el tercio superior y el bidé en el inferior. */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: var(--shot-pos, 50% 50%);
}
.kit__item {
  display: block;
  font-family: var(--font-display);
  font-weight: 800;
  /* Baja de --text-xl (28px) a --text-lg (22px). A 28px "Aire acondicionado"
     y "Cocina equipada" competían con el H2 de la propia sección, que es el
     que tiene que mandar; y en una columna de 300px el nombre partía en dos
     renglones, que es lo que hacía crecer la banda de alto. */
  font-size: var(--text-lg);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: #fff;
  text-wrap: balance;
}
.kit__detail {
  display: block;
  margin-top: var(--space-1);
  /* De --text-md (18px) a --text-sm (15px): es el renglón de apoyo de un
     rótulo, no cuerpo de lectura. A 18px en columna angosta daba 5 renglones. */
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-on-dark-soft);
  text-wrap: pretty;
}
.kit__detail strong { color: var(--color-on-dark); font-weight: 600; }

/* ⚠️ RONDA 32 — LOS DOS CORTES CAMBIAN, Y EL DE ABAJO DESAPARECE.
   La ronda 29 dejó escrito, medido, por qué en el teléfono los cuatro ítems
   iban EN UNA COLUMNA: puestos de a dos, la celda quedaba en 159px y ahí
   "Cocina equipada" y "Clima resuelto" parten en dos renglones, el detalle
   salta a cuatro y la fila entera toma el alto del ítem más alto. Ese
   diagnóstico sigue siendo verdadero y por eso la respuesta de esta ronda NO
   es volver a las dos columnas angostas: es cambiar de FORMATO. En el riel la
   tarjeta mide 78vw = 292px a 375, o sea casi el doble que la celda de dos
   columnas, y la lista entera ocupa el alto de UNA tarjeta en vez de cuatro.
   Se gana la foto y se gana alto al mismo tiempo, que es el mismo negocio que
   ya había hecho la galería de la ficha en la ronda 26.

   HAY UN SOLO CORTE, EN 1000, Y EL TRAMO DE DOS COLUMNAS SE MIDIÓ Y SE TIRÓ.
   La primera versión pasaba a 2 columnas en 620 y a 4 en 1000. Medida la
   sección con eso puesto: 878px a 620, 989 a 760 y 1.144 a 999. En dos
   columnas la celda llega a 464px y la foto a 348 de alto, así que dos filas
   de tarjetas se comen una pantalla entera de tablet para decir cuatro cosas
   — que es exactamente el defecto que la ronda 29 vino a corregir, reaparecido
   en otro ancho. La causa es que esta banda es full-width: con sólo dos
   columnas cada celda se vuelve enorme.
   El riel no tiene ese problema porque la tarjeta topa en `max-width: 320px`:
   a 375 mide 293, y de 414 para arriba se queda en 320 sin importar cuánto
   crezca la ventana. Así que el riel sigue hasta 999 (a 900 se ven 2,8
   tarjetas, con la tercera cortada: el peek ES la afordancia) y recién en 1000
   entran las cuatro en fila, que es donde la celda de 212px todavía aguanta el
   nombre en un renglón. Verificado a 1000, 1200 y 1440.
   Un solo cambio de formato en toda la escala, no dos. */
@media (min-width: 1000px) {
  .kit__list {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--space-5);
    row-gap: 0;
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
  }
  .kit__list > li { flex: none; max-width: none; }
}

/* Acá vivía un `@media (min-width: 900px) { .hero__seal { position: static } }`
   del hero viejo, 2000 líneas DESPUÉS de la definición del sello. Es el mismo
   modo de falla que la ronda 10: dos reglas de igual especificidad y gana la
   última escrita. Se eliminó en la ronda 12 en vez de contrarrestarse, y en
   la ronda 15 desapareció hasta el sello: la calificación es una línea de
   texto dentro del bloque del hero y no se posiciona en ningún lado. */
