/* ==========================================================================
   css/style.css — OGA Paraguay / Precio Justo Paraguay
   Marketplace inmobiliario: precio justo, verificado, sin sorpresas.
   Autocontenido: cero fuentes/íconos/CDN externos (fuentes locales en fonts/,
   pila de sistema como fallback + SVG inline).
   Mobile-first. Compuesto para casar con las clases emitidas por
   data.js / ui-core.js / ui-pages.js / ui-public.js / app.js (y, por convención,
   con lo que ui-panel.js / index.html añadan siguiendo el mismo patrón).

   IDENTIDAD 2026 — "herramienta funcional" (rediseño completo, ver commit):
   blanco funcional, alta densidad de información, el PRECIO como protagonista
   absoluto. Un solo acento de acción (verde = confianza, CTA, "precio justo"),
   terracota reservado EXCLUSIVAMENTE para alertas/errores/fuera-de-rango —
   nunca como acento decorativo. Esquinas mínimas (4px), sombras SIEMPRE
   suaves (nunca offset duro de estampado, nunca difusas tipo glass). Cero
   azul/violeta, cero glassmorphism, cero gradientes de texto, cero fotos de
   stock. Titulares en serif (Fraunces, itálica para la palabra de acento),
   cuerpo en sans neutra (Archivo), micro-etiquetas y datos en mono (IBM Plex
   Mono uppercase, tracking amplio). Listados en filas anchas foto-izquierda +
   contenido-derecha (como una herramienta inmobiliaria real), no en tarjetas
   de revista.
   ========================================================================== */

/* ---------- 0. FUENTES LOCALES ----------
   Todos los .ttf viven en fonts/ (hermano de css/), licencia SIL OFL 1.1 en
   fonts/*-OFL.txt. Fraunces y Archivo son variable fonts (un solo archivo
   cubre todo el rango de peso vía font-weight: min max); Fraunces trae
   ADEMÁS un archivo itálico real independiente (Fraunces-Italic-Variable.ttf)
   -> se declara como face aparte con font-style:italic, así el navegador usa
   la itálica DE VERDAD (nunca una oblicua sintética) para la palabra
   acentuada del H1. IBM Plex Mono solo existe en pesos estáticos (Regular +
   SemiBold) río arriba, así que son dos @font-face normales, sin rango.

   20-ago-2026 (auditoría CLAW): cada face pasa a servirse en WOFF2 primero, con el .ttf detrás
   como reserva. Los cinco archivos sumaban 1.669 KB en TTF y suman 683 KB en WOFF2 -- un 59% menos,
   medido, sin tocar un solo glifo (WOFF2 es el MISMO contorno con compresión Brotli específica de
   fuentes). La lista de src es por orden de preferencia: el navegador coge el primer formato que
   entiende, así que un navegador moderno JAMÁS descarga el .ttf; el .ttf se queda solo por si algo
   muy viejo llega a la web, y porque borrarlo no ahorra nada a nadie que la visite.
   Queda pendiente el subsetting a latín (recortar los glifos que esta web no usa nunca): bajaría
   otro buen pellizco, pero necesita fontTools y este servidor no tiene Python. Sin hacer. */
@font-face {
  font-family: "Fraunces Variable";
  src: url("../fonts/Fraunces-Variable.woff2") format("woff2"),
       url("../fonts/Fraunces-Variable.ttf") format("truetype");
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}

/* ===== Gestión de alquileres ===== */
.rental-landing-intro,
.rental-benefits,
.rental-roles,
.rental-steps,
.rental-legal-limit {
  margin: clamp(var(--s6), 6vw, calc(var(--s7) + var(--s5))) 0;
}
.rental-landing-intro { max-width: 920px; }
.rental-public-page > h1 { max-width: 1000px; font-size: clamp(2.2rem, 5.1vw, 4.2rem); line-height: 1.08; }
.rental-public-page .rental-landing-intro { margin-top: clamp(var(--s5), 3.2vw, var(--s7)); }
.rental-landing-intro .lead { max-width: 760px; font-size: clamp(1.08rem, 2vw, 1.32rem); line-height: 1.6; }
.rental-landing-intro .cta { margin-top: var(--s5); }
.rental-benefits > div:first-child,
.rental-roles,
.rental-steps,
.rental-legal-limit { max-width: 900px; }
.rental-benefit-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-top: var(--s5);
}
.rental-benefit-grid .panel-card { padding: clamp(var(--s4), 3vw, var(--s6)); }
.rental-benefit-grid h3 { margin: 0 0 var(--s3); font-size: 1.2rem; }
.rental-steps ol { display: grid; gap: 12px; padding-left: 24px; margin-top: var(--s4); list-style: decimal; }
.rental-steps li { padding-left: var(--s2); line-height: 1.55; }
.rental-legal-limit { padding: clamp(var(--s5), 4vw, var(--s6)); border: 1px solid var(--hairline); border-left: 4px solid var(--terracota); background: var(--bg-soft); }

/* Presentación editorial: un mapa de relaciones, no un dashboard ni datos de ejemplo.
   Todo el texto es HTML traducible; el SVG solo dibuja. El contenido nunca depende de JS
   ni de una animación para verse. Tokens de marca y márgenes en los cuatro lados. */
.rental-presentation {
  --rental-diagram-line: var(--primary-20);
  margin: var(--s7) 0;
  padding: clamp(var(--s5), 4vw, var(--s7));
  background: var(--bg-soft);
  border-top: 2px solid var(--primary);
  border-bottom: 1px solid var(--hairline);
}
.rental-presentation figcaption { max-width: 780px; }
.rental-presentation figcaption h2 { margin: var(--s3) 0 var(--s4); font-size: clamp(1.8rem, 3.2vw, 2.7rem); line-height: 1.15; text-wrap: balance; }
.rental-presentation figcaption > p:not(.kicker) { max-width: 650px; color: var(--ink-soft); line-height: 1.6; }
.rental-presentation figcaption .rental-presentation-hint { margin-top: var(--s4); color: var(--primary-dark); }
.rental-presentation-map { display: grid; gap: var(--s6); margin-top: var(--s6); }
.rental-presentation ul, .rental-presentation ol { margin: 0; padding: 0; list-style: none; }
.rental-presentation li { min-width: 0; }
.rental-presentation-people { display: grid; gap: var(--s4); }
.rental-presentation-people li { background: var(--bg); border: 1px solid var(--hairline); border-radius: var(--radius); }
.rental-presentation details { min-width: 0; width: 100%; }
.rental-presentation summary { display: flex; position: relative; align-items: flex-start; gap: var(--s3); min-height: 44px; padding: var(--s4); cursor: pointer; list-style: none; border-radius: var(--radius); }
.rental-presentation summary::-webkit-details-marker { display: none; }
.rental-presentation summary > div { flex: 1; min-width: 0; }
.rental-presentation summary strong, .rental-presentation summary span, .rental-presentation details > p { overflow-wrap: anywhere; }
.rental-presentation summary:hover { background: var(--primary-08); }
.rental-presentation summary:hover strong { text-decoration: underline; text-underline-offset: 3px; }
.rental-presentation summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
.rental-presentation details[open] > summary { background: var(--primary-08); }
.rental-presentation details > p { padding: var(--s4); margin: 0; color: var(--ink-soft); font-size: 1rem; line-height: 1.6; }
.rental-presentation summary .rental-presentation-toggle { position: relative; flex: 0 0 16px; width: 16px; height: 16px; margin: var(--s1) 0 0; color: var(--primary-dark); }
.rental-presentation-toggle::before, .rental-presentation-toggle::after { content: ""; position: absolute; left: 1px; top: 7px; width: 14px; height: 2px; background: currentColor; }
.rental-presentation-toggle::after { transform: rotate(90deg); }
.rental-presentation details[open] > summary .rental-presentation-toggle::after { transform: rotate(0); }
.rental-presentation strong { display: block; color: var(--ink); font-size: 1rem; line-height: 1.4; }
.rental-presentation li span, .rental-presentation-home-label span { display: block; margin-top: var(--s1); color: var(--ink-soft); font-size: 1rem; line-height: 1.5; }
.rental-presentation li svg { width: 32px; height: 32px; flex: 0 0 32px; color: var(--primary-dark); fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.rental-presentation-home { position: relative; min-width: 0; display: flex; flex-direction: column; align-items: center; align-self: center; padding: var(--s4) 0; }
.rental-presentation-home-details > summary { flex-direction: column; align-items: center; padding: var(--s2); }
.rental-presentation .rental-presentation-home-label > .rental-presentation-toggle { position: absolute; top: var(--s3); right: var(--s3); }
.rental-presentation-home-details[open] > p { background: var(--bg); border: 1px solid var(--hairline); }
.rental-presentation-house { display: block; width: min(100%, 440px); height: auto; overflow: visible; }
.rental-house-plan { fill: none; stroke: var(--rental-diagram-line); stroke-width: 1; }
.rental-house-building { stroke: var(--primary-dark); stroke-width: 1.6; stroke-linejoin: round; }
.rental-house-front, .rental-house-paper { fill: var(--bg); }
.rental-house-side { fill: color-mix(in srgb, var(--primary) 14%, var(--bg)); }
.rental-house-roof { fill: var(--primary-dark); }
.rental-house-edge { fill: none; }
.rental-house-window { fill: var(--primary-20); }
.rental-presentation-home-label { position: relative; padding: var(--s3) calc(var(--s4) + var(--s5)) var(--s3) var(--s4); background: var(--bg); text-align: center; border: 1px solid var(--rental-diagram-line); border-radius: var(--radius); }
.rental-presentation-home-label strong { color: var(--primary-dark); font-size: 1.15rem; }
.rental-presentation-modules { display: grid; align-content: center; gap: var(--s5); }
.rental-presentation-modules li { border-bottom: 1px solid var(--hairline); }
.rental-presentation-history { border-top: 1px solid var(--rental-diagram-line); padding-top: var(--s5); }
.rental-presentation-history .kicker { margin-bottom: var(--s5); }
.rental-presentation-timeline { display: grid; gap: var(--s5); }
.rental-presentation-timeline li { position: relative; padding-left: var(--s6); }
.rental-presentation-timeline summary { justify-content: space-between; padding: var(--s2); }
.rental-presentation-timeline details > p { padding: var(--s3) var(--s2); }
.rental-presentation-timeline li::before { content: ""; position: absolute; top: 6px; left: 0; width: 10px; height: 10px; border-radius: 50%; background: var(--primary); box-shadow: 0 0 0 4px var(--bg-soft); z-index: 1; }
.rental-presentation-timeline li:not(:last-child)::after { content: ""; position: absolute; left: 4px; top: var(--s4); bottom: calc(-1 * var(--s5) - var(--s2)); width: 1px; background: var(--rental-diagram-line); }
.rental-presentation-caption { margin-top: var(--s6); padding-top: var(--s5); border-top: 1px solid var(--hairline); }
.rental-presentation-caption > p:first-child { max-width: 700px; font-family: var(--font-display); font-size: clamp(1.35rem, 2.4vw, 1.9rem); line-height: 1.3; text-wrap: balance; }
.rental-presentation-caption > p:last-child { margin-top: var(--s3); color: var(--ink-soft); font-size: 1rem; line-height: 1.5; }
@media (min-width: 700px) {
  .rental-presentation-map { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: var(--s7); }
  .rental-presentation-people { grid-column: 1 / -1; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .rental-presentation-people summary { display: grid; grid-template-columns: minmax(0, 1fr) 16px; }
  .rental-presentation-people summary > div { grid-column: 1 / -1; grid-row: 2; }
  .rental-presentation-people summary > .rental-presentation-toggle { grid-column: 2; grid-row: 1; }
  .rental-presentation-home::after { content: ""; position: absolute; right: calc(-1 * var(--s5)); top: 50%; width: var(--s7); height: 1px; background: var(--rental-diagram-line); }
  .rental-presentation-history { grid-column: 1 / -1; }
  .rental-presentation-timeline { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); }
  .rental-presentation-timeline li { padding: var(--s5) var(--s2) 0 0; }
  .rental-presentation-timeline li::before { top: 0; }
  .rental-presentation-timeline li:not(:last-child)::after { top: 4px; left: 10px; bottom: auto; width: calc(100% + var(--s4)); height: 1px; }
}
@media (min-width: 980px) {
  .rental-presentation-map { grid-template-columns: minmax(0, .9fr) minmax(0, 1.2fr) minmax(0, 1fr); column-gap: var(--s6); }
  .rental-presentation-people { grid-column: auto; grid-template-columns: minmax(0, 1fr); align-content: center; gap: var(--s5); }
  .rental-presentation-people li { position: relative; }
  .rental-presentation-people summary { display: flex; }
  .rental-presentation-people li::after { content: ""; position: absolute; left: 100%; top: 50%; width: var(--s6); height: 1px; background: var(--rental-diagram-line); }
  .rental-presentation-home::after { right: calc(-1 * var(--s4)); width: var(--s6); }
}
@media (prefers-reduced-motion: no-preference) {
  .rental-presentation-house { animation: rental-presentation-appear .6s ease-out both; }
  .rental-presentation-people li, .rental-presentation-modules li { animation: rental-presentation-appear .5s ease-out both; }
  .rental-presentation-people li:nth-child(2), .rental-presentation-modules li:nth-child(2) { animation-delay: .1s; }
  .rental-presentation-people li:nth-child(3), .rental-presentation-modules li:nth-child(3) { animation-delay: .2s; }
  @supports (animation-timeline: view()) {
    .rental-presentation-house, .rental-presentation-people li, .rental-presentation-modules li, .rental-presentation-timeline li {
      animation: rental-presentation-appear ease-out both;
      animation-timeline: view();
      animation-range: entry 0% entry 85%;
    }
  }
}
@keyframes rental-presentation-appear { from { opacity: .45; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }

.rental-panel-intro {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: var(--s6);
}
.rental-panel-intro p { margin: 0; max-width: 720px; }
.rental-invites { margin-bottom: var(--s6); }
.rental-invites .panel-card { margin-top: var(--s3); padding: var(--s5); }
.rental-space-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.rental-space-card { padding: var(--s5); }
.rental-space-card h3 { margin: var(--s3) 0 var(--s2); font-size: 1.2rem; }
.rental-card-top,
.rental-detail-head,
.rental-module-head,
.rental-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}
.rental-detail-head { align-items: flex-start; margin: var(--s5) 0; padding-bottom: var(--s5); border-bottom: 1px solid var(--hairline); }
.rental-detail-head h3 { margin: var(--s3) 0 var(--s2); font-size: clamp(1.35rem, 3vw, 2rem); }
.rental-back { margin-bottom: var(--s1); }
.rental-panel-intro .btn,
.rental-detail-head .btn,
.rental-back.btn-sm,
.rental-danger-zone .btn,
.rental-inline-form .btn,
.rental-invites .btn-sm,
.rental-space-card .btn-sm,
.rental-coming-soon .btn-sm,
.panel-section .btn-sm { min-height: 44px; }
.rental-dashboard { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin: var(--s5) 0 var(--s6); }
.rental-module { min-width: 0; padding: var(--s5); }
.rental-module-head { padding-bottom: var(--s3); border-bottom: 1px solid var(--hairline); }
.rental-module-head h3 { margin: 0; font-size: 1.08rem; }
.rental-module-head .btn-xs,
.rental-row .btn-xs { min-height: 44px; }
.rental-row { align-items: flex-start; padding: var(--s3) 0; border-bottom: 1px solid var(--hairline); }
.rental-row:last-child { border-bottom: 0; padding-bottom: 0; }
.rental-row > div:first-child { min-width: 0; }
.rental-row strong,
.rental-row span { display: block; }
.rental-row strong { margin-bottom: 4px; overflow-wrap: anywhere; }
.rental-row span { color: var(--ink-soft); line-height: 1.45; }
.rental-row > div:last-child { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
.rental-row-stack { align-items: flex-start; }
.rental-empty { padding: var(--s5) 0 var(--s2); color: var(--ink-soft); }
.rental-empty p { margin: 0; }
.rental-consents { display: grid; gap: 12px; margin: var(--s4) 0 var(--s5); }
fieldset.rental-consents { min-width: 0; padding: 0; border: 0; }
fieldset.rental-consents legend { margin-bottom: var(--s2); font-weight: 700; }
.rental-consents .check {
  align-items: flex-start;
  padding: var(--s3);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: 1rem;
  line-height: 1.55;
}
.rental-consents .check input { margin-top: var(--s1); flex: 0 0 auto; }
.rental-permission-grid .check,
.rental-authority-check { font-size: 1rem; line-height: 1.5; }
.rental-legal-check { font-size: 1rem; line-height: 1.55; }
.rental-legal-note.nota { font-size: 1rem; line-height: 1.55; }
.rental-invite-scope { margin: var(--s4) 0; }
.rental-invite-scope > strong { display: block; margin-bottom: var(--s2); }
.rental-permission-summary { display: grid; gap: 0; margin: var(--s2) 0 0; padding: 0; list-style: none; border: 1px solid var(--hairline); border-radius: var(--radius); overflow: hidden; }
.rental-permission-summary li { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); min-height: 44px; padding: var(--s2) var(--s3); border-bottom: 1px solid var(--hairline); }
.rental-permission-summary li:last-child { border-bottom: 0; }
.rental-permission-summary .stamp { flex: 0 0 auto; }
.rental-message-list { display: grid; gap: 12px; margin-bottom: var(--s4); }
.rental-message-list article { padding: var(--s4); border: 1px solid var(--hairline); border-radius: var(--radius); background: var(--bg); }
.rental-message-list article > div { display: flex; justify-content: space-between; gap: 12px; }
.rental-message-list p { margin: var(--s2) 0 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.rental-inline-form { display: grid; gap: 12px; padding: var(--s4); border: 1px solid var(--hairline); background: var(--bg-soft); }
.rental-inline-form .field { margin: 0; }
.rental-timeline { position: relative; display: grid; gap: 0; margin: 0; padding: 0; list-style: none; }
.rental-timeline::before { content: ""; position: absolute; left: 7px; top: 10px; bottom: 10px; width: 1px; background: var(--hairline); }
.rental-timeline li { position: relative; display: grid; grid-template-columns: 16px 1fr; gap: 14px; padding: var(--s3) 0; }
.rental-timeline-dot { position: relative; z-index: 1; width: 15px; height: 15px; margin-top: var(--s1); border-radius: 50%; background: var(--verde); border: 3px solid var(--bg); box-shadow: 0 0 0 1px var(--verde); }
.rental-timeline strong { text-transform: capitalize; }
.rental-timeline p { margin: var(--s1) 0 0; color: var(--ink-soft); }
.rental-danger-zone { margin-top: var(--s6); padding: var(--s5); border: 1px solid color-mix(in srgb, var(--terracota) 45%, var(--hairline)); background: color-mix(in srgb, var(--terracota) 5%, var(--bg)); }
.rental-danger-zone h3 { margin-top: 0; }
.rental-row-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
.rental-pending-access { margin-top: var(--s4); padding-top: var(--s4); border-top: 1px solid var(--hairline); }
.rental-pending-access h4 { margin: 0; font-family: var(--font-body); font-size: .92rem; }
.rental-permission-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: var(--s4) 0; padding: var(--s4); border: 1px solid var(--hairline); }
.rental-permission-grid legend { padding: 0 8px; font-weight: 700; }
.rental-permission-grid .check { align-items: flex-start; min-width: 0; padding: var(--s3); background: var(--bg-soft); }
.rental-permission-grid input { flex: 0 0 auto; margin-top: var(--s1); }
.rental-permission-grid .rental-authority-check { box-sizing: border-box; min-height: 44px; margin-top: var(--s2); }

@media (max-width: 760px) {
  .rental-benefit-grid,
  .rental-space-grid,
  .rental-dashboard { grid-template-columns: 1fr; }
  .rental-panel-intro,
  .rental-detail-head { flex-direction: column; align-items: stretch; }
  .rental-detail-head .cta { display: grid; grid-template-columns: 1fr; }
  .rental-detail-head .cta .btn { width: 100%; }
  .rental-permission-grid { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
  .rental-card-top,
  .rental-row,
  .rental-message-list article > div { align-items: flex-start; flex-direction: column; }
  .rental-row > div:last-child { justify-content: flex-start; }
  .rental-row-actions { width: 100%; justify-content: flex-start; }
  .rental-row-actions .btn { flex: 1 1 140px; }
  .rental-module,
  .rental-space-card { padding: var(--s4); }
}
@font-face {
  font-family: "Fraunces Variable";
  src: url("../fonts/Fraunces-Italic-Variable.woff2") format("woff2"),
       url("../fonts/Fraunces-Italic-Variable.ttf") format("truetype");
  font-weight: 400 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Archivo Variable";
  src: url("../fonts/Archivo-Variable.woff2") format("woff2"),
       url("../fonts/Archivo-Variable.ttf") format("truetype");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-Regular.woff2") format("woff2"),
       url("../fonts/IBMPlexMono-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("../fonts/IBMPlexMono-SemiBold.woff2") format("woff2"),
       url("../fonts/IBMPlexMono-SemiBold.ttf") format("truetype");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

/* ---------- 1. RESET + TOKENS ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
/* ---------- RITMO VERTICAL (23-ago-2026) ----------
   La linea de arriba pone a CERO el margen de titulos, parrafos y listas. Como reset esta bien,
   pero nadie repone luego el ritmo: cada componente tiene que acordarse de su propio margen, y
   donde nadie se acordo el contenido sale pegado. Ese es el origen real de "titulos pegados a
   parrafos" que señalo Pol el 23-ago.
   Se repone con selectores de ADYACENCIA (`+`), no con un margin suelto en h2/p: asi solo actua
   cuando hay DOS elementos seguidos -- que es exactamente el caso "pegado" -- y no añade hueco a
   un titulo que vive solo dentro de una tarjeta ni a un parrafo unico. Especificidad 0,0,2: la
   pisa cualquier regla de componente que ya tenga su propio margen, asi que no rompe nada de lo
   que ya estaba bien colocado. */
h1 + p, h2 + p, h3 + p, h4 + p,
h1 + ul, h2 + ul, h3 + ul, h4 + ul,
p + p, p + ul, ul + p { margin-top: var(--s3); }
/* Un titulo seguido de otro (h2 de seccion + h3 de su primer apartado): sin esto salen pegados,
   que es el mismo agujero de adyacencia de siempre visto desde otro lado. Hueco menor que el de
   seccion: el h3 PERTENECE al h2 que tiene encima, y el espacio tiene que decirlo. */
h2 + h3, h3 + h4 { margin-top: var(--s4); }
p + h2, ul + h2, p + h3, ul + h3 { margin-top: var(--s6); }
/* Las adyacencias que FALTABAN (24-ago-2026). Se vio en /precio-justo: el h2 "La fórmula, en 4
   pasos" salía pegado a la caja verde de arriba, porque el ritmo cubría `p + h2` y `ul + h2` pero
   no `div + h2` -- y la caja es un <div class="note-box">. Los otros cinco h2 de esa página no lo
   sufrían porque llevaban un margen de 40 px escrito a mano en el propio atributo style: no era una regla, era un apaño
   repetido cinco veces y olvidado una. Se quitaron los cinco y el hueco lo pone esto.
   NO se pone `div + h2` a secas, que es medio sitio: solo los bloques de CONTENIDO que de verdad
   preceden a un título (nota, lista numerada, tabla, tarjeta de resultado).
   Mismo paso --s6 que `p + h2`, a propósito: el hueco delante de un título no puede depender de
   qué clase de bloque venga antes -- esa dependencia es justo la enfermedad que se está curando. */
ol + h2, ol + h3,
.note-box + h2, .note-box + h3,
.tbl-wrap + h2, .tbl-wrap + h3,
.panel-card + h2, .panel-card + h3,
.pj-resultado + h2, .pj-resultado + h3 { margin-top: var(--s6); }
p + .cta, ul + .cta, h2 + .cta, h3 + .cta,
p + .btn, ul + .btn { margin-top: var(--s4); }
img, picture { max-width: 100%; display: block; }
/* Nada rompe la caja por la derecha (23-ago-2026, Pol: "fijate que no se corte por la derecha
   nada, todos los margenes bien ajustados").
   Medido antes de tocar: el documento NO tenia scroll horizontal (scrollWidth == clientWidth a
   430px), asi que no habia un desborde que arreglar hoy. Esto es la red para que no aparezca
   manana: la causa tipica en movil es una cadena sin espacios -- una URL pegada, un email largo,
   un titulo de anuncio en MAYUSCULAS SIN_ESPACIOS -- que no puede partirse y empuja su contenedor.
   `overflow-wrap: anywhere` la parte en vez de reventar el ancho. No afecta al texto normal.
   Las tablas y los bloques de codigo se acotan por lo mismo. */
body { overflow-wrap: anywhere; }
table { max-width: 100%; }
pre, code { overflow-wrap: anywhere; white-space: pre-wrap; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; }
a { color: inherit; text-decoration: none; }
[hidden] { display: none !important; }

:root {
  /* ----- paleta (tokens EXACTOS del brand guide — la única fuente de color) ----- */
  --bg: #ffffff;                       /* blanco puro, fondo principal */
  --bg-soft: #f7f7f5;                  /* gris cálido muy suave: hover, secciones alternas, superficie "hundida" de inputs/meta-datos */
  --ink: #1a1a1a;                      /* negro casi puro: títulos, precios, botones sólidos (línea) */
  --ink-soft: #5c5c5c;                 /* gris medio: cuerpo, micro-datos, labels */
  --hairline: #e6e4df;                 /* gris cálido muy claro: separadores, bordes sutiles */
  --line-strong: #d8d5cd;              /* gris cálido un paso más oscuro: bordes de tarjeta y botones de línea */
  --primary: #0f7a4d;                  /* verde confianza: CTAs, éxito, "precio justo" — el ÚNICO acento de acción */
  --primary-dark: #0a5c3a;             /* verde hover */
  --alert: #b6482a;                    /* terracota: SOLO alertas, errores, "fuera de rango" — nunca decorativo */
  --ocre: #9a6b00;                     /* aviso / estado "pendiente" */
  --border-card: 1px solid var(--line-strong);

  /* ----- tintas derivadas (rgba de los acentos base, mismo patrón que
     ink-soft/hairline/line-strong) — para fondos de insignia/estado y anillos
     de foco, siempre <=.22 de opacidad. ----- */
  --primary-08: rgba(15, 122, 77, .08);
  --primary-14: rgba(15, 122, 77, .14);
  --primary-20: rgba(15, 122, 77, .20);
  --alert-08: rgba(182, 72, 42, .08);
  --alert-14: rgba(182, 72, 42, .14);
  --alert-22: rgba(182, 72, 42, .22);
  --ocre-16: rgba(154, 107, 0, .16);
  --ink-08: rgba(26, 26, 26, .06);

  /* ----- forma: esquinas mínimas, sensación de herramienta ----- */
  /* ----- escala de ESPACIADO (23-ago-2026) -----
     Hasta hoy :root tenia 34 tokens de color, tipo y sombra y CERO de espacio: el CSS usaba 37
     valores distintos de pixel (1,2,3,4,5,6,7,8,9,10,11,12,13,14,16,18,20,22,24...) mas 181
     estilos de margen escritos a mano dentro de js/*.js. No era una escala, era lo que salio en
     cada momento -- y por eso los "botones pegados" reaparecian en otra pantalla cada vez que se
     arreglaban donde se veian (ver scripts/test/test-botones-pegados.js, que nacio de que Pol lo
     señalara DOS veces el mismo dia).
     Siete pasos. Todo espaciado nuevo sale de aqui; lo viejo se migra por tandas. */
  --s1: 4px;   /* pegado a proposito: icono y su texto */
  --s2: 8px;   /* dentro de un mismo control */
  --s3: 12px;  /* entre elementos hermanos muy relacionados */
  --s4: 16px;  /* ritmo por defecto entre bloques de contenido */
  --s5: 24px;  /* separacion entre grupos distintos */
  --s6: 32px;  /* entre secciones dentro de una pagina */
  --s7: 48px;  /* entre secciones mayores */

  --radius: 4px;

  /* ----- sombra: SIEMPRE suave, nunca offset duro ni difusa tipo glass ----- */
  --shadow-card: 0 1px 2px rgba(26, 26, 26, .08);          /* reposo de tarjetas/paneles/inputs */
  --shadow-card-hover: 0 4px 16px rgba(26, 26, 26, .10);   /* tarjetas al hover */
  --shadow-float: 0 16px 40px rgba(26, 26, 26, .16);       /* overlays flotantes: modal/dropdown/toast/cookie banner */

  /* ----- tipografía ----- */
  --font-display: "Fraunces Variable", Georgia, serif;
  --font-body: "Archivo Variable", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", monospace;

  --container: 1180px;
  /* Relleno lateral del contenedor. Es un TOKEN porque hay bloques (el hero) que tienen que
     salirse EXACTAMENTE esa distancia para llegar de borde a borde. Con el numero escrito a mano
     el hero se salia 2px por lado a 360 y 430px y el sitio entero se movia de lado. */
  --wrap-pad: 18px;
  --header-h: 64px;
  --marquee-h: 30px;                   /* alto de la franja superior (.pj-marquee) */
  --chrome-h: calc(var(--header-h) + var(--marquee-h)); /* alto TOTAL del chrome fijo (marquee + top-bar) */

  /* ----- puente de compatibilidad (NO tocar sin revisar antes js/ui-pages.js,
     js/ui-public.js y js/ui-tasador.js): escriben style="color:var(--ac2)" /
     var(--soft) / var(--verde) / var(--terracota) inline al construir HTML por
     string (el gráfico de tendencia del tasador en particular pinta la línea
     subiendo/bajando con estos dos últimos). Se mantiene el mismo puente que ya
     existía (--ac2/--soft), ampliado con los dos nombres de color viejos que
     el rediseño 2026 renombró (--verde -> --primary, --terracota -> --alert),
     para que ese texto inline no se quede sin color. */
  --ac2: var(--primary);
  --soft: var(--ink-soft);
  --verde: var(--primary);
  --terracota: var(--alert);
}

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  font-synthesis: none; /* itálica y variable-weight reales están servidas; nunca fingir negrita/cursiva */
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

body {
  font-family: var(--font-body);
  background: var(--bg);
  color: var(--ink-soft);
  font-size: 1rem;
  line-height: 1.55;
  min-height: 100vh;
  position: relative;
  isolation: isolate;
  -webkit-tap-highlight-color: transparent;
  padding-top: var(--chrome-h);
}

/* ---------- FONDO: blanco funcional, sin tinte decorativo. --sc/--th los
   sigue escribiendo ui-core.js (tick() en js/ui-core.js) — mecánica intacta,
   simplemente ya no hay nada que pintar con ellos (herramienta, no revista). */

h1, h2, h3, h4 { font-family: var(--font-display); color: var(--ink); font-weight: 600; line-height: 1.12; }
/* H1: cada pageShell() de app.js es, en la práctica, el "hero" de su propia
   página (home incluida) -> escala grande. Excepción deliberada: dentro del
   panel (ui-panel.js:368 pinta un <h1>Hola, Nombre</h1> como saludo de
   dashboard, mismo tag, contexto MUY distinto) se acota para no reventar la
   columna de 1fr junto al nav de 240px — no hay forma de diferenciarlo sin
   tocar JS/HTML, así que se resuelve por selector descendente sobre
   .panel-layout/.panel-content, que ya existen. */
h1 { font-size: clamp(2.2rem, 1.4rem + 3.6vw, 4.6rem); letter-spacing: -.01em; }
.panel-layout h1, .panel-content h1 { font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.4rem); }
/* Palabra acentuada del H1 (p.ej. "Alquilá <em>justo</em>."): itálica real de
   Fraunces en --primary — pre-cableado para cuando el marcado envuelva la
   palabra en <em>/<i>. */
h1 em, h1 i { font-style: italic; font-weight: 400; color: var(--primary); }
h2 { font-size: clamp(1.5rem, 1.15rem + 1.3vw, 2.2rem); }
h3 { font-size: clamp(1.15rem, 1.05rem + .3vw, 1.3rem); }
h4 { font-size: 1.05rem; }
p { color: var(--ink-soft); font-size: 1rem; }
small { color: var(--ink-soft); font-size: .82em; }

/* ---------- 2. PRIMITIVAS DE LAYOUT ---------- */
#app { position: relative; z-index: 1; min-height: 60vh; }

.page-wrap {
  max-width: var(--container);
  margin: 0 auto;
  padding: 28px var(--wrap-pad) 56px;
  position: relative;
}
@media (min-width: 720px) { :root { --wrap-pad: 28px; } .page-wrap { padding: 40px var(--wrap-pad) 72px; } }
@media (min-width: 1200px) { :root { --wrap-pad: 0px; } .page-wrap { padding: 52px var(--wrap-pad) 88px; } }

.kicker {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--primary);
  margin-bottom: 8px;
}
.lead { font-size: 1.06rem; color: var(--ink-soft); max-width: 56ch; margin-top: 10px; }

/* .page-cta-profesional (30-ago-2026, pedido de Pol): CTA pequeño hacia /colaboradores desde el
   Tasador, para captar profesionales (tasadores) que buscan la pagina, no solo compradores. Vive
   arriba a la derecha del titulo, apoyado en position:relative de .page-wrap -- no depende del
   orden del DOM porque pageShell() renderiza kicker+h1 antes del body de cada pagina. Solo en
   desktop (md+): en mobile el titulo ya ocupa el ancho completo y un absolute ahi se solaparia. */
.page-cta-profesional {
  display: none;
}
@media (min-width: 720px) {
  .page-cta-profesional {
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    position: absolute;
    top: 28px;
    right: var(--wrap-pad);
    font-size: .82rem;
    font-weight: 600;
    color: var(--primary);
    border: 1px solid var(--primary);
    border-radius: 999px;
    padding: var(--s2) var(--s4);
    white-space: nowrap;
  }
  .page-cta-profesional:hover { background: var(--primary); color: #fff; }
}

/* .grid: contenedor de tarjetas de propiedad/demanda (propCard/demCard/panelPropCard/
   panelDemandCard) — lista apilada de una columna (filas anchas foto-izquierda +
   contenido-derecha, ver .card más abajo), no una grilla de mosaico. La densidad
   viene de la fila ancha, no de amontonar columnas. */
.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin-top: 22px;
}

.loading {
  padding: 60px 10px;
  text-align: center;
  color: var(--ink-soft);
  font-style: italic;
}
/* el aro giratorio es una excepción funcional deliberada: un loader circular ES
   el símbolo universal de carga. Es forma mecánica, no decoración. */
.loading::before {
  content: "";
  display: block;
  width: 34px; height: 34px;
  margin: 0 auto 14px;
  border-radius: 50%;
  border: 3px solid var(--line-strong);
  border-top-color: var(--primary);
  animation: pj-spin .8s linear infinite;
}
@keyframes pj-spin { to { transform: rotate(360deg); } }

.blank {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: .84em;
  color: var(--ocre);
  background: var(--ocre-16);
  border-bottom: 1px dashed var(--ocre);
  padding: 1px 6px;
  border-radius: var(--radius);
}

/* ---------- 2.5. FRANJA SUPERIOR (marquee) — tira fina y fija, SIEMPRE por encima del
   top-bar (que baja su `top` a --marquee-h para dejarle sitio; body/.panel-side ya suman
   --chrome-h = --header-h + --marquee-h). Contenido duplicado x2 en el HTML (index.html) para
   que translateX(-50%) cierre el bucle sin salto. Fondo tinta (separa visualmente la franja
   del top-bar blanco), separador en verde primario. */
/* 19-ago: ya no es una pista animada (ver el comentario en index.html) -- una sola línea fija,
   centrada, sin scroll ni bucle. Se conserva el mismo alto (--marquee-h) para no descuadrar el
   offset del header/modales que ya cuentan con esta franja arriba. */
.pj-marquee {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 30;
  height: var(--marquee-h);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ink);
}
.pj-marquee-item {
  padding: 0 20px;
  font-family: var(--font-mono);
  font-size: .64rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--bg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* ---------- 3. HEADER / TOP-BAR ---------- */
.top-bar {
  position: fixed;
  top: var(--marquee-h); left: 0; right: 0;
  z-index: 30;
  height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 0 16px;
  background: var(--bg); /* sólido: nada de vidrio/blur */
  border-bottom: 1px solid var(--hairline);
}
@media (min-width: 900px) { .top-bar { padding: 0 32px; } }

.logo {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.3rem;
  color: var(--ink);
  letter-spacing: -.01em;
  white-space: nowrap;
}
.logo span { color: var(--primary); } /* "Real" en el acento de marca */
/* La marca tambien es un enlace de navegacion. Su caja visual medía 32px y el botón de acceso
   heredaba los 40px de .btn-sm en escritorio; el resto de la cabecera ya trabaja con 44px.
   Se igualan aquí, sin agrandar los botones compactos del resto de la aplicación. */
.top-bar > .logo,
.top-bar #topUser .btn-sm { min-height: 44px; }
.top-bar > .logo { display: inline-flex; align-items: center; }

/* hallazgo real 18-ago (Pol no encontraba /guia -- no estaba en el menú): al sumarla, el menú de
   9 links + "Ingresar" YA NO cabía a partir de los 800px donde encendía -- y medido en vivo (no a
   ojo), resultó que con los 8 links de ANTES tampoco cabía del todo (se salía ~62px, invisible
   porque .top-bar es position:fixed y eso NO cuenta para el scrollWidth del documento -- un
   overflow "silencioso" que no dispara ningún chequeo de scroll horizontal). El breakpoint subió a
   1060px con 9 links (medido con getBoundingClientRect) y el gap bajó de 20 a 14px para no perder
   de golpe el rango 900-1050px sin necesidad. Con el 10º link (Panorama, mismo día) el umbral real
   volvió a moverse -- medido de nuevo, esta vez cabe recién a partir de 1078px -- así que el
   breakpoint se puso entonces en 1090px. Después el menú pasó de enlaces simples a cuatro grupos
   con rótulos más largos y sumó sus controles de cuenta: medido de nuevo el 30-ago, entre 1090 y
   1359px la fila obliga a comprimir o truncar el logo. Por eso el escritorio empieza en 1360px; por
   debajo se usa el panel lateral completo, no una versión apretada de la cabecera. Cada vez que se
   sume un punto de entrada hay que VOLVER A MEDIR este número (no asumirlo). */
/* ---- MENU: escritorio en fila, movil en panel lateral (20-ago-2026) ----
   ANTES de esta fecha el bloque de abajo era solo `display:none` + el @media de 1090px, sin
   ninguna alternativa por debajo: en movil NO habia menu. Ahora el mismo <nav> cumple los dos
   papeles y no se duplica el marcado (una sola lista de enlaces que mantener):
     - >=1360px: fila horizontal. El boton hamburguesa se oculta.
     - <1360px : panel deslizante desde la derecha, oculto hasta que .nav-open entra en <body>.
   El panel se pinta con translateX en vez de display para que la transicion exista, y se saca del
   arbol de accesibilidad con visibility:hidden cuando esta cerrado -- sin eso, los 10 enlaces
   siguen siendo tabulables aunque no se vean, y el tabulador se "pierde" fuera de la pantalla. */
.top-bar nav {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: stretch;
  position: fixed;
  top: 0; right: 0; bottom: 0;
  /* Deja siempre una franja de 48px del fondo para que "tocar fuera" sea una salida evidente,
     pero aprovecha mejor el ancho en telefonos estrechos que el antiguo 84vw. */
  width: min(340px, calc(100vw - 48px));
  z-index: 45;                        /* por encima del velo (40) y de la top-bar (30) */
  background: var(--bg);
  border-left: 1px solid var(--hairline);
  box-shadow: var(--shadow-float);
  padding: calc(var(--chrome-h) + 12px) 18px 24px;
  overflow-y: auto;
  overscroll-behavior: contain;       /* al llegar al final no arrastra el scroll de la pagina */
  transform: translateX(100%);
  visibility: hidden;
  transition: transform .22s ease, visibility .22s;
}
body.nav-open .top-bar nav { transform: translateX(0); visibility: visible; }
/* En el panel lateral de movil, los seis puntos de primer nivel tampoco llevaban separacion: el
   punto medio es solo de escritorio (ver la media query de 1360px), porque en vertical no
   significa nada. Aqui les toca la misma linea fina que a los desplegables, para que "separar las
   opciones del menu" sea cierto en las dos pantallas y no solo en una. */
@media (max-width: 1359px) {
  /* En movil y tablet la hamburguesa, el idioma y la sesion forman un unico grupo a la derecha.
     La accion de publicar ya vive, con su texto completo, dentro de "Publicar / Soy agente":
     repetirla como icono verde apretaba la cabecera y parecia un segundo boton de inicio. */
  .top-bar #topUser { margin-left: 0; }
  .top-bar #topUser [data-action="pub-open"] { display: none; }
  .top-bar nav > *:not(:last-child) {
    border-bottom: 1px solid var(--hairline);
    padding-bottom: var(--s2);
    margin-bottom: var(--s2);
  }
  /* El panel se dibuja por encima de la cabecera. Sin elevar tambien el mismo boton que lo abrio,
     el aspa de cierre quedaba tapada por la hoja blanca: se podia cerrar con Escape o tocando el
     velo, pero no habia una salida visible dentro del menu. */
  body.nav-open .nav-toggle {
    position: fixed;
    top: calc(var(--marquee-h) + 10px);
    right: 18px;
    z-index: 46;
    margin: 0;
    background: var(--bg-soft);
    border-color: var(--hairline);
    box-shadow: var(--shadow-card);
  }
  .top-bar nav > a,
  .nav-group-btn { min-height: 52px; }
  .nav-group.activo > .nav-group-btn {
    color: var(--primary);
    background: var(--primary-08);
  }
}

/* ⚠️ EL BUG QUE ESTO ARREGLA (22-ago-2026, tercera vez que Pol reporta "el menu movil no funciona"
   -- P11 y P48). Las dos veces anteriores se busco en el JavaScript y el JavaScript estaba BIEN:
   el fallo era de apilamiento CSS, y por eso los arreglos anteriores no sirvieron.

   `.top-bar` tiene `position:fixed; z-index:30`, asi que **crea un contexto de apilamiento**.
   `.top-bar nav` vive DENTRO y declara `z-index:45` con el comentario "por encima del velo (40)".
   Esa intencion NO se cumple: un z-index solo compite dentro del contexto de su padre, y el hijo
   no puede escapar de el. Frente al velo -- que es hijo de <body>, fuera del header -- lo que
   compite es el 30 del PADRE. Y 40 > 30, asi que **el velo quedaba ENCIMA del menu**.

   Sintomas exactos, los mismos que describia Pol: el panel se ve pero atenuado en gris (el velo
   semitransparente esta delante) y no responde a los toques (el velo captura los clics, y como
   lleva data-action="nav-close", tocar un enlace CIERRA el menu en vez de navegar). El boton
   hamburguesa, tambien dentro de .top-bar, quedaba igual de inalcanzable.

   Arreglo: mientras el menu esta abierto, la propia .top-bar sube por encima del velo. Asi el
   contexto entero (barra + nav + hamburguesa) queda delante, y el velo sigue tapando el resto de
   la pagina, que es su unico trabajo. No se toca el z-index del nav ni la estructura del HTML. */
body.nav-open .top-bar { z-index: 45; }
.top-bar nav a {
  font-size: 1rem; font-weight: 700; color: var(--ink);
  padding: 12px 10px;                 /* alto de toque comodo: 44px reales, guia de Apple/Google */
  border-radius: var(--radius);
}
.top-bar nav a:hover { background: var(--bg-soft); text-decoration: none; }
.top-bar nav a[aria-current="page"] { color: var(--primary); background: var(--primary-08); }
/* ---- Grupos desplegables del menu (22-ago-2026, decisiones.md punto 3) ----
   Sustituyen al antiguo .nav-pub-btn, que era un CTA suelto al principio del panel movil.
   ⚠️ BUG REAL QUE ESTO CIERRA (encontrado 22-ago con Pol viendo la web en escritorio): aquel
   .nav-pub-btn se ocultaba en escritorio con "@media (min-width:1090px){ .nav-pub-btn{display:none} }"
   -- pero el boton tambien llevaba la clase .btn, y ".btn{display:inline-flex}" esta MAS ABAJO en
   este mismo archivo. Misma especificidad (una clase cada una) y un @media no aporta especificidad,
   asi que ganaba la ultima escrita: el display:none nunca llegaba a aplicarse y salian DOS botones
   "Pon tu anuncio gratis" a la vez en escritorio. Ahora el CTA vive dentro del desplegable
   "Publicar / Soy agente" (.nav-drop-cta, sin la clase .btn) y no hay nada que ocultar por media
   query -- se arregla por estructura, no parcheando el peso del selector.
   Mismo marcado en los dos tamaños: aqui (movil) el desplegable se abre EN EL FLUJO y empuja lo de
   abajo; el @media de escritorio de mas abajo lo convierte en panel flotante. */
/* Los rotulos del menu NO se parten por dentro (24-ago-2026, visto en captura a 1120px de ancho:
   "Tasad/or", "Merca/do", "Ayu/da"). La causa no estaba aqui sino en `body { overflow-wrap: anywhere }`,
   la red que se puso para que una URL sin espacios no reviente el ancho en movil. Efecto colateral:
   con `anywhere` el ancho minimo de un enlace pasa a ser UNA LETRA, asi que flex lo encoge por
   debajo de su palabra y la corta a mitad. Volviendo a `normal` el minimo es la palabra entera y el
   menu deja de encogerse ahi -- reparte o baja de linea, pero no destroza el rotulo.
   Se acota al menu y no se toca la regla global: en el cuerpo de un anuncio, partir una URL larga
   sigue siendo lo correcto. Un menu no tiene cadenas sin espacios que partir. */
.main-nav a, .main-nav button, .nav-group-btn { overflow-wrap: normal; }
.nav-group { display: flex; flex-direction: column; }
.nav-group-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  width: 100%;
  font: inherit; font-size: 1rem; font-weight: 700; color: var(--ink);
  text-align: left;
  padding: 12px 10px;                 /* mismo alto de toque que los enlaces sueltos del panel */
  background: none; border: 0; border-radius: var(--radius);
  cursor: pointer;
}
.nav-group-btn:hover { background: var(--bg-soft); }
.nav-group-arrow { font-size: .7em; line-height: 1; transition: transform .18s ease; }
.nav-group.open > .nav-group-btn .nav-group-arrow { transform: rotate(180deg); }
/* El desplegable no usa el atributo [hidden]: con display gestionado por clase, el CSS de
   escritorio y el de movil no tienen que pelearse contra la regla del user-agent. */
.nav-group-drop { display: none; flex-direction: column; }
/* SEPARADOR DENTRO DE LOS DESPLEGABLES (25-ago-2026). Pol pidio separar "las diferentes opciones
   del menu" y yo solo lo hice en los seis puntos de PRIMER NIVEL; dentro de los desplegables
   seguian pegadas. Esto lo completa.
   Aqui NO puede ser el punto medio de la cabecera: alli las opciones van en FILA y un "·" cae en el
   hueco entre dos; aqui van APILADAS, y el equivalente vertical de un punto es una linea fina. Es
   la misma idea -- separar -- con la forma que corresponde a cada eje.
   --hairline y no --line-strong: separa sin dibujar una reja. Y :last-child fuera, que una linea
   bajo la ultima opcion no separa nada, solo subraya el borde del panel. */
.nav-group-drop > *:not(:last-child) { border-bottom: 1px solid var(--hairline); }
.nav-group.open > .nav-group-drop { display: flex; }
/* Sangria de los hijos del grupo. El selector lleva ".top-bar nav" por delante a proposito: sin
   eso pesa (0,1,1) y pierde contra ".top-bar nav a" (0,1,2), que ya fija el padding de los enlaces
   del panel -- la sangria no se aplicaria nunca. Mismo tipo de fallo de peso que causaba el doble
   CTA descrito arriba. */
.top-bar nav .nav-group-drop a, .nav-drop-cta { padding-left: 22px; }
/* Los de PRIMER NIVEL acaban de subir a peso 700 (25-ago-2026, Pol: "los principales tienen que
   resaltarse un poco mas"); sin esto los hijos del desplegable suben con ellos por herencia y la
   jerarquia vuelve a aplanarse. Bajan a 500 y a --ink-soft para que el contraste sea real, no solo
   la sangria de 22px. */
.top-bar nav .nav-group-drop a:not([aria-current="page"]) { font-weight: 500; color: var(--ink-soft); }
.nav-drop-cta {
  font: inherit; font-size: 1rem; font-weight: 600; color: var(--primary);
  text-align: left; padding: 12px 10px 12px 22px;
  background: none; border: 0; border-radius: var(--radius); cursor: pointer;
}
.nav-drop-cta:hover { background: var(--bg-soft); }

/* Velo: solo existe con el menu abierto. Cierra al tocarlo. */
.nav-scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(26, 26, 26, .38);
  opacity: 0; transition: opacity .22s ease;
}
body.nav-open .nav-scrim { opacity: 1; }

/* Boton hamburguesa: tres barras hechas con un solo <span> (la del medio es el propio elemento,
   las otras dos sus pseudoelementos). Al abrir se convierte en aspa. 44x44 de area de toque. */
.nav-toggle {
  margin-left: auto;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: none; border: 1px solid transparent; border-radius: var(--radius);
  cursor: pointer;
}
.nav-toggle:hover { background: var(--bg-soft); }
.nav-toggle-bars, .nav-toggle-bars::before, .nav-toggle-bars::after {
  content: ""; display: block;
  width: 20px; height: 2px; border-radius: 2px;
  background: var(--ink);
  transition: transform .2s ease, opacity .2s ease;
}
.nav-toggle-bars { position: relative; }
.nav-toggle-bars::before { position: absolute; top: -6px; }
.nav-toggle-bars::after  { position: absolute; top: 6px; }
body.nav-open .nav-toggle-bars { background: transparent; }             /* la barra del medio se va */
body.nav-open .nav-toggle-bars::before { transform: translateY(6px) rotate(45deg); }
body.nav-open .nav-toggle-bars::after  { transform: translateY(-6px) rotate(-45deg); }

@media (min-width: 1360px) {
  .nav-toggle, .nav-scrim { display: none; }
  .top-bar nav {
    position: static;
    flex-direction: row;
    gap: 6px;
    align-items: center;
    width: auto;
    background: var(--bg-soft);
    border: 1px solid var(--hairline);
    border-radius: calc(var(--radius) + 4px);
    box-shadow: none;
    padding: 3px;
    overflow: visible;
    transform: none; visibility: visible;
  }
  .top-bar nav > a {
    display: flex;
    align-items: center;
    min-height: 44px;
    font-size: .94rem;
    font-weight: 600;
    color: var(--ink-soft);
    padding: 8px 10px;
    border: 0;
    border-radius: calc(var(--radius) + 2px);
  }
  .top-bar nav > a:hover {
    color: var(--primary);
    background: var(--bg);
    text-decoration: none;
  }
  .top-bar nav > a[aria-current="page"] {
    color: var(--primary);
    background: var(--primary-08);
  }

  /* Grupos en escritorio: el boton se ve como un item mas de la fila y su desplegable FLOTA
     (position:absolute) en vez de empujar la cabecera hacia abajo. El .nav-group es el contexto
     de posicionamiento, de ahi el position:relative. */
  .nav-group { position: relative; flex-direction: row; align-items: center; }
  .nav-group-btn {
    width: auto;
    min-height: 44px;
    font-size: .94rem;
    color: var(--ink-soft);
    padding: 8px 10px;
    border: 0;
    border-radius: calc(var(--radius) + 2px);
  }
  .nav-group-btn:hover { color: var(--primary); background: var(--bg); }
  /* .activo: el grupo se marca cuando la pagina abierta es una de sus hijas (js/app.js,
     marcarEnlaceActivo) -- si no, al entrar en /propiedades el menu no señalaba nada y se perdia
     el "donde estoy" que si tenian las 10 pestañas sueltas. */
  .nav-group.activo > .nav-group-btn { color: var(--primary); background: var(--primary-08); }
  .nav-group.open > .nav-group-btn { color: var(--primary); background: var(--bg); }

  /* SEPARADOR ENTRE OPCIONES DEL MENU (25-ago-2026, Pol: "quiero separar con algun punto o algo
     las diferentes opciones del menu"). Cuatro decisiones, cada una con motivo:
     1) Va DENTRO de la media query de 1360px: en movil el mismo marcado se apila en vertical y un
        punto "entre dos cosas de una fila" ahi no significa nada.
     2) Es un ::after, no un caracter en el HTML: un lector de pantalla no debe leer "punto" entre
        cada opcion. Los pseudo-elementos decorativos no se anuncian.
     3) Separa los SIETE puntos de entrada de primer nivel (hijos directos del nav), no los enlaces
        de dentro de los desplegables: lo que Pol quiere distinguir son las opciones del menu, no
        el contenido de cada grupo.
     4) NO despues del ultimo, y con position:absolute + un gap ampliado a 20px para que el punto
        viva EN el hueco y no sume ancho a la fila. Esto ultimo importa: el 24-ago, a ~1120px, el
        menu ya partia palabras por dentro ("Tasad/or"); un separador que ocupase sitio lo habria
        vuelto a romper. Medido despues del cambio en test-desbordes.js. */
  /* El separador sigue sin costar layout: ahora es una barra fina dentro del hueco de 6px, mas
     limpia que la hilera de puntos de la version anterior. Los botones cortos viven sobre una
     unica superficie suave, así que se leen como un sistema y no como texto corrido. */
  .top-bar nav > *:not(:last-child) { position: relative; }
  .top-bar nav > *:not(:last-child)::after {
    content: "│";
    position: absolute;
    right: -5px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--line-strong, #d8d8d8);
    font-size: .7rem;
    font-weight: 400;
    pointer-events: none;
  }
  .nav-group > .nav-group-drop {
    position: absolute;
    top: calc(100% + 10px); left: 50%; transform: translateX(-50%);
    min-width: 210px;
    z-index: 50;                      /* por encima de la top-bar (30) */
    background: var(--bg);
    border: 1px solid var(--hairline); border-radius: var(--radius);
    box-shadow: var(--shadow-float);
    padding: 6px;
  }
  .nav-group > .nav-group-drop a, .nav-group > .nav-group-drop .nav-drop-cta {
    display: flex; align-items: center; min-height: 44px;
    font-size: .93rem; padding: 10px 12px; border-radius: var(--radius);
    border-bottom: 0; white-space: nowrap;
  }
  /* Separador DENTRO del desplegable. Va aqui abajo y con .top-bar delante a proposito, no por
     capricho: la regla de arriba pone border-bottom:0 con (0,2,1) y la de :hover con (0,3,1). Mi
     primer intento -- .nav-group-drop > *:not(:last-child), (0,3,0) -- lo perdio en silencio: el
     navegador daba 0px y la linea no salia. Con .top-bar delante son (0,4,0) y gana a las dos, asi
     que la linea tampoco desaparece al pasar el raton por encima.
     Septimo caso este mes de la misma familia: una competencia de especificidad que no avisa. */
  .top-bar .nav-group > .nav-group-drop > *:not(:last-child) {
    border-bottom: 1px solid var(--hairline);
  }
  .nav-group > .nav-group-drop a:hover, .nav-group > .nav-group-drop .nav-drop-cta:hover {
    background: var(--bg-soft); color: var(--primary); border-color: transparent;
  }
  .nav-group > .nav-group-drop a[aria-current="page"] { background: var(--primary-08); border-color: transparent; }
  .nav-group > .nav-group-drop .nav-drop-cta { color: var(--primary); font-weight: 700; }
}

/* Enlace de salto: fuera de la pantalla hasta que se enfoca con el tabulador. Se coloca ARRIBA del
   todo (z-index por encima del chrome fijo) para que sea lo primero visible al tabular. */
.skip-link {
  position: fixed; top: 8px; left: 8px; z-index: 100;
  transform: translateY(-200%);
  background: var(--ink); color: var(--bg);
  padding: 10px 16px; border-radius: var(--radius);
  font-weight: 600; text-decoration: none;
}
.skip-link:focus { transform: translateY(0); }

#topUser { display: flex; align-items: center; gap: 8px; margin-left: auto; position: relative; }

.chip {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 12px 5px 5px;
  border-radius: var(--radius);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  transition: background .15s ease, border-color .15s ease;
}
.chip:hover { background: var(--primary-08); border-color: var(--primary); text-decoration: none; }
.chip .av {
  width: 28px; height: 28px;
  border-radius: var(--radius);
  display: grid; place-items: center;
  background: var(--primary);
  color: #fff;
  font-size: .72rem; font-weight: 700;
  flex-shrink: 0;
}
.chip b { display: block; font-size: .82rem; line-height: 1.15; color: var(--ink); }
.chip small { display: block; line-height: 1.1; }

/* chip-sm: etiqueta compacta (panelAgentesAdmin en ui-panel.js — propiedad asignada a un agente) */
.chip-sm {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}

.bell {
  position: relative;
  width: 44px; height: 44px;
  border-radius: var(--radius);
  display: grid; place-items: center;
  font-size: 1.05rem;
  color: var(--ink-soft);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
}
.bell:hover { background: var(--primary-08); border-color: var(--primary); }
.bell-n {
  position: absolute;
  top: -4px; right: -4px;
  min-width: 17px; height: 17px;
  padding: 0 4px;
  border-radius: var(--radius);
  background: var(--alert);
  color: #fff;
  font-family: var(--font-mono);
  font-size: .64rem; font-weight: 600;
  display: grid; place-items: center;
  border: 2px solid var(--bg-soft);
}

.drop {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  width: min(320px, 88vw);
  max-height: 380px;
  overflow-y: auto;
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow-float); /* overlay flotante -> siempre sombra suave, no solo al hover */
  padding: 6px;
  z-index: 35;
}
.notif {
  padding: 10px 12px;
  border-radius: var(--radius);
  font-size: .86rem;
  color: var(--ink-soft);
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
}
.notif + .notif { margin-top: 2px; }
.notif.unread { background: var(--primary-08); font-weight: 600; color: var(--ink); }
.notif small { white-space: nowrap; }

/* ---------- 4. BOTONES ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  padding: 12px 22px;
  font-family: var(--font-body);
  font-size: .95rem; font-weight: 700;
  border-radius: var(--radius);
  border: 1.5px solid transparent;
  transition: box-shadow .12s ease, background .15s ease, border-color .15s ease, color .15s ease;
  white-space: nowrap;
  line-height: 1.1;
}
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }

/* Solid = el CTA primario del sistema: relleno verde, sombra suave. */
.btn-solid { background: var(--primary); color: #fff; border-color: var(--primary); box-shadow: var(--shadow-card); }
.btn-solid:hover:not(:disabled) { background: var(--primary-dark); border-color: var(--primary-dark); box-shadow: var(--shadow-card-hover); text-decoration: none; }

/* Line = borde tinta, fondo blanco/transparente — CTA secundario. */
.btn-line { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn-line:hover:not(:disabled) { border-color: var(--primary); color: var(--primary); text-decoration: none; }

/* Green = alias del CTA primario (mismo verde) — se conserva el nombre de clase
   porque varios formularios ya lo emiten para su acción final ("Publicar mi
   búsqueda", "Enviar a verificación"); visualmente es .btn-solid. */
.btn-green { background: var(--primary); color: #fff; border-color: var(--primary); box-shadow: var(--shadow-card); }
.btn-green:hover:not(:disabled) { background: var(--primary-dark); border-color: var(--primary-dark); box-shadow: var(--shadow-card-hover); text-decoration: none; }

.btn-sm { min-height: 40px; padding: 9px 16px; font-size: .86rem; }
.btn-xs { min-height: 36px; padding: 6px 11px; font-size: .76rem; border-radius: var(--radius); }
@media (max-width: 760px) {
  .btn-sm, .btn-xs { min-height: 44px; }
}

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 7px 13px;
  font-family: var(--font-mono);
  font-size: .74rem; font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  transition: all .15s ease;
}
.icon-btn:hover { color: var(--primary); border-color: var(--primary); background: var(--primary-08); }
.logout-btn { min-width: 44px; min-height: 44px; gap: 6px; }
.logout-btn svg { flex: 0 0 auto; }

/* ---------- 5. FORMULARIOS ---------- */
.form-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: 6px;
}
/* auto-fit y no `1fr 1fr` fijo (24-ago-2026): la media query mira la VENTANA, pero .form-grid se
   usa tambien dentro de columnas estrechas (el asistente de publicar). Ahi daba dos columnas de
   ~90px y "Precio (USD)" / "Expensas / Gastos comunes" salian partidos. Con auto-fit hay dos
   columnas cuando caben y una cuando no, lo mida quien lo mida. */
@media (min-width: 560px) { .form-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } }
.field { display: flex; flex-direction: column; gap: 6px; }
.field.full { grid-column: 1 / -1; }
/* .field-nota: aviso de privacidad pegado al campo, no enterrado en la politica (Pol, 23-ago-2026:
   "que no haya confusion" sobre que dato es publico y cual no). Va DENTRO del .field, que ya es
   flex-column con gap, asi que no necesita margen propio -- respeta la escala sin sumar deuda. */
.field-nota { font-size: .78rem; line-height: 1.45; color: var(--ink-soft); }
/* 19-ago, hallazgo real de CLAW: `.field` es flex-column sin `align-items` (default: stretch), así
   que un botón dentro de `.field.full` se estiraba de punta a punta del formulario -- "el botón va
   de lado a lado" cuando al lado hay campos de la mitad de ancho. Solo en pantallas donde el grid ya
   pasó a varias columnas (≥560px, mismo corte de arriba): en móvil un botón ancho sigue siendo buen
   objetivo táctil (Regla MADRE de usabilidad), no se toca ahí. */
@media (min-width: 560px) { .field.full > .btn { align-self: flex-start; } }

/* .form-grid.cols-4: variante de 4 columnas en fila para formularios cortos de puros selects (el
   calculador de /precio-justo, pjFormHtml()) -- hallazgo real 18-ago: antes forzaba
   "repeat(4,minmax(120px,1fr))" por ESTILO INLINE (nunca responsive, un inline siempre le gana a
   una media query de la hoja de estilos) y a 375px desbordaba la página entera (375 documentado
   vs 540 real de scrollWidth). Ahora crece por pasos reales: 1 columna en móvil, 2 en tablet, 4
   recién en escritorio -- donde si sobra sitio de verdad. */
.form-grid.cols-4 { grid-template-columns: 1fr; }
@media (min-width: 640px) { .form-grid.cols-4 { grid-template-columns: 1fr 1fr; } }
@media (min-width: 900px) { .form-grid.cols-4 { grid-template-columns: repeat(4, minmax(120px, 1fr)); } }

label {
  font-family: var(--font-body);
  font-size: .82rem;
  font-weight: 700;
  color: var(--ink);
}

input, select, textarea {
  width: 100%;
  padding: 12px 14px;
  font-size: 16px; /* evita zoom automático en iOS */
  color: var(--ink);
  background: var(--bg-soft);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease;
}
textarea { min-height: 110px; resize: vertical; }
input::placeholder, textarea::placeholder { color: var(--ink-soft); opacity: .7; }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--primary);
  background: var(--bg);
  box-shadow: 0 0 0 3px var(--primary-20);
}
/* :user-invalid, no :invalid (19-ago, hallazgo real de CLAW en /tasador): un campo `required` sin
   `placeholder` (ej. Superficie en ui-tasador.js) es SIEMPRE :invalid mientras esté vacío, así que
   `:invalid:not(:placeholder-shown)` lo pintaba en rojo desde la primera carga, antes de que la
   usuaria haya hecho nada -- un error que no cometió. `:user-invalid` solo se activa DESPUÉS de que
   el campo pierda el foco con contenido inválido (soporte real en navegadores modernos; en los que
   no lo soportan, simplemente no pinta el borde rojo -- degrada a "sin este detalle visual", nunca
   a "rojo falso desde el principio", que es el sentido correcto del fallback). */
input:user-invalid { border-color: var(--alert); }

/* La flecha de los desplegables (25-ago-2026, Pol: "no me termina de convencer ese icono por toda
   la web, pon solo una flechita normal").
   Antes era el motivo de ñandutí, el mismo de la cinta decorativa. La idea era firmar la marca en
   cada control, pero un adorno repetido en TODOS los selectores del sitio deja de ser una firma y
   pasa a ser ruido -- y encima no comunica lo unico que tiene que comunicar un select: "esto se
   despliega". Un chevron lo dice sin que haya que aprenderlo.
   La cinta de ñandutí sigue existiendo donde SI es una firma: una sola vez, al cerrar la ficha de
   propiedad y la guia de residencia (.nanduti). Es la regla de Chanel que ya rige el resto del
   sitio: quitarse un accesorio antes de salir.
   El color es --ink-soft (#5c5c5c) escrito a mano: dentro de un data: URI no se puede usar una
   variable CSS. Si algun dia cambia el token, hay que cambiarlo aqui tambien. */
select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1L5 5L9 1' fill='none' stroke='%235c5c5c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 36px;
}

/* Los radios necesitan lo mismo que las casillas, y no lo tenian: la regla global de arriba les
   daba `width: 100%`, asi que dentro de una tarjeta flex el propio radio se comia todo el ancho y
   empujaba su etiqueta al borde derecho. Se veia poco mientras las tarjetas eran estrechas; al
   ensancharlas (24-ago) quedo a la vista. Tercera vez en el mismo dia que ese `width: 100%` global
   muerde dentro de un flex -- las otras dos fueron el buscador del hero y el select de tipo. */
input[type="radio"] {
  width: auto;
  flex: 0 0 auto;
  accent-color: var(--primary);
}
input[type="checkbox"] {
  width: 18px; height: 18px;
  flex-shrink: 0;
  accent-color: var(--primary);
  margin-top: 2px;
}

.check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: .86rem;
  color: var(--ink-soft);
  cursor: pointer;
}
.check a { color: var(--primary); text-decoration: underline; }
.check b { color: var(--ink); }

.mono {
  font-family: var(--font-mono);
  font-size: .78rem;
  font-weight: 400;
  color: var(--ink-soft);
  letter-spacing: .01em;
}

.demo-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px dashed var(--line-strong);
}
.demo-row .mono { margin-right: 2px; }

/* nota informativa neutra: no es éxito/aviso/error, así que NO lleva ninguno
   de los acentos — se marca solo con tinta (barra + fondo hundido). */
.note-box {
  margin-top: 14px;
  padding: 12px 14px;
  font-size: .82rem;
  color: var(--ink-soft);
  background: var(--bg-soft);
  border-left: 3px solid var(--ink);
  border-radius: var(--radius);
}
/* .note-box.trust: modificador para una nota que SÍ es una buena noticia (frescura de datos,
   automatización) -- mismo bloque, acento verde en vez de tinta neutra (/precio-justo, 18-ago). */
.note-box.trust { border-left-color: var(--primary); background: var(--primary-08); color: var(--ink); font-size: .95rem; }
/* Comparte MEDIDA DE LINEA con el .lead que tiene encima (24-ago-2026): iba de borde a borde
   mientras el lead se corta en 56ch, asi que el ojo saltaba de un ancho a otro entre dos bloques
   seguidos -- visto por Pol en su captura de /precio-justo. */
.note-box.trust { max-width: 56ch; }

/* ---------- 6. OPCIONES / STEPPER / GALERÍA ---------- */
.opt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
/* Mismo motivo que .form-grid: 2 y 3 columnas FIJAS se aplastan dentro de un contenedor estrecho
   por ancha que sea la ventana. Con auto-fit el numero de columnas lo decide el hueco real. */
.opt-grid.two { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.opt-grid.three { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
@media (max-width: 480px) {
  .opt-grid.two, .opt-grid.three { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 360px) {
  .opt-grid.three { grid-template-columns: 1fr; }
}

.role-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 12px;
  margin-top: 10px;
}

.opt {
  position: relative;
  display: flex; flex-direction: column; gap: 4px;
  align-items: flex-start;
  text-align: left;
  padding: 14px 16px;
  background: var(--bg);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  transition: border-color .15s ease, background .15s ease;
}
.opt:hover { border-color: var(--primary); }
.opt .t { font-weight: 700; color: var(--ink); font-size: .95rem; }
.opt .d { font-size: .8rem; color: var(--ink-soft); }
.opt.sel {
  border-color: var(--primary);
  background: var(--primary-08);
  box-shadow: 0 0 0 3px var(--primary-20);
}
.opt.sel::after {
  content: "✓";
  position: absolute;
  top: 8px; right: 8px;
  width: 20px; height: 20px;
  border-radius: var(--radius);
  background: var(--primary);
  color: #fff;
  font-size: .68rem; font-weight: 900;
  display: grid; place-items: center;
}

.gal6 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 10px;
}
@media (min-width: 480px) { .gal6 { grid-template-columns: repeat(5, 1fr); } }
.opt.foto {
  padding: 0;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius);
}
.opt.foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.opt.foto.sel { box-shadow: 0 0 0 3px var(--primary); }
.opt.foto.sel::after { top: 6px; right: 6px; width: 22px; height: 22px; }

/* Fondo de la caja de acceso/registro (encargo de Pol, 24-ago-2026): "sutil, medio
   transparente, que se vea pero sin llamar la atencion". La foto es la que pidio por su nombre,
   04_ParaguayReal_Tradiciones_Colores_Marca: arpa paraguaya, chipa, terere y mantel de ñandutí.
   No es una foto cualquiera -- dice de que pais es esto en el momento en que alguien se registra.
   (La primera version traia una casa moderna de stock, que no era lo pedido.)

   Las dos opacidades son MEDIDAS sobre ESTA imagen, no elegidas a ojo, y hay que volver a medirlas
   si la foto cambia: dependen de cual sea el pixel mas oscuro del encuadre.
   El limite real NO es el texto negro (--ink, que aguanta de sobra) sino el gris de las notas de
   los campos (--ink-soft #5c5c5c, letra pequeña, minimo 4,5:1). Medido sobre el pixel mas oscuro
   de la foto ya compuesta, en la caja real de 520x544:
       0,95/0,98 -> gris 5,97:1   pero la foto no se ve (rango de luminancia 0,103)
       0,86/0,93 -> gris 4,90:1   y la foto se ve  (rango 0,264)   <-- esta
       0,84/0,92 -> gris 4,70:1   cumple, pero sin margen para otro encuadre
       0,80/0,90 -> gris 4,26:1   ya NO cumple
   0,86/0,93 deja la foto dos veces y media mas presente que la version original y conserva un
   margen razonable sobre el minimo. Baja de ahi y lo que se rompe es la legibilidad de las notas,
   no la estetica.
   El degradado va de menos a mas opaco a proposito: arriba esta el titulo (texto grande, aguanta
   mas foto) y abajo los campos y sus notas, que es donde hay que leer. */
.modal[data-modal="auth"] {
  /* background-color aparte del shorthand, y no es decorativo: se pinta DEBAJO de la foto y es lo
     que se ve mientras la imagen viaja. #706c50 es el color medio REAL del archivo, medido
     reduciendolo a 1x1 px; bajo el velo blanco al 86% queda en #ebeae6. Sin esto la caja abre en
     blanco y la foto "aparece" de golpe, que es el parpadeo que noto Pol. Con esto la caja abre ya
     del tono definitivo y la foto solo añade el detalle. */
  background-color: #706c50;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.86) 0%, rgba(248, 249, 250, 0.93) 100%),
              url('/img/auth-bg.webp') center center / cover no-repeat,
              #706c50;
  backdrop-filter: blur(10px);
}

.auth-tabs {
  display: flex;
  gap: 6px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 4px;
  margin-bottom: 18px;
}
.auth-tabs button {
  flex: 1;
  padding: 9px 10px;
  font-family: var(--font-body);
  font-size: .86rem; font-weight: 700;
  border-radius: var(--radius);
  color: var(--ink-soft);
  transition: all .15s ease;
}
.auth-tabs button.on { background: var(--bg); color: var(--primary); box-shadow: 0 0 0 1px var(--line-strong); }

.stepper {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 20px;
}
.step {
  padding: 7px 14px;
  font-family: var(--font-mono);
  font-size: .74rem; font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--ink-soft);
  background: var(--bg-soft);
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  transition: all .15s ease;
}
/* paso ACTUAL = énfasis neutro (tinta), para no confundirse con "completado" */
.step.on { background: var(--ink); border-color: var(--ink); color: #fff; }
/* paso COMPLETADO = éxito -> verde */
.step.done { background: var(--primary-14); border-color: var(--primary); color: var(--primary-dark); }

/* ---------- 7. BARRA DE PRECIO JUSTO ----------
   .pbar/.pb-band/.pb-mark/.pb-txt, lógica en pbar() de js/ui-core.js: pista fina,
   banda VERDE para el rango justo (±12%), marca TERRACOTA (alerta/precio pedido)
   para el precio pedido. Plano, sin gradiente. */
.pbar {
  position: relative;
  height: 8px;
  margin: 16px 2px 12px;
  background: var(--ink-08);
  border-radius: var(--radius);
}
.pb-band {
  position: absolute;
  top: 0; bottom: 0;
  background: var(--primary);
  border-radius: var(--radius);
}
.pb-mark {
  position: absolute;
  top: -6px;
  width: 3px; height: 20px;
  background: var(--alert);
  transform: translateX(-50%);
}
/* .pb-band.ext: el tramo de dato EXTERNO en la barra de "Cómo se calculó" del tasador (js/ui-tasador.js,
   tasMezclaHtml) -- mismo componente .pbar/.pb-band, solo cambia el color. Ocre y no verde a propósito:
   reutiliza la semántica que .stamp.pendiente ya le da a este color en toda la web (dato real, pero sin
   verificar por una persona -- ni error ni "malo", un escalón de confianza por debajo de lo propio). */
.pb-band.ext { background: var(--ocre); }
/* .pb-leyenda: los tres colores de la barra de "Cómo se calculó" no se entendían sin leer toda la
   tabla de abajo (Pol, 28-ago: "deja claro que significa el color marron"). Un pie de tres puntos
   con su nombre, mismo criterio de color que .pb-band/.pb-band.ext -- .base no pinta banda en la
   barra (es el resto sin colorear), así que aquí sí necesita su propio punto gris para nombrarse. */
.pb-leyenda { display: flex; flex-wrap: wrap; gap: var(--s3); margin: 0 0 var(--s3); font-size: 12.5px; color: var(--ink-soft); }
.pb-leyenda span { display: inline-flex; align-items: center; gap: var(--s1); }
.pb-dot { width: var(--s2); height: var(--s2); border-radius: 50%; background: var(--primary); flex: none; }
.pb-dot.ext { background: var(--ocre); }
.pb-dot.base { background: var(--ink-08); border: 1px solid var(--ink-soft); }
/* .tas-mezcla-total: fila de cierre del desglose "Cómo se calculó" (js/ui-tasador.js, tasMezclaHtml)
   -- una línea de suma necesita distinguirse de sus filas: mismo .tbl de siempre, un borde superior
   más grueso y el fondo sutil de "resultado" que ya usa .note-box. */
.tas-mezcla-total td { border-top: 2px solid var(--ink); background: var(--bg-soft); }
/* .tas-fuente-unica (21-ago-2026): fila de la tabla "Cómo se calculó" cuando el dato externo viene
   de UNA sola fuente -- ya pesa menos en el cálculo (factorCalidad ×0.60), pero necesitaba marcarse
   visualmente para no leerse como un dato más entre iguales. */
.tas-fuente-unica td:first-child { border-left: 3px solid var(--alert); padding-left: 8px; }
/* .tas-ajuste-tamano (24-ago-2026): la fila del ajuste por superficie va DESPUES de la suma, porque
   no es una capa mas del promedio ponderado -- es un factor que se aplica al resultado. El borde
   superior punteado dice visualmente eso mismo: no suma en la columna, la modifica. Sin fondo de
   "resultado" (eso es de .tas-mezcla-total) para que no compitan dos filas por ser el total. */
.tas-ajuste-tamano td { border-top: 1px dashed var(--line-strong); }
.tas-ajuste-tamano td:first-child { padding-left: 8px; }
.pb-txt { font-family: var(--font-mono); font-size: .78rem; font-weight: 600; color: var(--ink-soft); }
/* .nota: una FRASE nunca va en monospace (24-ago-2026, hallazgo de la revision visual de CLAW del
   19-ago que llevaba desde entonces sin tocar "porque necesitaba ojos reales" -- ahora el VPS tiene
   navegador y se pudo mirar).
   La regla que se fija: el monospace es de las CIFRAS y de los rotulos cortos (el kicker, el sello,
   un dato suelto). En cuanto hay una oracion con sujeto y verbo, la fuente es la del cuerpo: una
   frase entera en monospace se lee mas despacio, ocupa mas y parece salida de una terminal, no de
   una pagina. Medido en /tasador: 18 nodos en monospace que no eran cifras, y cuatro de ellos eran
   frases completas.
   Hereda tamaño y color de .pb-txt para no reabrir decisiones de estilo ya tomadas; lo unico que
   cambia es la familia y el peso. */
.nota, .pb-txt.nota {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: .82rem;
  color: var(--ink-soft);
  line-height: 1.5;
}
.pb-txt.ok { color: var(--primary); }
.pb-txt.warn { color: var(--alert); }

/* ---------- 8. SISTEMA DE MODALES ---------- */
.modal-ov {
  /* top:var(--chrome-h) en vez de inset:0 -- el overlay (y por tanto el centrado
     vertical del modal) queda SIEMPRE por debajo del marquee+header fijos, para
     que un modal alto nunca se monte encima de la navegación en vez de dejar un
     margen limpio contra ella. */
  position: fixed;
  top: var(--chrome-h); left: 0; right: 0; bottom: 0;
  z-index: 100;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 0;
  background: rgba(26, 26, 26, 0);
  transition: background .2s ease;
}
@media (min-width: 640px) { .modal-ov { align-items: center; padding: 24px; } }
.modal-ov.open { background: rgba(26, 26, 26, .44); }

.modal {
  width: 100%;
  max-width: 520px;
  /* calc(), no 92vh: 92vh se mide contra la ventana ENTERA, no contra el hueco que
     de verdad queda bajo el marquee+header fijos (.modal-ov ya arranca en
     top:var(--chrome-h)) -- con una ventana de navegador baja, un modal alto se
     salía igual por arriba, montado sobre el header, aunque el overlay ya no lo
     tapara. Restando --chrome-h queda SIEMPRE dentro del hueco visible real. */
  max-height: calc(100vh - var(--chrome-h));
  overflow-y: auto;
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow-float);
  transform: translateY(24px);
  opacity: 0;
  transition: transform .25s ease, opacity .25s ease;
  position: relative;
}
@media (min-width: 640px) {
  .modal { border-radius: var(--radius); transform: translateY(10px) scale(.98); max-height: calc(100vh - var(--chrome-h) - 48px); }
}
.modal.wide { max-width: 700px; }
/* Publicar tiene dos columnas: el ancho genérico de 700px dejaba solo 232px
   para el resumen y ocultaba el importe. No ensanchar los demás diálogos. */
.modal.wide[data-modal="publish"] { max-width: 1040px; }
.modal-ov.open .modal { transform: translateY(0) scale(1); opacity: 1; }

.modal-in { padding: 26px 20px 30px; }
@media (min-width: 640px) { .modal-in { padding: 34px 34px 38px; } }

.modal-x {
  position: sticky;
  float: right;
  top: 14px; right: 14px;
  margin: 8px 8px -44px auto;
  width: 44px; height: 44px;
  border-radius: var(--radius);
  display: grid; place-items: center;
  font-size: .8rem; font-weight: 800;
  color: var(--ink-soft);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  z-index: 2;
}
.modal-x:hover { background: var(--primary-08); color: var(--primary); border-color: var(--primary); }

.m-img {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-soft);
  aspect-ratio: 16 / 10;
}
.m-img img { width: 100%; height: 100%; object-fit: cover; }
/* .m-img .fair-stamp: posicionamiento unificado con .card-img .fair-stamp — ver §9.5/§10 más abajo. */

/* ---------- 9. TOAST ---------- */
#toast {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translate(-50%, 16px);
  z-index: 110;
  max-width: min(92vw, 420px);
  padding: 13px 22px;
  background: var(--ink);
  color: var(--bg);
  font-size: .88rem; font-weight: 600;
  text-align: center;
  border-radius: var(--radius);
  box-shadow: var(--shadow-float);
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s ease, transform .25s ease;
}
#toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ---------- 9.5. SELLO "PRECIO JUSTO" — la insignia redonda de la marca (24-ago-2026).
   ANTES: una capsula verde con un tic y el texto "PRECIO JUSTO" en monospace. AHORA: el sello que
   dibujo Pol, en imagen. Decision suya, literal: "perdemos [el texto] pero sale el sello, el sello
   se puede leer aunque sea mediano o pequeño que no sature visualmente".

   64 px Y NO MENOS, Y ESTO ES UNA MEDICION: rasterizado el original a 40, 48, 56, 72 y 96 px, por
   debajo de ~56 px las palabras del aro no se leen -- es una mancha verde. 64 es el primer tamaño
   redondo que se lee limpio. Bajarlo "para que sature menos" rompe lo unico que se pidio.

   El sello nuevo es tinta verde con transparencia real, sin disco ni fondo crema. Un halo minimo y
   una sombra siguen el contorno de la tinta para separarla tanto de fotos claras como oscuras; no
   dibujan una caja alrededor.

   El marcado lo pone fairSealHtml() (js/ui-core.js) -- un <img> con alt real, no decorativo: al
   perder el texto, el alt es la unica via para un lector de pantalla. */
.fair-stamp {
  width: 64px; height: 64px;
  display: block;
  /* El nuevo sello es SOLO tinta transparente. Un halo claro de 1 px protege la lectura sobre
     fachadas/vegetacion oscuras y la sombra oscura hace lo mismo sobre cielo o pared clara. Son
     sombras de la propia tinta (filter), nunca un disco ni una caja de fondo. */
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .96))
          drop-shadow(0 1px 3px rgba(26, 26, 26, .38));
}
.card-img .fair-stamp, .m-img .fair-stamp {
  position: absolute;
  top: 8px; right: 8px;
  z-index: 2;
  /* width/height REPETIDOS aqui a proposito, y esta es la linea que arregla un fallo real que
     llego a produccion el 24-ago-2026: dentro de .m-img (la foto grande de la vista rapida y de
     la ficha) manda la regla de las fotos --  .m-img img  con width:100%, height:100% y
     object-fit:cover, especificidad (0,1,1) -- sobre  .fair-stamp  con width:64px, que es (0,1,0).
     El sello se estiraba a 630x394 px y TAPABA LA FOTO ENTERA del anuncio.
     Por que no pasaba antes: hasta ese dia el sello era una CAPSULA DE TEXTO (un div), y la regla
     de las imagenes no lo tocaba. Al convertirlo en img (commit ea5d522) empezo a heredarla.
     La leccion: cambiar un div por un img hereda TODAS las reglas escritas para las imagenes de
     ese contenedor. Eso es lo que se paso por alto.
     object-fit: contain para que, si algun dia el archivo no es cuadrado, no se recorte el aro. */
  width: 64px;
  height: 64px;
  object-fit: contain;
}
/* En pantallas estrechas el sello baja solo a 58 px: conserva aire en la tarjeta sin cruzar el
   suelo de legibilidad medido (~56 px). La leyenda nunca debe depender de que el usuario la
   adivine. */
@media (max-width: 640px) {
  /* Los tres selectores, no solo .fair-stamp: dentro de .m-img hace falta la misma especificidad
     que arriba para ganarle a la regla de las fotos. Con solo .fair-stamp, el movil volveria a
     estirar el sello al 100%. */
  .fair-stamp,
  .card-img .fair-stamp,
  .m-img .fair-stamp { width: 58px; height: 58px; }
}

/* ---------- 9.6. BANNER DE SIMULACIÓN DE ROL (admin, "Simular rol") — franja fija, siempre
   visible mientras un admin explora la plataforma "vestido" de una cuenta demo (js/app.js:
   startSimulation()/endSimulation()/showSimBanner()). z-index por encima del top-bar/marquee
   fijos (30) para que ese chrome nunca se lo tape; por debajo de modal/toast (100/110) a
   propósito. En --alert (no --primary): es un aviso de "modo especial activo", no una acción
   positiva -- el mismo rol de atención que ya tiene ese acento en el resto del sistema. */
#simBanner {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 16px;
  padding: 10px 16px;
  background: var(--alert);
  color: #fff;
  text-align: center;
  font-size: .88rem;
  font-weight: 600;
}
#simBanner b { color: #fff; }
#simBanner .btn-green, #simBanner .btn-solid { background: var(--ink); border-color: var(--ink); box-shadow: none; }
#simBanner .btn-green:hover, #simBanner .btn-solid:hover { background: #000; border-color: #000; }

/* P30 roadmap: barra flotante del comparador -- mismo patron/z-index que #simBanner, pero en tinta
   (no --alert: no es un aviso de estado especial, es una accion neutra en curso). */
#compararBarra {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 16px;
  padding: 10px 16px;
  background: var(--ink);
  color: #fff;
  text-align: center;
  font-size: .88rem;
  font-weight: 600;
}
#compararBarra .btn-line { background: transparent; border-color: #fff; color: #fff; }
#compararBarra .btn-line:hover { background: rgba(255,255,255,.12); }
.comparar-on { border-color: var(--primary) !important; color: var(--primary) !important; }

/* ---------- 10. TARJETAS (propiedades / demanda) ----------
   Fila ancha: foto a la izquierda (260px), contenido a la derecha (1fr) — como
   una herramienta inmobiliaria real, no una tarjeta de revista. En móvil (donde
   260px no cabe cómodo junto a un 1fr) se apila: foto arriba, contenido abajo.
   Reposo con borde + sombra mínima; hover levanta la sombra y oscurece el borde. */
.card {
  position: relative; /* ancla el .fair-stamp y el .stamp superpuestos sobre la foto */
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow-card);
  transition: box-shadow .18s ease, border-color .18s ease;
  display: flex;
  flex-direction: column;
}
.card:hover { box-shadow: var(--shadow-card-hover); border-color: var(--line-strong); }
@media (min-width: 640px) {
  /* align-items:start (no stretch): la foto NO debe crecer/encogerse según lo
     largo que sea el texto de esa fila en particular (una fila con línea de
     rentabilidad es más alta que una sin ella) -- todas las fotos miden lo
     mismo (260x195, fijo por aspect-ratio) pase lo que pase en la columna de
     al lado; si el cuerpo es más alto que la foto, la fila simplemente crece,
     con la foto siempre arriba. */
  .card:not(.demand) { display: grid; grid-template-columns: 260px 1fr; align-items: start; }
  .card:not(.demand) .card-img { aspect-ratio: 4 / 3; height: auto; }
}

.card-img {
  position: relative; /* ancla el sello .fair-stamp (ver arriba) sobre la foto */
  aspect-ratio: 4 / 3;
  background-size: cover;
  background-position: center;
  background-color: var(--bg-soft);
}
/* P34 roadmap (22-ago-2026): abajo-izquierda de la foto -- las otras dos esquinas ya estan
   ocupadas (.fair-stamp arriba-derecha, .stamp de oper arriba-izquierda de toda la tarjeta).
   ⚠️ 23-ago-2026, Pol viendo el listado real: "si pones etiquetas del mismo color, satura la vista;
   precio justo siempre tiene que estar como mas llamativo que ninguno". Esta insignia estaba en
   --primary SOLIDO, igual que entonces el sello .fair-stamp (que desde el 24-ago es la insignia
   redonda en imagen, ya sin relleno CSS): dos verdes del mismo peso en la misma foto
   compiten y ninguna gana. Ahora la capsula es gris de tinta y **solo la cifra** va en verde -- se
   conserva la senal de descuento (que es el dato util) sin disputarle el primer golpe de vista al
   sello de marca. Es lo que manda la propia guia de color de :root, donde --primary se declara
   como "el UNICO acento de accion": si dos cosas lo usan igual, deja de significar nada. */
.card-buen-precio {
  position: absolute; bottom: 8px; left: 8px; z-index: 2;
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px;
  font-family: var(--font-mono); font-size: .62rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-soft); background: rgba(255, 255, 255, .94);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
.card-buen-precio b { color: var(--primary); font-weight: 700; }
/* Sello de ORIGEN de un anuncio sindicado (23-ago-2026, js/ui-core.js: origenSelloHtml).
   Abajo-DERECHA: es la unica esquina libre de la foto -- .stamp de operacion arriba-izq,
   .fair-stamp arriba-der, .card-buen-precio abajo-izq.
   En gris de tinta y NO en --primary a proposito: los otros dos sellos afirman algo bueno del
   precio, este solo dice de donde viene el anuncio. Si compartiera color, competiria con el sello
   de precio justo, que es el que de verdad tiene que verse primero. */
.card-origen {
  position: absolute; bottom: 8px; right: 8px; z-index: 2;
  display: inline-flex; align-items: center;
  padding: 4px 10px;
  font-family: var(--font-mono); font-size: .6rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  color: #fff; background: rgba(26, 26, 26, .78);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
/* Credito de la foto (fotoCreditoHtml): discreto pero legible, pegado bajo la imagen. No se usa
   .mono ni el gris mas claro del sistema porque esto NO es metadato decorativo -- es una
   atribucion, y tiene que poder leerse. */
.foto-credito {
  margin: 6px 0 0;
  font-size: .78rem; line-height: 1.4; color: var(--ink-soft);
}
.foto-credito a { color: inherit; text-decoration: underline; }

/* Bloque de origen en la ficha (origenCardHtml).
   ⚠️ 23-ago-2026, visto en una captura a 430px: .owner-card es `display:flex` en FILA -- pensada
   para "Publica · Nombre", dos elementos cortos. Al meter dentro un parrafo y un boton, los tres
   se repartian el ancho y el texto quedaba en una columna de diez caracteres, con el boton
   saliendose. En movil era ilegible. Aqui manda columna: este bloque es un parrafo con su accion,
   no una linea de datos. */
/* Selector DOBLE (.owner-card.origen-card) a proposito, no `.origen-card` a secas: el marcado
   lleva las dos clases y .owner-card se declara 180 lineas MAS ABAJO en este archivo, asi que con
   la misma especificidad ganaba ella y el display:block no se aplicaba nunca. Es justo el caso que
   avisa la guia de frontend-design: "es facil generar clases CSS que se anulan entre si". Con 0,2,0
   gana esta pase lo que pase con el orden del archivo. */
.owner-card.origen-card {
  display: block;
  padding: var(--s3) var(--s4);
}
.origen-card .origen-nota {
  margin: var(--s2) 0 var(--s3);
  font-size: .82rem; line-height: 1.45; color: var(--ink-soft);
}
.origen-card .btn { display: inline-flex; }

/* P14 roadmap: flechas + puntos del carrusel de la tarjeta (propCard(), js/ui-pages.js). Ocultas
   por defecto en escritorio y visibles solo al pasar el mouse por la tarjeta (`.card:hover`) -- en
   movil no hay hover, así que se fuerzan visibles siempre por debajo de 640px (mismo breakpoint que
   ya usa el resto del proyecto para "modo móvil"), donde el gesto principal es el swipe de todos
   modos y las flechas son solo el respaldo para quien no arrastra. */
.card-photo-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px; display: grid; place-items: center;
  background: rgba(255,255,255,.85); border: 1px solid var(--hairline); border-radius: 50%;
  font-size: 1.1rem; line-height: 1; color: var(--ink); cursor: pointer;
  opacity: 0; transition: opacity .15s ease, background .15s ease;
}
.card:hover .card-photo-nav { opacity: 1; }
.card-photo-nav:hover { background: #fff; }
.card-photo-nav.prev { left: 8px; }
.card-photo-nav.next { right: 8px; }
/* .foto-contador: sustituye a los antiguos .card-photo-dots (ver fotoContadorHtml en
   js/ui-pages.js). Abajo-CENTRO -- el unico hueco que queda entre .card-buen-precio (abajo-izq) y
   .card-origen (abajo-der). Sin :hover en ninguna regla: en tactil no existe, y Pol lo pidio
   explicitamente "en ordenador, tablet y movil". Mismo gris de tinta que .card-origen, nunca
   --primary: esto es metadato, no una afirmacion sobre el precio. */
.foto-contador {
  position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); z-index: 2;
  padding: 2px 8px;
  font-family: var(--font-mono); font-size: .6rem; letter-spacing: .04em;
  color: #fff; background: rgba(26, 26, 26, .78);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  pointer-events: none;
}
.foto-contador b { font-weight: 700; }
/* .video-muestra (24-ago-2026): el video de ejemplo del recorrido 360 en el formulario de publicar.
   Bordes de tarjeta y nada mas: el video ya es el elemento visual, no necesita marco decorado.
   display:block en el <video> quita el hueco de 4 px que deja el line-height por ser inline. */
.video-muestra { margin-top: var(--s3); }
.video-muestra video {
  display: block; width: 100%; height: auto;
  border: var(--border-card);
  border-radius: var(--radius);
  background: var(--bg-soft);
}
.video-muestra .nota { margin: var(--s2) 0 0; }

/* En /tour-360 el montaje es la prueba visual principal, pero no debe crecer a 1.470 px solo
   porque el monitor pueda: a 900 px conserva detalle, ritmo de lectura y un peso visual menor que
   el H1. En móvil ocupa el ancho disponible y mantiene siempre el 16:9 del archivo. */
.tour360-demo-video {
  width: 100%; max-width: 900px;
  margin: var(--s5) 0 var(--s7);
}
.tour360-demo-video video { aspect-ratio: 16 / 9; object-fit: cover; }

/* .video-muestra.explica existió del 24 al 30-ago-2026 para el video de /precio-justo a 740px en
   el flujo de la pagina. Retirada al pasar ese video a .video-flotante (ver mas abajo) -- ya no
   queda ningun uso de .explica en el codigo. */

/* .card-media (24-ago-2026, Pol: "cada activo que tenga recorrido 360º sale reflejado desde la
   portada del anuncio, sutilmente"). La cinta de abajo-centro que agrupa lo que hay para VER: el
   contador de fotos y la marca de 360°. Antes el contador se posicionaba solo; ahora lo hace este
   contenedor y el contador va en flujo dentro. Asi la marca nueva no ocupa una quinta esquina de la
   foto -- las cuatro estan tomadas y una quinta pegatina convierte la tarjeta en un tablon. */
.card-media {
  position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); z-index: 2;
  display: flex; align-items: center; gap: 6px;
  pointer-events: none;
}
.card-media .foto-contador { position: static; transform: none; bottom: auto; left: auto; }
/* .card-360: MISMO tratamiento que el contador -- gris de tinta, mono, minusculo. Nunca --primary:
   en esta tarjeta el verde significa "precio justo", y una segunda cosa verde le roba el
   significado. Es lo que hace que la marca sea sutil de verdad y no una segunda afirmacion. */
.card-360 {
  padding: 2px 8px;
  font-family: var(--font-mono); font-size: .6rem; font-weight: 700; letter-spacing: .04em;
  color: #fff; background: rgba(26, 26, 26, .78);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
@media (max-width: 640px) {
  .card-photo-nav { opacity: 1; }
}

.card-body { padding: 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; }
.card-body h3 { font-size: 1.05rem; margin-top: 4px; }
.card-body p { font-size: .86rem; color: var(--ink-soft); }
.card-body .cta { margin-top: auto; padding-top: 12px; }

.card.demand {
  border-style: solid;
  border-color: var(--line-strong);
  background: var(--bg);
}
.card.demand .card-body { gap: 8px; }
.card.demand p { color: var(--ink-soft); }
/* demCard() (js/ui-pages.js) y panelDemandCard() (js/ui-panel.js) pintan la
   descripción y/o el .cta como HERMANOS de .card-body, no dentro de él -- sin
   esto se quedaban pegados al padding cero del propio .card (texto tocando el
   botón de abajo, sin el mismo margen de 18px a los lados que ya tiene todo lo
   de dentro de .card-body). */
.card.demand > p {
  padding: 2px 18px 0;
}
.card.demand > .cta {
  padding: 12px 18px 18px;
}

/* fav-item: envoltorio de un .card + botón "Quitar de favoritos" (panelFavoritos, ui-panel.js) */
.fav-item { display: flex; flex-direction: column; gap: 10px; }
.fav-item .btn { align-self: flex-start; }

/* .stamp: etiqueta de TIPO/ESTADO. Sobre una foto (propiedad) se superpone como
   "tag absoluto" en la esquina opuesta al .fair-stamp; en una .card.demand (sin
   foto) se queda en flujo normal, arriba del título. Solo alquiler/aprobada tienen
   acento con significado propio (verde, "éxito/justo/alquiler"); venta usa un tag
   blanco con borde (tal como pide el rediseño); compra/inversión/estados neutros
   se apoyan en tinta. */
.stamp {
  display: inline-flex;
  align-self: flex-start;
  padding: 4px 11px;
  font-family: var(--font-mono);
  font-size: .64rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .06em;
  color: #fff;
  border-radius: var(--radius);
  background: var(--ink);
}
.card:not(.demand) .card-body > .stamp:first-child {
  position: absolute;
  top: 8px; left: 8px;
  z-index: 2;
  /* Filo claro de 1px ademas de la sombra: al pasar ALQUILER de verde a --ink (casi negro), sobre
     una foto oscura -- un salon de noche, una fachada en sombra -- se fundiria con el fondo. Un
     pelo de luz lo separa siempre. Es 1px y translucido a proposito: el sello de precio justo
     lleva un aro BLANCO de 2px, y si este llevara lo mismo volverian a parecerse, que es
     justamente lo que se venia a arreglar. */
  box-shadow: var(--shadow-card), 0 0 0 1px rgba(255, 255, 255, .55);
}
/* 23-ago-2026 -- Pol, con captura: "lo de precio justo me parece bien, pero abajo hay una etiqueta
   de ALQUILER con el mismo formato, color, etc., entonces el precio no se distingue de las demas
   etiquetas". Tiene razon, y ademas esto rompia una regla que este mismo archivo tiene escrita en
   la linea de --primary: "verde confianza: CTAs, exito, precio justo -- el UNICO acento de accion".
   .stamp.alquiler se habia quedado con ese verde, asi que el sello que ES el diferencial del
   proyecto y una etiqueta que solo dice "esto se alquila" salian identicos: mismo verde, misma
   tipografia mono, mismo tamaño, mismo radio, mismas mayusculas. En el movil de Pol, uno encima
   del otro, no habia forma de distinguirlos.
   La operacion es una CATEGORIA, no una promesa: pasa a los neutros que ya usaban compra e
   inversion. El verde queda libre para lo unico que lo merece. */
.stamp.venta { background: #fff; color: var(--ink); border: 1px solid var(--hairline); }
.stamp.alquiler { background: var(--ink); color: #fff; }
.stamp.compra { background: var(--ink); }
.stamp.inversion { background: var(--ink); }
/* estados de flujo (propiedades/agentes/contactos/demandas en el panel — panelPropCard,
   panelAprobaciones, panelAgentesAdmin, panelContactos*, panelDemandCard en ui-panel.js) */
.stamp.aprobada, .stamp.publicada { background: var(--primary); }
.stamp.pendiente { background: var(--ocre); }
.stamp.rechazada { background: var(--alert); }

.price {
  font-family: var(--font-body);
  font-size: 1.32rem;
  font-weight: 700;
  color: var(--ink);
  margin-top: 4px;
}
.price small { font-family: var(--font-mono); font-size: .55em; font-weight: 400; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); }

.prop-price {
  font-family: var(--font-body);
  font-size: 1.9rem;
  font-weight: 700;
  color: var(--ink);
  margin: 6px 0 2px;
}
.prop-price small { font-family: var(--font-mono); font-size: .42em; font-weight: 400; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); }

/* P16 roadmap: "Leer descripcion completa" -- quitar el triangulo nativo de <summary> y que se
   vea como un enlace de texto inline, no como un acordeon. */
/* Galeria de la ficha (propFactsHtml). Rejilla que se adapta: 3 columnas en movil, mas segun
   quepan. auto-fill con minmax deja que el navegador decida cuantas caben sin media queries.
   aspect-ratio en vez de alto fijo: asi todas las fotos ocupan el mismo rectangulo aunque vengan
   con proporciones distintas del portal de origen, que es lo que hace que la rejilla se lea
   ordenada en vez de como un mosaico irregular. */
/* Chips sobre la foto grande de la ficha ("6 fotos" / "Mapa", Pol 23-ago-2026). Abajo-derecha:
   la esquina de arriba-derecha la ocupa el sello de precio justo, que manda sobre todo lo demas.
   Son acciones, no insignias, asi que llevan fondo solido y area de toque de 34px -- por debajo de
   eso fallan los dedos en movil. Nunca en --primary: el verde esta reservado al sello. */
.hero-chips {
  position: absolute; right: 10px; bottom: 10px; z-index: 2;
  display: flex; gap: var(--s2);
}
.hero-chip {
  display: inline-flex; align-items: center; min-height: 34px;
  padding: 0 var(--s4);
  font-family: var(--font-mono); font-size: .7rem; font-weight: 600;
  letter-spacing: .03em; text-transform: uppercase; text-decoration: none;
  color: var(--ink); background: rgba(255, 255, 255, .94);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  box-shadow: var(--shadow-card); cursor: pointer;
}
.hero-chip:hover { background: #fff; }
/* Flechas de la galeria ampliada. Siempre visibles (no :hover): el modal se abre igual con el dedo
   en movil, donde no hay puntero al que reaccionar. */
.gal-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 40px; height: 40px; display: grid; place-items: center;
  background: rgba(255, 255, 255, .9); border: 1px solid var(--hairline);
  border-radius: 50%; font-size: 1.4rem; line-height: 1; color: var(--ink); cursor: pointer;
}
.gal-nav:hover { background: #fff; }
.gal-nav.prev { left: 10px; }
.gal-nav.next { right: 10px; }
.gal-contador { bottom: 12px; }
/* El ancla del chip "Mapa": sin esto el header fijo tapa el titulo de la seccion al bajar. */
#ficha-mapa { scroll-margin-top: calc(var(--chrome-h) + 12px); }

/* Aviso de "toca para ampliar" de los mapas (mapaRueda, js/ui-guia.js). Se pinta DENTRO del
   contenedor Leaflet, por encima de los tiles (.leaflet-* usan z-index hasta 800) pero sin capturar
   el puntero: el clic tiene que llegar al mapa, que es justo lo que lo activa. */
.mapa-aviso-rueda {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); z-index: 900;
  padding: var(--s1) var(--s4);
  font-family: var(--font-mono); font-size: .68rem; letter-spacing: .03em;
  color: #fff; background: rgba(26, 26, 26, .8);
  border-radius: var(--radius);
  pointer-events: none;
  transition: opacity .18s ease;
}
.mapa-rueda-on .mapa-aviso-rueda { opacity: 0; }

.prop-galeria {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--s2);
  margin: var(--s3) 0;
}
.prop-galeria-foto {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  cursor: zoom-in;
  transition: opacity .15s ease;
}
.prop-galeria-foto:hover { opacity: .88; }

/* Descripcion de la ficha (propFactsHtml, js/ui-public.js).
   ⚠️ 23-ago-2026, Pol: "al darle a leer completo sigue apareciendo lo de leer completo y se corta
   y aparece justo en medio". Es el comportamiento nativo de <details>: el <summary> SIEMPRE se ve,
   tambien abierto -- asi que el enlace se quedaba clavado entre el final del texto cortado y el
   principio del resto. Con el <details> en `display:inline` ademas partia la frase en dos.
   Se corrige en tres sitios: el summary desaparece al abrir, el bloque deja de ser inline, y el
   corte se hace en un espacio (js/ui-public.js) para no romper una palabra. */
.prop-desc .prop-desc-p + .prop-desc-p { margin-top: var(--s3); }

/* ---------- 9.7. EL BLOQUE DE LA FICHA (24-ago-2026) — Pol, sobre la caja de la descripcion:
   "mejora esa caja, dale una que mas estructurada, sale algo decorativo paraguayo".

   QUE ESTABA MAL, medido en la captura que mando: la linea de extras y la descripcion eran el
   UNICO texto suelto de la ficha. Todo lo que viene debajo (Precio, Ubicacion, Cosas que saber) ya
   son secciones con su etiqueta; esto colgaba de la rejilla de datos sin titulo ni separacion, con
   los extras en una tirada de nombres separados por puntos que se lee como una frase.

   Y QUE **NO** SE HACE: otra tarjeta blanca. El propio §9.6 de este archivo ya tiene apuntado que
   cuatro cajas identicas seguidas es lo que aplano la jerarquia de esta pagina. Aqui se usa el
   idioma que ya existe -- el kicker verde en mayusculas y las fichas .chip-sm -- sin envase nuevo. */
.prop-bloque { margin-top: var(--s5); }
.prop-chips { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s2); margin: 0; padding: 0; }

/* ---- Recorrido 360 propio en la ficha (P63, 27-ago-2026) ----
   Hereda el hueco superior de .prop-bloque, que ya esta declarado arriba: por eso la clase se
   AÑADE a .prop-bloque en vez de sustituirla. El ritmo vertical de este sitio no se hereda solo,
   se apunta; un div con clase nueva y sola sale pegado al anterior. */
.prop-tour360-preview {
  position: relative; margin-top: var(--s3); border-radius: 12px; overflow: hidden;
  background: #10201a; min-height: 220px;
}
.prop-tour360-preview img { display: block; width: 100%; height: auto; }
/* El aviso de que esto NO es una foto mas: una capa oscura con el boton en medio. Sin ella, una
   equirectangular estatica parece una foto rara y nadie la pulsa. */
.prop-tour360-overlay {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: linear-gradient(180deg, rgba(0,0,0,.05), rgba(0,0,0,.45));
}
.prop-tour360-cargando {
  display: grid; place-items: center; min-height: 220px; color: #f3f1ea; background: #10201a;
  border-radius: 12px; font-size: 14px;
}
/* El visor dentro de la ficha necesita alto de verdad para servir de algo, sobre todo en el movil,
   que es desde donde se mira una casa. */
.prop-tour360-bloque .tour360-visor { min-height: 560px; }
@media (max-width: 720px) {
  .prop-tour360-bloque .tour360-visor { min-height: 70vh; }
  .prop-tour360-preview, .prop-tour360-cargando { min-height: 180px; }
}
/* El pie del bloque: la fecha de publicacion. Mono y gris porque es un micro-dato, no una
   afirmacion -- el mismo tratamiento que el resto de micro-datos del sitio. */
.prop-bloque-pie {
  font-family: var(--font-mono);
  font-size: .78rem;
  color: var(--ink-soft);
  margin: var(--s3) 0 0;
}
/* .prop-veredicto: la frase que dice por que este anuncio esta en ParaguayReal. La escribimos
   NOSOTROS, no el anuncio, y mezclada entre las demas se leia como si la dijera el portal de
   origen. Se marca con el mismo recurso que .ficha-sec--destacada (filo verde + tinta al 8%): es
   lo que el sitio ya usa para "esto es nuestro y es lo importante", no un estilo nuevo. */
.prop-veredicto {
  margin: var(--s4) 0 0;
  padding: var(--s3) var(--s4);
  background: var(--primary-08);
  border-left: 3px solid var(--primary);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .94rem;
}
.prop-veredicto-tit {
  display: block;
  font-family: var(--font-mono);
  font-size: .68rem; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--primary-dark);
  margin-bottom: var(--s1);
}

/* .tour360-kicker / .tour360-nota (24-ago-2026): la senal de "Con recorrido 360°".
   Verde de marca porque es una capacidad REAL del anuncio, no un adorno -- el mismo criterio por el
   que "Dentro del rango de precio justo" tambien va en verde. No lleva fondo ni caja: en la tarjeta
   ya compiten el sello, el precio y la etiqueta de operacion, y una cuarta pieza con fondo seria
   la que gana por gritar mas, no por importar mas. */
.tour360-kicker { color: var(--primary); margin-top: var(--s1); }
.tour360-nota { margin: var(--s4) 0 0; display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }

/* ---------- ÑANDUTI — el unico elemento decorativo del sitio.
   QUE ES: el encaje de Itaugua, lo mas reconociblemente paraguayo que existe en artesania. Su
   motivo es una rosca RADIAL (rayos desde el centro y arcos concentricos) y sus bordes alternan la
   rosca completa con un arco simple. Eso es exactamente lo que dibuja la tesela de 48x12.

   POR QUE ASI Y NO UNA IMAGEN: es un SVG en data-URI dentro del CSS. No pesa una peticion, escala
   sin pixelarse en cualquier densidad de pantalla y hereda el verde de marca -- no se añade ni un
   color nuevo. (Un <script> inline no se podria: la CSP del sitio es script-src 'self'. El CSS si.)

   DONDE: UNA sola vez por ficha, cerrando la descripcion. Un motivo repetido en cada bloque deja
   de ser una firma y pasa a ser papel pintado -- la regla de Chanel, quitar un accesorio antes de
   salir. Es decoracion pura, asi que va con aria-hidden y no aparece en el arbol de accesibilidad.
   El motivo no lleva un hilo horizontal de union a proposito: haria de linea separadora y ya hay
   una jerarquia de separadores en el sitio (--hairline); esto es un remate, no un divisor. */
.nanduti {
  height: 12px;
  margin-top: var(--s4);
  /* `round` y no `repeat` (24-ago-2026, visto en una captura real): con repeat-x el ultimo motivo
     se corta por la mitad contra el borde derecho, y una rosca partida no se lee como un remate de
     encaje sino como un fallo de maquetacion. `round` estira el patron lo justo para que quepa un
     numero entero de repeticiones, asi que la cinta empieza y acaba en figura completa a cualquier
     ancho. El estiramiento es de pocos pixeles y no se nota; el corte si se notaba. */
  background-repeat: round no-repeat;
  background-position: left center;
  background-size: 48px 12px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='12' viewBox='0 0 48 12'%3E%3Cpath d='M8.4 11A3.6 3.6 0 0 1 15.6 11M5.9 11A6.1 6.1 0 0 1 18.1 11M3.5 11A8.5 8.5 0 0 1 20.5 11M12 11L3.5 11M12 11L7.7 3.6M12 11L14.9 2.1M12 11L12 2.5M12 11L18.5 5.2M12 11L20.5 11M14.8 11A2.2 2.2 0 0 1 19.2 11' fill='none' stroke='%230f7a4d' stroke-width='.7' stroke-opacity='.6' stroke-linecap='round'/%3E%3Cpath d='M33.8 11A2.2 2.2 0 0 1 38.2 11M32.2 11A3.8 3.8 0 0 1 39.8 11M30.7 11A5.3 5.3 0 0 1 41.3 11' fill='none' stroke='%230f7a4d' stroke-width='.7' stroke-opacity='.6' stroke-linecap='round'/%3E%3C/svg%3E");
}
@media (prefers-contrast: more) {
  /* En contraste alto, un adorno al 60 % de opacidad es ruido: se retira. Lo decorativo es lo
     primero que sobra cuando alguien necesita que el contenido se lea. */
  .nanduti { display: none; }
}
.prop-desc-details[open] > .prop-desc-leermas { display: none; }
.prop-desc-details[open] { margin-top: var(--s3); }
.prop-desc-leermas { display: inline-block; margin-top: var(--s2); color: var(--primary); font-weight: 600; cursor: pointer; list-style: none; }
.prop-desc-leermas::-webkit-details-marker { display: none; }
.prop-desc-leermas::marker { content: ""; }

/* P16 roadmap: barra fija de la ficha -- position:sticky puro, sin JS. z-index por debajo del
   chrome fijo (30) pero por encima del contenido normal de la pagina (que no declara z-index). */
.prop-sticky-bar {
  position: sticky;
  top: var(--chrome-h);
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 16px;
  margin: 14px 0;
  padding: 12px 16px;
  background: var(--bg);
  /* 24-ago-2026, Pol con la barra rodeada en amarillo en su captura: "visualmente se monta con el
     fondo... necesito que aunque sea el borde se diferencie un poco sutilmente con un poco de verde
     de marca". Tenia el mismo blanco del fondo y solo un filete gris, asi que al quedarse pegada
     arriba no se leia como una capa flotante: parecia texto suelto encima del contenido.
     Filete verde ARRIBA (3px) + sombra de elevacion: la sombra es la que dice "esto flota" y el
     verde la que dice de quien es. No se rellena de verde ni se pone borde verde entero -- esta
     barra convive con el sello "Precio justo" en la misma pantalla y el relleno verde solido es
     suyo, regla ya fijada en test-jerarquia-etiquetas.js. */
  border: var(--border-card);
  border-top: 3px solid var(--primary);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card-hover);
}
.prop-sticky-titulo { font-family: var(--font-display); font-weight: 700; font-size: 1.05rem; color: var(--ink); flex: 1 1 200px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.prop-sticky-precio { font-size: 1.2rem; font-weight: 700; color: var(--ink); }
.prop-sticky-precio small { font-size: .6em; font-weight: 400; color: var(--ink-soft); }
.prop-sticky-datos { font-size: .78rem; color: var(--ink-soft); }

.meta-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin: 16px 0;
}
@media (min-width: 480px) { .meta-grid { grid-template-columns: repeat(4, 1fr); } }
/* > li ademas de > div: el listado servido por el servidor (ssrFichaProp) usa <ul>/<li> para
   que el marcado sea valido; el cliente sigue pintando <div>. Misma pinta, dos marcados. */
.meta-grid > div, .meta-grid > li {
  padding: 10px 12px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  text-align: center;
}
.meta-grid span { display: block; font-family: var(--font-mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); margin-bottom: 3px; }
.meta-grid b { font-family: var(--font-body); font-size: .98rem; color: var(--ink); }

.owner-card {
  display: flex; align-items: center; gap: 8px;
  margin-top: 16px;
  padding: 12px 14px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: .9rem;
}

/* ---------- 11. TABLAS Y ESTADÍSTICAS ---------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  margin-top: 8px;
}
@media (min-width: 560px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
.stat-card {
  padding: 20px 16px;
  text-align: center;
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
}
.stat-n { font-family: var(--font-display); font-size: 1.9rem; font-weight: 600; color: var(--primary); }
.stat-l { margin-top: 4px; font-family: var(--font-mono); font-size: .68rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .06em; }

.tbl-wrap {
  margin-top: 18px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
}
/* tabla entera en mono ("prices in tables" en Plex Mono): en vez de recolorear solo la celda
   del precio (varias filas -matchRow()- lo escriben como texto plano, sin envoltorio propio),
   se le da la estética de planilla de datos a la tabla completa. */
.tbl { width: 100%; min-width: 560px; border-collapse: collapse; font-family: var(--font-mono); font-size: .86rem; }
.tbl th, .tbl td { padding: 13px 16px; text-align: left; border-bottom: 1px solid var(--hairline); white-space: nowrap; }
.tbl th { background: var(--bg-soft); font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); font-weight: 600; }
.tbl tbody tr:last-child td { border-bottom: none; }
.tbl tbody tr:hover { background: var(--bg-soft); }
.tbl td.mono { color: var(--ink-soft); white-space: nowrap; }
.tbl td b { color: var(--ink); font-weight: 600; }

.score {
  display: inline-block;
  padding: 4px 10px;
  font-family: var(--font-mono);
  font-weight: 600; font-size: .8rem;
  color: var(--primary-dark);
  background: var(--primary-14);
  border-radius: var(--radius);
}

/* ---------- 11.5. CONTENIDO INSTITUCIONAL (páginas estáticas tipo /agentes: pasos numerados,
   listas sí/no, bloque de CTA final, FAQ) -- componentes genéricos, reutilizables por cualquier
   página de pageShell() que necesite explicar un proceso, no solo /agentes. */
.steps-list { display: flex; flex-direction: column; gap: 14px; margin-top: 14px; counter-reset: pj-step; }
.steps-list li { display: flex; gap: 14px; align-items: flex-start; }
.steps-list li::before {
  counter-increment: pj-step;
  content: counter(pj-step);
  flex-shrink: 0;
  width: 28px; height: 28px;
  border-radius: var(--radius);
  background: var(--ink);
  color: #fff;
  font-family: var(--font-mono);
  font-size: .78rem; font-weight: 600;
  display: grid; place-items: center;
}
.steps-list li span { padding-top: 4px; font-size: .95rem; color: var(--ink-soft); }

.cond-grid { display: grid; grid-template-columns: 1fr; gap: 20px; margin-top: 14px; }
@media (min-width: 640px) { .cond-grid { grid-template-columns: 1fr 1fr; } }
.check-list { display: flex; flex-direction: column; gap: 10px; }
.check-list li { display: flex; gap: 8px; align-items: flex-start; font-size: .92rem; color: var(--ink-soft); }
.check-list li::before { flex-shrink: 0; font-weight: 700; }
.check-list.good li::before { content: "✓"; color: var(--primary); }
.check-list.bad li::before { content: "✕"; color: var(--alert); }

/* ---------- VISITAS Y CALIFICACIONES (25-ago-2026, VISITAS-RATINGS-SPEC.md) ----------
   Las estrellas son <input type="radio"> reales. El input se esconde con la tecnica de
   "clip", NO con display:none ni visibility:hidden: esas dos lo sacan del arbol de
   accesibilidad y del orden de tabulacion, o sea que la fila deja de poder usarse con teclado y
   un lector de pantalla no la anuncia. Con clip el control sigue existiendo, sigue recibiendo
   foco y sigue siendo un grupo de radios de verdad; lo unico que cambia es que se pinta la
   etiqueta en su lugar.
   :focus-visible + el contorno en la etiqueta: sin eso, quien tabula no ve donde esta. */
.estrellas { border: 0; padding: 0; margin: 0 0 var(--s4); }
.estrellas legend { font-size: .88rem; font-weight: 600; color: var(--ink); margin-bottom: var(--s2); padding: 0; }
.estrellas-fila { display: flex; flex-direction: row-reverse; justify-content: flex-end; gap: 4px; }
.estrellas-fila input {
  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;
}
.estrellas-fila label {
  font-size: 1.9rem; line-height: 1; cursor: pointer; color: var(--line-strong);
  transition: color .12s ease; padding: 2px; border-radius: var(--radius);
}
/* row-reverse + ~ : en CSS solo se puede seleccionar hacia ADELANTE, asi que la fila se pinta al
   reves y "las anteriores" quedan a la derecha en el DOM. Es el unico modo de encender de la 1 a
   la N sin JavaScript. */
.estrellas-fila input:checked ~ label,
.estrellas-fila label:hover,
.estrellas-fila label:hover ~ label { color: var(--primary); }
.estrellas-fila input:focus-visible + label { outline: 2px solid var(--primary); outline-offset: 2px; }
/* .sr-only ya existe mas arriba (css:1929): no se repite. */
.cal-form { max-width: 620px; }
.cal-form textarea { width: 100%; }

.visitas-lista, .ranking-agentes { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s3); }
.visita-item, .ag-item { margin-bottom: 0; }
.visita-item h4, .ag-item h4 { margin: var(--s2) 0 var(--s2); font-size: 1rem; }
.ag-nota { font-size: 1.1rem; color: var(--ink); }
.ag-sinnota { font-size: .86rem; color: var(--ink-soft); }
.ag-datos { font-size: .72rem; color: var(--ink-soft); }

.fav-tit { margin-top: var(--s6); margin-bottom: var(--s4); font-size: 1.05rem; }
.fav-noticias { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--s3); }
.fav-noticia { margin-bottom: 0; }
.fav-noticia h4 { margin: 0 0 var(--s2); font-size: 1rem; }
.fav-noticia .btn { margin-top: var(--s3); }
.fav-noticia-fuente { font-size: .72rem; color: var(--ink-soft); }
.fav-noticia.caduca { border-style: dashed; }

/* .video-flotante (30-ago-2026, Pol con captura de /mercado: "este video molesta cada vez que
   entro, quiero que salga en pequeño en una esquina o algo bien estructurado sin molestar tan
   grande"). Nacio para .video-noticiero, reemplazando el intento anterior (25-ago,
   .video-muestra.explica.video-noticiero a 640px centrado arriba de la lista): un tope de ancho
   seguia siendo grande Y seguia empujando las noticias hacia abajo en CADA visita, que es justo
   lo que Pol reporto. Generalizada la MISMA noche a base comun (Pol, viendo el video de
   /precio-justo: "con este video lo mismo que con el video de noticias") porque los otros dos
   videos del sitio (/precio-justo, /publicar) comparten el mismo problema de fondo: un video de
   presentacion interrumpiendo con su tamaño la entrada a la pagina.
   Ya no ocupa flujo ninguno: flota fija en la esquina inferior derecha, igual que el resto de
   widgets fijos del sitio (#toast, #compararBarra...).
   z-index 55: por debajo de las barras inferiores a ancho completo (#simBanner/#compararBarra/
   #cookieBanner, 60) para que si una de esas aparece no compitan por el mismo rincon, y por
   encima del chrome fijo de arriba (30). bottom:84px dejando hueco bajo un #cookieBanner tipico
   (~64px) para que no se tapen el primer momento de una visita nueva.
   Expandir: el <video> ya trae controles nativos, y el boton de pantalla completa del propio
   navegador CUBRE "ver mas grande" sin inventar un modal propio -- una segunda UI de expandir
   seria una tercera forma de hacer lo que el navegador ya hace. */
.video-flotante {
  position: fixed;
  right: var(--s4); bottom: 84px;
  z-index: 55;
  width: 200px;
  max-width: 42vw;
  box-shadow: var(--shadow-float);
  border-radius: var(--radius);
  background: var(--bg);
}
.video-flotante { padding-top: 44px; }
.video-flotante video { display: block; border-radius: var(--radius); }
.video-flotante .nota { display: none; } /* el pie explicativo solo tenia sentido a 640px en el flujo; aqui no hay espacio y el video ya se explica solo */
.video-flotante-cerrar {
  position: absolute;
  top: 0; right: 0;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: var(--border-card);
  background: var(--bg);
  color: var(--ink);
  font-size: 1rem; line-height: 1;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
}
@media (max-width: 480px) { .video-flotante { width: 150px; bottom: 76px; right: var(--s3); } }

/* La tarjeta de quien publica (25-ago-2026). 36px de avatar y no mas: la regla del 23-ago dice
   que el sello de precio justo manda en la ficha, y una cara grande le disputaria el primer golpe
   de vista. Pol lo pidio con esas palabras: "de forma muy bien planificada sin molestar". */
.owner-card { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); flex-wrap: wrap; padding: var(--s3) var(--s4); background: var(--bg-soft); border: 1px solid var(--hairline); border-radius: var(--radius); margin: var(--s4) 0; }
.owner-card-quien { display: flex; align-items: center; gap: var(--s3); min-width: 0; }
.pub-avatar { width: 36px; height: 36px; border-radius: 50%; object-fit: cover; flex-shrink: 0; background: var(--bg); border: 1px solid var(--hairline); }
.pub-avatar-txt { display: flex; align-items: center; justify-content: center; font-weight: 700; color: var(--ink-soft); }
.owner-card-rot { font-size: .72rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .05em; font-family: var(--font-mono); }
.owner-card-tipo { font-size: .75rem; padding: 4px 10px; border-radius: var(--radius); background: var(--bg); border: 1px solid var(--hairline); color: var(--primary); font-weight: 600; }

.news-card-acciones { display: flex; gap: var(--s2); align-items: center; }
.news-card-acciones .btn:first-child { flex: 1; }

/* ---------- RESIDENCIA POR NACIONALIDAD (25-ago-2026) ----------
   Pol, sobre la version publicada: "revisa todos los diseños de las residencias, separaciones,
   decoraciones de marca... necesito orden visual". Lo que hay aqui sale de tres cosas concretas,
   no de gusto:
   - Las cajas usan .panel-card (la caja de contenido real del sitio) y NO .card, que a partir de
     640px es una rejilla de 260px+1fr pensada para la tarjeta de un ANUNCIO y dejaba una columna
     de foto fantasma vacia.
   - Las listas usan .check-list.good y .pasos-list porque el reset del sitio quita las viñetas:
     una lista pelada aqui no pinta nada.
   - El aire sale de la escala --s*, no de numeros nuevos. */

/* .pasos-list: numeracion pintada por CSS, no escrita en el texto. Antes los pasos decian
   "Paso 1: ..." dentro de la cadena, asi que parecian numerados mientras la lista de documentos
   salia desnuda -- dos listas hermanas con comportamientos distintos por accidente. Con el
   contador, añadir o quitar un paso recoloca la numeracion sola. */
.pasos-list { counter-reset: paso; display: flex; flex-direction: column; gap: var(--s3); }
.pasos-list li { display: flex; gap: 10px; align-items: flex-start; font-size: .92rem; color: var(--ink); }
.pasos-list li::before {
  counter-increment: paso;
  content: counter(paso);
  flex-shrink: 0;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--primary); color: #fff;
  font-family: var(--font-mono); font-size: .72rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  margin-top: 1px;
}
/* .check-list.src: las fuentes tambien son una lista, pero su marca no puede ser un ✓ verde (eso
   significa "requisito cumplido"). Un guion neutro basta para dar estructura sin afirmar nada. */
.check-list.src li::before { content: "—"; color: var(--ink-soft); }

/* La linea de fases. Numeracion por CSS (counter), igual que .pasos-list: el numero no se
   escribe en el texto, asi que añadir o quitar una fase recoloca la cuenta sola.
   El filo verde a la izquierda hace de linea de tiempo sin dibujar una linea de tiempo. */
.res-fases { counter-reset: fase; display: flex; flex-direction: column; gap: var(--s4); margin-bottom: var(--s6); }
.res-fase {
  position: relative;
  border: var(--border-card);
  border-left: 3px solid var(--primary);
  border-radius: var(--radius);
  padding: var(--s4) var(--s4) var(--s4) calc(var(--s4) + 26px);
  background: var(--bg);
}
.res-fase::before {
  counter-increment: fase;
  content: counter(fase);
  position: absolute; left: 12px; top: var(--s4);
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--primary); color: #fff;
  font-family: var(--font-mono); font-size: .72rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.res-fase-cab { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s3); }
.res-fase h3 { margin: 0; font-size: 1.05rem; }
.res-fase-cuando { font-size: .8rem; color: var(--ink-soft); }
.res-fase p { margin: var(--s2) 0 0; }
.res-fase .check-list { margin-top: var(--s3); }
/* El "no se pide" es lo que mas tranquiliza: se marca con el fondo suave de la marca para que se
   lea antes que el resto del parrafo. */
.res-fase-nosepide { background: var(--primary-08); padding: var(--s2) var(--s3); border-radius: var(--radius); margin-top: var(--s3); }
.res-fase-plazo { font-size: .88rem; }
.res-fase-fuente { font-size: .74rem; color: var(--ink-soft); margin-top: var(--s3); }
@media (max-width: 640px) {
  .res-fase { padding-left: calc(var(--s3) + 24px); }
}

.res-fuente { margin: 0 0 var(--s5); }
.res-h2 { margin-top: var(--s7); margin-bottom: var(--s4); }
.res-migas { margin-bottom: var(--s4); font-size: .88rem; }

/* Los paises son ENLACES, no tarjetas decorativas: cada uno lleva a su propia URL, que es el
   motivo entero de que existan (que alguien encuentre "residencia paraguay brasileños"). Por eso
   son <a> con toda la caja pulsable, no un div con un enlace dentro. */
.res-paises { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: var(--s3); }
.res-pais {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4);
  border: var(--border-card); border-radius: var(--radius);
  background: var(--bg); color: var(--ink); text-decoration: none;
  transition: border-color .18s ease, box-shadow .18s ease;
}
.res-pais:hover { border-color: var(--primary); box-shadow: var(--shadow-card-hover); text-decoration: none; }
.res-bandera { font-size: 1.7rem; line-height: 1; flex-shrink: 0; }
.res-bandera-gr { font-size: 2rem; line-height: 1; flex-shrink: 0; }
.res-pais-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.res-regimen { font-size: .78rem; color: var(--ink-soft); }
.res-flecha { margin-left: auto; color: var(--primary); font-weight: 700; }

.res-etapas { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--s4); align-items: start; }
.res-etapa { margin-bottom: 0; }
.res-etapa h3 { margin: var(--s2) 0 var(--s3); }
.res-regimen-box { display: flex; gap: var(--s3); align-items: flex-start; }
.res-costos { margin-top: var(--s5); }
.res-costos p + p { margin-top: var(--s2); }
.res-fuentes { margin-top: var(--s5); }
.res-fuentes h3 { font-size: 1rem; margin-bottom: var(--s3); }
.res-cta { margin-top: var(--s7); }
.res-cta-btns { margin-top: var(--s5); }
.res-aviso { margin-top: var(--s4); font-size: .78rem; }

.tbl.wrap td { white-space: normal; }

.cta-block {
  margin-top: 40px;
  padding: 32px 24px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.cta-block h2 { margin-bottom: 6px; }
.cta-block .mono { display: block; margin-top: 12px; }
/* Un input/select/textarea normal usa --bg-soft como fondo "hundido" (línea 849) -- dentro de
   .cta-block, que ES --bg-soft, los dos se funden y el campo desaparece hasta que se hace foco
   (hallazgo real de Pol, captura 28-ago-2026, /colaboradores). Blanco puro rompe la fusión sin
   tocar el estilo global de formularios en el resto del sitio. */
.cta-block input, .cta-block select, .cta-block textarea { background: #fff; }

.faq { margin-top: 14px; border-top: 1px solid var(--hairline); }
.faq details { border-bottom: 1px solid var(--hairline); padding: 14px 0; }
/* last-child sin borde: con varios .faq seguidos (/ayuda, una categoría tras otra) la última
   pregunta ya no cierra con una línea pegada al título de la sección siguiente -- doble borde +
   cero espacio real entre "última pregunta" y "próximo H2" (hallazgo real de Pol, captura 18-ago). */
.faq details:last-child { border-bottom: none; }
.faq summary { cursor: pointer; font-weight: 700; color: var(--ink); font-size: .95rem; list-style: none; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::marker { display: none; content: ""; }
.faq summary::after { content: "+"; float: right; color: var(--primary); font-weight: 700; }
.faq details[open] summary::after { content: "\2013"; }
.faq details p { margin-top: 10px; font-size: .9rem; }

/* ---------- 12. NOTAS LEGALES / PIE ---------- */
.legal-links {
  display: flex; flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
  font-size: .78rem;
  color: var(--ink-soft);
}
.legal-links a { color: var(--ink-soft); }
.legal-links a:hover { color: var(--primary); text-decoration: underline; }

/* P31 roadmap: footer real (antes era una sola linea + enlaces legales). max-width:none + padding
   propio porque, a diferencia del resto de secciones, el footer se pinta a todo el ancho con su
   propio fondo (--bg-soft) -- el contenido interno SI respeta --container via mini-foot-cols. */
/* ===== El unico gesto grafico propio del sitio: una cenefa de ÑANDUTI (24-ago-2026) =====
   Pol pidio "un poco mas de personalidad paraguaya" y propuso la bandera difuminada de fondo. Se
   descarto por tres motivos concretos: es el cliché numero uno del sitio-de-pais, le roba fuerza al
   verde de marca -- que es el unico acento de accion y el que sostiene el sello "Precio justo" -- y
   el rojo de la bandera choca con el terracota, que aqui esta reservado a alertas y "fuera de
   rango" (regla ya fijada en test-jerarquia-etiquetas.js).
   El ñanduti es el encaje paraguayo, tejido en rueda. Aqui va como cenefa de UN pixel de grosor
   donde antes habia un filete gris: no compite con nada, no tapa contenido, no añade color y no
   pide una peticion mas (es un SVG en la propia hoja de estilos).
   UNO, y en un solo sitio. La regla de Chanel: quitate un accesorio antes de salir. */
.mini-foot {
  margin-top: var(--s6);
  border-top: 0;
  padding-top: var(--s4);
  background: var(--bg-soft);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='36' height='14' viewBox='0 0 36 14' fill='none' stroke='%23d8d5cd' stroke-width='1' stroke-linecap='round'><line x1='0' y1='13.5' x2='36' y2='13.5'/><path d='M 7 14 A 11 11 0 0 1 29 14'/><path d='M 12 14 A 6 6 0 0 1 24 14'/><line x1='18' y1='14' x2='7' y2='14'/><line x1='18' y1='14' x2='8.47' y2='8.5'/><line x1='18' y1='14' x2='12.5' y2='4.47'/><line x1='18' y1='14' x2='18' y2='3'/><line x1='18' y1='14' x2='23.5' y2='4.47'/><line x1='18' y1='14' x2='27.53' y2='8.5'/><line x1='18' y1='14' x2='29' y2='14'/></svg>");
  background-repeat: repeat-x;
  background-position: top left;
  font-size: .84rem;
  color: var(--ink-soft);
}
.mini-foot-cols {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s5);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s5) var(--s4);
}
.mini-foot-brand { grid-column: 1 / -1; }
@media (min-width: 360px) and (max-width: 899px) {
  .mini-foot-cols { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--s3); }
}
@media (min-width: 900px) {
  .mini-foot { margin-top: var(--s7); }
  .mini-foot-cols { grid-template-columns: 1.4fr 1fr 1fr 1fr; padding: var(--s7) var(--s6) var(--s6); }
  .mini-foot-brand { grid-column: auto; }
}
.mini-foot-col { display: flex; flex-direction: column; gap: 8px; }
.mini-foot-col h3 { font-size: .78rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--ink); margin: 0 0 4px; }
.mini-foot-col a { color: var(--ink-soft); }
.mini-foot-col a:hover { color: var(--primary); text-decoration: underline; }
.mini-foot-brand .logo { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; color: var(--ink); }
.mini-foot-brand .logo span { color: var(--primary); }
.mini-foot-brand p { max-width: 320px; }
.mini-foot-brand .btn { align-self: flex-start; margin-top: 4px; }
.mini-foot-bottom {
  max-width: var(--container);
  margin: 0 auto;
  padding: var(--s4) var(--s5) var(--s5);
  border-top: 1px solid var(--hairline);
  display: flex; flex-direction: column; gap: var(--s4);
}
@media (min-width: 900px) {
  .mini-foot-bottom { flex-direction: row; align-items: center; justify-content: space-between; padding: var(--s4) var(--s6) var(--s6); }
}

/* ---------- 13. BANNER DE COOKIES ---------- */
/* ---- Aviso de cookies (P46, rediseñado 23-ago) ----
   Pol: "diferencia un poco las cookies para que no parezca de esa misma página".
   ANTES usaba EXACTAMENTE las piezas del contenido: fondo --bg (blanco), border-card y radius de
   las tarjetas de propiedad, titulo en Fraunces y botones .btn/.btn-solid -- el verde de "Aceptar
   todas" era el mismo que el de "Publicar gratis". Solo lo separaba una sombra, que sobre la foto
   del hero no se nota. Resultado: se leia como una tarjeta mas de la web.

   Ahora se invierte a fondo OSCURO con texto claro. El color no es nuevo: es el mismo --ink de la
   franja "PRECIO JUSTO, SIEMPRE VERIFICADO" de arriba, o sea el que el propio sitio ya usa para
   decir "esto es del sitio, no es contenido". Y pasa de tarjeta centrada a BARRA a ancho completo:
   una barra se lee como aviso; una tarjeta con esquinas redondeadas, como contenido.
   Botones propios (.cookie-btn), no .btn, para que no compitan con los CTA reales de la pagina. */
#cookieBanner {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 60;                    /* por encima del velo del menu (40) y de la barra de comparar */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 16px 20px;
  background: var(--ink);
  color: #fff;
  border-top: 2px solid var(--primary);
  box-shadow: 0 -8px 30px rgba(26, 26, 26, .22);
}
/* El aviso de éxito no debe tapar las decisiones de cookies. Se coloca por encima de la barra
   solo mientras esta es visible y no hay un modal abierto. */
body:not(:has(#modalOv)):has(#cookieBanner:not([hidden])) #toast { bottom: 230px; }
@media (min-width: 700px) {
  body:not(:has(#modalOv)):has(#cookieBanner:not([hidden])) #toast { bottom: 108px; }
}
/* Mientras hay un diálogo O el menu lateral, el banner sigue pendiente pero no se muestra como
   una segunda capa por encima. En movil tapaba los ultimos destinos del menu (su z-index 60 era
   mayor que el 45 del panel). Al cerrar, reaparece intacto: no se convierte en una aceptacion
   implicita ni se pierde la decision. */
body:has(#modalOv) #cookieBanner,
body.nav-open #cookieBanner { opacity: 0; visibility: hidden; pointer-events: none; }
#cookieBanner .cookie-txt { flex: 1 1 300px; min-width: 0; }
#cookieBanner .cookie-txt b { display: block; font-size: .95rem; margin-bottom: 3px; }
#cookieBanner .cookie-txt p { font-size: .82rem; color: rgba(255, 255, 255, .82); margin: 0; }
#cookieBanner .cookie-txt a { color: #fff; text-decoration: underline; }

/* `flex: 0 0 auto` decia "no te encojas nunca", y con eso el `flex-wrap: wrap` de dentro no
   llegaba a activarse: la fila medía su ancho de contenido (356px) y se salia 16px de una pantalla
   de 360, cortando la "r" de "Configurar". Que pueda encogerse es justo lo que hace que envuelva.
   Medido a 360px: antes se salia; ahora los tres botones caben en dos filas. */
#cookieBanner .cookie-acciones { display: flex; flex-wrap: wrap; gap: 8px; flex: 0 1 auto; min-width: 0; }
.cookie-btn {
  font: inherit; font-size: .85rem; font-weight: 600;
  padding: 10px 16px;
  background: transparent; color: #fff;
  border: 1px solid rgba(255, 255, 255, .45); border-radius: var(--radius);
  cursor: pointer;
}
.cookie-btn:hover { border-color: #fff; }
.cookie-btn-si { background: var(--primary); border-color: var(--primary); }
.cookie-btn-si:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
.cookie-btn-link { border-color: transparent; text-decoration: underline; padding-left: 8px; padding-right: 8px; }

/* Que la barra no TAPE lo ultimo de la pagina mientras esta visible (Pol: en su movil cubria el
   buscador del hero). Se empuja el contenido con padding en <body>, retirado en cuanto se acepta. */
body:has(#cookieBanner:not([hidden])) { padding-bottom: 150px; }
@media (min-width: 700px) {
  body:has(#cookieBanner:not([hidden])) { padding-bottom: 90px; }
}

/* ---------- 14. CTA / FILTROS (buscador dominante) ---------- */
.cta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
/* Separacion con lo que tenga ENCIMA (23-ago-2026, P61).
   Pol, con captura de la Vista rapida: "ojo, hay botones todavia muy pegados". Medido sobre la
   pagina montada: entre la caja "Publica <vendedor>" y los botones Ficha/Ingresar habia
   exactamente 0 px. El gap de .cta separa los botones ENTRE SI, pero .cta no tenia ningun margen
   propio: quedaba pegada a la caja de arriba, sin aire, y el bloque se leia como una sola masa.

   Esto ya lo "arregle" hoy en la ficha (P56) poniendo un margen a mano en ese sitio concreto.
   Mal hecho: la ficha era UN caso, no LA causa. Cualquier grupo de botones nuevo volvia a nacer
   pegado -- que es exactamente lo que Pol acaba de encontrar en otra pantalla. Se arregla en la
   clase, una vez, y se retira el apaño puntual.

   Se usa "* + .cta" y no ".cta" a secas: solo lleva margen cuando de verdad hay algo encima. Si
   es lo primero de su contenedor, no sobra espacio.
   Comprobado ANTES de ponerlo (scripts/test/medir-cta-padres.js): ninguno de los 18 grupos .cta
   de las paginas publicas vive dentro de un contenedor con gap, asi que este margen no se suma a
   ninguna separacion que ya existiera. */
* + .cta { margin-top: 16px; }

/* .filters: el buscador — dominante en la home (bajo el hero) y como cabecera del
   listado en /propiedades. Fondo hundido para separarlo del blanco de la página,
   campos en fila con su micro-label, botón de acción en verde primario. */
.filters {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  padding: 18px;
  margin-top: 18px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.filters select, .filters input { margin: 0; background: var(--bg); }
/* .field-with-btn: envuelve el <select> de zona + el botón "Elegir zona en el mapa" (P6/P13 del
   roadmap) para que ocupen una sola celda del grid de .filters sin romper el resto de campos. */
.field-with-btn { display: flex; flex-direction: column; gap: 6px; }
.field-with-btn select { width: 100%; }
.field-with-btn .btn { width: 100%; white-space: nowrap; }
@media (min-width: 700px) {
  .filters { grid-template-columns: repeat(3, 1fr) auto auto; align-items: center; }
}
.hero-search { margin-top: 26px; }
.hero-search .filters { margin-top: 0; padding: 20px; }

/* .filters-panel: envuelve .filters en /propiedades (NO en el hero, que tiene su propio layout)
   para que en móvil arranque PLEGADO -- antes ocupaba toda la primera pantalla antes de llegar a
   un solo resultado. Toggle propio (.filters-toggle + clase .open), mismo patrón que el menú móvil
   (nav-toggle/nav-open) -- NO <details>/<summary>: ese elemento nativo tiene un bug de pintado en
   Chromium con texto + flex (queda medible en el layout pero nunca se pinta, verificado hasta en un
   <summary> aislado sin CSS propio). En escritorio (>=700px) el botón se oculta y los filtros se
   fuerzan visibles SIEMPRE, para no cambiar nada del comportamiento que ya existía ahí. */
.filters-panel { margin: 18px 0 0; }
/* .filters-toggle parte de .btn.btn-line.btn-xs (receta ya probada del resto del sitio) + solo lo
   necesario para ocupar el ancho completo -- una combinación de propiedades propias (display:flex +
   justify-content:space-between + font-family distinto + border propio, todas juntas) dejaba el
   texto sin pintar en Chromium/swiftshare aunque el layout se calculaba bien (medible con
   getBoundingClientRect, invisible en captura) -- reproducido y aislado en vivo, no es una
   suposición. Quedarse con la clase ya validada evita repetir ese problema. */
.filters-toggle { display: flex; width: 100%; justify-content: space-between; }
.filters-toggle-arrow { transition: transform .15s ease; }
.filters-panel.open .filters-toggle-arrow { transform: rotate(180deg); }
.filters-panel.open .filters-toggle { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.filters-panel .filters-body { display: none; }
.filters-panel.open .filters-body { display: block; }
/* REGLA MUERTA, y por eso los campos salian montados en el boton (Pol, 24-ago-2026, captura de su
   movil: "ahi veo todavia que se montan elementos", marcando "Filtros (1)" y el desplegable de
   zona pegado debajo).
   Esta linea daba al cuerpo del panel su caja (fondo, borde y relleno) y lo unia al boton... pero
   apunta a `.filters`, y el buscador del listado dejo de ser `.filters` cuando paso a ser
   `<form class="busq">`. Medido: en toda la web ya solo emite `.filters` el filtro de
   /coincidencias. Asi que el cuerpo se quedo SIN caja y SIN relleno, y el primer campo nacia a
   0 px del boton que lo abre: dos rectangulos con borde tocandose, que es exactamente lo que se
   lee como "montado".
   Se conserva el selector viejo (el filtro de /coincidencias sigue usandolo) y se le suma la caja
   al contenedor de verdad, `.filters-body`, que es quien la necesita ahora. */
.filters-panel .filters { margin-top: 0; border-top: none; border-top-left-radius: 0; border-top-right-radius: 0; }
.filters-panel .filters-body {
  padding: var(--s4);
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
}
@media (min-width: 700px) {
  .filters-toggle { display: none; }
  /* En escritorio el boton no existe, asi que el cuerpo no cuelga de nada: recupera su borde de
     arriba y las cuatro esquinas. Sin esto se veria una caja abierta por arriba, que es lo que
     senala "aqui falta algo". */
  .filters-panel .filters-body {
    display: block !important;
    border-top: 1px solid var(--hairline);
    border-radius: var(--radius);
  }
}

/* ---------- 15. PANEL / DASHBOARD (misma convención de nombres; .panel-layout/.panel-content/
   .panel-nav son las clases REALES que emite panelShell() en ui-panel.js — se agrupan aquí con
   .panel-shell/.panel-main, que eran su previsión "defensiva" original, para que ambas queden
   servidas por la misma regla en vez de duplicarla). ---------- */
.panel-shell, .panel-layout {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  max-width: var(--container);
  margin: 0 auto;
  padding: 28px 18px 60px;
}
@media (min-width: 900px) {
  .panel-shell, .panel-layout { grid-template-columns: 240px 1fr; align-items: start; padding: 40px 28px 72px; }
}
/* panel-layout ya vive dentro de un .page-wrap (panelShell lo envuelve en <section class="page-wrap
   panel-wrap">), que ya aporta su propio padding — no lo dupliques aquí. */
.panel-wrap .panel-layout { padding: 0; margin: 22px 0 0; }

.panel-nav-wrap { min-width: 0; }
.panel-nav-toggle {
  display: flex;
  width: 100%; min-height: 58px;
  align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 10px 14px;
  color: var(--ink); background: var(--bg);
  border: var(--border-card); border-radius: var(--radius);
  text-align: left;
  cursor: pointer;
  list-style: none;
}
.panel-nav-toggle::-webkit-details-marker { display: none; }
.panel-nav-toggle-kicker {
  display: block; margin-bottom: 2px;
  font-family: var(--font-mono); font-size: .66rem; font-weight: 600;
  line-height: 1.2; letter-spacing: .08em; text-transform: uppercase;
  color: var(--primary);
}
.panel-nav-toggle strong { display: block; font-size: .95rem; line-height: 1.35; }
.panel-nav-toggle-arrow { font-size: 1.35rem; line-height: 1; transition: transform .18s ease; }
.panel-nav-wrap[open] .panel-nav-toggle-arrow { transform: rotate(180deg); }
.panel-side, .panel-nav {
  display: none;
  gap: 6px;
}
.panel-nav-wrap[open] > .panel-nav {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  max-height: min(56vh, 430px); overflow-y: auto;
  margin-top: 8px; padding: 8px;
  background: var(--bg-soft); border: var(--border-card); border-radius: var(--radius);
}
@media (min-width: 600px) and (max-width: 899px) {
  .panel-nav-wrap[open] > .panel-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .panel-nav-wrap { position: sticky; top: calc(var(--chrome-h) + 20px); }
  .panel-nav-toggle { display: none; }
  .panel-side, .panel-nav,
  .panel-nav-wrap[open] > .panel-nav {
    display: flex; flex-direction: column;
    max-height: none; overflow: visible;
    margin: 0; padding: 0;
    background: transparent; border: 0; border-radius: 0;
  }
}
.panel-side a, .panel-nav a, .panel-nav button {
  display: block;
  min-height: 44px;
  padding: 10px 14px;
  font-size: .88rem; font-weight: 600;
  color: var(--ink-soft);
  border-radius: var(--radius);
  white-space: nowrap;
  border: 1px solid transparent;
  text-align: left;
}
.panel-side a:hover, .panel-nav a:hover, .panel-nav button:hover { background: var(--primary-08); color: var(--primary); text-decoration: none; }
.panel-side a.on, .panel-nav a.on,
.panel-side a[aria-current], .panel-nav a[aria-current],
.panel-side a.active, .panel-nav a.active {
  background: var(--bg);
  border-color: var(--line-strong);
  color: var(--primary);
}
/* "Eliminar mi cuenta" (panelShell, ui-panel.js): siempre visible al final del nav del panel,
   separado y en tono de alerta — nunca se confunde con una sección más del menú. */
.panel-nav .panel-nav-danger { margin-top: 8px; color: var(--alert); }
.panel-nav .panel-nav-danger:hover { background: var(--alert-08); color: var(--alert); }
@media (min-width: 900px) { .panel-nav .panel-nav-danger { margin-top: 16px; border-top: 1px dashed var(--line-strong); padding-top: 14px; border-radius: 0; } }

.panel-main, .panel-content { min-width: 0; }
/* P21 roadmap: tarjetas de estadisticas del dashboard admin -- mismo lenguaje visual que el resto
   del panel (border-card, --radius:4px), en grid que se acomoda solo segun el ancho disponible. */
.stat-tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; margin-bottom: 8px; }
.stat-tile { padding: 16px; background: var(--bg); border: var(--border-card); border-radius: var(--radius); }
.stat-tile-label { font-size: .78rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 6px; }
.stat-tile-valor { font-family: var(--font-display); font-size: 1.6rem; font-weight: 700; color: var(--ink); }
.stat-tile-sub { font-size: .78rem; color: var(--ink-soft); margin-top: 4px; }
.panel-title {
  margin: 0 0 var(--s3);
  font-size: clamp(1.75rem, 3vw, 2.2rem);
  line-height: 1.12;
}
.panel-section { margin-top: var(--s6); }
.panel-section:first-child { margin-top: var(--s4); }
.panel-subtitle {
  margin: var(--s6) 0 var(--s3);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2vw, 1.65rem);
  line-height: 1.2;
  color: var(--ink);
}
.panel-section > .panel-subtitle { margin-top: 0; }
.panel-subtitle-first { margin-top: var(--s4); }
.panel-sub { margin: 26px 0 10px; }
.panel-sub:first-child { margin-top: 0; }
.panel-head {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
}
.panel-card {
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 18px;
}

.badge {
  display: inline-block;
  padding: 4px 11px;
  font-family: var(--font-mono);
  font-size: .66rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .05em;
  border-radius: var(--radius);
  color: var(--ink-soft);
  background: var(--bg-soft);
  border: 1px solid var(--line-strong);
}
.badge.pendiente { color: var(--ocre); background: var(--ocre-16); border-color: transparent; }
.badge.aprobada, .badge.aprobado, .badge.publicada, .badge.hecho { color: var(--primary-dark); background: var(--primary-14); border-color: transparent; }
.badge.rechazada, .badge.rechazado { color: var(--alert); background: var(--alert-14); border-color: transparent; }
.badge.pausada { color: var(--ink-soft); background: var(--bg-soft); }

.empty-state {
  padding: var(--s7) var(--s5);
  text-align: center;
  color: var(--ink-soft);
  font-size: .92rem;
}
.empty-state-action p { max-width: 50ch; margin: 0 auto var(--s4); }

/* Rótulos del histórico del Tasador: HTML fuera del viewBox para que no se reduzcan junto con la
   línea del SVG. La forma puede comprimirse; una fecha o un valor no. */
.tas-chart { margin-top: var(--s2); }
.tas-chart-plot { position: relative; }
.tas-chart-svg { display: block; width: 100%; height: clamp(180px, 24vw, 240px); }
.tas-chart-rango {
  position: absolute; left: var(--s2); z-index: 1;
  padding: var(--s1) var(--s2);
  font-family: var(--font-mono); font-size: .75rem; line-height: 1;
  color: var(--ink-soft); background: rgba(255,255,255,.86);
}
.tas-chart-rango.max { top: var(--s2); }
.tas-chart-rango.min { bottom: var(--s2); }
.tas-chart-fechas {
  display: flex; justify-content: space-between; gap: var(--s2);
  margin-top: var(--s1);
  font-family: var(--font-mono); font-size: .75rem; color: var(--ink-soft);
}
.tas-chart-fechas.uno { justify-content: center; }

@media (max-width: 640px) {
  .meta-grid span, .stat-l, .tbl th { font-size: .72rem; }
  .news-card-cat, .news-card-fuente, .news-card-credito,
  .news-badge-ultima, .news-card-capturado { font-size: .75rem; }
}

/* ---------- 16. UTILIDADES ---------- */
::selection { background: var(--primary-20); color: var(--ink); }
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ---------- 17. DISUASIÓN EN FOTOS DE PROPIEDADES (tarjetas, ficha, visor a pantalla completa)
   ⚠️ Esto es DISUASIÓN, no protección real: cualquiera con devtools, "ver código fuente" o una
   captura de pantalla sigue accediendo a la imagen igual. Solo frena el clic-derecho-guardar-como
   y el arrastrar-a-escritorio casuales. El bloqueo del menú contextual va en JS (app.js, listener
   delegado en document para '.prop-photo'); aquí solo lo que CSS puede aportar: que no se pueda
   arrastrar la imagen ni seleccionarla como texto/elemento. NO tocar .opt.foto img (miniaturas del
   selector de fotos del wizard de publicar en ui-pages.js/pS2: es la UI de selección propia del
   usuario, no la foto pública de un listado). */
.prop-photo {
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}
/* prop-photo-shield: capa transparente sobre el <img> del visor a pantalla completa (openImg,
   ui-public.js) — recibe ella el clic-derecho en vez del <img> mismo. No hay gestos de pinch-zoom
   en el proyecto (comprobado antes de añadir esto), así que no hace falta pointer-events:none; no
   interfiere con cerrar el modal (el cierre escucha clics en el fondo #modalOv o en .modal-x,
   ninguno de los dos vive dentro de .m-img). */
.prop-photo-shield {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: transparent;
}

/* ---------- 18. NOTICIERO (/noticias) — grid vertical de tarjetas, imagen arriba, distinto del
   listado de propiedades (fila ancha foto-izq/contenido-der, §10): una noticia se lee de arriba
   abajo como una tarjeta de blog, no se compara en fila como un inmueble. 1 columna en móvil,
   2-3 en escritorio (orden de Pol, 17-ago). ---------- */
.news-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-top: 20px;
}
@media (min-width: 640px) { .news-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .news-grid { grid-template-columns: repeat(3, 1fr); } }

.news-card {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}
.news-card-img { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; background: var(--bg-soft); }
.news-card-credito {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--ink-soft);
  padding: 4px 16px 0;
}
.news-card-body { padding: 14px 16px 16px; display: flex; flex-direction: column; flex: 1; }
.news-card-cat {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--primary);
  margin-bottom: 6px;
}
.news-card-title { font-family: var(--font-display); font-weight: 600; font-size: 1.1rem; line-height: 1.3; margin: 0 0 8px; color: var(--ink); }
.news-card-title a { color: inherit; text-decoration: none; }
.news-card-title a:hover { text-decoration: underline; }
.news-card-resumen { color: var(--ink-soft); font-size: .92rem; line-height: 1.55; flex: 1; margin: 0 0 12px; }
.news-card-fuente { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); margin-bottom: 4px; }

/* P22 roadmap (22-ago-2026): buscador+filtro, destacada y badge "Última hora" -- jerarquia visual
   nueva que Pol pidio ("las de esta semana mas destacadas arriba, las de hace mas tiempo menos
   prominentes"), sin tocar el .news-card base (la destacada es el MISMO componente, mas grande). */
.news-filtros { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0; align-items: center; }
.news-filtros .guia-buscador { flex: 1 1 240px; max-width: 320px; margin: 0; }
.news-filtros select { padding: 12px 14px; border: 1px solid var(--hairline); border-radius: var(--radius); background: var(--bg); color: var(--ink); font-size: .88rem; }

.news-destacada { margin-bottom: 24px; }
.news-destacada .news-card { flex-direction: column; }
@media (min-width: 768px) {
  .news-destacada .news-card { flex-direction: row; }
  .news-destacada .news-card-img { width: 45%; aspect-ratio: auto; }
  .news-destacada .news-card-body { flex: 1; justify-content: center; padding: 24px 28px; }
  .news-destacada .news-card-title { font-size: 1.5rem; }
  .news-destacada .news-card-resumen { font-size: 1rem; }
}
.news-badge-ultima {
  display: inline-block;
  background: var(--alert); color: #fff;
  font-family: var(--font-mono); font-size: 9px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  padding: 2px 7px; border-radius: var(--radius);
  margin-left: 6px;
}
.news-card-fuente a { color: var(--primary); text-decoration: none; }
.news-card-fuente a:hover { text-decoration: underline; }
.news-card-capturado { font-family: var(--font-mono); font-size: 10px; color: var(--ink-soft); opacity: .75; margin-bottom: 12px; }

/* ---------- 18b. Ficha de una noticia (/noticias/:slug) — misma cabecera §18. Reescrito 18-ago (3ª
   vuelta, spec de Pol "estilo periódico online"): ya no es un párrafo corto, es un artículo real con
   bajada, byline, cuerpo con subtítulos/cita, caja de datos, "por qué importa", relacionadas y
   navegación anterior/siguiente. Columna de lectura ~68ch (spec B.4): a font-size 1.05-1.15rem con
   line-height 1.7, 720px de ancho de contenedor cae naturalmente en ese rango. ---------- */
.news-detail { max-width: 720px; margin-top: 16px; }
.news-detail-bajada { color: var(--ink-soft); font-size: 1.2rem; line-height: 1.5; margin: 0 0 14px; }
.news-detail-byline { font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft); margin-bottom: 14px; }
.news-detail-byline a { color: var(--primary); text-decoration: none; }
.news-detail-byline a:hover { text-decoration: underline; }

/* news-fuente-badge: bloque de atribución APARTE y bien visible -- pedido explícito de Pol (18-ago,
   "que quede muy claro de dónde viene la fuente"). Reemplaza el "· Fuente: X" chico que antes iba
   perdido dentro del byline: ahora es una caja con borde verde, lo primero que se ve después del
   título/bajada, con link directo a la nota original. */
.news-fuente-badge {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0 0 20px;
  padding: 12px 16px;
  border: 1px solid var(--primary);
  background: var(--primary-08);
  text-decoration: none;
}
.news-fuente-badge-label { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); letter-spacing: .01em; }
.news-fuente-badge-medio { font-family: var(--font-display); font-size: 1.1rem; font-weight: 600; color: var(--ink); }
.news-fuente-badge-cta { font-family: var(--font-mono); font-size: 12px; color: var(--primary); }
.news-fuente-badge:hover .news-fuente-badge-cta { text-decoration: underline; }

.news-detail-figure { margin: 0 0 20px; }
.news-detail-img { width: 100%; max-height: 420px; object-fit: cover; display: block; background: var(--bg-soft); border: 1px solid var(--hairline); border-bottom: none; }
/* news-detail-credito: crédito de la imagen, reforzado (mismo pedido de Pol) -- antes reutilizaba
   .news-card-credito (10px, sin fondo, se perdía). Ahora va pegado a la imagen, con fondo propio,
   como el pie de foto de un diario de verdad, no una nota al margen. */
.news-detail-credito {
  display: block;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-soft);
  background: var(--bg-soft);
  padding: 7px 10px;
  border: 1px solid var(--hairline);
}

.news-article-body { font-size: 1.08rem; line-height: 1.7; color: var(--ink); }
.article-p { margin: 0 0 18px; }
.article-h2 { font-family: var(--font-display); font-size: 1.35rem; font-weight: 600; color: var(--ink); margin: 30px 0 14px; }
.article-cita {
  margin: 24px 0;
  padding: 4px 0 4px 20px;
  border-left: 3px solid var(--primary);
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-style: italic;
  color: var(--ink);
}
.article-cita p { margin: 0 0 6px; }
.article-cita cite { display: block; font-family: var(--font-mono); font-style: normal; font-size: 12px; color: var(--ink-soft); }

.news-databox {
  margin: 26px 0;
  padding: 16px 18px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
}
.news-databox-title { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--primary); margin-bottom: 10px; }
.news-databox-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 0; border-top: 1px solid var(--hairline); }
.news-databox-row:first-of-type { border-top: none; }
.news-databox-label { color: var(--ink-soft); font-size: .92rem; }
.news-databox-valor { font-family: var(--font-mono); font-weight: 600; color: var(--ink); text-align: right; }

.news-why { margin: 26px 0; padding: 16px 18px; background: var(--bg-soft); border-left: 3px solid var(--primary); }
.news-why-title { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--primary); margin-bottom: 8px; }
.news-why p { margin: 0; color: var(--ink); line-height: 1.6; }

.news-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 24px 0 0; }
.news-tag { font-family: var(--font-mono); font-size: 11px; color: var(--ink-soft); background: var(--bg-soft); border: 1px solid var(--hairline); padding: 4px 10px; }

.news-related-section { margin-top: 30px; padding-top: 20px; border-top: 1px solid var(--hairline); }
.news-related-heading { font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--primary); margin-bottom: 12px; }
.news-related-grid { display: grid; grid-template-columns: 1fr; gap: 12px; }
@media (min-width: 640px) { .news-related-grid { grid-template-columns: repeat(2, 1fr); } }
.news-related-card { display: flex; flex-direction: column; border: 1px solid var(--hairline); text-decoration: none; color: inherit; background: var(--bg); }
.news-related-img { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; background: var(--bg-soft); }
.news-related-cat { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: var(--primary); padding: 10px 12px 0; }
.news-related-title { font-family: var(--font-display); font-size: .98rem; font-weight: 600; padding: 4px 12px 12px; line-height: 1.35; }
.news-related-card:hover .news-related-title { text-decoration: underline; }

.news-prevnext {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
}
.news-prevnext-link { font-family: var(--font-mono); font-size: 12px; color: var(--primary); text-decoration: none; max-width: 48%; }
.news-prevnext-link:hover { text-decoration: underline; }
.news-prevnext-der { text-align: right; margin-left: auto; }

.news-detail-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 18px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--hairline);
}
.news-detail-links a { font-family: var(--font-mono); font-size: 12px; color: var(--primary); text-decoration: none; }

/* ---------- 19. COINCIDENCIAS (/coincidencias) — el motor de matching, SIEMPRE cómodo: con datos
   muestra las cards de cruce; sin datos, los 3 pasos + el ejemplo solo-admin (orden de Pol,
   18-ago). Card de match: 3 columnas en desktop (oferta | conector | búsqueda), apiladas en
   mobile -- el conector nunca queda "flotando" solo, siempre entre sus dos mitades. ---------- */
.match-grid { display: flex; flex-direction: column; gap: 16px; margin-top: 20px; }
.match-card {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-areas: "oferta conector busqueda" "detalle detalle detalle" "cta cta cta";
  gap: 14px 18px;
  align-items: center;
  background: var(--bg);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-card);
  padding: 18px;
}
@media (max-width: 720px) {
  .match-card { grid-template-columns: 1fr; grid-template-areas: "oferta" "conector" "busqueda" "detalle" "cta"; }
}
/* fila (mini-foto + texto), no columna: así .match-oferta mide casi lo mismo que .match-busqueda
   (solo texto) y el conector central queda alineado con el título en vez de "flotar" en medio de
   una foto grande -- una foto a todo lo ancho (como en .card-img) rompía justo esa alineación. */
.match-oferta { grid-area: oferta; display: flex; align-items: center; gap: 12px; min-width: 0; }
.match-oferta-img { width: 72px; height: 72px; flex: 0 0 72px; object-fit: cover; background: var(--bg-soft) center/cover; border: 1px solid var(--hairline); }
.match-oferta-body { min-width: 0; }
/* aquí el sello NO va superpuesto sobre la foto (72px, sin sitio) -- va en flujo, arriba del
   título; .fair-stamp base ya es inline-flex, solo le falta el margen inferior. */
.match-oferta-body .fair-stamp { margin-bottom: 4px; }
.match-oferta-body h3 { font-family: var(--font-display); font-size: 1.05rem; margin: 0 0 4px; }
.match-precio { font-family: var(--font-display); font-weight: 600; font-size: 1.15rem; color: var(--ink); margin-top: 4px; }
.match-conector { grid-area: conector; display: flex; flex-direction: column; align-items: center; gap: 6px; justify-self: center; }
.match-score {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1rem;
  color: var(--primary);
  background: var(--primary-08);
  border: 1px solid var(--primary);
  border-radius: 50%;
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.match-arrow { font-size: 1.3rem; color: var(--ink-soft); }
.match-busqueda { grid-area: busqueda; display: flex; flex-direction: column; gap: 4px; }
.match-busqueda h3 { font-family: var(--font-display); font-size: 1.05rem; margin: 0; }
.match-busqueda p { color: var(--ink-soft); font-size: .92rem; margin: 0; }
.match-detalle { grid-area: detalle; font-size: 11px; color: var(--ink-soft); padding-top: 12px; border-top: 1px solid var(--hairline); }
.match-card .cta { grid-area: cta; }

.mc-empty { text-align: center; max-width: 640px; margin: 32px auto 0; }
.mc-empty-icon { font-size: 2.6rem; color: var(--primary); margin-bottom: 10px; }
.mc-empty h2 { margin: 0 0 6px; }
.mc-empty .steps-list { text-align: left; }

.mc-admin-preview { margin: 24px 0; padding: 16px; background: var(--alert-08); border: 1px dashed var(--alert); }
.mc-admin-badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--alert);
  background: var(--alert-14);
  padding: 4px 10px;
  margin-bottom: 12px;
}
.mc-admin-preview .match-card { border-color: var(--alert); box-shadow: none; }

/* ---------- 20. GUÍA DE PARAGUAY (/guia, /zona/:slug, /zona/:slug/:barrio) — spec de Pol 18-ago.
   El mapa (.guia-map) necesita una ALTURA EXPLÍCITA -- Leaflet mide el contenedor al arrancar
   (L.map()) y con altura auto/0 no pinta nada; 420px en desktop (pedido "full-width, ~420px" de
   la spec), algo más bajo en móvil para no empujar el resto de la página fuera de la primera
   pantalla. ---------- */
/* P24 roadmap: buscador con icono, mismo lenguaje visual que el resto de inputs con icono del
   sitio (hero de la home) en vez de un <input> suelto sin jerarquia. */
.guia-buscador-wrap {
  position: relative;
  max-width: 480px;
  margin: 24px 0;
}
.guia-buscador-wrap .guia-buscador-icono {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--ink-soft); display: flex; pointer-events: none;
}
.guia-buscador {
  width: 100%;
  padding: 12px 16px 12px 40px;
  font-size: 16px; /* iOS: <16px dispara zoom automático (Regla MADRE, mobile-quality-standard) */
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--ink);
}
.guia-buscador:focus { outline: none; border-color: var(--primary); }

/* P24 roadmap: cada departamento con un borde de acento (mismo verde de marca) en vez de un <h2>
   suelto -- jerarquia visual mas clara entre secciones al bajar la pagina. */
.guia-departamento { margin-top: 28px; }
.guia-departamento h2 { font-size: 1.15rem; margin-bottom: 12px; padding-left: 12px; border-left: 3px solid var(--primary); }
.guia-ciudad-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 14px; }
.guia-ciudad-card {
  position: relative;
  display: block;
  padding: 20px 16px 16px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.guia-ciudad-card:hover { border-color: var(--primary); box-shadow: var(--shadow-card-hover); transform: translateY(-2px); }
/* Con foto, la tarjeta pierde el relleno de arriba: la imagen llega a los bordes y el texto queda
   debajo. El icono de mapa se retira -- decia lo mismo que la foto y peor (regla de Chanel). */
.guia-ciudad-card.con-foto { padding: 0 0 var(--s3); overflow: hidden; }
.guia-ciudad-foto { display: block; background: var(--bg-soft); }
.guia-ciudad-foto img {
  display: block; width: 100%; height: auto;
  aspect-ratio: 16 / 9; object-fit: cover;
}
.guia-ciudad-card.con-foto h3,
.guia-ciudad-card.con-foto .mono,
.guia-ciudad-card.con-foto .guia-ciudad-precio { padding-left: var(--s4); padding-right: var(--s4); }
.guia-ciudad-card.con-foto h3 { margin-top: var(--s3); }
.guia-ciudad-icono { display: inline-flex; color: var(--primary); margin-bottom: 8px; }
.guia-ciudad-card h3 { font-family: var(--font-display); font-size: 1.05rem; margin: 0 0 6px; color: var(--ink); }
.guia-ciudad-card .mono { font-size: 12px; color: var(--ink-soft); margin: 0 0 4px; }
.guia-ciudad-precio { font-size: .88rem; color: var(--primary); font-weight: 600; margin: 0; }

.guia-map {
  width: 100%;
  height: 420px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  margin-top: 18px;
  z-index: 0; /* el z-index alto del chat/overlays (regla MADRE del proyecto) no debe treparle encima */
}
@media (max-width: 640px) { .guia-map { height: 300px; } }

.guia-atribucion { font-size: 11px; color: var(--ink-soft); margin-top: 8px; }
.guia-atribucion a { color: var(--ink-soft); }

.guia-capas { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.guia-capa-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  font-size: .82rem;
  border: 1px solid var(--chip-color, var(--hairline));
  border-radius: var(--radius);
  background: var(--bg);
  cursor: pointer;
  user-select: none;
}
.guia-capa-chip input { accent-color: var(--chip-color, var(--primary)); width: 15px; height: 15px; }

/* .guia-pin-wrap/.guia-pin: el marcador custom (L.divIcon) -- círculo de color por categoría con un
   emoji, en vez de sumar 11 imágenes de icono más al repo. Leaflet posiciona .guia-pin-wrap; el
   círculo visible es el <span> hijo. */
.guia-pin-wrap { background: transparent; border: none; }
.guia-pin {
  display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
  font-size: 13px;
  line-height: 1;
}

/* P13 roadmap: punto de propiedad en el mapa de seleccion de zonas -- verde de marca (pedido
   explicito de Pol, "los nuestros en verde de marca" a diferencia del amarillo de Idealista). */
.guia-prop-pin-wrap { background: transparent; border: none; }
.guia-prop-pin {
  display: flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--bg);
  color: var(--primary);
  border: 2px solid var(--primary);
  box-shadow: 0 1px 4px rgba(0,0,0,.35);
}

.guia-cat-bloque { margin-top: 18px; }
.guia-cat-bloque h3 { font-size: 1rem; margin-bottom: 8px; }
.guia-poi-list { columns: 2; column-gap: 24px; margin: 0; padding-left: 18px; }
.guia-poi-list li { font-size: .9rem; color: var(--ink-soft); break-inside: avoid; margin-bottom: 4px; }
@media (max-width: 560px) { .guia-poi-list { columns: 1; } }
/* P26 roadmap: enlace "Como llegar" por item de la lista plana -- discreto (no compite visualmente
   con el nombre del sitio), aparece siempre (no solo al hover) porque en movil no hay hover. */
.guia-poi-ir { font-size: .78rem; color: var(--primary); white-space: nowrap; margin-left: 6px; }
.guia-poi-ir:hover { text-decoration: underline; }

.guia-barrios-chips { margin-top: 28px; }
.guia-barrios-chips h2 { font-size: 1.1rem; margin-bottom: 10px; }
.guia-chips-wrap { display: flex; flex-wrap: wrap; gap: 8px; }
.guia-barrio-chip { padding: 6px 14px; font-size: .88rem; }

/* ---------- 21. AYUDA (/ayuda) -- cada categoría necesita un espacio CLARO por encima (la separa
   de la última pregunta de la categoría anterior) y uno chico por debajo (la une a SUS propias
   preguntas) -- con el h2 en margin:0 (reset global, línea 71) y .faq sin ese margin propio, las
   dos categorías quedaban pegadas una a otra sin distinguirse (hallazgo real de Pol, 18-ago). */
.ayuda-categoria { margin-top: 40px; }
.ayuda-categoria:first-of-type { margin-top: 28px; }
.ayuda-categoria h2 { font-size: 1.2rem; }

.news-detail-links a:hover { text-decoration: underline; }

/* ===== Selector de Idioma (ES, EN, PT-BR, GN) ===== */
.lang-switcher {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.lang-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  background: var(--bg-soft);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  min-height: 44px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.lang-btn:hover {
  border-color: var(--primary);
  background: #fff;
}
.lang-flag {
  font-size: 0.95rem;
  line-height: 1;
}
/* Windows puede dibujar la bandera como "ES": el botón ya muestra ese código.
   El idioma se identifica por su texto estable, sin duplicarlo según el sistema. */
.lang-btn .lang-flag { display: none; }
.lang-drop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 50;
  min-width: 140px;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: var(--shadow-float);
  padding: 4px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.lang-drop[hidden] {
  display: none !important;
}
.lang-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  background: none;
  border: none;
  border-radius: calc(var(--radius) - 2px);
  color: var(--ink);
  font-size: 0.82rem;
  font-weight: 600;
  min-height: 44px;
  text-align: left;
  cursor: pointer;
  transition: background .12s ease, color .12s ease;
}
.lang-opt:hover {
  background: var(--bg-soft);
}
.lang-opt.active {
  background: var(--primary-08);
  color: var(--primary);
  font-weight: 700;
}


/* === Publicación 'Pon tu anuncio gratis' (Estilo Idealista + Paper&Ink) === */
.btn-pub-header {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--primary);
  color: #ffffff !important;
  font-weight: 700;
  font-size: .84rem;
  padding: 8px 14px;
  border-radius: var(--radius);
  text-decoration: none;
  border: 1px solid var(--primary);
  transition: all .2s ease;
  white-space: nowrap;
  min-height: 44px;
}
.btn-pub-header:hover {
  /* Unificado con --primary-dark (30-ago-2026): era un verde propio, #0d6841, que no coincidia
     con el resto de hovers verdes del sitio (.btn-solid, .btn-green, .cookie-btn-si...). Sin
     razon documentada para ser distinto -- roadmap.md P18 lo dejaba pendiente de decidir. */
  background: var(--primary-dark);
  box-shadow: 0 2px 8px rgba(15, 122, 77, 0.25);
  transform: translateY(-1px);
}

/* En movil el idioma se muestra como texto estable ("ES"), no como bandera dependiente del sistema.
   El control conserva 44x44 px y margen interior real: se reconoce mejor y sigue siendo comodo de
   tocar sin competir con la marca ni con "Ingresar". */
@media (max-width: 640px) {
  .btn-pub-header { padding: 8px 10px; }
  .btn-pub-header-txt { display: none; }
  #topUser .lang-flag, #topUser .lang-arrow { display: none; }
  #topUser .lang-code { display: inline; }
  #topUser .lang-btn { min-width: 44px; justify-content: center; padding: var(--s1); }
  #topUser { gap: 6px; }
  /* La misma acción de publicar está dentro del menú lateral. En una sesión privada se libera
     ese espacio para notificaciones, panel y salida, que antes quedaban cortados a 375px. */
  #topUser.is-authenticated .btn-pub-header { display: none; }
  #topUser.is-authenticated .chip-copy { display: none; }
  #topUser.is-authenticated .chip { padding: 5px; }
  #topUser.is-authenticated .logout-label { display: none; }
}

/* La accion de publicar ya existe en el desplegable "Publicar / Soy agente". Entre 1360 y 1599
   no se duplica como CTA a la derecha: esa copia consumia 187px y dejaba la fila al limite justo
   donde nace el escritorio. A partir de 1600 vuelve como atajo visible para visitantes. */
@media (min-width: 1360px) and (max-width: 1599px) {
  #topUser .btn-pub-header { display: none; }
}
/* Una cuenta iniciada suma campana, identidad y salida. Medido con el menu nuevo: a 1600 esos
   controles comprimian 5px de la marca; desde 1700 la fila completa conserva todo su ancho. */
@media (min-width: 1360px) and (max-width: 1699px) {
  #topUser.is-authenticated .btn-pub-header { display: none; }
  #topUser.is-authenticated .chip-copy { display: none; }
  #topUser.is-authenticated .chip { padding: 5px; }
}

.stepper-pub {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 24px;
  background: var(--bg-soft);
  padding: 6px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  overflow-x: auto;
}
.step-pub {
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-soft);
  padding: 6px 12px;
  border-radius: var(--radius);
  white-space: nowrap;
}
.step-pub.active {
  background: var(--ink);
  color: #ffffff;
}
.step-pub.done {
  color: var(--primary);
  font-weight: 700;
}
.step-check {
  color: var(--primary);
  font-weight: bold;
}

.pub-layout {
  display: flex;
  gap: 28px;
  align-items: flex-start;
}
.pub-form-col {
  flex: 1;
  min-width: 0;
}
.pub-summary td { overflow-wrap: anywhere; }
.pub-info-col {
  width: 320px;
  flex-shrink: 0;
}
@media (max-width: 860px) {
  .pub-layout {
    flex-direction: column;
  }
  .pub-info-col {
    display: none;
  }
  .pub-mobile-info {
    display: block !important;
  }
}

.pub-step-box {
  background: #ffffff;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 20px;
}
.pub-step-heading {
  font-family: var(--font-title);
  font-size: 1.35rem;
  font-weight: 700;
  margin: 0 0 18px;
  color: var(--ink);
}
.pub-section {
  margin-bottom: 22px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--hairline);
}
.pub-section:last-child {
  border-bottom: none;
  margin-bottom: 0;
  padding-bottom: 0;
}
.pub-label {
  display: block;
  font-size: .92rem;
  font-weight: 700;
  margin-bottom: 8px;
  color: var(--ink);
}

.pub-sidebar-box {
  background: #fafaf8;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 20px;
  position: sticky;
  top: 80px;
}
.pub-sidebar-title {
  font-family: var(--font-title);
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 12px;
  color: var(--ink);
}
.pub-sidebar-content p {
  font-size: .84rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0 0 12px;
}
.pub-sidebar-badge {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  background: #ffffff;
  border: 1px solid var(--hairline);
  padding: 10px 12px;
  border-radius: var(--radius);
  margin-top: 16px;
}
.badge-icon {
  font-size: 1.2rem;
}

.pub-mobile-info {
  display: none;
  background: #f0f7f3;
  border: 1px solid #b8decb;
  border-radius: var(--radius);
  padding: 10px 14px;
  margin-bottom: 16px;
}
.pub-mobile-info-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;
  font-size: .86rem;
  color: var(--primary);
}
.pub-mobile-info-body {
  margin-top: 8px;
  font-size: .82rem;
  line-height: 1.4;
  color: var(--ink);
}

/* Rich Select Dropdown */
.rich-select-wrap {
  position: relative;
  width: 100%;
}
.rich-select-btn {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #ffffff;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 12px 14px;
  text-align: left;
  cursor: pointer;
  transition: all .15s ease;
}
.rich-select-btn:hover, .rich-select-btn:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(15, 122, 77, 0.1);
}
.btn-main-text {
  font-weight: 700;
  font-size: .95rem;
  color: var(--ink);
}
.btn-sub-text {
  font-size: .78rem;
  color: var(--ink-soft);
  margin-top: 2px;
}
.arrow-indicator {
  font-size: 1rem;
  color: var(--ink-soft);
}
.rich-select-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: #ffffff;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  max-height: 280px;
  overflow-y: auto;
  z-index: 100;
}
/* Buscador del selector de ciudad (P59, 23-ago-2026): con 247 distritos, un desplegable a pelo
   es peor que la lista de 11 de antes. La caja de busqueda va PEGADA arriba (sticky) para que siga
   a la vista mientras se baja por la lista; el max-height de .rich-select-menu ya crea el
   contenedor con scroll que el sticky necesita, asi que aqui funciona (ojo: si alguien le quita el
   overflow al menu, este sticky deja de pegar). */
.rich-select-buscador {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #fff;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--hairline);
}
.rich-select-buscador input {
  width: 100%;
  padding: 9px 12px;
  font: inherit; font-size: .9rem;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
}
.rich-select-buscador input:focus { outline: none; border-color: var(--primary); }
.rich-select-buscador small { display: block; margin-top: 5px; font-size: .74rem; color: var(--ink-soft); }
.rich-select-vacio { padding: 14px; font-size: .84rem; color: var(--ink-soft); text-align: center; }
/* zona-medida: distingue "acá tenemos precio de referencia calculado con datos reales" de "acá
   podés publicar igual, pero todavía no medimos". Publicar se puede en los 247; el sello de precio
   justo, solo en los 8 medidos. Mezclar las dos cosas seria prometer un dato que no tenemos. */
.zona-medida {
  display: inline-block;
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
  padding: 1px 7px; border-radius: 999px;
  background: var(--primary-14);   /* token que ya existe, no un rgba a mano */
  color: var(--primary);
  vertical-align: middle;
}
.rich-select-item {
  padding: 10px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--hairline);
  transition: background .12s ease;
}
.rich-select-item:last-child {
  border-bottom: none;
}
.rich-select-item:hover {
  background: #f0f7f3;
}
.rich-select-item:focus-visible {
  background: #f0f7f3;
  outline-offset: -3px;
}
.rich-select-item.active {
  background: #e1f2e9;
  border-left: 4px solid var(--primary);
}
.item-title {
  font-size: .92rem;
  font-weight: 600;
  color: var(--ink);
}
.item-desc {
  font-size: .78rem;
  color: var(--ink-soft);
  margin-top: 2px;
}

/* Radio Group & Pills */
/* 24-ago-2026, capturas de Pol del asistente de publicar: "Venta (en USD)" salia partido en
   "Vent / a (en / USD / )" y "A estrenar" en "A / estre / nar".
   Causa: este grupo era una fila SIN envolver y sus tarjetas eran `flex: 1` sin ancho minimo, asi
   que en la columna estrecha del asistente (el modal se parte en formulario + "Informacion util")
   cada opcion se quedaba en ~60px y el texto se rompia letra a letra.
   Y por que no lo salvaron las media queries que ya habia: porque miden el ANCHO DE LA VENTANA, y
   aqui la ventana era ancha -- lo estrecho era la columna. Para eso no sirve una media query; hace
   falta que el propio contenido declare de que ancho no baja. Misma leccion que el buscador del
   hero, el mismo dia. */
.pub-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.pub-radio-group.wrap-wrap {
  flex-wrap: wrap;
}
.pub-radio-card {
  /* base 180px: por debajo de eso "Alquiler (en Gs/mes)" no cabe en dos lineas legibles. */
  flex: 1 1 180px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: #ffffff;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  cursor: pointer;
  transition: all .15s ease;
}
.pub-radio-card.active {
  border-color: var(--primary);
  background: #f0f7f3;
}
.pub-radio-pill {
  display: inline-flex;
  align-items: center;
  /* Una pastilla es una etiqueta corta: o cabe entera o pasa a la linea siguiente. Nunca se parte
     por dentro -- "Buen estado" en tres lineas no es una pastilla, es un accidente. */
  white-space: nowrap;
  flex-shrink: 0;
  gap: 6px;
  padding: 8px 14px;
  background: #ffffff;
  border: 1.5px solid var(--line-strong);
  border-radius: 20px;
  cursor: pointer;
  font-size: .86rem;
  transition: all .15s ease;
}
.pub-radio-pill.active {
  background: var(--ink);
  color: #ffffff;
  border-color: var(--ink);
}

/* Stepper Counters */
.counter-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
}
.counter-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.counter-field label {
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink);
}
.counter-box {
  display: inline-flex;
  align-items: center;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--radius);
  background: #ffffff;
}
.btn-count {
  background: none;
  border: none;
  padding: 8px 14px;
  font-size: 1.1rem;
  font-weight: 700;
  cursor: pointer;
  color: var(--ink);
  transition: background .1s;
}
.btn-count:hover {
  background: #f0f0ee;
}
.count-val {
  min-width: 32px;
  text-align: center;
  font-weight: 700;
  font-size: .95rem;
}

/* Checkbox Grid */
.checkbox-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 10px;
}

/* Notice & Nav */
.pub-notice-box {
  background: #fff8e6;
  border: 1px solid #f0d693;
  padding: 10px 14px;
  border-radius: var(--radius);
  font-size: .82rem;
  line-height: 1.4;
  color: #735100;
  margin-bottom: 8px;
}
.pub-nav-bar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 16px;
}
.btn-pub-submit {
  background: var(--primary) !important;
  color: #ffffff !important;
  font-size: .95rem !important;
  font-weight: 700 !important;
  padding: 10px 20px !important;
}

/* Photos Gallery */
.pub-photos-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}
.pub-photo-thumb {
  position: relative;
  aspect-ratio: 4/3;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1.5px solid var(--line-strong);
}
.pub-photo-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.btn-thumb-del {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 2;
  background: transparent;
  color: #fff;
  border: none;
  border-radius: 50%;
  width: 44px;
  height: 44px;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.btn-thumb-del::before {
  content: "";
  position: absolute;
  inset: 8px;
  border-radius: 50%;
  background: rgba(0,0,0,0.72);
}
.thumb-badge {
  position: absolute;
  bottom: 4px;
  left: 4px;
  background: rgba(0,0,0,0.65);
  color: #fff;
  font-size: .68rem;
  padding: 2px 6px;
  border-radius: 3px;
}
.pub-upload-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius);
  padding: 24px;
  background: #fafaf8;
  cursor: pointer;
  transition: all .2s ease;
  font-weight: 600;
  font-size: .9rem;
  color: var(--ink);
}
.pub-upload-btn:hover {
  border-color: var(--primary);
  background: #f0f7f3;
}


/* === Panorama: Badges de Fechas y Fuentes de Alta Visibilidad === */
.panorama-header-badge {
  display: inline-block;
  background: #f0f7f3;
  color: var(--primary);
  border: 1px solid #b8decb;
  padding: 8px 14px;
  border-radius: var(--radius);
  font-size: .88rem;
  margin: 12px 0 16px;
}
.panorama-badge-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: #fafaf8;
  border: 1.5px solid var(--line-strong);
  padding: 8px 14px;
  border-radius: var(--radius);
  margin: 12px 0 6px;
}
.p-badge-date {
  font-size: .86rem;
  color: var(--ink);
}
.p-badge-source {
  font-size: .82rem;
  color: var(--ink-soft);
}
.p-badge-source a {
  color: var(--primary);
  text-decoration: none;
}
.p-badge-source a:hover {
  text-decoration: underline;
}
.panorama-explica {
  font-size: .80rem;
  color: var(--ink-soft);
  margin: 4px 0 16px;
  line-height: 1.4;
}


/* ==========================================================================
   HERO PRINCIPAL & BUSCADOR ESTILO IDEALISTA (Responsive Desktop & Mobile)
   ========================================================================== */
.home-hero {
  position: relative;
  min-height: 480px;
  background: #111; /* la foto la pinta .home-hero-bg, ver mas abajo */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s7) var(--s5);
  margin-top: -24px;
  margin-left: calc(-1 * var(--wrap-pad));
  margin-right: calc(-1 * var(--wrap-pad));
  margin-bottom: var(--s6);
  border-radius: 0 0 16px 16px;
  overflow: visible;
}
@media (min-width: 1090px) {
  .home-hero {
    min-height: 520px;
    padding: 64px 32px;
  }
}
/* .home-sec / .home-sep (25-ago-2026, Pol con una raya roja entre las cifras y el listado:
   "separa un poco mejor las secciones a nivel visual").
   Dos piezas y ninguna inventa un numero: el aire sale de la escala (--s7 = 48px entre secciones
   mayores, --s6 = 32px dentro), que es justo lo que pedia el pendiente de "migrar el espaciado a
   la escala" apuntado el 23-ago. Escribir aqui un `margin: 44px` habria sumado una declaracion
   mas a las 276 que ya estan fuera de escala.
   La LINEA existe porque Pol dibujo una frontera, no un hueco: con solo mas aire, en una pantalla
   larga las secciones se siguen leyendo como una lista continua. Es --hairline, el gris mas suave
   que ya usa el sitio: separa sin llamar la atencion.
   Sin linea DESPUES de la ultima seccion (:last-child) -- una raya al final del contenido no
   separa nada, solo subraya el aire. */
.home-sec { margin-top: var(--s7); }
.home-sec:first-child { margin-top: 0; }
.home-sec + .home-sec { padding-top: var(--s7); border-top: 1px solid var(--hairline); }
.home-sec-tit {
  font-size: 1.02rem;
  margin: 0 0 var(--s4);
  color: var(--ink);
}
@media (max-width: 640px) {
  /* En movil el hero y el buscador ya se comen la primera pantalla: 48px entre secciones alejan
     el listado todavia mas del pulgar. --s6 mantiene la separacion legible sin empujar tanto. */
  .home-sec { margin-top: var(--s6); }
  .home-sec + .home-sec { padding-top: var(--s6); }
}

/* .home-hero-bg — la foto del hero y su respiracion lenta (encargo de Pol, 24-ago-2026).
   Capa aparte y no el fondo de .home-hero por dos motivos concretos:
     1) animar el zoom sobre .home-hero escalaria tambien el H1 y el buscador que van dentro;
     2) .home-hero es overflow:visible A PROPOSITO -- el desplegable de ciudades del buscador
        tiene que poder salirse del hero. El recorte lo hace esta capa, que si recorta.
   El movimiento es de 4% en 18s: a esa velocidad no se percibe como animacion sino como que la
   imagen "respira". Un zoom mas rapido o mas amplio compite con el buscador, que es lo que la
   persona vino a usar.
   GPU: translateZ(0) saca la capa a su propio plano de composicion y will-change avisa al motor
   antes de empezar. Se anima SOLO transform -- ni width, ni margin, ni background-position:
   cualquiera de esas recalcularia el layout en cada fotograma y la home entera daria tirones.
   Coste de carga: 0 KB. Es la misma imagen de siempre, solo cambia quien la pinta. */
.home-hero-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  border-radius: inherit;
}
/* 28-ago-2026: la foto fija con "respiración" (zoom CSS) se reemplazó por un video real (tour dron,
   pedido de Pol) -- ver el comentario en homeHeroHtml() de js/app.js. El `poster` hace de imagen
   fija para quien pidió menos movimiento (ahí nunca se agrega `autoplay`, así que el video ni
   arranca y se ve como una foto quieta) y para el instante antes de que cargue el archivo. */
.home-hero-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.home-hero-overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(10, 18, 14, 0.45) 0%, rgba(10, 18, 14, 0.72) 100%);
  border-radius: inherit;
  z-index: 1;
}
.home-hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: 920px;
  text-align: center;
  color: #ffffff;
}
.home-hero-kicker {
  display: inline-block;
  background: rgba(15, 122, 77, 0.9);
  color: #fff;
  font-size: .80rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 4px 12px;
  border-radius: 20px;
  margin-bottom: var(--s4);
  backdrop-filter: blur(4px);
}
.home-hero-title {
  font-family: var(--font-display, serif);
  font-size: 2.1rem;
  line-height: 1.2;
  font-weight: 700;
  margin: 0 0 var(--s3);
  color: #ffffff;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}
@media (min-width: 768px) {
  .home-hero-title {
    font-size: 2.8rem;
  }
}
.home-hero-sub {
  font-size: 1.05rem;
  line-height: 1.4;
  max-width: 640px;
  margin: 0 auto var(--s5);
  color: rgba(255, 255, 255, 0.92);
  text-shadow: 0 1px 4px rgba(0,0,0,0.6);
}
/* Teléfonos bajos: conservar Buscar en la primera pantalla sin reducir el tamaño
   táctil ni el padding de los campos. El ajuste no altera tablet/escritorio. */
@media (max-width: 640px) and (max-height: 760px) {
  .home-hero { padding-top: var(--s4); padding-bottom: var(--s4); }
  .home-hero-title { font-size: 1.85rem; }
  .home-hero-sub { margin-bottom: var(--s4); }
}

/* Caja de Búsqueda Idealista */
.idealista-search-box {
  background: var(--bg);
  border-radius: calc(var(--radius) * 4);
  padding: var(--s3);
  box-shadow: var(--shadow-float);
  text-align: left;
  position: relative;
  z-index: 10;
}
@media (min-width: 768px) {
  .idealista-search-box {
    padding: var(--s4);
  }
}

/* Tabs Operación (Comprar / Alquilar) */
.hero-search-tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s1);
  width: 100%;
  margin-bottom: 0;
  padding: var(--s1);
  border: 1px solid var(--hairline);
  border-radius: calc(var(--radius) * 3);
  background: var(--bg-soft);
}
/* 22-ago-2026: variante EN LINEA con el resto de la fila de busqueda (Idealista real las pone asi,
   no en una fila propia arriba) -- solo aplica a partir de donde .hero-search-row ya es una fila
   horizontal (ver el mismo breakpoint 768px de abajo). */
@media (min-width: 768px) {
  .hero-search-tabs-inline {
    margin-bottom: 0;
    width: auto;
    gap: 1px;
    padding: 1px;
    flex-shrink: 0;
  }
  .hero-search-tabs-inline .hero-tab-btn {
    padding-inline: var(--s3);
  }
}
.hero-tab-btn {
  background: transparent;
  border: 1px solid transparent;
  min-height: 44px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-soft);
  padding: 0 var(--s4);
  border-radius: calc(var(--radius) * 2);
  cursor: pointer;
  transition: color .15s ease, background-color .15s ease, border-color .15s ease, box-shadow .15s ease;
}
.hero-tab-btn:hover {
  color: var(--ink);
  background: var(--primary-08);
}
.hero-tab-btn.active {
  color: var(--primary);
  background: var(--bg);
  border-color: var(--line-strong);
  box-shadow: var(--shadow-card);
}
.hero-tab-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* Fila Principal de Búsqueda */
.hero-search-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
@media (min-width: 768px) {
  /* flex-wrap (24-ago-2026, fallo real que Pol fotografio en produccion): en fila, la unica pieza
     flexible era .hero-loc-container y se comia TODA la falta de espacio -- se encogia hasta ~30px,
     el input de ciudad desaparecia dejando solo el 📍, y el desplegable (que se ancla a los bordes
     de ese contenedor) salia de 30px de ancho con el texto en vertical, letra por letra.
     El boton "Buscar" tambien se apretaba y partia en "Bu/sca/r".
     Dos arreglos que van juntos: que la fila pueda pasar a dos lineas antes de aplastar nada, y que
     cada pieza declare de que ancho no baja. Uno solo no basta -- sin los minimos, wrap nunca se
     dispara porque todo "cabe" comprimido a cero. */
  .hero-search-row {
    flex-direction: row;
    align-items: stretch;
    flex-wrap: wrap;
  }
}

.hero-tipo-container {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-width: 150px;
  flex-shrink: 0;
}
.hero-field-label {
  position: absolute;
  top: 5px;
  left: var(--s4);
  z-index: 2;
  color: var(--ink-soft);
  font-size: .75rem;
  font-weight: 700;
  line-height: 1;
  letter-spacing: .04em;
  text-transform: uppercase;
  pointer-events: none;
}
.hero-tipo-select {
  height: 48px;
  padding: var(--s4) var(--s6) 0 var(--s4);
  border: 1.5px solid var(--line-strong);
  border-radius: 8px;
  /* Conservar el chevron de la regla global de select: `background` lo borraria entero. */
  background-color: var(--bg);
  font-size: 1rem;
  color: var(--ink);
  font-weight: 500;
  cursor: pointer;
  outline: none;
  min-width: 150px;
  /* El ancho lo gobierna .hero-tipo-container: su columna flex estira el select en móvil y vuelve
     a su ancho intrínseco desde 768px. `auto` es importante: el width:100% global de los select no
     puede pedir toda la fila y empujar la ubicación o el botón. */
  width: auto;
}
.hero-tipo-select:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-20);
}

/* Input de Ubicación con Icono y Dropdown Desplegable */
.hero-loc-container {
  position: relative;
}
.hero-loc-container > .hero-field-label { left: 42px; }
/* El `flex: 1 1 220px` de aqui abajo SOLO puede vivir dentro de esta media query (24-ago-2026,
   hueco que fotografio Pol en produccion). El eje principal de un flex es el que marca su
   direccion, y en movil .hero-search-row es una COLUMNA: alli ese 220px no es un ancho base, es un
   ALTO base. El campo de ciudad media 220px de alto en vez de 48 y dejaba 172px de blanco entre el
   campo y el boton "Buscar" -- justo el hueco de la captura. Lo mismo vale para cualquier flex-basis
   que se escriba pensando en una fila: si el mismo contenedor es columna en movil, hay que acotarlo.
   Base 220 y suelo de 180 en escritorio: por debajo de eso el desplegable de zonas es ilegible (es
   el que salio con el texto en vertical). Cuando no quepa, la fila envuelve; no se comprime esto. */
@media (min-width: 768px) {
  /* La base es 180 y no 220 a proposito (medido 24-ago-2026). El navegador decide si parte la fila
     con la base, ANTES de encoger nada: con 220 la suma daba 705 en una fila de 692 y el boton
     "Buscar" caia solo a una segunda linea justo a 768px. Con 180 da 665 y cabe. No se pierde
     ancho: el campo crece igual con el hueco sobrante, y 180 es ademas el suelo por debajo del
     cual el desplegable de zonas es ilegible. */
  .hero-tipo-container { width: auto; }
  .hero-loc-container {
    flex: 1 1 180px;
    min-width: 180px;
  }
}
.hero-loc-wrap {
  display: flex;
  align-items: center;
  border: 1.5px solid var(--line-strong);
  border-radius: 8px;
  background: var(--bg);
  height: 48px;
  padding: 0 var(--s4);
  transition: border-color .15s;
}
.hero-loc-wrap:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px var(--primary-20);
}
.hero-loc-icon {
  display: inline-flex;
  align-items: center;
  color: var(--primary);
  margin-right: var(--s2);
  flex-shrink: 0;
}
.hero-loc-input {
  height: 100%;
  width: 100%;
  min-width: 0;
  /* El wrapper ya aporta el margen horizontal y reserva el hueco del icono. Hay que sobrescribir
     los cuatro lados: si solo se cambia padding-top, este input conserva los 14px laterales y los
     12px inferiores del shorthand global, y el valor deja de alinear con su etiqueta. */
  padding: var(--s4) 0 0;
  border: none;
  background: transparent;
  font-size: 1rem;
  color: var(--ink);
  outline: none;
}
.hero-loc-input::placeholder {
  color: #666;
}

/* Dropdown Inteligente de Zonas y Modos de Búsqueda */
.hero-loc-dropdown {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
  box-shadow: 0 14px 32px rgba(0,0,0,0.18);
  padding: 8px 0;
  z-index: 50;
  max-height: 380px;
  overflow-y: auto;
  text-align: left;
}

.hero-drop-option {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: var(--s3) var(--s4);
  cursor: pointer;
  color: var(--ink);
  text-decoration: none;
  transition: background .12s;
  border: none;
  width: 100%;
  background: transparent;
  text-align: left;
}
.hero-drop-option:hover {
  background: var(--primary-08);
}
/* P20 roadmap (22-ago-2026): eran emojis del sistema ("iconitos de whatsapp", rechazados por Pol) --
   ahora un <svg> de trazo (ver ICONOS/ico() en ui-core.js), por eso el cambio de font-size/text-align
   a un centrado flex real. */
.hero-drop-icon {
  flex-shrink: 0;
  width: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-soft);
}
.hero-drop-title {
  font-size: .92rem;
  font-weight: 600;
  color: var(--ink);
  display: block;
}
.hero-drop-sub {
  font-size: .78rem;
  color: var(--ink-soft);
  display: block;
  margin-top: 1px;
}
.hero-drop-divider {
  height: 1px;
  background: var(--hairline);
  margin: var(--s2) 0;
}
.hero-drop-heading {
  font-size: .72rem;
  font-weight: 700;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: .06em;
  padding: var(--s2) var(--s4) var(--s1);
}
.hero-drop-city {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 8px 16px;
  font-size: .88rem;
  color: var(--ink);
  cursor: pointer;
  transition: background .12s;
}
.hero-drop-city:hover,
.hero-drop-city:focus-visible {
  background: var(--primary-08);
  color: var(--primary);
  outline: none;
}

/* Botón Buscar Hero */
.btn-hero-buscar {
  height: 48px;
  padding: 0 var(--s6);
  flex-shrink: 0;
  white-space: nowrap;
  background: var(--primary);
  color: #fff;
  border: none;
  border-radius: 8px;
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: background .15s, transform .1s;
}
.btn-hero-buscar:hover {
  /* Unificado con --primary-dark, mismo motivo que .btn-pub-header:hover arriba (30-ago-2026,
     roadmap.md P18). */
  background: var(--primary-dark);
}
.btn-hero-buscar:active {
  transform: scale(0.98);
}

/* En móvil Tipo y Ubicación se leen como un único instrumento de búsqueda, no como dos tarjetas
   independientes. La unión es solo visual: la estructura, el desplegable y sus acciones siguen
   intactos. Desde 768px vuelven a ser piezas separadas dentro de la fila horizontal medida. */
@media (max-width: 767px) {
  .hero-search-row { gap: 0; }
  .hero-search-tabs { margin-bottom: var(--s2); }
  .hero-tipo-select { border-radius: calc(var(--radius) * 3) calc(var(--radius) * 3) 0 0; }
  .hero-loc-wrap {
    border-top: 0;
    border-radius: 0 0 calc(var(--radius) * 3) calc(var(--radius) * 3);
  }
  .btn-hero-buscar {
    margin-top: var(--s2);
    border-radius: calc(var(--radius) * 3);
  }
}
@media (min-width: 560px) and (max-width: 767px) {
  .hero-search-row {
    display: grid;
    grid-template-columns: minmax(150px, 2fr) minmax(0, 3fr);
  }
  .hero-search-tabs,
  .btn-hero-buscar { grid-column: 1 / -1; }
  .hero-tipo-select {
    border-radius: calc(var(--radius) * 3) 0 0 calc(var(--radius) * 3);
  }
  .hero-loc-wrap {
    border-top: 1.5px solid var(--line-strong);
    border-left: 0;
    border-radius: 0 calc(var(--radius) * 3) calc(var(--radius) * 3) 0;
  }
}

/* Pastillas Rápidas bajo el Buscador */
.hero-quick-pills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: var(--s5);
}
.hero-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(0, 0, 0, 0.45);
  color: #ffffff;
  border: 1px solid rgba(255, 255, 255, 0.35);
  padding: var(--s2) var(--s4);
  border-radius: 20px;
  font-size: .84rem;
  font-weight: 500;
  text-decoration: none;
  backdrop-filter: blur(6px);
  transition: all .15s ease;
}
.hero-pill:hover {
  background: rgba(255, 255, 255, 0.92);
  color: var(--primary);
  border-color: #ffffff;
}

/* ---- P16 roadmap: bloque de Precio con contraoferta / aviso de bajada ---- */
/* Bloque "Precio justo" de la ficha (leadsAccionesHtml). Reestructurado el 23-ago-2026 tras la
   captura de Pol a 430px: era una fila en baseline que en movil se partia y dejaba el precio por m²
   como un numero huerfano debajo del precio, sin etiqueta. Ahora es una columna con jerarquia
   explicita -- el precio manda, el precio por metro es su subtitulo -- y la accion queda separada
   por una linea, no solo por un hueco. */
.lead-precio-bloque { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s1); }
.lead-precio { font-family: var(--font-display, inherit); font-size: 1.9rem; line-height: 1.1; color: var(--ink); }
.lead-precio-mes { color: var(--ink-soft); font-size: 1rem; }
.lead-precio-m2 { color: var(--ink-soft); font-size: .85rem; }
.lead-precio-acciones {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--hairline);
  flex-wrap: wrap;
}
.lead-precio-acciones .btn { min-width: 0; max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
.lead-precio-nota { margin-top: var(--s3); }

/* ---- Costo de reforma (P19) — corregido 22-ago tras la captura de Pol ----
   El bloque usaba `.card` (la tarjeta de PROPIEDAD: overflow:hidden y sin padding, porque su primer
   hijo es la foto a sangre), asi que el contenido salia pegado a los bordes y el buscador recortado.
   Ahora usa `.panel-card`, el contenedor de seccion real que ya usaba la calculadora de hipoteca.
   El buscador es flex y se apila solo en movil; los filtros son chips, no botones gigantes. */
.costeo-buscador { display: flex; gap: 10px; align-items: stretch; flex-wrap: wrap; }
.costeo-buscador input {
  flex: 1 1 260px;             /* crece hasta llenar, y por debajo de 260px se va a su propia linea */
  min-width: 0;                /* sin esto un input flex NUNCA baja de su ancho intrinseco y desborda */
}
.costeo-buscador .btn { flex: 0 0 auto; }

.costeo-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0 12px; }
.costeo-chip {
  font: inherit; font-size: .88rem; font-weight: 600;
  padding: 7px 14px;
  background: var(--bg); color: var(--ink-soft);
  border: 1px solid var(--line-strong); border-radius: 999px;
  cursor: pointer;
}
.costeo-chip:hover { border-color: var(--primary); color: var(--primary); }
.costeo-chip.sel { background: var(--primary); border-color: var(--primary); color: #fff; }

/* .ficha-bloque: lo usa leadsAccionesHtml() (P16, bloque de Precio con contraoferta/aviso).
   ⚠️ Se escribio el marcado sin crear la clase -- el bloque salia sin ningun estilo. Encontrado al
   revisar TODAS las clases introducidas hoy a raiz del fallo de .card, antes de que se viera en la
   web. Mismo aspecto de seccion que .panel-card, que es lo que es. */
.ficha-bloque {
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  padding: 20px;
  margin-top: 24px;
}

/* ---------- 11.9. SECCIONES DE LA FICHA DE PROPIEDAD (P62, 23-ago-2026) ----------
   Pol: "veo muy monotono todas las secciones, por si puedes ser un poco mas creativo respetando
   el diseño".

   Tenia razon y la causa era mecanica, no de gusto: Precio, Ubicacion, Estadisticas y Cosas que
   saber usaban TODAS el mismo contenedor (.panel-card / .ficha-bloque, que son el mismo aspecto)
   con el mismo titulo serif. Cuatro cajas blancas identicas seguidas. Al no haber jerarquia, el
   bloque de Precio -- que es el diferencial del proyecto -- pesaba lo mismo que el contador de
   favoritos.

   NO se inventan colores ni tipografias nuevas: se usan tres PESOS del sistema que ya existe.
     1. .ficha-sec--destacada  fondo --primary-08 y filo verde. Solo para Precio.
     2. .ficha-sec             la tarjeta blanca de siempre. Ubicacion, Cosas que saber.
     3. .ficha-sec--llana      sin caja, sobre --bg-soft. Para lo secundario (Estadisticas).
   Y cada seccion lleva su .kicker (mono, mayusculas, verde), que ya es el idioma del sitio para
   etiquetar: da identidad propia a cada bloque sin añadir ni una pieza visual nueva. */
.ficha-sec {
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  padding: 22px;
  margin-top: 28px;              /* un solo ritmo; antes eran 24/26/26/26 puestos a mano */
}
.ficha-sec > h2 { margin: 0 0 4px; font-size: 1.3rem; }
/* El kicker de seccion lleva una raya corta a su izquierda: marca el arranque del bloque y hace
   de ancla para el ojo al bajar. 2px y del verde de marca -- el mismo acento, no uno nuevo. */
.ficha-sec-kicker {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--font-mono);
  font-size: .68rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--primary);
  margin-bottom: 10px;
}
.ficha-sec-kicker::before {
  content: ''; width: 18px; height: 2px; flex: 0 0 18px;
  background: var(--primary);
}
.ficha-sec-nota { font-size: .84rem; color: var(--ink-soft); margin: 2px 0 0; }

/* 1. DESTACADA — solo el bloque de Precio. Es lo unico de esta pagina que el proyecto promete de
   verdad, asi que es lo unico que se lleva el acento. Si mañana se destaca una segunda seccion,
   esto deja de significar nada: la jerarquia solo funciona mientras hay un solo primero. */
.ficha-sec--destacada {
  background: var(--primary-08);
  border-color: transparent;
  border-left: 3px solid var(--primary);
}
.ficha-sec--destacada > h2 { color: var(--primary-dark); }

/* 3. LLANA — sin borde ni fondo blanco: es un descanso visual entre dos cajas. Lo secundario no
   necesita el mismo envase que lo principal, y alternar respiro y caja es lo que rompe la
   monotonia sin inventarse nada. */
.ficha-sec--llana {
  background: var(--bg-soft);
  border: 0;
  border-radius: var(--radius);
  padding: 20px 22px;
}

/* Estadisticas en fila compacta. Antes eran dos .stat-tile enormes, cada una con un "0" en
   tipografia de titular: en el movil de Pol ocupaban una pantalla entera para decir cero y cero.
   El dato es real y se queda, pero con el tamaño que le corresponde. */
.ficha-stats { display: flex; flex-wrap: wrap; gap: 10px 28px; margin: 0; }
.ficha-stat { display: flex; align-items: baseline; gap: 8px; }
.ficha-stat-n {
  font-family: var(--font-display); font-size: 1.45rem; font-weight: 700;
  color: var(--ink); line-height: 1;
}
.ficha-stat-l { font-size: .8rem; color: var(--ink-soft); }

/* Las tres columnas de "Cosas que tenes que saber", separadas por un filo en vez de solo por
   hueco: apiladas en movil se leian como un unico parrafo largo. */
.ficha-saber { display: grid; gap: 0; }
.ficha-saber > div { padding: 14px 0; border-top: 1px solid var(--hairline); }
.ficha-saber > div:first-child { padding-top: 0; border-top: 0; }
.ficha-saber b { display: block; margin-bottom: 4px; font-size: .95rem; }
.ficha-saber p { margin: 0; font-size: .84rem; color: var(--ink-soft); }
@media (min-width: 760px) {
  .ficha-saber { grid-template-columns: repeat(3, 1fr); gap: 0 28px; }
  .ficha-saber > div { padding: 0; border-top: 0; border-left: 1px solid var(--hairline); padding-left: 20px; }
  .ficha-saber > div:first-child { border-left: 0; padding-left: 0; }
}

/* ---- P43: buscador de /propiedades por revelacion progresiva (22-ago) ----
   Lo esencial (operacion, zona, tipo) siempre visible y con peso propio; el resto detras de
   "Mas filtros". Sustituye a la reja de 12 controles iguales donde "Piscina" pesaba lo mismo que
   "Zona". Movil primero: todo apilado, y a partir de 760px la fila principal se reparte. */
.busq { display: flex; flex-direction: column; gap: 14px; }

/* Pestañas de operacion (24-ago-2026). Eran pildoras con relleno: la elegida se pintaba de verde
   SOLIDO, el mismo peso que el sello "Precio justo" de las tarjetas que hay justo debajo -- dos
   verdes macizos compitiendo en la misma pantalla, que es lo que Pol ya hizo corregir el 23-ago.
   Ahora son pestañas subrayadas, como el listado de Idealista que mando de referencia, y el verde
   se reduce a la LINEA de la activa. La franja gris de abajo las une como una fila de pestañas, que
   es lo que son: no botones sueltos. */
/* Una fila de pestañas NO se parte en dos (24-ago-2026). Con `flex-wrap: wrap`, a 430px la
   cuarta —"A estrenar (0)"— caia sola a una segunda linea y la franja gris quedaba entre las dos:
   ya no se leia como una fila de pestañas sino como un boton suelto debajo de una raya. Medido:
   las cuatro piden 413px y a 430 hay 394; a 360 hay 324, asi que NINGUN ajuste de hueco las mete.
   Por eso la fila se desplaza de lado en vez de partirse, que es lo que hace cualquier app con mas
   pestañas que ancho. El hueco baja a --s4 para que a 430 —el movil de Pol— entren las cuatro sin
   tener que desplazar nada; por debajo de eso se arrastra. */
.busq-opers {
  display: flex; gap: var(--s4); flex-wrap: nowrap;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;                 /* la barra tapaba la linea verde de la pestaña activa */
  border-bottom: 1px solid var(--hairline);
  margin-bottom: var(--s4);
}
.busq-opers::-webkit-scrollbar { display: none; }
/* Sin esto una pestaña larga se encoge hasta partir su texto en dos lineas, que es el mismo fallo
   por otro camino. */
.busq-oper { flex: 0 0 auto; white-space: nowrap; }
.busq-oper {
  font: inherit; font-size: 1rem; font-weight: 600;
  padding: var(--s3) 2px;
  background: none; color: var(--ink-soft);
  border: 0; border-bottom: 3px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
}
.busq-oper:hover { color: var(--ink); }
.busq-oper.sel { color: var(--ink); border-bottom-color: var(--primary); }

/* Conteo + orden en la misma linea: son las dos cosas que se leen justo antes de la rejilla. */
.busq-barra {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap;
  margin: var(--s5) 0 var(--s2);
}
.busq-orden { display: inline-flex; align-items: center; gap: var(--s2); }
.busq-orden .mono { white-space: nowrap; font-size: .72rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .05em; }
/* padding-right propio: la regla global de <select> reserva 36px para la flecha, y el atajo
   `padding: a b` la pisaba -- el texto largo ("Más baratos por m²") se metia debajo del chevron.
   Visto en produccion, no en el codigo. */
.busq-orden select { font: inherit; font-size: .9rem; padding: var(--s2) var(--s3); padding-right: var(--s6); }
.busq-orden-nota {
  margin: 0 0 var(--s4);
  font-size: .8rem; line-height: 1.5; color: var(--ink-soft);
  max-width: 62ch;
}

.busq-principal { display: flex; flex-direction: column; gap: 10px; }
.busq-principal > * { min-width: 0; }          /* sin esto un select flex desborda en movil */
.busq-mapa { white-space: nowrap; }

/* "Mas filtros": mismo patron toggle+clase que .filters-panel y el menu movil -- NO <details>,
   por el bug de pintado en Chromium ya documentado en propsPage(). */
.busq-mas {
  font: inherit; font-size: .9rem; font-weight: 600; color: var(--ink-soft);
  background: none; border: 0; padding: 6px 0; cursor: pointer;
}
.busq-mas:hover { color: var(--primary); }
.busq-avanzado > .busq-avanzado-body { display: none; }
.busq-avanzado.open > .busq-avanzado-body { display: flex; flex-direction: column; gap: 12px; padding-top: 12px; }
.busq-campos { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.busq-extras { display: flex; flex-wrap: wrap; gap: 12px; }
/* Aplicar / Limpiar al cierre del bloque avanzado. "Aplicar" va primero y solido porque es la
   accion que la persona vino a hacer; "Limpiar" a su lado, de linea, porque destruye trabajo.
   flex-wrap para que a 360px pasen a dos lineas en vez de aplastarse (misma leccion que el hero). */
.busq-avanzado-acciones { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

.busq-total { color: var(--ink-soft); margin: 14px 0 10px; }

@media (min-width: 760px) {
  .busq-principal { flex-direction: row; align-items: center; }
  .busq-principal .field-with-btn { flex: 1 1 auto; }
  .busq-campos { grid-template-columns: repeat(4, 1fr); }
}

/* ---- Cabecera en pantallas estrechas: que NUNCA se corte (22-ago, P49) ----
   Pol, con captura de su movil: "Se corta en el movil" -- "Ingresar" quedaba partido por el borde
   derecho. El recorte de P10 (icono solo en publicar, bandera sola en idioma) ya estaba puesto y
   NO bastaba. Presupuesto real de ancho a 360-390px, que es lo que falla:
     16+16 padding · logo ~140 · gap 14 · hamburguesa 44 · #topUser ~174  ==>  ~404px necesarios.
   Sobran unos 40px, asi que hay que recuperarlos, no maquillarlos.

   Se recortan las tres cosas que no pierden funcion: el logo baja de tamaño, los margenes se
   ajustan y "Ingresar" se compacta. Ningun boton desaparece ni deja de ser tocable. */
@media (max-width: 420px) {
  .top-bar { padding: 0 12px; gap: 6px; }
  .logo { font-size: 1.05rem; }                /* ~140px -> ~112px */
  .nav-toggle { width: 44px; height: 44px; }
  #topUser { gap: 5px; }
  #topUser .btn-sm { padding: 8px 11px; font-size: .82rem; }   /* "Ingresar" ~92px -> ~72px */
  /* La misma accion abre el primer desplegable del menu. Quitar aqui la copia con solo un icono
     devuelve el nombre completo de la marca a 320px sin perder ningun destino. */
  #topUser .btn-pub-header { display: none; }
  /* Con sesión hay cuatro controles imprescindibles a la derecha. En vez de cortar el nombre
     como "ParaguayRe…", la marca usa aquí su isotipo oficial de casa con check. El enlace y su
     nombre accesible siguen siendo ParaguayReal; solo cambia la representación visual. */
  .top-bar.is-authenticated > .logo {
    flex: 0 0 34px;
    width: 34px;
    font-size: 0;
    background: url('/img/logo.svg') center / 32px 32px no-repeat;
  }
}

/* Red de seguridad: aunque un dia vuelva a no caber, que se encoja el LOGO (que se lee igual
   truncado) y nunca los botones de accion. Sin esto, flex reparte el recorte por donde le toca y
   se come justo lo de la derecha -- que es lo que le pasaba a "Ingresar". */
.top-bar .logo { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.top-bar #topUser { flex: 0 0 auto; }
.top-bar .nav-toggle { flex: 0 0 auto; }

/* ⚠️ NO se pone "html, body { overflow-x: hidden }" aunque seria lo tipico para matar un scroll
   horizontal: un overflow distinto de visible en un ancestro ROMPE position:sticky en los
   descendientes, y la ficha de propiedad usa sticky desde hoy mismo (.prop-sticky-bar, P16).
   Habria arreglado la cabecera rompiendo la ficha. El desbordamiento se corta en la propia barra,
   que es donde ocurre, sin tocar el documento entero.
   Tampoco se pone overflow en .top-bar: el panel del menu movil es hijo suyo y position:fixed, y
   un overflow en el ancestro puede recortarlo en algunos navegadores -- volveria a romper el menu
   que se acaba de arreglar. La unica proteccion es que el contenido QUEPA (reglas de arriba) y que,
   si algun dia no cabe, se trunque el logo -- que se lee igual -- y nunca los botones. */

/* ---- Botones dentro de los popups del mapa (22-ago, P52) ----
   Pol: "en cómo llegar el diseño no es correcto, no se entiende lo que pone".
   Causa medida: Leaflet trae `.leaflet-container a { color:#0078A8 }` -- especificidad (0,1,1),
   una clase MAS un elemento. Nuestro `.btn-solid { color:#fff }` pesa (0,1,0), solo una clase.
   Gana Leaflet, asi que el texto del boton salia azul #0078A8 sobre el verde de marca #0f7a4d:
   **1,09:1 de contraste** cuando el minimo legible (WCAG AA) es 4,5:1. Practicamente invisible.
   Y no es cuestion de orden: leaflet.css se carga despues (cargarLeaflet inyecta el <link>), pero
   es que ademas pesa mas -- subir el nuestro de sitio no habria bastado.

   Se recupera el blanco con un selector que gane a ese: se antepone .leaflet-popup-content, que
   suma otra clase (0,2,1). Acotado a los popups del mapa: no toca los enlaces normales de Leaflet
   (atribucion de OpenStreetMap, zoom), que deben seguir con su color propio. */
.leaflet-popup-content a.btn-solid,
.leaflet-popup-content a.btn-solid:hover { color: #fff; }
.leaflet-popup-content a.btn-line,
.leaflet-popup-content a.btn-line:hover { color: var(--ink); }

/* ---- Leyenda del mapa de zonas (P51/P53, 22-ago) ---- */
.mapa-leyenda { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 10px; font-size: .82rem; color: var(--ink-soft); }
.mapa-leyenda-item { display: inline-flex; align-items: center; gap: 6px; }
.mapa-punto { width: 12px; height: 12px; border-radius: 3px; background: rgba(185,182,176,.35); border: 1px solid #b9b6b0; }
.mapa-punto.medido { background: rgba(15,122,77,.22); border-color: #0f7a4d; }
.mapa-punto.con-anuncios { background: rgba(15,122,77,.55); border-color: #0f7a4d; }
/* Numero de anuncios pintado sobre el distrito (zonaMapaInit). Fijo, no en el hover: en tactil no
   hay puntero, y un dato que solo aparece al pasar el raton no existe en movil ni en tablet. */
.zona-conteo {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 20px; padding: 0 var(--s2);
  font-family: var(--font-mono); font-size: .7rem; font-weight: 700;
  color: #fff; background: #0f7a4d;
  border-radius: 999px; box-shadow: 0 1px 3px rgba(0,0,0,.3);
}

/* ---- Tabla de comparación en móvil (P54b, 22-ago) ----
   Pol: "se corta en el móvil y hay que mover con el dedo... por lo menos que lo indique".
   Dos problemas, no uno: (1) nada avisaba de que se puede arrastrar; (2) al arrastrar se iba
   tambien la columna de etiquetas, y te quedabas mirando "USD 1.143" sin saber de que fila era.

   La primera columna se queda FIJA con position:sticky. Funciona porque el contenedor de scroll es
   .tbl-wrap (overflow-x:auto), que es justo el ancestro que sticky necesita -- no es el caso del
   overflow en <html>/<body> que se descarto en P49 precisamente por romper sticky. */
/* El aviso lo INSERTA marcarTablasDesplazables() (js/app.js) solo en las tablas que de verdad se
   salen, medido con scrollWidth. Por eso ya no se oculta/muestra por ancho de pantalla: si esta en
   el DOM es porque hace falta. */
.tabla-desliza { color: var(--ink-soft); font-size: .82rem; margin: 0 0 8px; }

/* .col-fija (P55, 23-ago-2026): la primera columna se queda fija al deslizar. Nacio para la tabla
   de comparar (P54b) y ahora la pone marcarTablasDesplazables() en js/app.js midiendo el ancho
   real, no a mano tabla por tabla.
   ⚠️ NO se aplica a toda tabla que no quepa (como hacia P55 al nacer): ademas tiene que pasar
   primeraColumnaSirveDeAncla() -- la columna fija no puede llevarse mas del 40% del ancho visible.
   Sin ese limite, en una tabla de dos columnas de TEXTO (la de permisos de /agentes) la columna fija
   se comia media pantalla y por la rendija que quedaba solo asomaba el medio de una palabra: la
   tabla quedaba ilegible por culpa del arreglo. Ver P63.
   Sin esto, al deslizar hacia el costado se pierde de vista de que fila es cada numero: el aviso
   de P57 dice que se puede deslizar, pero deslizar sin referencia deja los datos huerfanos.
   OJO: sticky necesita que el ancestro con scroll sea .tbl-wrap. Si alguien le quita el overflow,
   esto deja de pegar y no avisa nadie. */
/* Texto solo para lectores de pantalla: se lee, no se ve. Hace falta para que "el mejor de esta
   fila" no dependa del color. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Celda ganadora de cada fila del comparador (23-ago-2026, Pol: "si algo es mejor quiero que lo
   subrayes de color verde claro"). Fondo tenue -- --primary-08, la tinta mas suave del sistema --
   y el numero en verde oscuro con peso: destaca sin gritar, y no compite con el sello de precio
   justo porque no es un relleno solido. Mantiene la ★ y un texto solo para lectores de pantalla:
   el color nunca puede ser la UNICA señal.
   ⚠️ Va ANTES de la regla de columna fija a proposito: esa pone fondo opaco a la primera columna
   (etiquetas), que nunca es "la mejor", asi que no se pisan. */
.comparar-tabla td.comparar-mejor {
  background: var(--primary-08);
  color: var(--primary-dark);
  font-weight: 700;
  box-shadow: inset 3px 0 0 var(--primary);
}
/* Encabezados del comparador: el titulo completo de un anuncio ("Casa en venta en Fernando de la
   Mora · 201 m² · 4 dormitorios") ensanchaba tanto la columna que la tabla se salia de la pantalla
   y habia que deslizarla siempre, incluso en escritorio. Con un ancho maximo por columna el titulo
   pasa a dos o tres lineas y la tabla CABE, que es lo que pidio Pol: "cuando hagas una comparativa
   no cortes las tablas". */
.comparar-tabla th { max-width: 260px; white-space: normal; overflow-wrap: anywhere; vertical-align: top; }
.comparar-tabla th:first-child { max-width: none; }

.comparar-tabla th:first-child,
.comparar-tabla td:first-child,
.col-fija th:first-child,
.col-fija td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
}
/* El fondo tiene que ser OPACO o el contenido que se desliza por debajo se transparenta encima de
   las etiquetas. Y tiene que ser EL MISMO que tendria esa celda si no fuese fija:
   - la esquina de la cabecera es un <th>, y .tbl th ya usa --bg-soft; ponerle --bg la dejaria
     blanca en mitad de una cabecera gris.
   - las celdas normales, --bg. */
.comparar-tabla td:first-child,
.col-fija td:first-child { background: var(--bg); }
.comparar-tabla th:first-child,
.col-fija th:first-child { background: var(--bg-soft); }
/* .tbl tbody tr:hover pinta la fila entera; sin esto la columna fija se quedaria blanca mientras
   el resto de su fila se pone gris. (No hay rayado par/impar en .tbl -- comprobado: el gris que se
   ve en las capturas es este hover, que en un movil se queda pegado tras el toque.) */
.comparar-tabla tbody tr:hover td:first-child,
.col-fija tbody tr:hover td:first-child { background: var(--bg-soft); }
/* Linea que separa la columna fija del contenido que se desliza bajo ella. */
.comparar-tabla th:first-child::after,
.comparar-tabla td:first-child::after,
.col-fija th:first-child::after,
.col-fija td:first-child::after {
  content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 1px;
  background: var(--hairline);
}


/* .tbl.compacta — tablas de dos columnas (etiqueta + valor), como el resultado de la calculadora
   de hipoteca. `.tbl` fuerza `min-width:560px` porque esta pensada para tablas de datos anchas que
   viven dentro de un `.tbl-wrap` con scroll; una de dos columnas cortas no necesita ese ancho y,
   sin wrap alrededor, desbordaria la pagina en movil. Aqui se le quita el minimo y se deja que el
   texto fluya. */
.tbl.compacta { min-width: 0; }
.tbl.compacta td { white-space: normal; }

/* ---- Comparador: barra y botón desactivado (P54, rediseño 22-ago) ----
   El "Comparar" de la barra aparece desde la PRIMERA seleccion pero apagado hasta que hay dos: se
   ve adonde vas antes de poder ir. Es un <span>, no un <button disabled>, porque el destino es un
   enlace y un enlace deshabilitado no existe en HTML -- asi no hay nada que pulsar por error. */
.comparar-btn-off {
  opacity: .45;
  cursor: not-allowed;
  pointer-events: none;    /* que ni siquiera reciba el toque, no solo que no navegue */
}
.comparar-barra-txt { line-height: 1.3; }

/* ---- Guía: "ver los N restantes" en la lista de sitios (P59, 23-ago) ----
   Pol: "si hay más, tiene que desplegar todas las que falten al darle ver más". Antes el
   "+ 47 más" era texto plano: anunciaba que habia mas y no habia forma de verlos.
   Los sitios extra estan YA en el HTML, solo tapados aqui: desplegar no pide nada al servidor. */
.guia-poi-list:not(.abierta) .guia-poi-extra { display: none; }
.guia-poi-mas { list-style: none; margin-top: 8px; }

/* ---- P44: simulador de reforma (23-ago) ----
   Pol, tres veces: "el simulador de reforma tienes que mejorarlo". Lo que habia era un buscador de
   precios; ahora hay dos pestañas y el buscador es la segunda. Todo con piezas que ya existen
   (.costeo-chip para elegir la obra, .check para las casillas, .tbl para nada -- aqui NO hay tabla:
   una tabla de 4 columnas en un movil de 360px es justo el problema de P63/P55).
   Por eso cada partida es una FILA APILADA, no una fila de tabla: en movil se lee de arriba abajo
   y en escritorio se reparte en una linea. */
.reforma-pestanas { display: flex; gap: 4px; margin: 14px 0 18px; border-bottom: 1px solid var(--hairline); }
.reforma-pestana {
  font: inherit; font-size: .92rem; font-weight: 600;
  padding: 10px 16px; background: none; border: 0; border-bottom: 2px solid transparent;
  color: var(--ink-soft); cursor: pointer; margin-bottom: -1px;
}
.reforma-pestana:hover { color: var(--ink); }
.reforma-pestana.sel { color: var(--primary); border-bottom-color: var(--primary); }

.reforma-paso-n { margin: 0 0 6px; color: var(--ink-soft); }
.reforma-h3 { font-size: 1.15rem; margin: 0 0 12px; }
.reforma-cab { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }

.reforma-m2 { margin: 16px 0 12px; }
.reforma-m2 label { display: block; font-size: .9rem; margin-bottom: 6px; }
.reforma-m2 input { max-width: 160px; }

.reforma-lista { display: grid; gap: 0; margin: 16px 0 20px; }
.reforma-fila { padding: 14px 0; border-top: 1px solid var(--hairline); }
.reforma-fila:first-child { border-top: 0; }
.reforma-fila-cab small { display: block; font-size: .8rem; color: var(--ink-soft); margin-top: 2px; }

/* Los campos se apilan en movil. min-width:0 en el <select>: sin el, un select con nombres largos
   ("M. de O. por Aislacion hidrofuga de rebaje de losa...") NO baja de su ancho minimo y desborda
   la pantalla -- la misma trampa que ya mordio en el buscador de /propiedades (P43). */
.reforma-fila-campos { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.reforma-fila-campos > select { min-width: 0; width: 100%; }
.reforma-cant { display: flex; align-items: center; gap: 8px; }
.reforma-cant input { max-width: 140px; }
.reforma-unidad { font-size: .85rem; color: var(--ink-soft); }
.reforma-sub { font-weight: 700; font-variant-numeric: tabular-nums; }

.reforma-total { border-top: 2px solid var(--ink); padding-top: 14px; }
.reforma-total-fila { display: flex; justify-content: space-between; gap: 16px; padding: 5px 0; font-size: .95rem; }
.reforma-total-fila b { font-variant-numeric: tabular-nums; }
.reforma-total-fila.total { font-size: 1.15rem; padding-top: 10px; margin-top: 6px; border-top: 1px solid var(--hairline); }
.reforma-total-fila.total b { font-family: var(--font-display); }

@media (min-width: 700px) {
  .reforma-fila-campos { flex-direction: row; align-items: center; }
  .reforma-fila-campos > select { flex: 1 1 auto; }
  .reforma-sub { min-width: 130px; text-align: right; }
}

/* .pb-fecha (22-ago-2026): la fecha de la referencia de zona bajo el rango de precio justo. Mas
   pequeña y apagada que el veredicto -- es procedencia del dato, no el dato. Regla de Pol: ningun
   numero sin decir de cuando es. */
.pb-fecha { font-size: .74rem; color: var(--ink-soft); margin-top: 6px; }
.pb-fecha a { color: inherit; text-decoration: underline; }


/* ===== Foto de ciudad en la Guia (24-ago-2026, Pol: "en cada ciudad pones la foto") =====
   Proporcion fija para que la pagina no salte mientras carga la imagen: sin aspect-ratio el
   contenido de debajo baja de golpe cuando llega la foto, que es el mismo tipo de defecto que el
   hueco del hero -- se ve como un fallo aunque no lo sea.
   El credito NO es letra pequeña decorativa: una CC BY obliga a nombrar al autor, asi que se lee.
   Contenido, no adorno. */
.foto-ciudad {
  margin: 0 0 var(--s5);
  border: var(--border-card);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-soft);
}
.foto-ciudad img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
.foto-ciudad figcaption {
  padding: var(--s2) var(--s3);
  font-size: .78rem;
  color: var(--ink-soft);
  border-top: 1px solid var(--hairline);
}
.foto-ciudad figcaption a { color: inherit; }

/* ---------- /negocios — donde falta un servicio (26-ago-2026) ----------
   Pol, viendo la pagina: "cuadrados, decoracion... no tan mecanico". Era texto plano de arriba
   abajo, y lo que hace legible este dato es COMPARAR, que en una lista corrida no se ve.

   Se construye con lo que ya existe siempre que se puede: la cabecera de cifras usa .stat-grid/
   .stat-card tal cual, y las secciones el ritmo vertical normal. Lo unico nuevo son estas dos
   clases, para la rejilla de titulares y para la ficha de rubro.

   COLOR: el numero grande va en TINTA (--ink), no en verde. --primary es el unico acento de accion
   y significa exito/justo; un hueco de mercado no es ninguna de las dos cosas, y pintarlo de verde
   lo leeria como "bien" o como recomendacion -- exactamente lo que esta pagina no hace. Cuando algo
   no es accion, exito ni aviso, es tinta neutra: la regla del sistema, aplicada. */
.neg-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3);
  margin-top: var(--s4);
}
@media (min-width: 560px) { .neg-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .neg-grid { grid-template-columns: repeat(3, 1fr); } }
.neg-card {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  padding: var(--s4);
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}
/* La cifra que manda: cuantos faltarian para llegar a la mediana del pais. Serif de titular, como
   .stat-n y .price -- es el dato protagonista de su tarjeta, igual que el precio en una ficha. */
.neg-falta {
  font-family: var(--font-display);
  font-size: 2.1rem;
  line-height: 1;
  font-weight: 600;
  color: var(--ink);
}
.neg-falta small {
  font-family: var(--font-mono);
  font-size: .62rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-soft);
  display: block;
  margin-top: var(--s1);
}
.neg-card h3 { font-size: 1rem; margin: 0; }
.neg-card .neg-donde {
  font-family: var(--font-mono);
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-soft);
}
.neg-card p { font-size: .86rem; color: var(--ink-soft); margin-top: var(--s2); }
/* Ficha de rubro: misma caja, en una sola columna y mas baja. Separa cada rubro sin convertir la
   seccion en veinte tarjetas sueltas que compitan con los titulares de arriba. */
.neg-rubro {
  padding: var(--s3) var(--s4);
  background: var(--bg-soft);
  border: var(--border-card);
  border-radius: var(--radius);
  margin-top: var(--s3);
}
.neg-rubro h3 { font-size: .98rem; margin: 0; }
.neg-rubro p { font-size: .86rem; color: var(--ink-soft); margin-top: var(--s1); }

/* Ficha de distrito de /negocios (26-ago-2026). Antes eran un h3, un ul y un enlace sueltos, uno
   detras de otro sin nada que dijera donde acaba una ciudad y empieza la siguiente: veinte bloques
   pegados leidos como un unico muro. Se agrupan en una caja propia -- misma mecanica que .neg-rubro
   -- para que la separacion la haga la FORMA y no un margen suelto que alguien tendra que recordar.
   Es el mismo fallo de adyacencia de siempre visto por tercera vez esta noche. */
.neg-distrito {
  padding: var(--s4);
  background: var(--bg);
  border: var(--border-card);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  margin-top: var(--s4);
}
.neg-distrito h3 { font-size: 1.02rem; margin: 0; }
.neg-distrito h3 .mono { font-size: .68rem; color: var(--ink-soft); }
.neg-distrito ul { margin-top: var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.neg-distrito li {
  font-size: .88rem;
  color: var(--ink-soft);
  padding-left: var(--s3);
  border-left: 2px solid var(--hairline);
}
.neg-distrito li b { color: var(--ink); }
.neg-distrito > p { margin-top: var(--s3); font-size: .86rem; }
/* Ninguna de estas lineas puede cortarse por la derecha: son cadenas largas con nombres propios y
   cifras, justo lo que en movil se sale de la caja. Pol lo pidio expresamente el 23-ago ("que no se
   corte por la derecha nada") y aqui se repite la guarda en el sitio donde el riesgo es real. */
.neg-card, .neg-rubro, .neg-distrito { overflow-wrap: anywhere; }
/* La cabecera de cifras respira del parrafo que tiene encima: .stat-grid trae 8px pensados para ir
   dentro de un panel, y aqui abre una seccion. */
.page-wrap .stat-grid { margin-top: var(--s5); }

/* ---------- Ritmo de /negocios (26-ago-2026, segunda pasada) ----------
   Pol, con una captura de escritorio: "fijate que esta todo muy pegado". La primera pasada metio
   estos bloques en la regla general de adyacencia (--s6, 32px) y eso arreglo el "pegado del todo",
   pero 32px es el paso PARA UNA SECCION DENTRO de una pagina de texto; aqui cada seccion abre con
   un titular serif grande y va seguida de una rejilla de cajas, y a esa escala 32 se lee como nada.
   Sube a --s7 (48). No es un numero al gusto: es el paso que la escala ya tiene para "entre
   secciones mayores", que es exactamente lo que separan estos titulos.
   Va aparte y no dentro de la regla general para no mover el ritmo del resto del sitio, que Pol no
   ha pedido tocar y que lleva dias asentado. */
.stat-grid + h2, .stat-grid + h3,
.neg-grid + h2, .neg-grid + h3,
.neg-rubro + h2, .neg-rubro + h3,
.neg-distrito + h2, .neg-distrito + h3 { margin-top: var(--s7); }

/* Y el aire DENTRO de cada seccion, que era la otra mitad de lo pegado: entre el parrafo que
   presenta la seccion y su primera caja, y entre cajas hermanas. */
p + .neg-grid, p + .neg-rubro, p + .neg-distrito { margin-top: var(--s5); }
.neg-grid { gap: var(--s4); margin-top: var(--s5); }
.neg-rubro + .neg-rubro { margin-top: var(--s4); }
.neg-distrito + .neg-distrito { margin-top: var(--s5); }
.neg-card { padding: var(--s5); gap: var(--s2); }
.neg-rubro { padding: var(--s4) var(--s5); }
.neg-distrito { padding: var(--s5); }
.page-wrap .stat-grid { margin-top: var(--s6); gap: var(--s4); }

/* ---------- /negocios: hero e indice de oportunidad (26-ago-2026, spec proptech de Pol) ----------
   Pol pidio "sensacion de producto digital, no de articulo". Se consigue con jerarquia y espacio,
   no con una paleta nueva: el sitio es blanco + verde y esa decision la tomo el mismo esta noche
   ("identidad actual de la marca"). Aqui no entra ningun color que no este ya en :root. */
.neg-hero { padding: var(--s6) 0 var(--s5); }
.neg-hero h1 { font-size: clamp(1.9rem, 5.2vw, 3.1rem); line-height: 1.08; max-width: 18ch; }
.neg-hero .lead { font-size: clamp(1rem, 2.2vw, 1.18rem); max-width: 52ch; margin-top: var(--s4); }

/* La barra del indice. El relleno va en TINTA y no en verde a proposito: --primary significa
   exito/justo en este sistema, y una barra verde al 74% se leeria como "74% de bien", cuando lo que
   mide es cuanta oferta FALTA. Lo que no es accion, exito ni aviso es tinta neutra. */
.neg-indice { display: flex; align-items: baseline; gap: var(--s2); }
.neg-indice b { font-family: var(--font-display); font-size: 2.6rem; line-height: 1; color: var(--ink); }
.neg-indice span {
  font-family: var(--font-mono); font-size: .68rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-soft);
}
.neg-barra {
  margin-top: var(--s2); height: 6px; background: var(--ink-08);
  border-radius: var(--radius); overflow: hidden;
}
.neg-barra i { display: block; height: 100%; background: var(--ink); }
/* Nota del indice dentro de una tarjeta de titular: mas pequeña, sin barra. */
.neg-card .neg-nota { font-family: var(--font-mono); font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-soft); }

/* ===== /tour-360 (26-ago-2026) — visor 3D, plano 2D y formulario de lista de espera.
   Los margenes se declaran AQUI y no se esperan del flujo: el reset del sitio deja todo a cero y
   el hueco lo reponen reglas de adyacencia que nombran clase por clase, asi que una clase nueva
   sale pegada a lo anterior hasta que se le pone su propio margen. (Tercera vez que muerde: ver
   scripts/test/test-ritmo-vertical.js.) */
.tour360-visor-wrap { margin: var(--s5) 0 var(--s2); }
.tour360-visor {
  position: relative; width: 100%; height: min(60vh, 520px);
  background: #0d0f10; border-radius: 14px; overflow: hidden; cursor: grab; touch-action: none;
}
.tour360-visor canvas { display: block; }
.tour360-rotulo, .tour360-aviso {
  position: absolute; top: 12px; padding: var(--s2) var(--s3); border-radius: 10px;
  background: rgba(0,0,0,.55); color: #fff; font-size: 14px; font-weight: 600;
}
.tour360-rotulo { left: 12px; }
.tour360-aviso { right: 12px; color: #ffd98a; font-weight: 400; font-size: 12px; }
.tour360-salto {
  position: absolute; transform: translate(-50%,-50%); display: none;
  background: rgba(15,122,77,.94); color: #fff; border: 2px solid #fff; border-radius: 999px;
  padding: var(--s2) var(--s3); font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0,0,0,.35);
}
.tour360-salto:hover { background: var(--primary-dark); }
.tour360-bajo { display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap; margin-top: var(--s4); }
.tour360-plano { width: 320px; flex: 0 0 auto; background: #f7f7f5; border: 1px solid var(--line, #e6e4df); border-radius: 12px; padding: var(--s3); }
.tour360-plano-tit { font: 11px/1.4 ui-monospace, monospace; letter-spacing: .12em; color: #7b8783; margin-bottom: var(--s2); }
.tour360-svg { width: 100%; display: block; }
/* El plano (rehecho 27-ago). Jerarquia de linea, que es lo que distingue un plano de siete
   rectangulos: tabique fino entre estancias, muro exterior el doble de grueso, y las puertas como
   hueco + hoja abatida. */
.t360-zona rect { fill: #fff; stroke: #b9c0bb; stroke-width: 1; cursor: pointer; }
.t360-zona:hover rect { fill: #f0f7f3; }
.t360-zona.activa rect { fill: #e4f1ea; stroke: var(--primary); stroke-width: 1.4; }
.t360-zona text { font: 600 8px/1 ui-monospace, monospace; letter-spacing: .09em; fill: #7b8783; pointer-events: none; }
.t360-zona.activa text { fill: var(--primary-dark, #0b5c3a); }
.t360-muro { fill: none; stroke: #3d4b45; stroke-width: 2.4; pointer-events: none; }
.t360-hueco { stroke: #fff; stroke-width: 3.2; pointer-events: none; }
.t360-hoja { fill: none; stroke: #c3cac5; stroke-width: .9; pointer-events: none; }
.t360-nodo { fill: #fff; stroke: var(--primary); stroke-width: 1.6; cursor: pointer; }
.t360-nodo:hover { fill: var(--primary); }
.t360-cono { fill: rgba(15,122,77,.28); }
.t360-pin { fill: var(--primary); stroke: #fff; stroke-width: 2; }
.tour360-lado { flex: 1; min-width: 240px; }
.tour360-pastillas { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: var(--s3); }
.tour360-pastilla { border: 1px solid var(--line, #e6e4df); background: #fff; border-radius: 999px; padding: var(--s2) var(--s4); font-size: 14px; cursor: pointer; }
.tour360-pastilla.activa { background: var(--primary); color: #fff; border-color: var(--primary); }
.tour360-ficha { font-size: 14px; color: #41504b; margin: 0 0 var(--s3); }
.tour360-form { max-width: 520px; background: #f7f7f5; border: 1px solid var(--line, #e6e4df); border-radius: 14px; padding: var(--s5); margin-top: var(--s4); }
.tour360-form label { display: block; font-size: 14px; font-weight: 600; margin: var(--s3) 0 var(--s1); }
.tour360-form input, .tour360-form select { width: 100%; padding: var(--s3) var(--s3); border: 1px solid var(--line, #e6e4df); border-radius: 9px; font-size: 15px; background: #fff; }
.tour360-form .btn { margin-top: var(--s4); width: 100%; }
.tour360-resp { margin: var(--s3) 0 0; font-size: 14px; font-weight: 600; }
.tour360-resp.ok { color: var(--primary-dark); }
.tour360-resp.mal { color: #a4342a; }
@media (max-width: 700px) {
  .tour360-plano { width: 100%; }
  .tour360-visor { height: 52vh; }
}
