/* ============================================================================
   tokens.css — Estándar visual Inntegra / Cingolani.ar
   Versión: 2026-07-23 · Fuente única: DEV\_Estandares\tokens.css (NO editar copias)

   CÓMO SE USA
   1. Copiar este archivo completo al proyecto (dónde, lo dice el arquetipo:
      A → inicio de resources/css/app.css · B → inicio de style.css
      C → primer bloque del <style> de app.html).
   2. Elegir la paleta del proyecto poniendo el atributo en el <html>:
        <html data-palette="4-indigo-saas">
      Paletas disponibles (referencia visual: docs/standards/mockup-paletas.html):
        1-banca-marina      · Tradicional (banca, seguros, fintech)
        2-salud-serena      · Tradicional (salud, clínicas, bienestar)
        3-gobierno-sobrio   · Tradicional (gobierno, trámites, B2G)
        4-indigo-saas       · App (B2B moderno, tipo Linear/Stripe)
        5-azul-producto     · App (azul clásico de apps masivas)
        6-carbon-minimal    · App (monocromo tipo Notion/Vercel)
        7-teal-inntegra     · App (marca Inntegra; DEFAULT si no hay atributo)
        8-violeta-creativo  · Joven (comunidades, herramientas creativas)
        9-coral-calido      · Joven (consumo, food, lifestyle)
        10-menta-fresca     · Joven (hábitos, fitness, fresco)
        11-violeta-electrico· Joven (indigo-violeta app moderna; marca Nete.ar)
        12-momix            · App (marca Momix — CRM inmobiliario; navy + teal)
   3. Modo oscuro: agregar la clase .dark al <html> (ver design-system.md).

   REGLAS
   - Los componentes usan SOLO los nombres de variables (var(--accent)), nunca hex.
   - Cambiar de paleta = cambiar el data-palette. Nada más.
   - Un cambio de valores se hace en _Estandares\tokens.css y se re-copia.
   ========================================================================== */

/* ── Tokens independientes de la paleta ────────────────────────────────── */
:root {
  --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif;
  --radius-lg: 16px;    /* cards, modales, contenedores */
  --radius-md: 10px;    /* botones, inputs */
  --radius-pill: 999px; /* badges, chips */
  /* Foco (H-07, sincronizado del estándar el 2026-07-30) — el anillo que cumple WCAG 2.4.11 /
     1.4.11 es `--ring`, y es el ACENTO SÓLIDO. Antes era el halo `color-mix(... 45%, transparent)`
     de acá abajo, y ese valor NO puede llegar a los 3:1 exigidos en ninguna paleta: un color al 45%
     sobre blanco se aclara demasiado. Momix lo usaba en todos sus `:focus-visible`, así que este
     cambio arregla el indicador de foco de TODA la app de una sola línea.
     `--ring-soft` conserva el halo, para usarlo solo como refuerzo secundario. */
  --ring: var(--accent);
  --ring-soft: color-mix(in srgb, var(--accent) 45%, transparent);
  /* Borde en reposo de un campo editable: 3:1 contra la superficie. `--border-default` da 1,29:1,
     que es un borde que prácticamente no se ve — sirve para separar bloques, no para decir "acá se
     escribe" (H-09). */
  --border-field: var(--text-3);
  /* El acento COMO TEXTO (links, solapa activa, botón ghost). `--accent` queda para fondos, bordes
     y foco: en la paleta 12-momix es un teal claro pensado para fondo, y como texto sobre blanco da
     1,92:1 (H-10). Es el token que ya se usó al corregir `.dropzone-link`. */
  --accent-text: var(--accent-soft-fg);

  /* Semánticos — modo claro (iguales para todas las paletas) */
  --ok: #10B981;   --ok-soft: #D9F2E7;   --ok-strong: #0B6E4F;
  --warn: #F59E0B; --warn-soft: #FDF0D9; --warn-strong: #935F06;
  --err: #EF4444;  --err-soft: #FCE3E3;  --err-strong: #A32B2B;
  --info: #2E80DA; --info-soft: #DEEDFB; --info-strong: #1A5AA0;   /* celeste (ficha interna) */
  /* Texto sobre el color sólido de estado (botón destructivo, badge saturado). Es #FFFFFF en los
     DOS modos, por eso no se redeclara en `html.dark` (H-08). */
  --ok-fg: #FFFFFF; --warn-fg: #FFFFFF; --err-fg: #FFFFFF;

  /* Default sin data-palette = 7-teal-inntegra (claro) */
  --surface-page: #F1F5F6; --surface: #FFFFFF; --surface-2: #E7EEEF;
  --text-1: #16282B; --text-2: #587076; --text-3: #85989D;
  --border-default: #DBE4E5; --border-strong: #C2D1D3;
  --accent: #2E878C; --accent-hover: #226E72; --accent-fg: #FFFFFF;
  --accent-soft: #E6F5F6; --accent-soft-fg: #1D5E62;
}

html.dark {
  /* Semánticos — modo oscuro */
  --ok: #34D399;   --ok-soft: #123A2C;   --ok-strong: #5CDFA8;
  --warn: #FBBF24; --warn-soft: #3D2E0E; --warn-strong: #F5C462;
  --err: #F87171;  --err-soft: #41201F;  --err-strong: #F79E9A;
  --info: #60A5FA; --info-soft: #10314F; --info-strong: #8AC2F2;   /* celeste (ficha interna) */

  /* Default sin data-palette = 7-teal-inntegra (oscuro) */
  --surface-page: #0C1516; --surface: #152123; --surface-2: #1C2B2D;
  --text-1: #E2EDEE; --text-2: #8CA4A7; --text-3: #688084;
  --border-default: #233436; --border-strong: #32484B;
  --accent: #4FBDC2; --accent-hover: #74CDD1; --accent-fg: #0C1516;
  --accent-soft: #163336; --accent-soft-fg: #86DCE0;
}

/* ── 1 · Banca marina ───────────────────────────────────────────────────── */
html[data-palette='1-banca-marina'] {
  --surface-page: #F2F4F8; --surface: #FFFFFF; --surface-2: #E9EDF3;
  --text-1: #17233B; --text-2: #5A6580; --text-3: #8891A6;
  --border-default: #DBE1EA; --border-strong: #C2CBD9;
  --accent: #2050A0; --accent-hover: #183E7E; --accent-fg: #FFFFFF;
  --accent-soft: #E7EEF9; --accent-soft-fg: #1B4383;
}
html[data-palette='1-banca-marina'].dark {
  --surface-page: #0C1322; --surface: #151E31; --surface-2: #1C2740;
  --text-1: #E4E9F3; --text-2: #93A0B8; --text-3: #6E7B95;
  --border-default: #26324A; --border-strong: #374763;
  --accent: #7FA6E8; --accent-hover: #9BBAF0; --accent-fg: #0C1322;
  --accent-soft: #1C2A44; --accent-soft-fg: #AFC8F2;
}

/* ── 2 · Salud serena ───────────────────────────────────────────────────── */
html[data-palette='2-salud-serena'] {
  --surface-page: #F3F7F5; --surface: #FFFFFF; --surface-2: #E9F0EC;
  --text-1: #182A22; --text-2: #5B6D64; --text-3: #88998F;
  --border-default: #DCE5E0; --border-strong: #C3D2CA;
  --accent: #1E7A5A; --accent-hover: #175F46; --accent-fg: #FFFFFF;
  --accent-soft: #E2F1EA; --accent-soft-fg: #14563F;
}
html[data-palette='2-salud-serena'].dark {
  --surface-page: #0D1512; --surface: #16211C; --surface-2: #1D2B24;
  --text-1: #E2EDE7; --text-2: #90A69B; --text-3: #6C8276;
  --border-default: #24352D; --border-strong: #33493E;
  --accent: #56C69A; --accent-hover: #74D3AE; --accent-fg: #0D1512;
  --accent-soft: #1B2F26; --accent-soft-fg: #8FDDBB;
}

/* ── 3 · Gobierno sobrio ────────────────────────────────────────────────── */
html[data-palette='3-gobierno-sobrio'] {
  --surface-page: #F3F2F1; --surface: #FFFFFF; --surface-2: #EAE9E7;
  --text-1: #121312; --text-2: #595D5F; --text-3: #87898B;
  --border-default: #DCDBD9; --border-strong: #C4C3C0;
  --accent: #1D70B8; --accent-hover: #155B99; --accent-fg: #FFFFFF;
  --accent-soft: #E4EFF8; --accent-soft-fg: #144E7F;
}
html[data-palette='3-gobierno-sobrio'].dark {
  --surface-page: #121312; --surface: #1C1D1C; --surface-2: #242625;
  --text-1: #EBEBE9; --text-2: #9DA1A3; --text-3: #787C7E;
  --border-default: #2E302F; --border-strong: #404342;
  --accent: #64AEE4; --accent-hover: #85C0EC; --accent-fg: #121312;
  --accent-soft: #1D2B37; --accent-soft-fg: #9CCBEF;
}

/* ── 4 · Índigo SaaS ────────────────────────────────────────────────────── */
html[data-palette='4-indigo-saas'] {
  --surface-page: #F7F8FC; --surface: #FFFFFF; --surface-2: #EEF0F8;
  --text-1: #1C1E2B; --text-2: #61667C; --text-3: #8D91A5;
  --border-default: #E2E4EE; --border-strong: #C9CCDD;
  --accent: #4F46E5; --accent-hover: #4238CA; --accent-fg: #FFFFFF;
  --accent-soft: #ECEBFC; --accent-soft-fg: #3B32B4;
}
html[data-palette='4-indigo-saas'].dark {
  --surface-page: #0F1016; --surface: #181A23; --surface-2: #20222E;
  --text-1: #E6E7EF; --text-2: #9A9EB4; --text-3: #757990;
  --border-default: #292C3B; --border-strong: #3A3E51;
  --accent: #8F94F5; --accent-hover: #A8ACF8; --accent-fg: #0F1016;
  --accent-soft: #232343; --accent-soft-fg: #B3B6FA;
}

/* ── 5 · Azul producto ──────────────────────────────────────────────────── */
html[data-palette='5-azul-producto'] {
  --surface-page: #F4F8FB; --surface: #FFFFFF; --surface-2: #EAF1F6;
  --text-1: #15222C; --text-2: #5A6B79; --text-3: #8797A4;
  --border-default: #DDE5EC; --border-strong: #C4D1DB;
  --accent: #0273BB; --accent-hover: #025F9A; --accent-fg: #FFFFFF;
  --accent-soft: #E2F1FA; --accent-soft-fg: #085280;
}
html[data-palette='5-azul-producto'].dark {
  --surface-page: #0C141B; --surface: #152029; --surface-2: #1C2934;
  --text-1: #E3EBF1; --text-2: #8FA2B1; --text-3: #6B7E8D;
  --border-default: #243441; --border-strong: #334757;
  --accent: #4BB8F0; --accent-hover: #71C8F4; --accent-fg: #0C141B;
  --accent-soft: #16374B; --accent-soft-fg: #8AD2F6;
}

/* ── 6 · Carbón minimal ─────────────────────────────────────────────────── */
html[data-palette='6-carbon-minimal'] {
  --surface-page: #F6F6F5; --surface: #FFFFFF; --surface-2: #ECECEA;
  --text-1: #171717; --text-2: #666663; --text-3: #92928F;
  --border-default: #E3E3E1; --border-strong: #CACAC7;
  --accent: #1A1A1A; --accent-hover: #000000; --accent-fg: #FFFFFF;
  --accent-soft: #EDEDEB; --accent-soft-fg: #333333;
}
html[data-palette='6-carbon-minimal'].dark {
  --surface-page: #0B0B0C; --surface: #161617; --surface-2: #1F1F20;
  --text-1: #EDEDEC; --text-2: #9C9C99; --text-3: #767674;
  --border-default: #2A2A2B; --border-strong: #3C3C3D;
  --accent: #F2F2F0; --accent-hover: #FFFFFF; --accent-fg: #111111;
  --accent-soft: #242425; --accent-soft-fg: #D4D4D2;
}

/* ── 7 · Teal Inntegra (la marca; igual al default de :root) ────────────── */
html[data-palette='7-teal-inntegra'] {
  --surface-page: #F1F5F6; --surface: #FFFFFF; --surface-2: #E7EEEF;
  --text-1: #16282B; --text-2: #587076; --text-3: #85989D;
  --border-default: #DBE4E5; --border-strong: #C2D1D3;
  --accent: #2E878C; --accent-hover: #226E72; --accent-fg: #FFFFFF;
  --accent-soft: #E6F5F6; --accent-soft-fg: #1D5E62;
}
html[data-palette='7-teal-inntegra'].dark {
  --surface-page: #0C1516; --surface: #152123; --surface-2: #1C2B2D;
  --text-1: #E2EDEE; --text-2: #8CA4A7; --text-3: #688084;
  --border-default: #233436; --border-strong: #32484B;
  --accent: #4FBDC2; --accent-hover: #74CDD1; --accent-fg: #0C1516;
  --accent-soft: #163336; --accent-soft-fg: #86DCE0;
}

/* ── 8 · Violeta creativo ───────────────────────────────────────────────── */
html[data-palette='8-violeta-creativo'] {
  --surface-page: #FAF8FE; --surface: #FFFFFF; --surface-2: #F2EFFA;
  --text-1: #241C36; --text-2: #6B6382; --text-3: #9891AB;
  --border-default: #E7E2F2; --border-strong: #D0C8E3;
  --accent: #7C3AED; --accent-hover: #6A28DB; --accent-fg: #FFFFFF;
  --accent-soft: #F1E9FE; --accent-soft-fg: #5B21B6;
}
html[data-palette='8-violeta-creativo'].dark {
  --surface-page: #120F1D; --surface: #1C172B; --surface-2: #251F38;
  --text-1: #EAE5F5; --text-2: #A79EC0; --text-3: #82789F;
  --border-default: #2F2745; --border-strong: #41375C;
  --accent: #A78BFA; --accent-hover: #BDA7FC; --accent-fg: #120F1D;
  --accent-soft: #2B2152; --accent-soft-fg: #C4B0FD;
}

/* ── 9 · Coral cálido ───────────────────────────────────────────────────── */
html[data-palette='9-coral-calido'] {
  --surface-page: #FDF8F4; --surface: #FFFFFF; --surface-2: #F6EDE5;
  --text-1: #33231C; --text-2: #7A6659; --text-3: #A18E81;
  --border-default: #EEE2D9; --border-strong: #DBC9BC;
  --accent: #D9532F; --accent-hover: #C24329; --accent-fg: #FFFFFF;
  --accent-soft: #FBE9E2; --accent-soft-fg: #A03A20;
}
html[data-palette='9-coral-calido'].dark {
  --surface-page: #191110; --surface: #241A17; --surface-2: #2F221E;
  --text-1: #F3E9E4; --text-2: #B49A8D; --text-3: #8F776B;
  --border-default: #3A2B25; --border-strong: #4F3B33;
  --accent: #FF8A68; --accent-hover: #FFA687; --accent-fg: #191110;
  --accent-soft: #40241B; --accent-soft-fg: #FFB39A;
}

/* ── 10 · Menta fresca ──────────────────────────────────────────────────── */
html[data-palette='10-menta-fresca'] {
  --surface-page: #F2FBF6; --surface: #FFFFFF; --surface-2: #E7F4ED;
  --text-1: #122A20; --text-2: #55705F; --text-3: #83988C;
  --border-default: #DCEBE2; --border-strong: #C2DACB;
  --accent: #0E9F6E; --accent-hover: #0B7F58; --accent-fg: #FFFFFF;
  --accent-soft: #DDF4E9; --accent-soft-fg: #0A6B4A;
}
html[data-palette='10-menta-fresca'].dark {
  --surface-page: #0B1611; --surface: #14211B; --surface-2: #1B2C24;
  --text-1: #E1EFE7; --text-2: #8BA795; --text-3: #678272;
  --border-default: #22362C; --border-strong: #314B3E;
  --accent: #3BD597; --accent-hover: #61E0AC; --accent-fg: #0B1611;
  --accent-soft: #14382A; --accent-soft-fg: #7BE8BC;
}

/* ── 11 · Violeta eléctrico (indigo-violeta app moderna; marca Nete.ar) ──── */
html[data-palette='11-violeta-electrico'] {
  --surface-page: #F4F3FB; --surface: #FFFFFF; --surface-2: #ECEBF8;
  --text-1: #1B1B2E; --text-2: #65647F; --text-3: #9695AE;
  --border-default: #E4E3F1; --border-strong: #D2D1E8;
  --accent: #6644EE; --accent-hover: #5836E2; --accent-fg: #FFFFFF;
  --accent-soft: #ECE9FE; --accent-soft-fg: #4A2FC2;
}
html[data-palette='11-violeta-electrico'].dark {
  --surface-page: #0F0E1A; --surface: #181726; --surface-2: #211F34;
  --text-1: #EAE9F6; --text-2: #A5A3C1; --text-3: #79779A;
  --border-default: #292742; --border-strong: #393658;
  --accent: #A79BFA; --accent-hover: #BBB1FC; --accent-fg: #120F22;
  --accent-soft: #272150; --accent-soft-fg: #C7BFFC;
}

/* ── 12 · Momix (marca Momix — CRM inmobiliario; navy + teal) ───────────── */
html[data-palette='12-momix'] {
  --surface-page: #F8FAFC; --surface: #FFFFFF; --surface-2: #F1F5F9;
  /* text-2/text-3 corridos un escalón de la rampa slate (2026-07-29): con #64748B/#94A3B8 el
     texto secundario daba 4,34:1 y el terciario 2,34-2,56:1 sobre los tres fondos — por debajo
     del AA 4.5:1 de WCAG, medido con axe-core sobre pantallas reales. Ahora 6,92+ y 4,81+.
     Se corre la MISMA rampa en vez de inventar colores, así se conserva la jerarquía entre los
     tres niveles de texto. */
  --text-1: #0F172A; --text-2: #475569; --text-3: #5E6D82;
  --border-default: #E2E8F0; --border-strong: #CBD5E1;
  /* accent teal claro → texto oscuro (blanco sobre #22D3A6 no pasa AA 4.5:1) */
  --accent: #22D3A6; --accent-hover: #14B8A6; --accent-fg: #08111F;
  --accent-soft: #DDFBF5; --accent-soft-fg: #0B6E63;
}
html[data-palette='12-momix'].dark {
  --surface-page: #08111F; --surface: #0F172A; --surface-2: #1E293B;
  /* Mismo corrimiento que en claro, para el otro lado: en oscuro el terciario (#64748B) daba
     3,07-3,97:1. Ahora 5,71+ y el secundario 9,85+. */
  --text-1: #F1F5F9; --text-2: #CBD5E1; --text-3: #94A3B8;
  --border-default: #334155; --border-strong: #475569;
  --accent: #22D3A6; --accent-hover: #4ADEC4; --accent-fg: #08111F;
  --accent-soft: #12362D; --accent-soft-fg: #7CEAD7;
}

/* ── Colores INDEPENDIENTES DEL TEMA (tokens propios del proyecto) ─────────────────────
   La Regla 1 del design-system dice que ningún color va suelto en una vista. Estos NO salen de
   `_Estandares/tokens.css` porque el contrato de tokens no los tiene, y usar el token "parecido"
   daría el color equivocado. Son tres familias con el mismo motivo de fondo: su valor NO puede
   depender del tema.

   1. Texto sobre fondo oscuro fijo o color saturado. `--accent-fg` NO sirve: en la paleta
      `12-momix` es navy (el botón teal lleva texto navy para llegar a AA), así que usarlo sobre un
      overlay negro dejaría texto navy sobre negro.
   2. La marca. Un logo que cambia de color con el tema deja de ser una marca.
   3. Escalas que codifican DATOS, no jerarquía visual: los 6 estados de una propiedad y los 8
      colores de avatar. La triada `--ok/--warn/--err` cubre 3 significados; acá hacen falta 6. Y el
      color del avatar de una persona tiene que ser el mismo en claro y en oscuro, o la misma
      persona se ve distinta según el tema.

   Mejora propuesta al estándar (ver docs/07): el contrato debería nombrar estas tres familias. */
:root {
  --fixed-white: #FFFFFF;          /* texto/íconos sobre overlay oscuro o color saturado */
  --brand-navy: #0F172A;           /* fondo de la marca (logo, badge del SuperAdmin, hero) */
  --brand-navy-2: #1E293B;         /* segundo tono del degradé del hero */
  --brand-navy-fg: #F1F5F9;        /* texto sobre la marca */
  --brand-navy-fg-2: #94A3B8;      /* texto secundario sobre la marca */
  --brand-teal: #22D3A6;           /* el teal de marca sobre fondo oscuro */
  --viewer-bg: #0B1220;            /* visor de fotos: más oscuro que la marca — una foto se juzga
                                      mejor sobre casi negro, donde el navy competiría con la imagen */
  --overlay-rgb: 8, 17, 31;        /* overlays sobre fotos: rgba(var(--overlay-rgb), .55) */
  /* Estados de propiedad (mt_44): el color codifica DÓNDE SE VE, no qué pasó con el negocio —
     eso último es el Motivo y va en texto. Por eso son cuatro colores para cinco estados
     (borrador y baja comparten el gris oscuro: las dos están fuera de circulación).
     Estos son los colores de IDENTIDAD: el punto del menú y el tinte de fondo del badge. */
  --st-borrador: #5B6675;
  --st-activo: #0E9F6E;
  --st-inactivo: #94A3B8;
  --st-default: #64748B;
  /* …y estos, el TEXTO del badge. Hacen falta aparte porque el badge pinta el fondo con un tinte
     del color de identidad, y usar ese mismo color de texto da 1,6:1 — el texto y el fondo son el
     mismo tono. Medido con axe sobre el render real: borrador 4,38 · publicada 2,63 · interna 1,59 ·
     suspendida 2,09, los cuatro por debajo del 4,5 que pide WCGA AA. Es la misma separación que ya
     hacen `--ok-soft`/`--ok-strong` más arriba: un token para el fondo y otro para el texto. */
  --st-fg-borrador: #36404E;   /* 7,90:1 */
  --st-fg-activo:   #095C42;   /* 6,22:1 */
  --st-fg-interna:  #0C6853;   /* 5,58:1 */
  --st-fg-inactivo: #4A5665;   /* 6,11:1 */
  --st-fg-default:  #4A5665;
  /* Paleta de avatares: estable por usuario, igual en claro y en oscuro.
     Los ocho llevan iniciales BLANCAS encima a 12px, así que cada uno tiene que dar 4,5:1 contra
     el blanco. Cuatro no llegaban (medido con axe sobre la ficha real, que hasta ahora no estaba
     en el catálogo de UX): av-0 4,24 · av-4 3,68 · av-5 3,77 · av-6 3,19. Se corrigen bajando UN
     escalón de la misma rampa (mismo criterio que se usó con text-2/text-3: se corre la rampa, no
     se inventan colores), así la familia de tonos y la distinción entre los ocho no cambian:
       av-0 → 5,47  ·  av-4 cyan-700 → 5,36  ·  av-5 emerald-700 → 5,48  ·  av-6 amber-700 → 5,02 */
  --av-0: #0F766E; --av-1: #2563EB; --av-2: #7C3AED; --av-3: #DC2626;
  --av-4: #0E7490; --av-5: #047857; --av-6: #B45309; --av-7: #DB2777;
}

/* El TEXTO del badge de estado sí depende del tema, a diferencia del color de identidad de al lado.
   No es una excepción a la regla de arriba: el fondo del badge es un tinte del color de identidad
   COMPUESTO sobre la superficie de la página, así que en claro el fondo queda claro y en oscuro,
   oscuro — el texto tiene que ir en la dirección contraria en cada tema. El dato que codifica el
   color (qué estado es) lo sigue dando `--st-*`, que no cambia. Ratios medidos con axe sobre el
   render real, en el fondo compuesto de cada tema. */
html.dark {
  --st-fg-borrador: #B6C0CD;   /* 7,02:1 */
  --st-fg-activo:   #6FE5B4;   /* 7,71:1 */
  --st-fg-interna:  #70E6C8;   /* 7,49:1 */
  --st-fg-inactivo: #BEC8D6;   /* 6,67:1 */
  --st-fg-default:  #BEC8D6;
}


/* ════════════════════════════════════════════════════════════════════════
   Momix — componentes base (design-system.md). Tokens arriba: NO editar.
   ════════════════════════════════════════════════════════════════════════ */
/* Tipografía Manrope (self-hosted, variable, font-src 'self') */
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/manrope-latin.woff2') format('woff2');
}
:root { --font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif; }
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--surface-page); color: var(--text-1);
  font: 400 14px/1.5 var(--font-sans);
}
h1, h2, h3 { font-weight: 500; margin: 0 0 8px; }
h1 { font-size:20px; } h2 { font-size:15px; } h3 { font-size:14px; }
p { margin: 0 0 10px; }
a { color: var(--accent-soft-fg); text-decoration: none; }
a:hover { text-decoration: underline; }
/* Un link METIDO EN UN PÁRRAFO va subrayado siempre, no solo al pasar el mouse: si la única
   diferencia con el texto que lo rodea es el color, quien no distingue ese color no lo ve
   (WCAG 1.4.1 — "uso del color"; axe lo reporta como `link-in-text-block`). Un link SUELTO
   —una fila de tabla, un ítem de menú, un botón-link— no necesita subrayado: ahí la posición
   ya lo separa del texto corrido. */
p > a, .hint > a, .muted > a, li > a:not(.btn):not(.menu-item):not(.nav-item) { text-decoration: underline; }
.muted { color: var(--text-2); }
.tnum { font-variant-numeric: tabular-nums; }
:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

/* Botones */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  /* 44px: mínimo ÚNICO para todo control operable (H-28 del estándar, WCAG 2.5.8). Antes eran 36 —
     un objetivo táctil de 36px se falla seguido en un teléfono, y "es un botón chico de escritorio"
     no es una excepción que el estándar admita. Las dos únicas salidas son `.btn-sm` (36px, solo
     dentro de una fila de tabla o lista que ya mida 44) y los botones de ícono de la topbar. */
  height: 44px; padding: 0 16px; border-radius: var(--radius-md);
  font: 500 14px/1 var(--font-sans); cursor: pointer; text-decoration: none;
  border: 1px solid transparent; background: none; color: var(--text-1);
  transition: transform 160ms ease-out, background 150ms ease;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: scale(0.97); }
.btn-primary { background: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary { background: var(--surface); border-color: var(--border-default); }
.btn-secondary:hover { background: var(--surface-2); }
.btn-ghost:hover { background: var(--surface-2); }
/* `--err-fg` y no `--fixed-white`: el estándar agregó un token propio para el texto sobre el color
   sólido de un estado (H-08). El valor es el mismo (#FFFFFF en los dos modos) — lo que cambia es
   que ahora sale del contrato y no de un token que este proyecto inventó para otra cosa (texto
   sobre fotos y overlays). */
/* OJO — `--err-strong` NO sirve como fondo de este botón, aunque en modo claro el número dé bien.
   Es un token de TEXTO sobre fondos suaves, así que invierte su rol entre temas: #A32B2B en claro
   (oscuro) pero #F79E9A en oscuro (claro). Usarlo de fondo con --err-fg (#FFFFFF en los dos modos)
   arregla el claro y rompe el oscuro: 7,15:1 allá, 2,03:1 acá. Probado y revertido — queda anotado
   para que no se vuelva a intentar.
   PENDIENTE DE DECISIÓN (owner): tal como lo fija el contrato, blanco sobre --err da 3,76:1 en claro
   y menos en oscuro, o sea que el botón destructivo no llega al AA 4.5 en NINGÚN tema a 12-14px.
   Resolverlo bien toca el contrato de tokens de _Estandares (o --err, o --err-fg), que este
   proyecto no edita por su cuenta. */
.btn-danger { background: var(--err); color: var(--err-fg); }
.btn-block { width: 100%; }
.btn[disabled] { opacity: .55; cursor: not-allowed; transform: none; }

/* Visualmente oculto pero accesible (screen readers) — para un <h1> de página que ya se ve
   reflejado en otro lado (ej. el journey de "Nueva propiedad"), sin duplicar el texto en pantalla. */
.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}

/* Cards, badges, inputs */
.card {
  background: var(--surface); border: 1px solid var(--border-default);
  border-radius: var(--radius-lg); padding: 18px 20px;
}
.badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 2px 10px;
  border-radius: var(--radius-pill); font-size: 12px; font-weight: 500;
  background: var(--accent-soft); color: var(--accent-soft-fg);
}
.badge-ok { background: var(--ok-soft); color: var(--ok-strong); }
.badge-warn { background: var(--warn-soft); color: var(--warn-strong); }
.badge-err { background: var(--err-soft); color: var(--err-strong); }
.badge-info { background: var(--info-soft); color: var(--info-strong); }   /* celeste (ficha interna) */

/* Barra de progreso de "Publicando en ZonaProp" (background job, ficha) */
.zpp-progress { margin-top: 4px; }
.zpp-bar { height: 8px; border-radius: 999px; background: var(--accent-soft); overflow: hidden; }
.zpp-bar-fill { display: block; height: 100%; width: 3%; border-radius: 999px; background: var(--accent);
  transition: width .5s ease; }
/* rayado animado para dar sensación de "trabajando" sin depender de que el % avance seguido */
.zpp-bar-fill::after { content: ""; display: block; height: 100%;
  background-image: linear-gradient(45deg, rgba(255,255,255,.25) 25%, transparent 25%, transparent 50%,
    rgba(255,255,255,.25) 50%, rgba(255,255,255,.25) 75%, transparent 75%, transparent);
  background-size: 16px 16px; animation: zpp-stripes 1s linear infinite; }
@keyframes zpp-stripes { from { background-position: 0 0; } to { background-position: 16px 0; } }
@media (prefers-reduced-motion: reduce) { .zpp-bar-fill::after { animation: none; } }


/* Panel de diferencias Momix vs ZonaProp: por cada campo distinto, dos opciones excluyentes
   (radios). La elegida se resalta; la otra queda tenue. Compacto para la columna del sidebar. */
.zpc-row { border: 1px solid var(--border-default); border-radius: var(--radius-md); padding: 8px 10px; margin-bottom: 8px; }
.zpc-field { font-weight: 600; font-size: 12.5px; margin-bottom: 6px; }
.zpc-opt { display: grid; grid-template-columns: auto auto 1fr; align-items: baseline; gap: 6px;
  padding: 4px 6px; border-radius: var(--radius-sm); cursor: pointer; }
.zpc-opt + .zpc-opt { margin-top: 2px; }
.zpc-opt:has(input:checked) { background: var(--accent-soft); }
.zpc-opt input { margin: 0; accent-color: var(--accent); }
.zpc-src { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .02em; color: var(--text-3); }
.zpc-opt:has(input:checked) .zpc-src { color: var(--accent-soft-fg); }
.zpc-val { font-size: 12.5px; color: var(--text-2); word-break: break-word; }
.zpc-opt:has(input:checked) .zpc-val { color: var(--text-1); }
/* Tabla del mapeo de estados Momix→ZonaProp (parametrizaciones > ZonaProp). */
.zpp-map-table { width: 100%; border-collapse: collapse; }
.zpp-map-table th { text-align: left; font-size: 12px; color: var(--text-2); font-weight: 600; padding: 6px 10px; border-bottom: 1px solid var(--border-default); }
.zpp-map-table td { padding: 8px 10px; border-bottom: 1px solid var(--border-default); vertical-align: middle; }
.zpp-map-table td .input { min-width: 140px; }
.zpp-map-table td:last-child .input { min-width: 200px; width: 100%; }
/* Aviso NARANJA de propagación diferida (los cambios tardan minutos en verse en ZonaProp). Naranja
   fijo en ambos temas (con leve ajuste en oscuro para el contraste); es informativo, no un error. */
.zpp-delay-note { font-size: 12.5px; line-height: 1.4; margin: 0 0 10px; padding: 8px 10px;
  border-radius: var(--radius-md); background: #FFF3E6; color: #9A4E00; border: 1px solid #FFD9AD; }
html.dark .zpp-delay-note { background: #3A2410; color: #FFB366; border-color: #5C3B18; }
/* NORMA (2026-08-08): todo aviso [data-dismissible] lleva una × para cerrarlo (la agrega bindDismissibleNotices).
   El aviso pasa a relative y con lugar a la derecha para la ×; la × hereda el color del aviso. */
[data-dismissible] { position: relative; padding-right: 30px; }
.notice-x { position: absolute; top: 6px; right: 6px; width: 20px; height: 20px; display: inline-flex;
  align-items: center; justify-content: center; border: 0; background: transparent; color: inherit;
  font-size: 16px; line-height: 1; cursor: pointer; opacity: .6; border-radius: 6px; padding: 0; }
.notice-x:hover { opacity: 1; background: color-mix(in srgb, currentColor 14%, transparent); }
.notice-x:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; opacity: 1; }
/* Estado "apagado": neutro a propósito. Un método que el admin decidió no ofrecer no es un error
   ni una advertencia — pintarlo de rojo diría que algo se rompió. */
.badge-off { background: var(--surface-2); color: var(--text-2); }
label { display: block; font-weight: 500; margin: 0 0 6px; }
.input {
  /* 44px, igual que los botones (H-28). Y el borde en reposo pasa a `--border-field`: el
     `--border-strong` que había da 1,29:1 contra la superficie, un borde que casi no se ve — sirve
     para separar bloques, no para decir "acá se escribe" (H-09). */
  width: 100%; height: 44px; padding: 0 12px; border-radius: var(--radius-md);
  border: 1px solid var(--border-field); background: var(--surface);
  color: var(--text-1); font: 400 14px var(--font-sans);
}
.input:focus-visible { outline: 2px solid var(--ring); outline-offset: 0; border-color: var(--accent); }
.field { margin-bottom: 14px; }
.hint { font-size:12px; color: var(--text-3); margin-top: 5px; }
/* Campo con error (REVISION-UX-2 puntos 2 y 3) — mismo tratamiento tanto si lo marcó "Revisar y
   activar" en vivo como si vino de un error real del server al recargar. Cubre los distintos
   wrappers de input que usa el form de propiedad (input simple, unit-field, stepper, money-field,
   calc-field, textarea) con un solo selector por tipo. */
.field.has-error > .input, .field.has-error .unit-field, .field.has-error .stepper-mid,
.field.has-error .money-field, .field.has-error > .textarea, .calc-field.has-error .unit-field {
  border-color: var(--err);
}
.field-error-msg { margin: 5px 0 0; font-size:12px; color: var(--err-strong); }
.form-error {
  background: var(--err-soft); color: var(--err-strong);
  border-radius: var(--radius-md); padding: 9px 12px; margin-bottom: 14px; font-size: 13px;
}
.form-info {
  background: var(--accent-soft); color: var(--accent-soft-fg);
  border-radius: var(--radius-md); padding: 9px 12px; margin-bottom: 14px; font-size: 13px;
}

/* ── Tamaño del texto (desplegable Normal/Grande/Muy grande): escala toda la interfaz — pensado para gente grande ── */
html.fs-2 body{zoom:1.12}
html.fs-3 body{zoom:1.25}
.fs-menu{position:relative}
.fs-btn.on{color:var(--accent);background:var(--accent-soft)}
.fs-menu-panel{right:0;left:auto;min-width:170px}
.fs-menu-panel .menu-item.current{background:var(--surface-2);color:var(--accent-soft-fg);font-weight:500}
.fs-opt-glyph{font-weight:500;line-height:1;flex:none;width:20px;text-align:center;color:var(--text-2)}
.fs-opt-sm{font-size:13px}
.fs-opt-md{font-size:15px}
.fs-opt-lg{font-size:20px}
.fs-menu-panel .menu-item.current .fs-opt-glyph{color:inherit}

/* Logo */
.logo { display: inline-flex; align-items: center; gap: 9px; font-weight: 500; color: var(--text-1); }
/* Logo como enlace al sitio público (panel): se comporta como el logo de siempre — sin subrayado
   ni color de link — y solo insinúa que es clickeable al pasar por encima. */
.logo-link { display:inline-flex; text-decoration:none; color:inherit; border-radius:8px; transition:opacity .15s; }
.logo-link:hover { text-decoration:none; opacity:.75; }
.logo-link:focus-visible { outline:2px solid var(--focus, #2E878C); outline-offset:3px; }
/* ── El isotipo ────────────────────────────────────────────────────────────────────────────────
   El isotipo tiene DOS partes de distinto color —la M y las barras— y la marca entrega dos
   variantes según el fondo (colores medidos sobre los PNG del set en `Imagenes Pagina/Logo Momix`):
      sobre claro  → M #0F172A + barras #14B8A6   (momix-isotipo-navy)
      sobre oscuro → M #F8FAFC + barras #22D3A6   (momix-isotipo-blanco)
   Acá va SIEMPRE la variante para fondo oscuro, en los dos temas, porque la pastilla es navy fijo
   (`--brand-navy` no se redefine en `html.dark`): es la composición del app-icon. Por eso no son
   tokens de tema sino de este componente — un contexto que algún día ponga el isotipo suelto sobre
   la página los redefine, y el resto no se entera.
   Los colores van literales y no por `--accent`: en esta paleta `--accent` vale #22D3A6 en los dos
   temas, así que "funcionaría" hoy y rompería el logo el día que se cambie la paleta. */
.logo-mark {
  width: 30px; height: 30px; border-radius: 9px; background: var(--brand-navy);
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  --logo-ink: #F8FAFC;   /* la M */
  --logo-bar: #22D3A6;   /* las barras */
}
/* La M iba en `--brand-teal`, o sea del MISMO color que las barras: el isotipo se leía como una
   mancha teal y no como una M con barras al lado. */
.logo-mark svg { color: var(--logo-ink); }
/* Las barras van por clase y no por `stroke="var(--logo-bar)"` en el atributo: `var()` dentro de un
   atributo de presentación no lo resuelven todos los motores, y si falla el trazo queda en negro. */
.logo-mark .logo-bar { stroke: var(--logo-bar); }
.logo-word { font-size:15px; letter-spacing: -.01em; }

/* Marca del broker (abajo del logo Momix) + selector de modo broker del SuperAdmin */
.broker-switch { position: relative; }
.broker-brand {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  padding: 7px 10px; margin-bottom: 10px; color: var(--text-1); font-weight: 500;
}
button.broker-brand { background: none; border: 0; cursor: pointer; border-radius: var(--radius-md); font-family: inherit; font-size: inherit; }
button.broker-brand:hover { background: var(--surface-2); }
.broker-brand-name {
  flex: 1; min-width: 0; font-size:13px; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.broker-caret { color: var(--text-3); flex: none; }
.bk {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  border-radius: 9px; overflow: hidden; font-weight:500; color: var(--fixed-white);
}
/* `contain` y no `cover`: un logo horizontal (marca con texto) NO se recorta, se muestra entero
   dentro del recuadro. Ver process_broker_logo(), que ya lo guarda con su proporción. */
.bk img { width: 100%; height: 100%; object-fit: contain; }
.bk-30 { width: 30px; height: 30px; font-size: 12px; }
.bk-24 { width: 24px; height: 24px; font-size:12px; border-radius: 7px; }
.bk-40{width:40px;height:40px;font-size:15px;border-radius:9px}
.bk-64 { width: 64px; height: 64px; font-size: 24px; border-radius: 14px; }
.bk-sa { background: var(--brand-navy); color: var(--brand-teal); }
.broker-panel {
  left: 4px; right: 4px; top: calc(100% - 4px); min-width: 0;
  max-height: min(420px, 60vh); overflow: auto;
}
.broker-search { padding: 8px; border-bottom: 1px solid var(--border-default); position: sticky; top: 0; background: var(--surface); z-index: 1; }
.broker-search .input { height: 32px; font-size: 13px; }
.broker-panel .menu-item { gap: 9px; }
/* Selector de INMOBILIARIA de quien trabaja en más de una (mt_121): nombre arriba, rol abajo. En un
   solo renglón, un nombre largo se sale del panel —que tiene ancho fijo—, y el `min-width:0` es
   obligatorio: sin él un ítem de flex no se achica por debajo de su contenido y el ellipsis no
   llega a aplicarse nunca (la trampa del `min-width:auto`, otra vez). */
.agency-item-info{min-width:0;display:flex;flex-direction:column;gap:1px;text-align:left}
.agency-item-info b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.agency-item-info > span{font-size:12px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Auth (login / reset) */
.auth-wrap {
  min-height: 100dvh; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: var(--surface-page);
}
.auth-card { width: 100%; max-width: 380px; }
.auth-card .card { padding: 26px 26px 22px; }
.auth-brand { display: flex; justify-content: center; margin-bottom: 18px; }
.auth-title { text-align: center; margin-bottom: 4px; }
.auth-sub { text-align: center; color: var(--text-2); margin-bottom: 18px; }
.auth-foot { text-align: center; margin-top: 16px; font-size:12px; color: var(--text-3); }

/* Shell del dashboard */
.layout { display: flex; min-height: 100dvh; }
/* position:sticky + align-self:flex-start = la barra acompaña el scroll en páginas largas (una
   ficha de propiedad o el form de alta miden varios miles de px). Sin align-self, el stretch por
   defecto del flex la hace tan alta como TODA la página y sticky no tiene contra qué pegarse.
   El overflow propio es para el caso opuesto: un menú más alto que la pantalla (SuperAdmin en
   fs-3) tiene que poder scrollear adentro de la barra. */
.sidebar {
  width: 232px; flex: none; background: var(--surface); border-right: 1px solid var(--border-default);
  padding: 16px 12px; display: flex; flex-direction: column; gap: 4px;
  position: sticky; top: 0; align-self: flex-start; height: 100dvh;
  overflow-y: auto; overscroll-behavior: contain;
  /* Scrollbar propia (fina, tenue, con la estética de Momix) en vez de la nativa del navegador */
  scrollbar-width: thin; scrollbar-color: var(--border-default) transparent;
}
.sidebar::-webkit-scrollbar { width: 10px; }
.sidebar::-webkit-scrollbar-track { background: transparent; }
.sidebar::-webkit-scrollbar-thumb { background: var(--border-default); border-radius: 999px; border: 3px solid var(--surface); }
.sidebar:hover::-webkit-scrollbar-thumb { background: var(--border-strong); }
.sidebar .logo { padding: 4px 10px 14px; }
.nav-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px;
  border-radius: var(--radius-md); color: var(--text-2); font-weight: 500;
}
.nav-item:hover { background: var(--surface-2); text-decoration: none; }
.nav-item.active { background: var(--accent-soft); color: var(--accent-soft-fg); }
.nav-item.disabled { opacity: .5; cursor: default; }
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex; align-items: center; gap: 12px; padding: 12px 20px;
  border-bottom: 1px solid var(--border-default); background: var(--surface);
}
.topbar .spacer { flex: 1; }
/* 1120px: el estándar eliminó el rango y fijó UN ancho de contenido (H-25). Momix tenía 1100 —
   20px de diferencia, pero el punto es que ahora es un número del contrato y no una elección por
   proyecto. La excepción sigue siendo T1/T8, con su card de 400–440px. */
.content { padding: 22px 20px; max-width: 1120px; width: 100%; margin: 0 auto; }
/* Opt-in por página ($contentWide en shell_start.php): solo lo pide el form de propiedad, que a
   partir de 1600px pone la vista previa en un riel al costado y necesita más de 1100px para que
   entren las dos columnas. Por debajo de ese ancho se comporta igual que cualquier otra página. */
@media (min-width: 1600px) { .content.content-wide { max-width: 1560px; } }
.grid-kpi { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; }
.kpi-num { font-size:24px; font-weight: 500; font-variant-numeric: tabular-nums; }
/* Subtítulo del KPI (2026-07-29): el número grande es acumulado y envejece mal — a los dos años
   "300 vendidas" no informa nada. Esta línea da el ritmo actual ("12 en los últimos 30 días"). */
.kpi-sub { margin: 2px 0 0; font: 400 12px/1.3 var(--font-sans); color: var(--text-3); }

.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  /* 40px: la única excepción que el estándar admite a los 44 (H-28) — los botones de ícono de la
     topbar, donde entran cuatro en una barra que además tiene el buscador y el avatar. */
  /* `flex:none` no es decorativo: la topbar es un flex y a 360px con el texto en A++ los
     hermanos la desbordan, así que el navegador encoge estos botones a 38-39px — por debajo
     incluso de la excepción de 40. Un mínimo que se puede achicar no es un mínimo. */
  width: 40px; height: 40px; flex: none; border-radius: var(--radius-md); border: 0;
  background: none; color: var(--text-2); cursor: pointer;
}
.icon-btn:hover { background: var(--surface-2); color: var(--text-1); }
/* Variante de acción destructiva (ej. borrar agente): en hover se tiñe de rojo para avisar. */
.icon-btn-danger:hover { background: var(--err-soft, var(--surface-2)); color: var(--err); }

/* Toggle de tema: luna en claro, sol en oscuro. Los dos SVG están siempre en el DOM y se
   intercambian por CSS — el <html class="dark"> ya está puesto por el script de head.php antes
   del primer pintado, así que no hay parpadeo ni dependencia de app.js. Se usa .theme-ico-*
   (clases propias) y no el atributo [hidden]: en este proyecto ya se pisó tres veces el mismo
   error de que un `display` propio le gana por cascada al [hidden]{display:none} del navegador. */
.theme-btn { position: relative; }
.theme-ico { transition: transform .25s ease, opacity .2s ease; }
.theme-ico-sun { display: none; }
html.dark .theme-ico-moon { display: none; }
html.dark .theme-ico-sun { display: inline-block; }
.theme-btn:hover .theme-ico { transform: rotate(-18deg); }
html.dark .theme-btn:hover .theme-ico { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) { .theme-ico { transition: none; } }

/* Drawer móvil (design-system.md) */
.nav-toggle { display: none; }
.scrim { display: none; }
@media (max-width: 860px) {
  body { overflow-x: hidden; }
  .nav-toggle { display: inline-flex; }
  .sidebar {
    position: fixed; top: 0; left: 0; height: 100dvh; width: 250px; z-index: 60;
    transform: translateX(-100%); transition: transform .22s ease;
  }
  .layout.nav-open .sidebar { transform: none; }
  .scrim {
    display: block; position: fixed; inset: 0; background: rgba(0,0,0,.45);
    opacity: 0; visibility: hidden; transition: opacity .2s, visibility .2s; z-index: 55;
  }
  .layout.nav-open .scrim { opacity: 1; visibility: visible; }
}

/* Empty state */
.empty {
  text-align: center; padding: 40px 20px; color: var(--text-2);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.empty svg { color: var(--text-3); }


/* Estados del sistema — T7: 403 / 404 / 500 / mantenimiento. Una sola pantalla (lib/errors.php)
   con la marca de la app: un error que no se parece a la app se lee como "esto se rompió del
   todo". El código grande es informativo; el mensaje humano es el que manda. */
.errpage { background: var(--surface-page); color: var(--text-1); }
.errpage-wrap {
  min-height: 100dvh; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 12px; padding: 32px; text-align: center; max-width: 34em;
  margin: 0 auto;
}
.errpage-logo { margin-bottom: 4px; }
.errpage-code {
  width: 64px; height: 64px; border-radius: 50%; background: var(--err-soft);
  color: var(--err-strong); display: flex; align-items: center; justify-content: center;
  font-size:20px; font-weight: 500;
}
.errpage-wrap h1 { margin: 0; }
.errpage-wrap .btn { margin-top: 6px; }
/* Salida de emergencia: el <form> del cierre de sesión no debe estirarse a lo ancho del flex
   (align-items:center ya lo centra) ni sumar su propio margen sobre el del botón. */
.errpage-out { margin: 0; }
/* El ID del error: lo único técnico que se le muestra al usuario, y por eso tiene que ser fácil de
   copiar (a mano se transcribe mal por teléfono). */
.errpage-id {
  display: flex; align-items: center; gap: 8px; background: var(--surface-2);
  border: 1px solid var(--border-default); border-radius: var(--radius-md); padding: 6px 8px 6px 12px;
}
.errpage-id code { font-size: 14px; letter-spacing: .06em; }




@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* Tablas de datos y helpers de plataforma */

/* EL contenedor de toda tabla ancha — hay uno solo a propósito (antes había también
   `.table-scroll`, ver más abajo dónde estaba). El patrón COMPLETO, que no es sólo esta clase:

     <div class="table-wrap" tabindex="0" role="region" aria-label="… (se puede desplazar)">
       <table class="data"> … </table>
     </div>

   Las cuatro partes hacen falta y cada una tapa un agujero distinto:
     · `overflow-x:auto` — la tabla se desplaza DENTRO de su caja, así la página nunca scrollea de
       costado (regla dura del proyecto, docs/04). Sin esto, en ≤860px el `body{overflow-x:hidden}`
       que tapa el drawer se come las columnas de la derecha y quedan INALCANZABLES.
     · `tabindex="0"` — una región que scrollea tiene que poder recorrerse con teclado (WCAG 2.1.1;
       axe: `scrollable-region-focusable`). Sin esto anda el dedo y el mouse, y nada más.
     · `role="region"` + `aria-label` — para que esa parada del tabulador se anuncie con un nombre
       en vez de ser un salto mudo.
   No hace falta `min-width:0` acá: es un hijo de bloque normal, no un ítem de flex/grid. Si algún
   día esto se mete adentro de un flex, va a necesitarlo (trampa conocida del proyecto).
   El invariante lo verifica tests/unit/test_tablas_anchas.php sobre TODAS las tablas. */
.table-wrap{overflow-x:auto;background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-lg)}
/* `min-width:640px` es deliberado: una tabla de administración de 4-8 columnas apretada a 320px no
   se lee, se adivina. Se prefiere que se desplace a que se apretuje — por eso existe `.table-wrap`. */
table.data{border-collapse:collapse;width:100%;min-width:640px;font-size:13px}
table.data th,table.data td{text-align:left;padding:10px 14px;border-bottom:1px solid var(--border-default);vertical-align:middle}
/* Un checkbox nativo mide 13×13, muy por debajo de cualquier mínimo táctil — y en la matriz de
   permisos son ~60 en pantalla, cada uno con su propio significado. Se agranda el control y se le
   da un área de 44px con el padding de su celda: `accent-color` mantiene el look nativo (y el
   estado indeterminado que usa el tilde maestro), que es lo que pide el estándar en H-15 para T6. */
table.data input[type="checkbox"]{width:22px;height:22px;margin:0;accent-color:var(--accent);cursor:pointer}
table.data td.cell-center{padding:0;text-align:center}
/* El objetivo táctil real: el label ocupa la celda entera, así los 44px se pueden clickear de
   verdad. Un `margin` alrededor del checkbox NO sirve — el margen no activa el control. */
.cell-check{display:flex;align-items:center;justify-content:center;min-width:44px;min-height:44px;cursor:pointer}
.cell-check:hover{background:var(--surface-2)}
.cell-check:has(input:focus-visible){outline:2px solid var(--ring);outline-offset:-2px}
table.data thead th{background:var(--surface-2);font-weight:500;font-size:12px;color:var(--text-2)}
table.data tbody tr:last-child td{border-bottom:0}
tr.row-module td{background:var(--surface-2);font-weight:500}
td.cell-center,th.cell-center{text-align:center}
/* `min-width:0` por el mismo motivo que en los grids: como flex item arrastra `min-width:auto` y no
   se encoge por debajo de su min-content, así que un <select> ancho adentro (el de Template en
   /screens) lo empujaba fuera de la pantalla en A++ en vez de dejar que sus propios hijos
   envolvieran — que es justamente para lo que ya tenía `flex-wrap`. */
.inline-form{display:inline-flex;gap:6px;align-items:center;flex-wrap:wrap;min-width:0;min-height:44px}
.type-chips{display:flex;flex-wrap:wrap;gap:4px 12px;max-width:520px}
.type-chips label{display:flex;align-items:center;min-height:44px;padding:0 4px;gap:4px;font-size:12px;white-space:nowrap;color:var(--text-2)}
.input-xs{height:36px;width:88px;padding:0 8px;border:1px solid var(--border-strong);border-radius:8px;background:var(--surface);color:var(--text-1);font:400 13px var(--font-sans)}
/* `.btn-sm` — la ÚNICA reducción que admite el estándar (H-40): 36px, y solo dentro de una fila de
   tabla o de lista cuyo alto ya sea ≥44px, donde el objetivo táctil real lo da la fila entera. */
.btn-sm{height:36px;padding:0 12px}
/* `.btn-xs` es anterior al estándar y sobrevive en un solo lugar legítimo: los botones superpuestos
   a una miniatura de foto, donde el control no puede tapar la imagen que se está juzgando. Ahí ya
   sube a 44px en touch (ver la regla de `.manage-actions` más abajo), que es donde importa. */
.btn-xs{height:36px;padding:0 12px;font-size:12px}
.link-box{display:flex;gap:8px;align-items:center}
.link-box input{flex:1}

/* ── Cuenta en la sidebar + avatares ── */
.nav-group{margin:14px 12px 4px;font-size:12px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
.avatar-lg{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;
  background:var(--accent-soft);color:var(--accent-soft-fg);font-weight:500;overflow:hidden;flex:none}
.avatar-lg{width:64px;height:64px;font-size:24px}
.avatar-lg img{width:100%;height:100%;object-fit:cover}

/* ── Solapas (Perfil / Seguridad) ── */
.sectabs{display:flex;gap:2px;border-bottom:1px solid var(--border-default);margin-bottom:18px;flex-wrap:wrap}

/* ── Métodos de ingreso (T5-app, primera solapa · D18) ───────────────────────────────────────── */
.am-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.am-row{display:flex;gap:14px;align-items:flex-start;padding:14px 0;border-top:1px solid var(--border-default)}
.am-row:first-child{border-top:0}
.am-main{flex:1;min-width:0}
.am-head{display:flex;gap:12px;align-items:center}
/* Hueco del switch para un método sin credenciales: la columna se mantiene alineada con las filas
   que sí lo tienen, sin dibujar un control que no se puede usar. */
.am-nocreds{width:44px;height:26px;flex:none;border-radius:999px;border:1.5px dashed var(--border-strong)}
.am-title{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:14px}
.am-hint{margin:6px 0 0 56px}
.am-usage{margin:4px 0 0 56px;font-size:12px;color:var(--text-2)}
.am-lock{margin:6px 0 0 56px;font-size:12px;color:var(--warn-strong)}
.am-domains{margin:10px 0 0 56px;max-width:420px}
.am-side{flex:none;padding-top:2px;position:relative}
/* El `summary` lleva las clases del botón: es el mismo control del DS, no uno dibujado aparte.
   `list-style:none` + `::-webkit-details-marker` sacan el triangulito nativo, que en un botón se
   ve como un error de maquetado. */
.am-config>summary{list-style:none;cursor:pointer}
.am-config>summary::-webkit-details-marker{display:none}
/* Mismo criterio en /agents: el "Asignar / cambiar contraseña" es un <summary> que ES el botón (el
   contenido sensible queda oculto hasta abrirlo). El botón arranca inline; al abrir, separa del form. */
.pw-summary{list-style:none;cursor:pointer;display:inline-flex}
.pw-summary::-webkit-details-marker{display:none}
.pw-details[open]>.pw-summary{margin-bottom:12px}
/* La sombra va escrita, no por token: este proyecto no tiene una escala de elevación — la primera
   versión usaba `var(--shadow-md)`, que no existe, así que el panel salía plano y sin separarse
   del fondo. Es el mismo valor que usan los otros desplegables de la app. */
/* SIN `max-width:100%`. En una caja absoluta el porcentaje se resuelve contra el BLOQUE CONTENEDOR,
   que acá es `.am-side` — el ancla, de 97px. Con esa regla el panel de 300px quedaba clampeado a 97
   y `GOOGLE_CLIENT_SECRET`, que no tiene dónde cortar, se salía de la pantalla. Medido a 768 y
   1024px. El `right:0` ya lo mantiene dentro de la card, y abajo de 640 el panel pasa al flujo. */
.am-config-body{position:absolute;right:0;top:calc(100% + 6px);z-index:5;width:300px;
  background:var(--surface);border:1px solid var(--border-strong);border-radius:var(--radius-md);
  padding:12px 13px;box-shadow:0 12px 30px rgba(0,0,0,.2);font-size:13px;line-height:1.45}
/* Los nombres de las claves son una sola palabra larga: sin esto no tienen dónde cortar y se salen
   de su caja en cualquier ancho, no solo en móvil. */
.am-config-body b,.am-config-body code{overflow-wrap:anywhere}
.am-config-body p{margin:0 0 8px}
.am-config-body p:last-child{margin:0}
.am-config-body code{font-size:12px;background:var(--surface-2);padding:1px 5px;border-radius:4px}
@media (max-width:640px){
  /* En una sola columna el panel deja de flotar y pasa al flujo. `flex:1 1 100%` + `min-width:0`
     no son adorno: `.am-side` es `flex:none`, así que sin esto el texto del panel (que tiene
     palabras largas como GOOGLE_CLIENT_SECRET) ensancha la fila y desborda el viewport en vez de
     envolver — medido a 360 y 390px, se salía 40px. */
  .am-side{position:static;flex:1 1 100%;min-width:0}
  .am-config-body{position:static;width:auto;margin-top:8px}
}
.am-row.is-unconfigured .am-title strong{color:var(--text-2)}
@media (max-width:640px){
  .am-hint,.am-usage,.am-lock,.am-domains{margin-left:0}
  .am-row{flex-wrap:wrap}
}
/* 44px de alto: una solapa es un control operable como cualquier otro (H-28). Con `padding:10px`
   quedaba en 43 — un píxel, pero el mínimo es un mínimo. */
.sectab{background:none;border:0;font-family:inherit;font-size:14px;font-weight:500;color:var(--text-2);
  min-height:44px;display:inline-flex;align-items:center;
  padding:0 16px;cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;text-decoration:none}
.sectab:hover{color:var(--text-1);text-decoration:none}
.sectab.active{color:var(--accent-soft-fg);border-bottom-color:var(--accent)}
.sectab:focus-visible{outline:2px solid var(--ring);outline-offset:2px}

/* ── Toggle / switch ── */
.toggle-row{display:flex;gap:12px;align-items:flex-start}
/* Switch 44×26 (ux-templates.md §T5 "Métodos de ingreso"). El thumb lleva BORDE porque sin él el
   control no llega a los 3:1 que pide WCAG 1.4.11 para un objeto gráfico — y no falla donde uno
   esperaría. Medido sobre los tokens reales de 12-momix, apagado y prendido fallan en TEMAS
   DISTINTOS:
     · claro apagado  → pastilla #C2D1D3 vs thumb blanco = 1,57  ✗   (lo arregla el borde --text-2: 3,34)
     · oscuro prendido→ pastilla #4FBDC2 vs thumb blanco = 2,24  ✗   (lo arregla el borde --accent-fg)
     · claro prendido 4,23 ✓ · oscuro apagado 9,70 ✓
   Por eso el borde cambia de color con el estado en vez de ser uno fijo: `--accent-fg` es
   justamente "lo que se lee encima de --accent", que en claro es blanco (borde invisible, y no
   hace falta) y en oscuro es casi negro (el anillo que faltaba). El estándar solo nombra el caso
   claro-apagado; el otro aparece únicamente en una paleta con el acento claro, como ésta. */
.switch{position:relative;display:inline-block;width:44px;height:26px;flex:none;margin-top:2px}
.switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
.slider{position:absolute;inset:0;background:var(--border-strong);border-radius:999px;transition:background .18s}
.slider::before{content:"";position:absolute;left:3px;top:3px;width:20px;height:20px;box-sizing:border-box;
  background:var(--fixed-white);border:1.5px solid var(--text-2);border-radius:50%;transition:transform .18s,border-color .18s}
.switch input:checked + .slider{background:var(--accent)}
.switch input:checked + .slider::before{transform:translateX(18px);border-color:var(--accent-fg)}
.switch input:focus-visible + .slider{outline:2px solid var(--ring);outline-offset:2px}

/* ── Segmented control (política 2FA) ── */
/* `flex-wrap:wrap`: el segmentado (moneda, Fotos/Planos) es el único control en línea del
   formulario que no envolvía, así que con el texto en A++ sus opciones se salían de la pantalla en
   vez de pasar a la línea de abajo. */
.seg{display:inline-flex;flex-wrap:wrap;background:var(--surface-2);border-radius:var(--radius-md);padding:3px;gap:2px}
.seg-opt{position:relative}
.seg-opt input{position:absolute;opacity:0;inset:0;cursor:pointer;margin:0}
.seg-opt span{display:block;padding:7px 14px;border-radius:calc(var(--radius-md) - 3px);color:var(--text-2);font-weight:500;font-size:13px}
.seg-opt input:checked + span{background:var(--surface);color:var(--text-1);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.seg-opt input:focus-visible + span{outline:2px solid var(--ring);outline-offset:2px}
.seg-opt input:disabled{cursor:not-allowed}
.seg-opt input:disabled + span{cursor:not-allowed}
/* Variante de .seg con <button> en vez de radios, para segmentados que NO son campos del
   formulario (hoy: Fotos/Planos del uploader). Mismo look que .seg-opt span; el estado activo
   sale de aria-pressed, que es además lo que lee un lector de pantalla. */
.seg-btn{display:block;padding:7px 14px;border:0;background:none;cursor:pointer;
  border-radius:calc(var(--radius-md) - 3px);color:var(--text-2);font:500 13px/1.4 var(--font-sans)}
.seg-btn[aria-pressed="true"]{background:var(--surface);color:var(--text-1);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.seg-btn:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
.seg-btn-count{margin-left:5px;color:var(--text-3);font-variant-numeric:tabular-nums}
.seg-btn[aria-pressed="true"] .seg-btn-count{color:var(--text-2)}

/* Cobertura cochera (2026-07-28): depende de Cocheras — en 0 se grisa y se deshabilita entera, así
   queda claro que no aplica sin cocheras (antes quedaba activa y con impacto visual igual). */
.garage-coverage-field.is-disabled{opacity:.45}

/* ── Protecciones activas (checklist RO) ── */
.sec-guards{list-style:none;margin:0;padding:0}
.sec-guard{display:flex;gap:10px;align-items:flex-start;padding:11px 0;border-bottom:1px solid var(--border-default)}
.sec-guard:last-child{border-bottom:0}
.sec-guard svg{color:var(--ok);flex:none;margin-top:1px}
.sec-guard strong{display:block}
.sec-guard .muted{font-size:13px}

/* ── Recovery codes + setup TOTP ── */
.recovery-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;margin-top:12px}
.recovery-grid code,.setup-secret code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  background:var(--surface-2);border:1px solid var(--border-default);border-radius:8px;padding:8px 10px;text-align:center;font-size:14px}
.setup-secret{margin:12px 0}
.setup-secret code{display:inline-block;letter-spacing:2px;font-size:15px}

/* ── Avatar (clases, sin style inline por la CSP) ── */
.av{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;overflow:hidden;flex:none;font-weight:500;color:var(--fixed-white);line-height:1}
.av img{width:100%;height:100%;object-fit:cover}
.av-32{width:32px;height:32px;font-size:14px}
.av-40{width:40px;height:40px;font-size:15px}
.av-c0{background:var(--av-0)}.av-c1{background:var(--av-1)}.av-c2{background:var(--av-2)}.av-c3{background:var(--av-3)}
.av-c4{background:var(--av-4)}.av-c5{background:var(--av-5)}.av-c6{background:var(--av-6)}.av-c7{background:var(--av-7)}

/* ── Menú de usuario (topbar dropdown) ── */
.usermenu{position:relative}
/* Solo la burbuja (2026-07-30): sin nombre ni caret. El botón es el avatar, así que el área
   táctil se define por el avatar mismo — 40px, arriba del mínimo de 38px del estándar — y el
   estado de 'abierto' lo marca un anillo, que es lo que reemplaza al caret que giraba. */
.usermenu-trigger{display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:1px solid transparent;border-radius:999px;background:none;color:var(--text-1);cursor:pointer}
.usermenu-trigger:hover{background:var(--surface-2)}
.usermenu-trigger:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
/* Sin caret que gire: el estado "abierto" lo marca el anillo del propio botón. */
.usermenu-trigger[aria-expanded="true"]{background:var(--surface-2);border-color:var(--border-strong)}
.menu-panel{position:absolute;top:calc(100% + 6px);right:0;min-width:248px;background:var(--surface);
  border:1px solid var(--border-default);border-radius:var(--radius-lg);box-shadow:0 8px 28px rgba(15,23,42,.14);
  padding:6px;z-index:70;opacity:0;visibility:hidden;transform:translateY(-4px);transition:opacity .16s,transform .16s,visibility .16s}
.menu-panel.open{opacity:1;visibility:visible;transform:none}
html.dark .menu-panel{box-shadow:0 8px 28px rgba(0,0,0,.45)}
.menu-head{display:flex;gap:10px;align-items:center;padding:10px;border-bottom:1px solid var(--border-default);margin-bottom:6px}
.menu-head-info{min-width:0;display:flex;flex-direction:column;gap:2px}
.menu-head-info strong{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.menu-head-info .muted{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.menu-role{align-self:flex-start;margin-top:2px}
.menu-item{display:flex;align-items:center;gap:10px;width:100%;padding:9px 10px;border-radius:var(--radius-md);
  color:var(--text-1);font:500 14px var(--font-sans);text-decoration:none;background:none;border:0;cursor:pointer;text-align:left}
.menu-item:hover{background:var(--surface-2);text-decoration:none}
.menu-item svg{color:var(--text-2);flex:none}


.menu-sep{height:1px;background:var(--border-default);margin:6px 4px}

/* ── Login: divisor y botones de proveedor ── */
.auth-divider{display:flex;align-items:center;gap:12px;color:var(--text-2);font-size:12px;margin:16px 0}
.auth-divider::before,.auth-divider::after{content:"";flex:1;height:1px;background:var(--border-default)}

/* ── T1 reescrito (ux-templates.md §T1, 2026-07-30) ──────────────────────────────────
   «Cambiar de email»: primer elemento del paso 2, arriba del título. Es la salida de quien se
   equivocó de cuenta, y tiene que estar antes de que lea nada más. */
/* (El parche que había acá para llevar los controles del login a 44px se eliminó: desde el
   2026-07-30 los 44px son la regla GLOBAL de `.btn` y `.input`, así que ya no hace falta.) */

.auth-back{display:inline-flex;align-items:center;gap:5px;margin-bottom:12px;
  font-size:13px;color:var(--text-2);text-decoration:none}
.auth-back:hover{color:var(--text-1)}
.auth-back:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:4px}

/* «Olvidé mi contraseña» va ARRIBA del botón primario y como link real (H-20): quien no se acuerda
   la contraseña lo busca justo ahí, antes de intentar — no en un pie después de fallar. */
.auth-inline-link{margin:-4px 0 16px;font-size:13px}

/* Proveedores más allá del tercero (§T1: máximo 3 en el paso 1). */
.auth-more{margin-top:8px}
.auth-more summary{cursor:pointer;font-size:13px;color:var(--text-2);padding:6px 0;
  width:max-content;list-style:none}
.auth-more summary::-webkit-details-marker{display:none}
.auth-more summary:hover{color:var(--text-1)}
.auth-more summary:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:4px}

/* Bloqueo temporal: `--warn`, NUNCA `--err`. Un bloqueo por intentos no es un error de la persona
   sino una medida de protección, y pintarlo de rojo la trata como culpable (design-system.md →
   «Bloqueos temporales»). */
.form-warn{background:var(--warn-soft);color:var(--warn-strong);border:1px solid color-mix(in srgb,var(--warn) 35%,transparent);
  border-radius:var(--radius-md);padding:11px 13px;margin-bottom:14px;font-size:13px;line-height:1.45}

/* Código de un solo uso: UN input, con la apariencia de dígitos separados hecha con letter-spacing
   y no con seis controles (§T1). El padding-left compensa el espaciado del último carácter, que si
   no descentra el texto. */
.otp{font-size:20px;letter-spacing:.42em;text-align:center;padding-left:calc(.42em + 12px);
  font-variant-numeric:tabular-nums}
.btn-provider{margin-top:8px;justify-content:center;gap:10px}
.btn-provider svg{flex:none}

/* ── Campo de contraseña con ojo (mostrar/ocultar — estándar auth.md) ── */
.input-wrap{position:relative}
.input-wrap .input{padding-right:48px}
/* 44×44 pegado al borde: el objetivo táctil mínimo del estándar (H-28) también adentro de un input.
   Ninguna excepción lo cubre (no es topbar ni fila de tabla), así que cumple el mínimo pleno. */
.pw-toggle{position:absolute;right:0;top:50%;transform:translateY(-50%);width:44px;height:44px;
  display:inline-flex;align-items:center;justify-content:center;border:0;background:none;color:var(--text-3);cursor:pointer;border-radius:8px}
.pw-toggle:hover{color:var(--text-1);background:var(--surface-2)}
.pw-toggle:focus-visible{outline:2px solid var(--ring);outline-offset:1px}
.pw-toggle .icon-eye-off{display:none}
.pw-toggle.on .icon-eye{display:none}
.pw-toggle.on .icon-eye-off{display:inline-block}

/* ── Encabezado de página + filtros ── */
.page-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:16px;flex-wrap:wrap}
.page-head h1{margin:0}
.filters{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;margin-bottom:18px}
.filters .field{margin:0}
.filters .input{min-width:150px}

/* ── Filtros avanzados del listado (2026-07-29, paridad TodresApp 0301/0302/0303/0305/0306) ──
   Operación y Estado pasan de un <select> de un solo valor a fichas multi-selección: buscar
   "venta o alquiler" es el caso normal y antes había que hacer dos búsquedas. Los filtros menos
   usados (mías / ambientes / precio) viven detrás de un <details> nativo que arranca ABIERTO si
   alguno está aplicado — si no, un filtro activo quedaría escondido y el listado parecería roto. */
.filters-adv{flex-direction:column;align-items:stretch;gap:12px}
.filters-row{display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap}
/* Búsqueda: campo ANCHO y adaptable (hay lugar de sobra) — ocupa el espacio libre de la fila. */
.filters-row > .field:first-child{flex:1 1 360px;min-width:220px}
.filters-row > .field:first-child .input{width:100%}
.filters-chips{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.filters-label{font:500 12px/1 var(--font-sans);color:var(--text-3);text-transform:uppercase;
  letter-spacing:.04em;margin-right:2px}
/* La ficha ES el <label>: el checkbox real vive adentro (accesible, navegable por teclado) y el
   estado se pinta con :has(:checked), que refleja el DOM real — no con una clase que haya que
   mantener sincronizada desde JS (ese error ya se cometió con .tile.on, ver CLAUDE.md). */
.fchip{position:relative;display:inline-flex;align-items:center;cursor:pointer}
.fchip input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
/* OJO: usar `> span` (hijo directo), NO `span` (descendiente): el chip de tier anida el ícono de la
   portada, cuyas ESQUINAS son <span> (.ptier-c). Con `.fchip span` estas reglas les pisaban el borde a
   0 y las esquinas nunca se veían (el bug del filtro de nivel). */
.fchip > span{display:block;padding:6px 12px;border-radius:999px;border:1px solid var(--border-default);
  background:var(--surface);color:var(--text-2);font:500 13px/1.2 var(--font-sans);white-space:nowrap}
.fchip:hover > span{border-color:var(--border-strong);color:var(--text-1)}
.fchip:has(input:checked) > span{background:var(--accent-soft);border-color:var(--accent);color:var(--accent-soft-fg)}
.fchip:has(input:focus-visible) > span{outline:2px solid var(--ring);outline-offset:2px}
/* Chip de "Nivel de aviso": SOLO el ícono del tier, IDÉNTICO al de la portada (misma placa + esquinas),
   apenas más grande. El nombre va en el tooltip [data-tip] (sin texto). NO se le tocan fondo ni esquinas:
   la placa oscura translúcida es la que hace que el logo naranja y las esquinas se lean limpias (probar
   overrides —fondo claro, esquinas más gruesas— lo dejaba amontonado). Solo se escala la placa 26→30. */
/* El chip de tier es SOLO la placa (como en la portada), sin el "pill" de los otros chips: ese pill
   ensuciaba y opacaba las esquinas. Placa oscura opaca (para que el logo/esquinas naranjas resalten) +
   un anillo de acento cuando está elegido. */
.fchip-tier > span{display:inline-flex;padding:0;background:transparent;border:0}
.fchip-tier .ptier{width:30px;height:30px;border-radius:8px;background:#23272d;transition:box-shadow .12s}
.fchip-tier .ptier-mark{width:17px;height:17px}
.fchip-tier:hover .ptier{box-shadow:0 0 0 2px var(--border-strong)}
.fchip-tier:has(input:checked) .ptier{box-shadow:0 0 0 2px var(--accent)}
.fchip-tier:has(input:focus-visible) .ptier{box-shadow:0 0 0 2px var(--accent)}
.filters-more{border-top:1px solid var(--border-default);padding-top:10px}
.filters-more summary{cursor:pointer;font:500 13px/1 var(--font-sans);color:var(--accent-soft-fg);
  width:max-content;padding:2px 0}
.filters-more summary:focus-visible{outline:2px solid var(--ring);outline-offset:3px}
/* Más filtros: secciones apiladas con un título chico y una línea sutil entre ellas, para que los
   grupos se encuentren fácil y no abrume (#3). */
.filters-more-body{display:block;margin-top:10px}
.filters-sec{padding:12px 0;border-top:1px solid var(--border-default)}
.filters-sec:first-child{border-top:0;padding-top:2px}
.filters-sec-t{display:block;font:600 11px/1 var(--font-sans);text-transform:uppercase;letter-spacing:.05em;
  color:var(--accent-soft-fg);margin-bottom:8px}
.filters-sec .filters-group{margin:6px 0}
.filters-more-body .field{width:200px;margin:0}
.filters-loc{max-width:220px}
.filters-amen>summary{cursor:pointer;list-style:none;width:max-content}
.filters-amen>summary::-webkit-details-marker{display:none}
.filters-amen>summary::before{content:"▸";color:var(--text-3);font-size:11px;margin-right:6px}
.filters-amen[open]>summary::before{content:"▾"}
/* Autocomplete de búsqueda (#4): lista de sugerencias debajo del campo. */
.search-suggest{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:60;background:var(--surface);
  border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:0 10px 28px rgba(0,0,0,.20);
  overflow:hidden auto;max-height:320px}
.search-suggest-item{display:block;width:100%;text-align:left;border:0;background:transparent;cursor:pointer;
  padding:9px 12px;font:400 14px/1.3 var(--font-sans);color:var(--text-1)}
.search-suggest-item:hover,.search-suggest-item.is-active{background:var(--surface-2)}
.ss-t{display:inline-block;min-width:56px;font-size:11px;font-weight:600;text-transform:uppercase;
  letter-spacing:.03em;color:var(--text-3);margin-right:8px}
.filters-group{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.filters-num{width:110px;min-width:0}
/* 0307 — con JS los filtros se aplican solos, así que el botón "Filtrar" sobra visualmente. NO se
   saca del DOM: es el camino sin JS y el que espera quien navega por teclado, así que queda
   accesible por foco (aparece al tabular hasta él) aunque no ocupe lugar. */
.filters.is-auto .filters-submit{position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
.filters.is-auto .filters-submit:focus-visible{position:static;width:auto;height:auto;clip:auto;
  clip-path:none;overflow:visible}
@media (max-width:640px){
  .filters-more-body{flex-direction:column;align-items:flex-start}
  .filters-num{width:100%}
}

/* Badges de la card del listado (0310/0311) + contador de vistas (0312) */
.prop-thumb-tags{position:absolute;top:8px;right:8px;z-index:2;display:flex;flex-direction:column;gap:6px;align-items:flex-end}
.prop-thumb-tags:empty{display:none}

/* Indicador de jerarquía de aviso por portal (handoff "03"): logo del marketplace + marco de esquinas
   que escala con el tipo (0=Simple, 2=Destacado, 4+tinte=Super destacado). Placa translúcida para que
   se lea sobre la foto. Color por portal via --pt-brand/--pt-corner (inline). */
.ptier{position:relative;width:26px;height:26px;border-radius:7px;flex:none;
  display:inline-flex;align-items:center;justify-content:center;background:rgba(18,16,14,.34)}
/* Color de marca por portal (del handoff), por data-portal para no usar `style` inline (CSP). */
.ptier[data-portal="zonaprop"]    {--pt-brand:#FF5500;--pt-corner:#FF5500}
.ptier[data-portal="argenprop"]   {--pt-brand:#6E9B2C;--pt-corner:#8CB63C}
.ptier[data-portal="mercadolibre"]{--pt-brand:#FFE600;--pt-corner:#FFE600}
.ptier[data-portal="metroprop"]   {--pt-brand:#006AFF;--pt-corner:#006AFF}
.ptier[data-portal="buscadorprop"]{--pt-brand:#EA7628;--pt-corner:#EA7628}
.ptier--premium{background:color-mix(in srgb,var(--pt-brand) 34%,transparent)}
.ptier-mark{width:15px;height:15px;object-fit:contain;display:block;border-radius:3px}
.ptier-c{position:absolute;width:8px;height:8px;border:0 solid var(--pt-corner);pointer-events:none}
.ptier-tl{top:2px;left:2px;border-top-width:2px;border-left-width:2px;border-radius:3px 0 0 0}
.ptier-tr{top:2px;right:2px;border-top-width:2px;border-right-width:2px;border-radius:0 3px 0 0}
.ptier-bl{bottom:2px;left:2px;border-bottom-width:2px;border-left-width:2px;border-radius:0 0 0 3px}
.ptier-br{bottom:2px;right:2px;border-bottom-width:2px;border-right-width:2px;border-radius:0 0 3px 0}
.ptier--premium .ptier-tl{border-top-width:2.8px;border-left-width:2.8px}
.ptier--premium .ptier-tr{border-top-width:2.8px;border-right-width:2.8px}
.ptier--premium .ptier-bl{border-bottom-width:2.8px;border-left-width:2.8px}
.ptier--premium .ptier-br{border-bottom-width:2.8px;border-right-width:2.8px}
/* Indicadores de tier en la vista LISTA (a la derecha, antes de dormitorios/baños) */
.prop-list-tiers{display:flex;gap:4px;align-items:center;flex:none}
.prop-views{position:absolute;bottom:8px;right:8px;display:inline-flex;align-items:center;gap:4px;
  padding:2px 7px;border-radius:999px;background:rgba(var(--overlay-rgb), .62);color:var(--fixed-white);
  font:500 12px/1 var(--font-sans);font-variant-numeric:tabular-nums}

/* ── Grillas de formulario ── */
.form-grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:0 16px}
.form-grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:0 16px}
.form-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:0 16px}
/* Misma salvaguarda que .pf-grid > *: un item de grid arranca con `min-width:auto`
   y entonces se planta en el min-content de su contenido en vez de respetar su pista. Se ve recién
   con el texto en A++ (`html.fs-3 body{zoom:1.25}`), que es cuando el contenido de un campo —el
   stepper de "Ambientes", el select de "Cantidad de niveles"— pasa a pedir más de lo que la columna
   tiene: la pista quedaba en 304px y el campo en 380, y el alta de propiedad scrolleaba de costado
   a 360/390. Justo la combinación que menos conviene romper: A++ existe para los usuarios mayores,
   y el alta es la pantalla donde más tiempo se pasa. */
.form-grid-2 > *,.form-grid-3 > *,.form-grid-4 > *{min-width:0}
/* REVISION-UX-4 punto B1 (2026-07-29) — container query en vez de media query, ver .prop-form
   más abajo: un media query mide el VIEWPORT completo, y con el sidebar fijo de 232px un
   viewport de 1024px le deja al form ~792px reales — la franja 861–1180px (tablet apaisada,
   notebooks) quedaba con columnas apretadas porque el viewport todavía no había cruzado el
   breakpoint de 860px aunque el ESPACIO REAL disponible ya era angosto. El container query mide
   el ancho real de .prop-form (`container-name: propform`), así que resuelve tablet apaisada,
   notebook de 1280px Y el usuario con fs-3 (zoom:1.25, que achica el ancho efectivo pero un media
   query en px nunca lo nota) con la misma regla. El breakpoint global de form-grid-*
   (880/560px, más abajo) sigue igual para el resto de las páginas (owners, neighborhoods, import,
   property.php) que no pasaron por este rediseño. */
@container propform (max-width:820px){.prop-form .form-grid-2,.prop-form .form-grid-3,.prop-form .form-grid-4{grid-template-columns:1fr}}
.form-section{border-top:1px solid var(--border-default);padding-top:14px;margin-top:14px}
.form-section:first-child{border-top:0;margin-top:0;padding-top:0}
.form-section h3{margin-bottom:10px;color:var(--text-2)}

/* ── Nueva propiedad — 3 pasos (2026-07-29, rediseño design_handoff_nueva_propiedad) ──
   Columna de contenido del form propia (property-new.php/property-edit.php ya no ponen
   class="card" en el <form>: cada card vive DENTRO, una por subgrupo). */
/* container-type:inline-size habilita los @container propform de acá abajo (B1) — mide el ancho
   REAL de este contenedor (lo que sobra después del sidebar), no el viewport completo. */
.prop-form{max-width:1060px;margin:0 auto;display:flex;flex-direction:column;gap:14px;
  container-type:inline-size;container-name:propform}
/* REVISION-UX-4 punto B4 (2026-07-29) — riel derecho permanente de vista previa en pantallas
   grandes: en un 27" (≥1600px) el form quedaba en su columna de 1060px con enormes márgenes
   muertos, y la vista previa (lo más valioso) seguía escondida en el paso 3. bindPreviewRail()
   (app.js) MUEVE el nodo real de .preview-wrap acá (mismo patrón "mover, no clonar" del modal),
   visible sticky durante los 3 pasos. Por debajo de 1600px el riel queda vacío/oculto y la previa
   vive donde siempre, adentro del step3-grid. */
.prop-preview-rail{display:none}
.prop-preview-rail:empty{display:none}
/* BUG del 2026-07-29 (reportado por Máximo con captura): a ≥1600px el form se aplastaba a 0px de
   ancho y todo el contenido quedaba superpuesto con la vista previa. DOS causas encadenadas, las
   dos del mismo tipo que el bug de las 2 columnas de la ficha — una regla que depende de un
   contenedor que no puede darle el ancho que pide:
   1. `.content` tiene `max-width:1100px`. El riel vive adentro, así que por ancha que fuera la
      pantalla la fila nunca tenía más de 1060px reales para form (1060) + gap (32) + riel (360)
      = 1452. Se resuelve dejando que ESTA página pida más ancho (.content-wide, shell_start.php).
   2. `.prop-form` tiene `container-type:inline-size` (B1), que aplica contención de tamaño en el
      eje inline: su ancho intrínseco pasa a ser 0 sin importar lo que tenga adentro. Como item
      flex con `flex:0 1 auto`, su base era entonces 0 y nunca crecía — de ahí el ancho 0 medido.
      Se resuelve con `flex:1 1 0` explícito (crece hasta su max-width) + `min-width:0`.
   Verificado con el harness de tests/ux (superposición al 86% antes, cero después). */
@media (min-width:1600px){
  .prop-layout{display:flex;gap:32px;align-items:flex-start;justify-content:center}
  .prop-form{margin:0;flex:1 1 0;min-width:0}
  .prop-preview-rail:not(:empty){display:block;width:360px;flex:none;position:sticky;
    top:calc(var(--journey-h, 130px) + 12px)}
  /* Journey y barra inferior se alinean con el bloque form+riel (1060+32+360), no con los 1060
     de siempre — si no, quedan visiblemente descentrados respecto de lo que enmarcan. */
  .prop-journey-top,.prop-bottombar-inner{max-width:1452px}
  /* La previa ya no vive en el step3-grid a este ancho — vuelve a una sola columna, mismo
     criterio que la franja angosta (B1) pero por el motivo opuesto: sobra lugar, no falta. */
  .step3-grid{grid-template-columns:1fr}
}
.step-head{display:flex;align-items:center;gap:11px;margin:14px 0 -4px}
.step-head:first-child{margin-top:0}
.step-num{width:26px;height:26px;border-radius:50%;flex:none;border:1.5px solid var(--border-strong);
  color:var(--text-3);display:inline-flex;align-items:center;justify-content:center;font:500 12px/1 var(--font-sans)}
.step-head h2{font:500 15px/1.3 var(--font-sans);margin:0}
.step-head-sub{font:400 13px/1.4 var(--font-sans);color:var(--text-3)}
.step-head-rule{flex:1;height:1px;background:var(--border-default)}
.step-card{background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);padding:18px 22px}
.step-card-head{display:flex;align-items:center;gap:11px;margin-bottom:15px;flex-wrap:wrap}
.step-card-head h3{font:500 15px/1.3 var(--font-sans);margin:0}
.step-card-head .hint{margin:0;font-size:13px}
.step-card-head .step-card-chip{margin-left:auto;display:inline-flex;align-items:center;gap:6px;
  font:500 12px/1 var(--font-sans);color:var(--accent-soft-fg);background:var(--accent-soft);
  padding:5px 11px;border-radius:var(--radius-pill)}

/* Fila colapsable ("Detalles opcionales", "Propietario y datos privados", "Otras N comodidades"):
   el contenido SIGUE en el DOM siempre — igual criterio que $typeWrapOpen — solo cambia `hidden`,
   nunca se borra.
   REVISION-UX-4 punto A1 (2026-07-29) — antes se leía como una ETIQUETA informativa, no como un
   control: el chevron era del mismo color que el texto secundario y no había ninguna otra señal
   de que eso se abre. Ahora: texto de acción explícito ("Abrir"/"Cerrar") + el chevron como un
   afford real (círculo con fondo/borde propios, no un glifo pegado al texto), los dos a la
   DERECHA — el clic sigue funcionando en toda la fila (sigue siendo un solo <button>). */
/* `color` explícito (2026-07-29): .collapse-row es un <button>, y un <button> NO hereda el color
   del documento — la hoja del navegador le pone `buttontext`, que es negro. En modo claro pasaba
   desapercibido; en oscuro el título quedaba negro puro sobre el card (#000 sobre #1e293b,
   1,43:1), tan tapado que el subtítulo gris se leía mejor que el título. Detectado por axe-core
   en la suite de UX. */
.collapse-row{display:flex;align-items:center;gap:10px;margin-top:14px;padding:11px 14px;
  border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-2);
  color:var(--text-1);
  cursor:pointer;text-align:left;width:100%;transition:background .15s ease,border-color .15s ease}
.collapse-row:hover{background:color-mix(in srgb, var(--accent) 6%, var(--surface-2))}
.collapse-row:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
/* Abierta: borde más marcado y esquinas inferiores rectas — se lee como una tapa unida al
   contenido que revela justo debajo (.collapse-body), no como un botón suelto. */
.collapse-row.is-open{border-color:var(--border-strong);border-bottom-left-radius:0;border-bottom-right-radius:0}
.collapse-row-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.collapse-row-action{flex:none;font:500 13px/1 var(--font-sans);color:var(--accent-soft-fg)}
.collapse-row-chevron{flex:none;width:28px;height:28px;border-radius:50%;display:flex;
  align-items:center;justify-content:center;background:var(--surface);
  border:1px solid var(--border-default);color:var(--text-2);transition:transform .15s ease}
.collapse-row-chevron svg{width:15px;height:15px}
.collapse-row.is-open .collapse-row-chevron{transform:rotate(180deg)}
/* Candado de "Propietario y datos privados": es el ÚNICO ícono de esta familia que no es un
   chevron, y girarlo 180° no significaba nada (un candado dado vuelta sigue cerrado). Con
   .collapse-row-lock el círculo no rota; lo que se mueve es el arco del candado, que se abre —
   el gesto que el ícono representa de verdad. transform-box:view-box permite fijar el pivote en
   coordenadas del propio SVG: el pie izquierdo del arco (7,11), así levanta el lado derecho,
   igual que el ícono "lock-open" de Lucide. */
.collapse-row-lock{transition:none;transform:none}
.collapse-row-lock svg{overflow:visible}
.lock-shackle{transform-box:view-box;transform-origin:7px 11px;
  transition:transform .3s cubic-bezier(.34,1.32,.64,1)}
.collapse-row.is-open .lock-shackle{transform:rotate(-24deg)}
/* transform:none tiene que ir ACÁ y no en `.collapse-row-lock` a secas: la regla del chevron
   (`.collapse-row.is-open .collapse-row-chevron{transform:rotate(180deg)}`) le gana por
   especificidad a un selector de una sola clase, así que el candado seguía dando la vuelta.
   Verificado con el navegador: antes de este fix el círculo medía matrix(-1,0,0,-1,0,0). */
.collapse-row.is-open .collapse-row-lock{transform:none;border-color:var(--accent);color:var(--accent-soft-fg)}
@media (prefers-reduced-motion: reduce){ .lock-shackle{transition:none} }
.collapse-row-title{font:500 13px/1.3 var(--font-sans)}
.collapse-row-sub{font:400 12px/1.4 var(--font-sans);color:var(--text-3)}
.collapse-body{margin-top:12px}
.collapse-body[hidden]{display:none}

/* ── Videos (REVISION-UX-4 punto A5, 2026-07-29) — hasta 10, name="video_url[]", reordenables
   por arrastre (el primero es el que se usa en la ficha/página pública). ── */
.video-row{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 0}
.video-row.is-dragging{opacity:.4}
.video-row-index{flex:none;font:500 12px/1 var(--font-sans);color:var(--text-3);min-width:52px}
.video-row-index[hidden]{display:none}
.video-row .input{flex:1;min-width:200px}
.video-row-error{flex-basis:100%;margin:0 0 0 60px}
.has-error-border{border-color:var(--err) !important}
.extref-remove,.video-row .extref-remove{flex:none;width:44px;height:44px;display:flex;
  align-items:center;justify-content:center;border:1px solid var(--border-default);
  border-radius:var(--radius-md);background:var(--surface);color:var(--text-2);cursor:pointer}
.extref-remove:hover{color:var(--err-strong);border-color:var(--err)}

/* ── Publicación equivalente en otro portal (REVISION-UX-4 punto A6, 2026-07-29) — hasta 5. ── */
.extref-detected-chip{flex:none;font:500 12px/1 var(--font-sans);color:var(--accent-soft-fg);
  background:var(--accent-soft);padding:4px 8px;border-radius:var(--radius-pill)}
.extref-row-error{flex-basis:100%;margin:0}
.extref-row-dup{flex-basis:100%;margin:2px 0 0;font-size:12px;color:var(--warn-strong)}
.extref-row-dup a{color:inherit;text-decoration:underline;text-underline-offset:2px}

/* ── Marketplaces (REVISION-UX-5, 2026-07-29) — panel fijo por marketplace tildado, revelado por
   el tile_group multi de arriba. Reemplaza el selector .seg por fila de "Publicación equivalente"
   (las filas 'otro' heredadas usan .mkt-panel — la .extref-row pelada se borró el 2026-08-14). ── */
.mkt-panel{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:10px 0;
  border-top:1px solid var(--border-default)}
/* Mismo bug de especificidad ya documentado en este proyecto (dropzone de fotos, 2026-07-28):
   .mkt-panel{display:flex} y el [hidden]{display:none} del navegador tienen la MISMA
   especificidad — sin este empate a favor, el orden de cascada (esta hoja va DESPUÉS) hacía que
   los 4 paneles se vieran siempre, tildado o no (bug real, visto con un screenshot real). */
.mkt-panel[hidden]{display:none}
.mkt-panel-head{display:flex;align-items:center;gap:8px;flex:none;font:500 13px/1 var(--font-sans);min-width:130px}
.mkt-panel-head img{display:block}
.mkt-panel .input{flex:1;min-width:180px}
/* Link que Momix completa solo (ZonaProp): grisado y de solo lectura, se ve que no se escribe a mano. */
.mkt-panel .input.is-autofilled{background:var(--surface-2);color:var(--text-2);cursor:not-allowed;font-style:italic}
.mkt-auto-hint{flex-basis:100%;margin:2px 0 0;font-size:12px;color:var(--text-3)}
.mkt-panel .tile-group{flex-basis:100%;margin-top:4px}
/* Nivel de publicación (tier) dentro de un panel de portal: el tile_group envuelve en .field, que como
   flex-item se encogía a su contenido y hacía colapsar la grilla (fichas apiladas). El wrapper .mkt-tier
   es ahora el flex-item a 100%, y las 3 fichas (super/destacado/simple) van en una fila pareja. */
.mkt-tier{flex-basis:100%;width:100%;margin-top:6px}
.mkt-tier .field{width:100%}
.mkt-tier .field-label{display:block;margin-bottom:6px}
.mkt-tier .tile-group{grid-template-columns:repeat(3,minmax(0,1fr));margin-top:0}
.mkt-tier .tile{height:auto;min-height:74px;padding:12px 8px}
/* El logo del portal con sus esquinas (property_portal_tiers_html) adentro de la ficha: el
   contenedor .ptier trae su propio tamaño pensado para la portada, acá se lo deja respirar. */
.mkt-tier .tile-ico .ptier{transform:scale(1.05)}
.mkt-tier .tile-hint b{color:inherit;font-weight:700;font-variant-numeric:tabular-nums}
/* "Hoy en ZonaProp está publicada como X" (MX-007): va ARRIBA del selector porque es el estado real
   contra el que se elige, no una aclaración al pie. Ancho completo para que no lo empuje el flex. */
.mkt-tier-actual{flex-basis:100%;width:100%;margin:6px 0 0}
/* La fecha del relevamiento del cupo, debajo del estado real (02/09/2026). */
.mkt-tier-cupo{flex-basis:100%;width:100%;margin:2px 0 0;font-size:12.5px}

/* ── Aviso de posible duplicado (parte G) — ámbar, nunca bloquea el guardado. ── */
.dup-warning{display:flex;gap:11px;align-items:flex-start;margin-top:12px;padding:11px 13px;
  border-radius:var(--radius-md);background:var(--warn-soft);border:1px solid rgba(245,158,11,.35)}
.dup-warning[hidden]{display:none}
.dup-warning-ico{flex:none;margin-top:1px;color:var(--warn-strong)}
.dup-warning-ico svg{width:17px;height:17px}
.dup-warning-title{font:500 13px/1.4 var(--font-sans);color:var(--warn-strong);margin:0}
.dup-warning-text{margin:2px 0 0;font:400 12px/1.45 var(--font-sans);color:var(--warn-strong)}
.dup-warning-text a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.dup-warning-dismiss{border:0;background:transparent;color:inherit;text-decoration:underline;
  text-underline-offset:2px;font:inherit;cursor:pointer;padding:0}

/* "Dónde está": dirección a la izquierda, mapa a la derecha (README: grilla 1fr 320px). */
.location-grid{display:grid;grid-template-columns:1fr 320px;gap:16px;align-items:start}
@container propform (max-width:820px){.location-grid{grid-template-columns:1fr}}

/* ── Inicio rápido por link de portal (parte F, 2026-07-29) — solo en alta. ── */
.quickstart{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:12px 14px;
  border:1px solid var(--border-strong);border-radius:var(--radius-lg);
  background:linear-gradient(180deg,var(--surface),var(--surface-page))}
.quickstart-title{font:500 13px/1 var(--font-sans);flex:none}
.quickstart-input-wrap{flex:1;min-width:280px;display:flex;align-items:center;gap:8px;height:44px;
  padding:0 12px;border:1px solid var(--border-strong);border-radius:var(--radius-md);background:var(--surface)}
.quickstart-input-ico{flex:none;color:var(--text-3);display:flex}
.quickstart-input-ico svg{width:15px;height:15px}
.quickstart-input{height:44px;flex:1;min-width:0;border:0;outline:none;background:transparent;font:400 13px var(--font-sans);color:var(--text-1)}
.quickstart-input::placeholder{color:var(--text-3)}
.quickstart-status{flex-basis:100%;margin:0;font-size:12px;color:var(--text-2)}
.quickstart-status.is-error{color:var(--err-strong)}
.quickstart-status.is-ok{color:var(--ok-strong)}
/* Resalta un instante cada campo que "Traer datos" completó solo. */
.quickstart-filled{animation:quickstartFill 2.2s ease}
@keyframes quickstartFill{0%{background:var(--accent-soft);border-radius:var(--radius-md)}100%{background:transparent}}
@media (prefers-reduced-motion:reduce){.quickstart-filled{animation:none}}
/* Parte H — dos cosas que ANTES viajaban juntas en un `max-width:860px` y son distintas:
   · el ALTO es una cuestión de dedo (44px vs 36px de escritorio) → `pointer:coarse`, así también
     alcanza al iPad horizontal, que se toca aunque mida 1024px de ancho;
   · el input a ancho completo en su propia fila es una cuestión de ANCHO (no entra al lado de los
     botones) → sigue en `max-width:860px`. En una tablet apaisada sí entra, y forzarlo ahí sería
     romper una fila que estaba bien. */
@media (pointer:coarse){
  .quickstart-input-wrap{height:44px}
  .quickstart .btn{height:44px}
}
@media (max-width:860px){
  .quickstart-input-wrap{flex-basis:100%}
}

/* ── Journey de 3 pasos (parte A, reemplaza .prop-sticky) + calidad del aviso (parte D) ──
   Sticky arriba de todo (mismo criterio que .prop-sticky: position:sticky, sin bleed porque ya
   no vive dentro de un <form class="card">). */
.prop-journey{position:sticky;top:0;z-index:15;background:var(--surface);
  border-bottom:1px solid var(--border-default);margin-bottom:14px}
.prop-journey-top{max-width:1060px;margin:0 auto;padding:14px 24px 0;display:flex;align-items:center;gap:14px}
.prop-journey-title-wrap{min-width:0;flex:1}
.prop-journey-crumb{font:400 12px/1 var(--font-sans);color:var(--text-3)}
.prop-journey-title-row{display:flex;align-items:center;gap:10px;margin-top:3px}
.prop-journey-quality{display:flex;flex-direction:column;gap:5px;width:170px;flex:none}
.prop-journey-quality-row{display:flex;justify-content:space-between;font:500 12px/1 var(--font-sans);color:var(--text-2)}
.prop-journey-quality-row span:last-child{color:var(--text-1);font-weight:500}
.prop-journey-quality-bar{height:6px;border-radius:999px;background:var(--surface-2);overflow:hidden}
.prop-journey-quality-bar > div{height:100%;border-radius:999px;background:var(--accent);transition:width .2s ease}
.prop-journey-quality-bar.is-low > div{background:var(--warn)}

.prop-journey-steps{max-width:1060px;margin:0 auto;padding:16px 24px 0;display:flex;align-items:flex-start}
.journey-step{flex:1;min-width:0;display:flex;flex-direction:column;gap:7px;padding:0 0 14px;
  border:0;background:transparent;cursor:pointer;text-align:left;font:inherit;color:inherit}
.journey-step-top{display:flex;align-items:center;gap:8px}
.journey-step-circle{width:26px;height:26px;border-radius:50%;flex:none;border:1.5px solid var(--border-strong);
  color:var(--text-3);display:inline-flex;align-items:center;justify-content:center;font:500 12px/1 var(--font-sans)}
.journey-step-line{flex:1;height:2px;border-radius:999px;background:var(--border-default)}
.journey-step:last-child .journey-step-line{visibility:hidden}
.journey-step-body{padding-right:14px;min-width:0}
.journey-step-label{display:block;font:500 13px/1.3 var(--font-sans);color:var(--text-2)}
.journey-step-meta{display:block;margin-top:2px;font:400 12px/1.35 var(--font-sans);color:var(--text-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.journey-step.is-current .journey-step-circle{background:var(--text-1);color:var(--accent);border-color:transparent}
.journey-step.is-current .journey-step-label{color:var(--text-1)}
.journey-step.is-done .journey-step-circle{background:var(--accent);color:var(--accent-fg);border-color:transparent}
.journey-step.is-done .journey-step-line{background:var(--accent)}
.journey-step-circle svg{width:13px;height:13px}

/* Paso con un error de server sin resolver (REVISION-UX-2 punto 3) — gana a is-current/is-done
   mientras esté presente: un error real pesa más que "en qué círculo estoy mirando ahora". */
.journey-step.has-error .journey-step-circle{border-color:var(--err);color:var(--err-strong)}
.journey-step.has-error .journey-step-label{color:var(--err-strong)}

/* REVISION-UX-4 punto B3 (2026-07-29) — journey que se condensa al scrollear: en un notebook de
   1366×768 (el monitor más común de oficina) el cromo fijo (topbar+journey+bottombar) se comía
   un tercio de la pantalla. Pasados 120px de scroll (bindPropertyJourney, app.js) el journey ya
   cumplió su función informativa del primer vistazo, así que pasa a una sola línea ~56px: círculos
   más chicos, sin las líneas de resumen, con la barra de calidad como una línea de 3px pegada al
   borde inferior en vez del bloque completo de la derecha. Vuelve a expandirse solo al subir. */
.journey-step-meta{transition:opacity .16s ease,max-height .16s ease;max-height:20px;overflow:hidden}
.prop-journey.is-condensed .journey-step-meta{max-height:0;opacity:0}
.prop-journey-quality{transition:opacity .16s ease,max-height .16s ease;max-height:60px;overflow:hidden}
.prop-journey.is-condensed .prop-journey-quality{max-height:0;opacity:0;width:0;padding:0}
.journey-step-circle{transition:width .16s ease,height .16s ease,font-size .16s ease}
.prop-journey.is-condensed .journey-step-circle{width:20px;height:20px;font-size:12px}
.prop-journey.is-condensed .journey-step-circle svg{width:11px;height:11px}
.prop-journey-top{transition:padding .16s ease}
.prop-journey.is-condensed .prop-journey-top{padding-top:8px}
.journey-step{transition:padding .16s ease}
.prop-journey.is-condensed .journey-step{padding-bottom:8px}
.prop-journey.is-condensed .prop-journey-steps{padding-top:8px}
/* Barra de calidad condensada — 0 de alto (invisible) salvo en modo condensado. */
.prop-journey-condensed-bar{height:0;overflow:hidden;transition:height .16s ease;background:var(--surface-2)}
.prop-journey-condensed-bar > div{height:3px;background:var(--accent)}
.prop-journey.is-condensed .prop-journey-condensed-bar{height:3px}
@media (prefers-reduced-motion:reduce){
  .journey-step-meta,.prop-journey-quality,.journey-step-circle,.prop-journey-top,.journey-step,.prop-journey-condensed-bar{transition:none}
}

/* Resumen de errores del server — vive DENTRO de .prop-journey (mismo bloque sticky), no arriba
   del form: así nunca queda fuera de vista con la cabecera pegajosa ocupando la parte alta. */
.prop-journey-errors{max-width:1060px;margin:10px auto 0;padding:10px 24px 14px}
.prop-journey-error{margin:0;padding:7px 11px;font:400 13px/1.4 var(--font-sans);color:var(--err-strong);
  background:var(--err-soft);border-radius:var(--radius-md)}
.prop-journey-error + .prop-journey-error{margin-top:6px}

/* Parte H (mobile <860px): journey a una línea compacta (círculo + nombre, sin resumen) con
   scroll horizontal — la barra de calidad baja debajo del título (wrap del propio flex). */
@media (max-width:860px){
  .prop-journey-top{flex-wrap:wrap;padding:12px 16px 0;gap:10px}
  .prop-journey-quality{width:100%}
  .prop-journey-steps{padding:12px 16px 0;overflow-x:auto;-webkit-overflow-scrolling:touch;
    scrollbar-width:none}
  .prop-journey-steps::-webkit-scrollbar{display:none}
  .journey-step{flex:none;flex-direction:row;align-items:center;min-height:44px;padding:0 16px 12px 0}
  .journey-step-top{gap:6px}
  .journey-step-line{width:18px;flex:none}
  .journey-step-body{padding-right:0}
  .journey-step-meta{display:none}
  .journey-step-label{white-space:nowrap}
  .prop-journey-errors{padding:8px 16px 12px}

  /* REVISION-UX-4 punto B5 (2026-07-29): va más lejos que el condensado de B3 — en mobile
     .prop-journey-steps arranca OCULTO siempre (no solo tras 120px de scroll) y en su lugar se ve
     una sola línea "Paso N de 3 · Nombre" (bindPropertyJourney, app.js); tocarla revela el
     detalle de los 3 pasos debajo, sin perder nada del mecanismo existente (mismo <button>,
     mismo click-para-scrollear). */
  .journey-mobile-summary{display:flex;align-items:center;gap:8px;width:100%;min-height:44px;
    padding:10px 16px;border:0;background:transparent;color:var(--text-1);cursor:pointer;
    font:500 13px/1.3 var(--font-sans);text-align:left}
  .journey-mobile-summary:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}
  .journey-mobile-summary span:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .journey-mobile-caret{flex:none;color:var(--text-3);transition:transform .15s ease}
  .journey-mobile-caret svg{width:15px;height:15px}
  .prop-journey.mobile-steps-open .journey-mobile-caret{transform:rotate(180deg)}
  .prop-journey-steps{display:none}
  .prop-journey.mobile-steps-open .prop-journey-steps{display:flex}
}
@media (min-width:861px){.journey-mobile-summary{display:none}}

/* ── Paso 3 "Publicación": checklist "antes de activar" (parte D) + vista previa (parte E) ── */
/* REVISION-UX-4 punto C2 (2026-07-29): la previa pasa de 340px a 420px — "es la recompensa de
   haber cargado todo, tiene que verse como una ficha de verdad, no como un thumbnail". */
.step3-grid{display:grid;grid-template-columns:1fr 420px;gap:22px;align-items:start}
@container propform (max-width:820px){.step3-grid{grid-template-columns:1fr}}

/* REVISION-UX-4 punto C2 (2026-07-29) — "Revisión final" pasa a compartir card con la vista
   previa (.step3-grid, ver más arriba): .review-col ya no dibuja su propia caja (fondo/borde) —
   el .step-card que envuelve todo el panel ya la da — salvo en estado "listo para publicar",
   donde SÍ gana un fondo propio para leerse como un cambio de estado real. */
.review-col{display:flex;flex-direction:column}
.review-col.is-ready{background:var(--ok-soft);border-radius:var(--radius-md);padding:14px;margin:-2px}
.review-ready{display:flex;flex-direction:column;align-items:flex-start;gap:6px;margin-bottom:14px}
.review-ready svg{width:26px;height:26px;color:var(--ok-strong);stroke-width:2.5}
.review-ready-title{margin:0;font:500 15px/1.2 var(--font-sans);color:var(--ok-strong)}
.review-ready .btn-activate{margin-top:4px}
.quality-panel-head{display:flex;justify-content:space-between;font:500 13px/1 var(--font-sans)}
.quality-panel-head span:last-child{color:var(--text-2)}
.review-col [data-quality-bar-2-wrap]{margin-top:9px;background:var(--border-default)}
/* Separa bloqueantes (property_quality()['blocking']) de recomendaciones (['quality']) — parte D,
   REVISION-UX-2 punto 1: solo la primera lista puede bloquear "Revisar y activar". */
.quality-group-title{margin:14px 0 6px;font:500 12px/1 var(--font-sans);letter-spacing:.03em;
  text-transform:uppercase;color:var(--warn-strong)}
.quality-group-title:first-of-type{margin-top:12px}
.quality-group-title.is-ok{color:var(--ok-strong)}
.quality-group-title-optional{color:var(--text-3)}
.quality-checklist{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.quality-check-item{display:flex;align-items:flex-start;flex-wrap:wrap;gap:4px 9px;font:400 12px/1.4 var(--font-sans);color:var(--text-2)}
.quality-check-label{flex:1;min-width:0}
.quality-check-badge{width:16px;height:16px;border-radius:50%;flex:none;display:inline-flex;align-items:center;
  justify-content:center;background:var(--border-default);color:var(--text-3)}
.quality-check-badge svg{width:10px;height:10px;stroke-width:3}
.quality-check-item.is-done{color:var(--text-1)}
/* Completo = VERDE (antes usaba el acento teal, que no se leía como "ok"). Vale para obligatorios y
   recomendados: un campo cargado, aunque sea opcional, se ve verde. #2 (Máximo, 2026-08-04). */
.quality-check-item.is-done .quality-check-badge{background:var(--ok);color:var(--ok-fg)}
/* Obligatorio que FALTA (solo el grupo "Necesario para activar"): ÁMBAR, para distinguirlo del
   completo (verde) y del recomendado pendiente (neutro). Los "bloqueados con motivo" (is-locked)
   quedan afuera: son "todavía no puede", no un pendiente. */
[data-quality-checklist-blocking] .quality-check-item:not(.is-done):not(.is-locked) .quality-check-badge{
  background:var(--warn);color:var(--warn-fg)}
[data-quality-checklist-blocking] .quality-check-item:not(.is-done):not(.is-locked){color:var(--warn-strong)}
/* Acción por ítem pendiente (C2) — "Faltan 2 fotos → Subir fotos" en vez de una lista pasiva. */
/* Es un control operable (lleva al campo que falta), así que va a 44px de alto aunque el texto sea
   chico — el área táctil no es el texto (H-28). */
.quality-check-action{flex:none;margin-left:25px;border:0;background:none;padding:0 4px;
  min-height:44px;display:inline-flex;align-items:center;
  font:500 12px/1.4 var(--font-sans);color:var(--accent-text);text-decoration:underline;
  text-underline-offset:2px;cursor:pointer}

/* Controles del mapa: los dibuja MapLibre en 29px. Son parte de la pantalla igual que cualquier
   otro botón, así que se los lleva al mínimo del estándar en vez de dejarlos afuera por ser de una
   librería — un zoom de 29px en un teléfono se falla igual que cualquier otro. */
.maplibregl-ctrl button{width:44px !important;height:44px !important}
.quality-check-action:hover{color:var(--accent-hover)}
/* Ítem "bloqueado con motivo" (punto 11) — ej. Fotos antes de que exista property_id: nunca
   ámbar, solo texto tenue con un link real al lugar que lo desbloquea. */
.quality-check-item.is-locked{color:var(--text-3)}
.quality-locked-link{border:0;background:none;padding:0;min-height:44px;display:inline-flex;align-items:center;font:inherit;color:inherit;
  text-decoration:underline;text-underline-offset:2px;cursor:pointer;text-align:left}
.quality-locked-link:hover{color:var(--text-2)}

/* ── Línea de resumen copiable (REVISION-UX-4 punto C3, 2026-07-29) ── */
.prop-summary-line{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:12px 16px;
  border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--accent-soft)}
.prop-summary-line p{flex:1;min-width:200px;margin:0;font:500 13px/1.4 var(--font-sans);color:var(--accent-soft-fg)}
.prop-summary-line .btn{flex:none}
.prop-summary-line .btn svg{width:14px;height:14px;margin-right:4px}
.prop-summary-line .btn.is-copied{color:var(--ok-strong);border-color:var(--ok)}

/* REVISION-UX-4 punto B2 (2026-07-29) — offset dinámico: top:96px fijo asumía una altura del
   journey que ya no es constante (errores de server, journey condensado al scrollear — B3 — y
   la propia altura cambia con fs-2/fs-3). --journey-h la publica bindPropertyJourney() (app.js)
   con la altura REAL medida en vivo; 130px es el fallback antes de que corra el JS. */
.preview-wrap{position:sticky;top:calc(var(--journey-h, 130px) + 12px)}
.preview-wrap-head{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.preview-wrap-head .field-label{margin:0;flex:1;min-width:0}
/* REVISION-UX-4 punto C2 — segmentado Escritorio/Celular EN LÍNEA (antes solo dentro del modal):
   reusa .preview-device-seg/.preview-device-opt (mismo look, ver el bloque del modal más abajo)
   pero íconos en vez de texto — acá el espacio es más chico que la cabecera del modal. */
.preview-device-seg-inline{flex:none}
.preview-device-seg-inline .preview-device-opt{padding:6px 8px;display:flex}
.preview-device-seg-inline .preview-device-opt svg{width:15px;height:15px}
/* Ancho "celular" — mismo criterio que .preview-modal-stage[data-device="mobile"], acá aplicado
   directo sobre .preview-wrap en vez del stage del modal. */
.preview-wrap[data-device="mobile"] .preview-card{width:390px;max-width:100%;margin:0 auto}
.preview-expand-btn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;
  flex:none;border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface);
  color:var(--text-2);cursor:pointer}
.preview-expand-btn:hover{background:var(--surface-2);color:var(--text-1)}
.preview-expand-btn svg{width:15px;height:15px}
.preview-card{border:1px solid var(--border-default);border-radius:var(--radius-lg);overflow:hidden;background:var(--surface)}
.preview-cover{height:172px;background:var(--surface-2);position:relative}
.preview-cover img{width:100%;height:100%;object-fit:cover;display:block}
/* El contador va SOBRE la foto de portada, así que lleva colores fijos y no tokens de tema: usar
   `--text-1` de FONDO lo daba vuelta en oscuro (pasaba a casi blanco) y el teal encima quedaba en
   1,75:1. Es el mismo error que ya había tenido `.btn-activate` — un par invertido que funciona en
   un tema y se rompe en el otro. Sobre una foto el fondo tiene que ser oscuro en los dos. */
.preview-cover-count{position:absolute;left:10px;top:10px;font:500 12px/1 var(--font-sans);
  background:rgba(var(--overlay-rgb), .78);color:var(--fixed-white);padding:4px 8px;border-radius:6px}
.preview-cover[data-empty] .preview-cover-count{background:var(--surface);color:var(--text-2);
  border:1px solid var(--border-default);box-shadow:none}
.preview-body{padding:14px 15px}
.preview-price-row{display:flex;align-items:baseline;gap:8px}
.preview-price{font:500 20px/1.2 var(--font-sans);font-variant-numeric:tabular-nums}
.preview-op{font:500 12px/1 var(--font-sans);color:var(--text-2)}
.preview-title{margin-top:6px;font:500 14px/1.4 var(--font-sans);text-wrap:pretty}
.preview-loc{margin-top:3px;font:400 13px/1.4 var(--font-sans);color:var(--text-2)}
.preview-metrics{display:flex;gap:14px;flex-wrap:wrap;margin-top:10px;padding-top:10px;
  border-top:1px solid var(--border-default);font:500 12px/1 var(--font-sans);color:var(--text-2)}

/* ── Modal de vista previa a pantalla completa (REVISION-UX-3 punto 1) — propio, no reusa
   .modal-scrim/.modal-box (esos son para confirmaciones chicas, max-width:400px): acá hace falta
   "pantalla casi completa". El contenido (.preview-card) es el MISMO nodo que vive en el
   sidebar — bindPreviewModal (app.js) lo mueve, nunca lo clona. ── */
.preview-modal-scrim{position:fixed;inset:0;z-index:100;display:none;background:rgba(15,23,42,.55);
  padding:24px}
.preview-modal-scrim.open{display:flex}
.preview-modal-box{margin:auto;width:100%;height:100%;max-width:1100px;background:var(--surface-page);
  border-radius:var(--radius-lg);display:flex;flex-direction:column;overflow:hidden;
  box-shadow:0 24px 70px rgba(0,0,0,.35)}
.preview-modal-head{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 16px;border-bottom:1px solid var(--border-default);background:var(--surface);flex:none}
.preview-device-seg{display:inline-flex;background:var(--surface-2);border-radius:var(--radius-md);padding:3px;gap:2px}
.preview-device-opt{border:0;background:none;min-height:44px;min-width:44px;padding:0 14px;border-radius:calc(var(--radius-md) - 3px);
  color:var(--text-2);font:500 13px/1 var(--font-sans);cursor:pointer}
.preview-device-opt.is-on{background:var(--surface);color:var(--text-1);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.preview-modal-close{display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;
  flex:none;border:0;border-radius:var(--radius-md);background:none;color:var(--text-2);cursor:pointer}
.preview-modal-close:hover{background:var(--surface-2);color:var(--text-1)}
.preview-modal-close svg{width:17px;height:17px}
/* BUG real (2026-07-29, reportado por Máximo con captura de pantalla): align-self:flex-start
   anclaba la tarjeta arriba del todo del stage — con una tarjeta corta (sin fotos, sin datos
   cargados todavía) y un modal de altura completa, el resultado era una tarjeta chica perdida en
   un mar de espacio vacío, se leía como roto. align-items:center (patrón estándar de lightbox) la
   centra en el alto disponible; con contenido más alto que el viewport sigue siendo scrolleable
   igual, el overflow:auto no se pierde. */
.preview-modal-stage{flex:1;overflow:auto;display:flex;justify-content:center;align-items:center;padding:32px 20px}
.preview-modal-stage[data-device="desktop"] .preview-card{width:100%;max-width:480px}
.preview-modal-stage[data-device="mobile"] .preview-card{width:390px;max-width:100%}
/* Dentro del modal el sticky del sidebar no aplica — es el mismo nodo, así que se lo neutraliza acá. */
.preview-modal-stage .preview-wrap,.preview-modal-stage .preview-card{position:static}
.preview-modal-public-link{display:block;text-align:center;padding:12px 16px;border-top:1px solid var(--border-default);
  background:var(--surface);color:var(--accent-soft-fg);font:500 13px/1 var(--font-sans);
  text-decoration:none;flex:none}
.preview-modal-public-link:hover{text-decoration:underline}
@media (max-width:640px){
  .preview-modal-scrim{padding:0}
  .preview-modal-box{border-radius:0}
  .preview-modal-stage[data-device="mobile"] .preview-card{width:100%}
}

/* EL INTERLINEADO VA DENTRO DEL SHORTHAND, y no al lado. Hasta el 13/09/2026 esta regla decía
   `line-height:1.5; … font:400 14px var(--font-sans)`, en ese orden — y el shorthand `font` RESETEA
   `line-height` a `normal`. O sea que el 1.5 estaba escrito pero el navegador nunca lo usó: todos los
   textareas de la aplicación venían con el interlineado por defecto desde que se escribió la regla.
   Lo encontró stylelint (`declaration-block-no-shorthand-property-overrides`); ninguna disciplina
   agarra esto, porque el CSS "dice" lo correcto.
   Medido en Chrome antes y después: el `line-height` computado pasa de `normal` a `21px` (14 × 1.5)
   y el alto crece +10px en el textarea de descripción de /property-new y +4px en el de /owners. Es
   un cambio VISIBLE —los textareas quedan un poco más altos— pero es lo que la regla siempre quiso
   decir, y no empuja barras fijas ni cambia dónde corta ninguna pantalla. */
.textarea{height:auto;padding:10px 12px;resize:vertical;font:400 14px/1.5 var(--font-sans)}

/* Dictado por voz en Descripción (REVISION-UX-4 punto A8, 2026-07-29) — botón en la esquina
   superior derecha del textarea, oculto hasta que bindDictation() confirme soporte real. */
.textarea-mic-wrap{position:relative}
.textarea-mic{position:absolute;top:8px;right:8px;z-index:1;width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;background:var(--surface);
  border:1px solid var(--border-default);border-radius:50%;color:var(--text-2);cursor:pointer}
.textarea-mic[hidden]{display:none}
.textarea-mic:hover{color:var(--accent);background:var(--surface-2)}
.textarea-mic svg{width:15px;height:15px}
.textarea-mic-dot{position:absolute;top:-2px;right:-2px;width:9px;height:9px;border-radius:50%;
  background:var(--err);opacity:0}
.textarea-mic.is-on{color:var(--err-strong);border-color:var(--err)}
.textarea-mic.is-on .textarea-mic-dot{opacity:1;animation:micPulse 1.1s ease-in-out infinite}
@keyframes micPulse{0%,100%{opacity:1}50%{opacity:.25}}
@media (prefers-reduced-motion:reduce){.textarea-mic.is-on .textarea-mic-dot{animation:none}}
.textarea-mic-hint{margin-top:4px}
.textarea-mic-hint.is-listening{color:var(--err-strong)}
.textarea-mic-hint.is-error{color:var(--err-strong)}
.textarea-mic-hint[hidden]{display:none}

.quickstart-retry-photos{border:0;background:transparent;color:inherit;text-decoration:underline;
  text-underline-offset:2px;cursor:pointer;font:inherit;padding:0}

/* ── Toolbar Tarjetas/Lista (2026-07-29): toggle de vista de la grilla de Propiedades, sin form —
   dos <button> planos (no .seg/.seg-opt, que es radio+span pensado para campos de formulario) con
   aria-pressed, persistido en localStorage (bindPropertiesView, app.js). Mismo lenguaje visual que
   .seg (fondo surface-2 + pill activa) para que se sienta parte de la misma familia de controles. */
.list-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:12px}
.list-toolbar-count{margin:0}
.view-toggle{display:inline-flex;background:var(--surface-2);border-radius:var(--radius-md);padding:3px;gap:2px}
.view-toggle-btn{display:flex;align-items:center;gap:6px;padding:7px 12px;border:0;background:transparent;
  color:var(--text-2);font-weight:500;font-size:13px;font-family:inherit;border-radius:calc(var(--radius-md) - 3px);cursor:pointer;white-space:nowrap}
.view-toggle-btn svg{flex:none}
.view-toggle-btn[aria-pressed="true"]{background:var(--surface);color:var(--text-1);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.view-toggle-btn:focus-visible{outline:2px solid var(--ring);outline-offset:2px}

/* ── Ficha (tile) deshabilitada ──────────────────────────────────────────────
   Se ve, no se puede elegir, y el cursor lo dice antes del clic. No se baja tanto la opacidad como
   para volverla ilegible: el usuario tiene que poder LEER qué es lo que no puede elegir. */
/* Medido en Chrome (2026-08-26): las tres fichas salían con el MISMO fondo y el MISMO borde, y lo
   único que distinguía a la deshabilitada era `opacity:.55`. O sea: la que SÍ se podía elegir no
   parecía elegible y la que no, tampoco parecía bloqueada. Máximo lo dijo mirando la pantalla
   ("no parece que es seleccionable") y el navegador lo confirmó.
   Ahora la diferencia es estructural, no de transparencia: la elegible tiene borde marcado y
   superficie propia; la bloqueada se ve HUNDIDA, con el borde punteado y el texto tachado en el
   cupo. Y el aviso "sin cupo" pasa a rojo, que es lo que hay que leer. */
.tile.is-off{cursor:not-allowed;background:var(--surface-2);border-style:dashed;
  border-color:var(--border-default);color:var(--text-3);opacity:1}
.tile.is-off .tile-label{color:var(--text-3)}
.tile.is-off .tile-hint{color:var(--err-strong);font-weight:600}
.tile.is-off .tile-ico{opacity:.4;filter:grayscale(1)}
.tile.is-off:hover{background:var(--surface-2);border-color:var(--border-default);color:var(--text-3)}
/* Cinturón y tiradores: una ficha bloqueada NO puede verse elegida ni aunque algo la marque.
   El input ya sale sin `checked` y con `disabled`, pero esta regla cierra la puerta a que un
   estado viejo, un autocompletado del navegador o un JS futuro la pinten de verde. */
.tile.is-off:has(input:checked){background:var(--surface-2);border-color:var(--border-default);
  color:var(--text-3)}
.tile.is-off:has(input:checked)::after{content:none}

/* ── Cupo de publicaciones del portal ──────────────────────────────────────── */
.zpc{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 12px;margin:0 0 12px;
  background:var(--surface-2);border-radius:var(--radius-md)}
.zpc-t{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-2)}
.zpc-i{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;
  background:var(--surface);font-size:13px}
.zpc-l{color:var(--text-2)}
.zpc-n{font-variant-numeric:tabular-nums}
.zpc-ok .zpc-n{color:var(--ok-strong)}
.zpc-no .zpc-n{color:var(--err-strong)}
.zpc-nd .zpc-n{color:var(--text-2)}
.zpc-a{color:var(--text-2);font-size:11.5px}
.zpc-f{margin:0}
.zpc-nota{flex:1 1 100%;min-width:0;font-size:12px;color:var(--text-2)}

/* ── Import: avisos vs notas ────────────────────────────────────────────────────────────────
   El aviso es lo que puede requerir una decisión; la nota es el importador contando que hizo lo
   correcto. Se distinguen por peso y color, no solo por orden: en una tabla de 200 filas el orden
   no se ve. */
.imp-aviso{display:block;color:var(--warn-strong);font-weight:500}
.imp-nota{display:block;color:var(--text-3, var(--text-2));font-size:12px;opacity:.85}

/* ── Visitas a la vidriera: barras del "por página" ─────────────────────────────────── */
.vh-barras{display:flex;flex-direction:column;gap:8px}
.vh-barra{display:grid;grid-template-columns:minmax(0,180px) 1fr auto;align-items:center;gap:10px}
.vh-barra-t{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--text-2)}
.vh-barra-g{height:10px;border-radius:999px;background:var(--surface-2);overflow:hidden}
.vh-barra-f{display:block;height:100%;border-radius:999px;background:var(--primary)}
.vh-barra-n{font-size:13px;color:var(--text-1);font-variant-numeric:tabular-nums}
@media (max-width:520px){.vh-barra{grid-template-columns:1fr auto}.vh-barra-g{grid-column:1/-1}}

/* ── Reconversión: barra de selección por tandas ────────────────────────────────────────────── */
.reconv-tandas{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:0 0 10px}
.reconv-cuenta{margin-left:auto;color:var(--text-2);font-size:13px;font-variant-numeric:tabular-nums}

/* ── Menú "Ordenar por" (toolbar del listado) ──────────────────────────────────────────────────
   Es un <details>/<summary>: abre y cierra sin JS, así que no depende de app.js ni puede quedar
   muerto por la CSP (que bloquea todo handler inline). El popover va absolute para no empujar
   el toolbar al abrirse. */
.sortmenu{position:relative;margin-left:auto}   /* cuenta a la izquierda; orden y vista juntos a la derecha */
.sortmenu>summary{list-style:none}
.sortmenu>summary::-webkit-details-marker{display:none}
.sortmenu-btn{display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:7px 12px;
  background:var(--surface-2);border-radius:var(--radius-md);color:var(--text-2);
  font-weight:500;font-size:13px;cursor:pointer;white-space:nowrap;user-select:none}
.sortmenu-btn:hover{color:var(--text-1)}
.sortmenu-btn svg{flex:none}
.sortmenu[open] .sortmenu-btn{background:var(--surface);color:var(--text-1);box-shadow:0 1px 2px rgba(0,0,0,.08)}
.sortmenu>summary:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:var(--radius-md)}
/* El popover se oculta EXPLÍCITAMENTE con display, no se confía en el ocultado nativo del
   <details>: Chrome oculta el contenido con `content-visibility` sobre ::details-content, y un
   hijo `position:absolute` se escapa de eso — medido acá, 162px de alto con el menú CERRADO.
   Es el mismo tropiezo que ya dio tres veces en este proyecto con [hidden]. */
.sortmenu-pop{display:none}
.sortmenu[open] .sortmenu-pop{display:block}
.sortmenu-pop{position:absolute;top:calc(100% + 6px);right:0;z-index:30;min-width:220px;padding:6px;
  background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-md);
  box-shadow:0 10px 30px rgba(0,0,0,.14)}
.sortmenu-op{display:block;padding:9px 12px;border-radius:calc(var(--radius-md) - 4px);
  color:var(--text-1);text-decoration:none;font-size:13px;white-space:nowrap}
.sortmenu-op:hover{background:var(--surface-2)}
.sortmenu-op.on{color:var(--primary);font-weight:600}
.sortmenu-op:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}
/* En pantalla chica el popover se ancla al borde y no se sale. */
@media (max-width:520px){.sortmenu-pop{right:auto;left:0}.sortmenu-cur{display:none}}

/* ── Semáforo de origen (SOLO SuperAdmin en Modo Admin) ────────────────────────────────────────
   Verde nativa · amarillo vino del portal y se intervino · rojo vino del portal tal cual.
   El color NO es la única señal: cada punto lleva `title` + `aria-label` con el texto, porque un
   dot de color a secas no lo lee ni un lector de pantalla ni alguien que no distingue rojo/verde
   (WCAG 1.4.1). */
.sem{flex:none;width:10px;height:10px;border-radius:50%;box-shadow:0 0 0 2px var(--surface),0 0 0 3px rgba(0,0,0,.06)}
.sem-ok{background:var(--ok)}
.sem-warn{background:var(--warn)}
.sem-bad{background:var(--err)}
/* El mismo punto, adentro de la ficha del filtro: se ve QUÉ color se está pidiendo. */
.fchip.sem-inline>span::before{content:"";display:inline-block;width:8px;height:8px;border-radius:50%;
  margin-right:6px;vertical-align:baseline}
.fchip.sem-ok>span::before{background:var(--ok)}
.fchip.sem-warn>span::before{background:var(--warn)}
.fchip.sem-bad>span::before{background:var(--err)}

/* ── Grilla de propiedades ── */
/* [hidden] explícito: .prop-grid/.prop-list fijan su propio `display`, que empata en
   especificidad con el [hidden]{display:none} nativo del navegador y le gana por orden de
   cascada — mismo bug ya visto con .mkt-panel/.photo-uploader-controls en este proyecto. */
.prop-grid[hidden],.prop-list[hidden]{display:none}
.prop-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px}
.prop-card{display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border-default);
  border-radius:var(--radius-lg);overflow:hidden;text-decoration:none;color:var(--text-1);transition:transform .14s,box-shadow .14s}
.prop-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(15,23,42,.1);text-decoration:none}
html.dark .prop-card:hover{box-shadow:0 8px 24px rgba(0,0,0,.4)}
/* Miniatura con MARCO FIJO 16:10 y la foto recortada al centro (decisión de Máximo 2026-08-31,
   mockup A): la grilla tenía tarjetas de alturas distintas según la proporción de cada portada —un
   plano vertical estiraba su tarjeta al doble que una panorámica— y la grilla quedaba desprolija.
   `overflow:hidden` + `min-height:0` son parte del arreglo, no adorno: `.prop-thumb` es un flex item
   en columna, así que su `min-height:auto` implícito es el alto INTRÍNSECO de la foto y le ganaba al
   `aspect-ratio`. Es la misma trampa que el `min-width:auto` de los ítems de flex/grid, en el otro
   eje. La foto además va `position:absolute` (abajo): fuera del flujo no aporta al alto mínimo, y
   entonces el marco manda siempre. */
.prop-thumb{position:relative;aspect-ratio:16/10;min-height:0;overflow:hidden;background:var(--surface-2);display:flex;align-items:center;justify-content:center;color:var(--text-3)}
/* Todas las tarjetas de una MISMA FILA, del mismo alto. El ítem del grid es `.prop-sel-wrap` (el
   envoltorio del checkbox de selección), no la tarjeta: sin esto la tarjeta conservaba su alto
   natural dentro de una celda ya estirada, y dos tarjetas de la misma fila terminaban a distinta
   altura cuando una tenía un renglón de más (un segundo precio, una dirección larga). */
.prop-grid > .prop-sel-wrap{display:flex}
.prop-grid > .prop-sel-wrap > .prop-card{flex:1;min-width:0}
.prop-status{position:absolute;top:8px;left:8px}
.prop-body{padding:12px 14px;display:flex;flex-direction:column;gap:3px}
.prop-price{font-size:15px;font-weight:500;font-variant-numeric:tabular-nums}
.prop-title{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prop-meta,.prop-stats{font-size:12px}
.prop-stats{display:flex;gap:10px;margin-top:2px}

/* ── Lista de propiedades (vista alternativa a .prop-grid, misma data) ── */
.prop-list{display:flex;flex-direction:column;gap:8px}
.prop-list-row{display:flex;align-items:center;gap:14px;padding:10px 14px;background:var(--surface);
  border:1px solid var(--border-default);border-radius:var(--radius-md);text-decoration:none;color:var(--text-1);
  transition:box-shadow .1s}
.prop-list-row:hover{box-shadow:0 4px 14px rgba(15,23,42,.08);text-decoration:none}
html.dark .prop-list-row:hover{box-shadow:0 4px 14px rgba(0,0,0,.35)}
.prop-list-thumb{position:relative;flex:none;width:64px;height:48px;border-radius:8px;overflow:hidden;
  background:var(--surface-2);display:flex;align-items:center;justify-content:center;color:var(--text-3)}
.prop-list-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.prop-list-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.prop-list-title-row{display:flex;align-items:center;gap:8px;min-width:0}
.prop-list-title{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.prop-list-meta{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prop-list-stats{flex:none;display:none;gap:10px;font-size:12px}
.prop-list-price{flex:none;min-width:110px;text-align:right;font-weight:500;font-variant-numeric:tabular-nums}
@media (min-width:700px){ .prop-list-stats{display:flex} }
@media (max-width:480px){
  .prop-list-row{gap:10px;padding:8px 10px}
  .prop-list-thumb{width:48px;height:36px}
  .prop-list-price{min-width:0;font-size:13px}

  /* Fila CON acción propia (hoy /destacadas: miniatura · título · precio · botón). Los cuatro
     ítems son de ancho fijo menos el título, así que el título paga toda la diferencia: medido a
     360px le quedaban 32px —"Terr…"— y a 320px, 13px. Y no era solo apretado: a 320px el badge
     "Destacada" terminaba ENCIMA del precio, dos `superposición` de 47% que la suite marcaba como
     error (los únicos dos errores que /destacadas tenía en la matriz táctil).
     Se parte en dos renglones: arriba miniatura + título, abajo precio + acción. El título recupera
     el ancho de la fila, que es lo que hay que leer para decidir si destacar la propiedad.
     `:has(> form)` acota el cambio a las filas que TIENEN esa acción: el listado de /properties usa
     el mismo componente pero sin botón, entra holgado, y no hay por qué reacomodarle nada. */
  .prop-list-row:has(> form){flex-wrap:wrap}
  .prop-list-row:has(> form) .prop-list-body{flex:1 1 60%}
  .prop-list-row:has(> form) .prop-list-price{flex:1 1 auto;text-align:left}
}

/* ── Detalle ── */
.kv{display:grid;grid-template-columns:auto 1fr;gap:8px 16px;margin:0}
.kv dt{color:var(--text-2);font-size:13px}
.kv dd{margin:0;text-align:right;font-size:13px}
.private-card{border-color:color-mix(in srgb,var(--warn) 40%,var(--border-default))}
.timeline{list-style:none;margin:0;padding:0;font-size:13px}
.timeline li{padding:6px 0;border-bottom:1px solid var(--border-default)}
.timeline li:last-child{border-bottom:0}
/* ── Auditoría (2026-07-28): diff campo a campo dentro de un ítem del timeline ── */
.audit-changes{list-style:none;margin:6px 0 0;padding:0 0 0 4px;font-size:12px}
.audit-changes li{padding:2px 0;border-bottom:0}

/* ── Modal de confirmación (propio, nunca confirm() nativo) ── */
.modal-scrim{position:fixed;inset:0;background:rgba(var(--overlay-rgb), .55);display:none;align-items:center;justify-content:center;z-index:80;padding:20px}
.modal-scrim.open{display:flex}
.modal-box{background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);
  padding:22px;max-width:400px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,.3)}
.modal-msg{margin:0 0 18px;font-size:15px}
.modal-actions{display:flex;justify-content:flex-end;gap:10px}

@media (max-width:880px){
  .form-grid-3,.form-grid-4{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  .form-grid-2,.form-grid-3,.form-grid-4{grid-template-columns:1fr}
}

/* Miniaturas de las fotos cargadas: 100 → 140 px, un 40% más grandes (pedido de Máximo,
   02/09/2026). A 100 px cuesta distinguir una foto de otra justo cuando hay que decidir cuál va
   de portada y en qué orden siguen las demás — que es exactamente para lo que se miran. */
.manage-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
.manage-img{position:relative;aspect-ratio:1;border-radius:8px;overflow:hidden;background:var(--surface-2)}
.manage-img img{width:100%;height:100%;object-fit:cover;display:block}
.manage-cover-tag{position:absolute;top:4px;left:4px;background:var(--accent);color:var(--accent-fg);font-size:12px;font-weight:500;padding:1px 6px;border-radius:5px}
.manage-actions{position:absolute;bottom:0;left:0;right:0;display:flex;justify-content:space-between;gap:4px;padding:4px;background:linear-gradient(transparent,rgba(var(--overlay-rgb), .6))}

/* ── Propietario: buscar o crear sin salir del formulario (punto 10, 2026-07-30) ── */
.owner-picker{position:relative;margin-bottom:14px}
.owner-picker-input{display:flex;gap:8px;align-items:center}
.owner-picker-input .input{flex:1;min-width:0}
/* Sin JS este bloque se muestra y el <select> de siempre sigue siendo el camino; con JS lo esconde
   `bindOwnerPicker()`. Por eso NO lleva `display:none` en CSS: el formulario tiene que seguir
   funcionando con JS apagado. */
.owner-picker-raw{margin-top:10px}
.owner-new{margin-top:12px;padding:14px;border:1px solid var(--border-default);
  border-radius:var(--radius-md);background:var(--surface-2)}
.owner-new[hidden]{display:none}
.owner-new-title{margin:0 0 10px;font:500 13px/1 var(--font-sans);color:var(--text-1)}
.owner-new-actions{display:flex;gap:8px;margin-top:12px}

/* Motivación del propietario (punto 9): tres opciones con su descripción debajo — la etiqueta sola
   dice el estado, no qué hacer con él. Se pinta con :has(input:checked) y NO con una clase que haya
   que mantener desde JS (ese error ya se cometió con `.tile.on`). */
.owner-prio-opts{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:8px}
.owner-prio-opt{position:relative;cursor:pointer}
.owner-prio-opt input{position:absolute;opacity:0;inset:0;margin:0;cursor:pointer}
.owner-prio-box{display:flex;flex-direction:column;gap:3px;height:100%;padding:10px 12px;
  border:1.5px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface)}
.owner-prio-opt:hover .owner-prio-box{border-color:var(--border-strong)}
.owner-prio-opt:has(input:checked) .owner-prio-box{border-color:var(--accent);background:var(--accent-soft)}
.owner-prio-opt:has(input:focus-visible) .owner-prio-box{outline:2px solid var(--ring);outline-offset:2px}
.owner-prio-label{font:500 13px/1.25 var(--font-sans);color:var(--text-1)}
.owner-prio-opt:has(input:checked) .owner-prio-label{color:var(--accent-soft-fg)}
.owner-prio-note{font-size:12px;line-height:1.35;color:var(--text-2)}
.owner-prio-none .owner-prio-box{justify-content:center}
.hint.is-ok{color:var(--ok-strong)}
.hint.is-err{color:var(--err-strong)}

/* ── Girar la foto + verla grande (puntos 6/7, 2026-07-30) ──
   Los botones de girar van sobre la miniatura, así que llevan color fijo: el degradé oscuro de
   .manage-actions es el mismo en los dos temas y `--text-1` se daría vuelta con el tema y en claro
   quedaría texto oscuro sobre negro. Es uno de los tres casos de color independiente del tema que
   documenta el bloque de tokens. */
.photo-rot-btn{display:inline-flex;align-items:center;justify-content:center;padding:4px 7px;
  color:var(--fixed-white);border-color:rgba(255,255,255,.45)}
.photo-rot-btn:hover:not(:disabled){background:rgba(255,255,255,.16)}
.photo-rot-btn:disabled{opacity:.5;cursor:progress}

/* Herramientas de imagen (girar, ver grande) arriba a la derecha de la miniatura. Van separadas de
   `.manage-actions` (portada, borrar) porque son cosas distintas y porque los cuatro botones juntos
   NO entran en una celda de ~170 px: en un teléfono de 390 px el ✕ se salía de la pantalla. */
.manage-tools{position:absolute;top:4px;right:4px;display:flex;gap:4px}
.manage-tool{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;
  border-radius:999px;border:1px solid rgba(255,255,255,.45);background:rgba(var(--overlay-rgb), .55);
  color:var(--fixed-white);cursor:pointer;padding:0}
.manage-tool:hover:not(:disabled){background:rgba(var(--overlay-rgb), .85)}
.manage-tool:disabled{opacity:.5;cursor:progress}
.manage-tool:focus-visible,.photo-rot-btn:focus-visible{outline:2px solid var(--fixed-white);outline-offset:1px}
/* Red de seguridad: si algún día se suma otro botón, la barra se parte en dos filas en vez de
   desbordar la celda (que es como se detectó este problema, con la suite midiendo un 390 px real). */
.manage-actions{flex-wrap:wrap}
/* "Portada" es un botón fantasma sobre una FOTO: con el degradé solo, encima de un cielo claro se
   volvía ilegible (visto en el navegador, 02/09/2026). Mismo tratamiento que las herramientas de
   arriba — pastilla oscura y texto blanco, fijo en los dos temas. */
.manage-actions .btn-ghost{background:rgba(var(--overlay-rgb), .6);color:var(--fixed-white);
  border-color:rgba(255,255,255,.4);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
.manage-actions .btn-ghost:hover:not(:disabled){background:rgba(var(--overlay-rgb), .85)}

/* Visor: fondo casi negro, igual que el de la ficha — una foto se juzga mejor sin que la superficie
   de la página le compita. Es un componente aparte del de property_gallery.php a propósito: ese
   tiene zoom, deslizar y solapas, y está atado a su markup; acá solo hace falta ver y girar. */
.photo-viewer{position:fixed;inset:0;z-index:120;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;padding:20px;background:var(--viewer-bg)}
.photo-viewer-img{max-width:min(100%,1100px);max-height:calc(100dvh - 110px);object-fit:contain;
  border-radius:10px;background:rgba(255,255,255,.04)}
.photo-viewer-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;justify-content:center}

/* Panel de ajustes (punto 8). Va sobre el fondo oscuro del visor, así que lleva colores fijos —
   los tokens de tema darían texto oscuro sobre negro en modo claro. */
.photo-adjust{width:min(100%,720px);padding:14px 16px;border-radius:var(--radius-md);
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.16);color:var(--fixed-white)}
.photo-adjust[hidden]{display:none}
.photo-adjust-presets{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px}
.photo-adjust-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px 18px}
.photo-adjust-slider{display:flex;flex-direction:column;gap:3px;font-size:12px}
.photo-adjust-head{display:flex;justify-content:space-between;align-items:baseline}
.photo-adjust-val{font-variant-numeric:tabular-nums;opacity:.75}
.photo-adjust-slider input[type=range]{width:100%;accent-color:var(--brand-teal);margin:0}
.photo-adjust-status{margin:10px 0 0;font-size:12px;min-height:1.2em;opacity:.8}
.photo-adjust-actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px}
@media (max-width:640px){
  .photo-adjust{padding:12px}
  .photo-adjust-actions .btn{flex:1;min-height:44px}
}
.photo-viewer-bar .photo-rot-btn{color:var(--fixed-white)}
@media (max-width:640px){
  .photo-viewer{padding:12px}
  .photo-viewer-bar .btn{min-height:44px}
}
/* REVISION-UX-4 punto B5 (2026-07-29) — objetivos táctiles: en touch, "Portada" y "✕" (30px,
   btn-xs) quedaban apretados en una celda de ~105px, dos botones diminutos superpuestos sobre la
   miniatura. Celdas más anchas + botones a 44px (mínimo táctil real, no solo el CSS de escritorio).
   La consulta pasó de `max-width:860px` a `pointer:coarse` (ver el porqué largo en el bloque de
   `.pbr-tile`): en el iPad horizontal estas herramientas seguían midiendo 28px. La celda más ancha
   viaja JUNTO con los botones a propósito — es lo que les hace lugar; separarlas amontonaría de
   nuevo los cuatro controles sobre una miniatura de ~105px. */
@media (pointer:coarse){
  .manage-grid{grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:14px}
  .manage-actions .btn-xs{height:44px;padding:0 12px;font-size:13px}
  /* Las herramientas de imagen también a 44 px en touch (WCAG 2.5.8), pero SIN texto: son íconos,
     así que crecen en cuadrado y siguen entrando tres en una celda de ~170 px. */
  .manage-tool{width:44px;height:44px}
}
.photo-uploader-controls{display:flex;flex-direction:column;margin-bottom:4px}
.photo-uploader-controls[hidden]{display:none}
.prop-thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}

/* ── Mientras la foto carga (Máximo, 02/09/2026) ───────────────────────────────────────────────
   Antes quedaba un rectángulo vacío y una grilla de doce tarjetas parecía rota. Ahora: la M de
   Momix quieta y un barrido de luz que la cruza — el patrón que ya usan Mercado Libre, Facebook y
   LinkedIn, así que se entiende sin explicación.

   El barrido vive en la CAJA, no en la imagen, así que aparece desde el primer pintado (antes de
   que el navegador siquiera empiece a bajar la foto). Se apaga solo cuando la imagen carga:
   `.prop-thumb.cargada`. */
.ph-momix{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  color:var(--text-3);opacity:.5;pointer-events:none;z-index:0}
.ph-momix svg{width:32px;height:32px}
.prop-thumb::before{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(100deg,transparent 20%,rgba(255,255,255,.55) 50%,transparent 80%);
  transform:translateX(-100%);animation:ph-barrer 1.5s ease-in-out infinite}
@keyframes ph-barrer{to{transform:translateX(100%)}}
/* Foto lista: se van el barrido y la M. La imagen queda sola, sin nada animándose debajo. */
.prop-thumb.cargada::before{display:none}
.prop-thumb.cargada .ph-momix{display:none}
/* SIN foto: la M se queda, el barrido NO. No hay nada cargando, y un barrido eterno mentiría.
   Es la distinción que pidió Máximo: "cargando" y "no tiene foto" no pueden verse igual. */
.prop-thumb.empty::before{display:none}
.prop-thumb.empty .ph-momix{opacity:.35}
/* Quien pidió menos movimiento no ve el barrido: la M sola alcanza para decir lo mismo. */
@media (prefers-reduced-motion: reduce){
  .prop-thumb::before{animation:none;display:none}
}


/* ── Ubicación en el mapa (MapLibre self-hosted, pin arrastrable) ── */
.map-widget{position:relative;margin:10px 0 4px;border:1px solid var(--border-default);border-radius:var(--radius-md);overflow:hidden;
  display:flex;flex-direction:column}
.map-widget-canvas{height:320px;width:100%;position:relative}
.map-widget-canvas .maplibregl-canvas{cursor:grab}
.map-widget-canvas .maplibregl-canvas:active{cursor:grabbing}
/* Pantalla completa (2026-07-28): el widget entero (canvas + barra), no solo el canvas — la
   Fullscreen API nativa hace que :fullscreen ocupe todo el viewport solo, pero el canvas de
   adentro tiene una altura fija en px que no crece con el contenedor — hay que estirarlo a mano. */
.map-widget:fullscreen{height:100vh}
.map-widget:fullscreen .map-widget-canvas{height:auto;flex:1}
.map-widget:-webkit-full-screen{height:100vh}
.map-widget:-webkit-full-screen .map-widget-canvas{height:auto;flex:1}
/* Cartel "Arrastrá el marcador…": angosto para no taparle el paso a los controles nativos
   (zoom +/− y pantalla completa, arriba a la derecha, ~39px de columna) y más chico/discreto que
   antes (2026-07-28, a pedido de Máximo — antes ocupaba todo el ancho y a veces les tapaba el
   control si el texto envolvía a 2 líneas). */
/* Buscador dentro del mapa (REVISION-UX-4 punto A9, 2026-07-29 — "arriba a la izquierda, como
   Google"): mismo endpoint que el campo Dirección de arriba, para no obligar al agente a salir
   del mapa e ir a buscar en otro lado cuando el mapa es justamente donde está mirando. */
.map-widget-search{position:absolute;top:10px;left:10px;z-index:3;display:flex;align-items:center;gap:7px;
  width:min(230px,calc(100% - 68px));height:36px;padding:0 10px;background:var(--surface);
  border:1px solid var(--border-default);border-radius:var(--radius-md);box-shadow:0 3px 10px rgba(0,0,0,.14)}
.map-widget-search-ico{flex:none;color:var(--text-3);display:flex}
.map-widget-search-ico svg{width:15px;height:15px}
/* height:100%: el input llenaba 44px dentro de una caja de 36px (captura de la suite 2026-08-04)
   y desbordaba el borde del buscador. Llena la caja, mida lo que mida. */
.map-widget-search-input{flex:1;min-width:0;height:100%;border:0;outline:none;background:transparent;
  font:400 13px var(--font-sans);color:var(--text-1)}
.map-widget-search-input::placeholder{color:var(--text-3)}
.map-widget-search .city-suggest{position:absolute;top:100%;left:0;margin-top:4px}
/* El cartel "Arrastrá el marcador…" baja debajo del buscador (antes arrancaba en top:10px, mismo
   lugar) para no superponerse. */
/* Ayuda "(i)" al lado del título de un campo (item 11 del feedback del 2026-07-29): reemplaza al
   cartel que flotaba sobre el mapa y tapaba justo la zona donde uno quiere soltar el pin.
   <details>/<summary> nativos — teclado y lector de pantalla sin una línea de JS. */
/* El popover se ancla al INICIO de la fila del label, no al ícono: anclado al ícono se salía del
   viewport cuando el campo cae en la columna derecha del formulario (detectado por el harness de
   tests/ux a 1366px: right 1399 sobre un viewport de 1366). Desde el borde izquierdo del label
   siempre entra, porque el label nunca es más angosto que su propia columna. */
.field-label-with-help{display:inline-flex;align-items:center;gap:7px;position:relative}
.field-help{display:inline-block}
.field-help summary{list-style:none;cursor:pointer;width:17px;height:17px;border-radius:50%;
  border:1px solid var(--border-strong);color:var(--text-3);background:var(--surface);
  display:flex;align-items:center;justify-content:center;
  font:500 12px/1 var(--font-sans);font-style:italic;user-select:none}
.field-help summary::-webkit-details-marker{display:none}
.field-help summary:hover{color:var(--text-1);border-color:var(--text-3)}
.field-help summary:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
/* Chrome oculta el contenido de un <details> cerrado con `content-visibility`, no con
   `display:none` — y eso NO alcanza para un hijo `position:absolute`: el popover se seguía
   midiendo (y potencialmente viendo) con el detalle cerrado. Se explicita. */
.field-help:not([open]) .field-help-body{display:none}
.field-help-body{position:absolute;z-index:20;top:calc(100% + 6px);left:0;width:min(280px,70vw);
  padding:9px 11px;border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);box-shadow:0 6px 20px -8px rgba(15,23,42,.35);
  font:400 12px/1.45 var(--font-sans);color:var(--text-2);font-style:normal;text-transform:none;
  letter-spacing:normal;font-weight:400}
.map-widget-use-near-done{color:var(--ok-strong);font-weight:500}
/* Aviso de distancia (punto 2 de la revisión 2026-07-29): la Dirección resolvió lejos de donde
   está el pin (>5 km) — nunca bloquea, solo informa; mismo criterio ámbar que el resto de los
   avisos del proyecto (duplicados, etc.). Misma posición que .map-widget-tip (arriba del canvas,
   deja el margen de los controles de zoom a la derecha). */
.map-widget-distance-warn{position:absolute;top:56px;left:10px;right:49px;z-index:2;display:flex;align-items:center;gap:6px;
  background:var(--warn-soft);border:1px solid color-mix(in srgb,var(--warn) 45%,var(--border-default));
  border-radius:var(--radius-md);box-shadow:0 3px 10px rgba(0,0,0,.14);padding:6px 10px;
  font-size:12px;color:var(--warn-strong)}
.map-widget-distance-warn[hidden]{display:none}
.map-widget-equalize{margin-top:8px}
.map-widget-equalize:disabled{opacity:.5;cursor:not-allowed}
/* Barra inferior — FUERA del canvas (flujo normal, no flota encima del mapa): coordenadas, botón
   de copiar y "cerca de X", todo junto (2026-07-28, a pedido de Máximo — antes flotaba sobre el
   mapa y tapaba esa esquina). */
.map-widget-bar{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--surface);
  border-top:1px solid var(--border-default);font:500 12px var(--font-sans);color:var(--text-2);
  font-variant-numeric:tabular-nums}
.map-widget-coords{flex:none;white-space:nowrap}
.map-widget-copy{flex:none;display:flex;align-items:center;justify-content:center;width:44px;height:44px;
  border:1px solid var(--border-default);border-radius:6px;background:var(--surface-2);color:var(--text-3);
  cursor:pointer;padding:0}
.map-widget-copy:hover{color:var(--text-1);background:var(--surface-3,var(--surface-2))}
.map-widget-copy svg{width:13px;height:13px}
.map-widget-copy.copied{color:var(--accent);border-color:var(--accent)}
.map-widget-near{flex:1;min-width:0;display:flex;align-items:center;gap:8px;font-weight:400;color:var(--text-3)}
.map-widget-near:empty{display:none}
.map-widget-near > span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* "Usar esta dirección" (REVISION-UX-4 punto A9 — sync bidireccional): antes "Cerca de X" solo
   informaba, arrastrar el pin nunca tocaba el campo Dirección. Ahora ofrece completarlo, sin
   pisarlo nunca en silencio. */
.map-widget-use-near{flex:none;border:0;background:transparent;color:var(--accent-soft-fg);
  font:500 12px var(--font-sans);text-decoration:underline;text-underline-offset:2px;cursor:pointer;padding:0}
.map-widget-use-near:hover{color:var(--accent-hover)}
/* Botón "recentrar" (2026-08-04): pasa al cluster de abajo a la derecha, ENCIMA de "Mi
   ubicación", redondo como él — el layout de Google Maps. El top:125px hardcodeado anterior
   asumía la altura exacta de zoom+fullscreen y quedaba MONTADO sobre el botón de pantalla
   completa (captura de la suite 2026-08-04); abajo no hay nada que pisar y no depende de
   ninguna medida de los controles nativos. */
.map-widget-recenter{position:absolute;bottom:88px;right:10px;z-index:2;width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;background:var(--surface);
  border:1px solid var(--border-default);border-radius:50%;color:var(--text-2);cursor:pointer;
  box-shadow:0 1px 4px rgba(0,0,0,.16)}
.map-widget-recenter:hover{color:var(--text-1);background:var(--surface-2)}
.map-widget-recenter svg{width:16px;height:16px}
/* "Mi ubicación" (REVISION-UX-4 punto A9, 2026-07-29) — abajo a la derecha, mismo lugar que
   Google Maps; hijo de .map-widget-canvas (no de .map-widget) para anclar al borde real del
   mapa, no al borde del widget entero (que incluye la barra de coordenadas debajo). */
.map-widget-geolocate{position:absolute;bottom:36px;right:10px;z-index:2;width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;background:var(--surface);
  border:1px solid var(--border-default);border-radius:50%;color:var(--text-2);cursor:pointer;
  box-shadow:0 1px 4px rgba(0,0,0,.16)}
.map-widget-geolocate:hover{color:var(--accent);background:var(--surface-2)}
.map-widget-geolocate svg{width:17px;height:17px}
.map-widget-geolocate.is-loading{color:var(--accent);animation:mapGeoPulse 1s ease-in-out infinite}
.map-widget-geolocate:disabled{cursor:default}
@keyframes mapGeoPulse{0%,100%{opacity:1}50%{opacity:.4}}
@media (prefers-reduced-motion:reduce){.map-widget-geolocate.is-loading{animation:none}}
.map-widget-geolocate-status{position:absolute;bottom:140px;right:10px;z-index:2;max-width:220px;
  padding:8px 10px;background:var(--surface);border:1px solid var(--border-default);
  border-radius:var(--radius-md);box-shadow:0 3px 10px rgba(0,0,0,.14);font-size:12px;color:var(--text-2);
  text-align:right}
.map-widget-geolocate-status[hidden]{display:none}
/* "Levantar" el pin mientras se arrastra (2026-07-28): se desvanece un poco y gana una sombra
   marcada — la sensación de "despegado de la superficie" de Google Maps, sin tocar ningún
   transform/posición propia del marcador (eso definiría literalmente dónde cae, y depende de
   offsets internos de MapLibre — más seguro no tocarlo). */
.momix-pin-draggable{transition:opacity .15s ease,filter .15s ease}
.momix-pin-draggable.is-dragging{opacity:.85;filter:drop-shadow(0 10px 8px rgba(0,0,0,.45))}
/* Atribución (© OpenMapTiles © OpenStreetMap contributors, requisito de licencia — no se puede
   sacar, ver CLAUDE.md) más chica y discreta (2026-07-28, a pedido de Máximo): el botón "i"
   colapsado baja de opacidad hasta que se lo toca. */
.maplibregl-ctrl-attrib{font-size:12px !important}
.maplibregl-ctrl-attrib-button{opacity:.55}
.maplibregl-ctrl-attrib-button:hover,.maplibregl-ctrl-attrib.maplibregl-compact-show .maplibregl-ctrl-attrib-button{opacity:1}
/* Ocupa TODO el ancho del campo (pedido 2026-08-15): antes un max-width:360px lo cortaba y el
   secundario se truncaba enseguida ("San Isidro, Buenos Air…"). A lo ancho del campo entra la
   dirección completa. En el buscador embebido del mapa el contenedor es angosto, así que igual se
   adapta solo con width:100%. */
.city-suggest{position:absolute;z-index:5;margin-top:4px;width:100%;max-height:320px;overflow-y:auto;
  background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-md);
  box-shadow:0 12px 30px rgba(0,0,0,.2)}
.city-suggest-item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;border:0;
  background:transparent;padding:8px 12px;font:inherit;cursor:pointer}
.city-suggest-item:hover,.city-suggest-item.active{background:var(--surface-2)}
.city-suggest-ico{flex:none;display:flex;color:var(--text-3)}
.city-suggest-item.active .city-suggest-ico{color:var(--accent)}
.city-suggest-ico svg{width:16px;height:16px}
/* Una sola línea, estilo Google Maps (pedido de Máximo 2026-08-15): nombre + ubicación en gris,
   separados por un punto medio, y la línea ENTERA trunca con puntos suspensivos. Como el nombre va
   primero, lo que se recorta es la ubicación (la parte larga), nunca el nombre. Reemplaza el layout
   de dos líneas de M11 — que seguía el formato de la API de Google Places (mainText/secondaryText),
   correcto pero más alto que el Google Maps de consumidor. */
.city-suggest-body{display:block;min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;line-height:1.35}
/* Lo NO tipeado va en peso normal para que la negrita del match resalte de verdad (pedido: "no se
   ve muy clara la negrita"). Con todo el nombre ya en semibold, el <mark> no se distinguía. */
.city-suggest-main{color:var(--text-1);font-weight:400}
/* El resaltado es negrita FUERTE (700), sin el fondo amarillo default de <mark> (como Google). Vale
   para el nombre y la ubicación: `color:inherit` deja el secundario gris y el principal oscuro. */
.city-suggest-main mark,.city-suggest-sub mark{background:none;color:inherit;font-weight:700;padding:0}
.city-suggest-sep{color:var(--text-3);margin:0 5px}
.city-suggest-sub{color:var(--text-3)}
/* Encabezado "Búsquedas recientes" del historial (localStorage). */
.city-suggest-head{margin:0;padding:9px 12px 4px;font-size:11px;font-weight:600;letter-spacing:.03em;text-transform:uppercase;color:var(--text-3)}
.city-suggest-empty{padding:14px 12px;margin:0;text-align:center;color:var(--text-3);font-size:13px}
/* M11 — columna derecha del desplegable: distancia al centro del mapa y aviso de altura.
   `flex:none` + `min-width:0` en el cuerpo: sin eso, un subtítulo largo ("Puertos del Lago,
   Escobar, Buenos Aires") empuja esta columna fuera de la caja en mobile — es el min-width:auto
   implícito de todo ítem flex, la misma trampa que ya mordió dos veces. */
.city-suggest-meta{flex:none;display:flex;flex-direction:column;align-items:flex-end;gap:2px;
  font-size:12px;color:var(--text-3);white-space:nowrap}
.city-suggest-dist{font-variant-numeric:tabular-nums}
/* El aviso NO es un error: la calle existe y se puede elegir igual. Por eso es un dato más de la
   fila (con el color de advertencia), no un bloqueo ni un ícono de alarma. */
.city-suggest-warn{color:var(--warn-strong);font-variant-numeric:tabular-nums}
@media (max-width:380px){.city-suggest-meta{display:none}}

/* ── Fotos y planos: dropzone (D14 uploads-ui, docs/standards/ux-templates.md) ──────
   Reglas visibles ANTES de elegir (tipos + peso máximo), progreso por archivo, rechazo con el
   motivo exacto en la fila del archivo — la validación real siempre está en el server
   (process_property_image, lib/images.php); esto es solo la capa de UX. */
.dropzone{position:relative;border:2px dashed var(--border-default);border-radius:var(--radius-md);
  padding:22px 16px;text-align:center;cursor:pointer;transition:border-color .15s,background .15s}
.dropzone:hover,.dropzone:focus-visible{border-color:var(--accent);background:var(--accent-soft)}
.dropzone.is-dragover{border-color:var(--accent);background:var(--accent-soft)}
.dropzone:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
.dropzone-icon{width:26px;height:26px;color:var(--text-3);margin:0 auto 6px}
.dropzone-text{margin:0;font-size:14px;color:var(--text-2)}
/* `--accent-soft-fg` y no `--accent`: el acento de la paleta 12-momix es un teal claro (#22D3A6)
   pensado para FONDOS, y como texto sobre blanco da 1,92:1. El token `-soft-fg` es justamente la
   versión del acento legible sobre una superficie clara (y su equivalente clara en tema oscuro).
   El propio bloque de tokens ya lo avisa: "blanco sobre #22D3A6 no pasa AA". */
.dropzone-link{color:var(--accent-soft-fg);font-weight:500;text-decoration:underline}
.dropzone-rules{margin:4px 0 0;font-size:12px;color:var(--text-3)}
.dropzone-title{margin:0 0 2px;font:500 15px/1.3 var(--font-sans);color:var(--text-1)}
/* El selector de tipo va ARRIBA del dropzone: primero se elige qué se sube, después se suelta.
   Debajo quedaba después de la acción que condiciona, que es el orden inverso al del uso. */
.dropzone-cat{margin-bottom:10px}

/* Grillas separadas por tipo (item 6 del feedback del 2026-07-29): una sola grilla mezclada
   obligaba a abrir cada miniatura para saber si el plano ya estaba cargado. */
/* Arrastrar para reordenar (0428) + deshacer el borrado (0429) */
.manage-img{cursor:grab}
.manage-img.is-dragging{opacity:.4;cursor:grabbing}
.manage-img.is-deleting img,.manage-img.is-deleting .manage-actions,
.manage-img.is-deleting .manage-cover-tag{opacity:.15;pointer-events:none}
.photo-undo{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:6px;background:rgba(var(--overlay-rgb), .82);color:var(--fixed-white);
  font:500 12px/1.3 var(--font-sans);text-align:center;padding:6px;border-radius:8px}
.photo-undo-btn{border:1px solid rgba(255,255,255,.5);background:transparent;color:var(--fixed-white);
  border-radius:999px;padding:4px 12px;font:500 12px/1 var(--font-sans);cursor:pointer}
.photo-undo-btn:hover{background:rgba(255,255,255,.15)}
.photo-undo-btn:focus-visible{outline:2px solid var(--fixed-white);outline-offset:2px}

/* Editar una nota en la línea de tiempo (0617) */
.pf-note-edit{margin-top:6px}
.pf-note-edit textarea{width:100%;font:inherit}
.pf-note-edit-actions{display:flex;gap:6px;margin-top:6px}
.pf-note-actions{display:flex;gap:4px;align-items:center;margin-top:2px}
.pf-owner-link{margin:-4px 0 10px;font-size:13px}


.photo-groups{display:flex;flex-direction:column;gap:14px;margin-top:12px}
.photo-groups:empty{display:none}
.photo-group[hidden]{display:none}
.photo-group-title{margin:0 0 7px;font:500 12px/1 var(--font-sans);color:var(--text-3);
  text-transform:uppercase;letter-spacing:.04em}

.photo-upload-list{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.photo-upload-row{display:flex;align-items:center;gap:10px;padding:8px 10px;
  border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface)}
.photo-upload-row.is-error{border-color:var(--err)}
.photo-upload-thumb{flex:none;width:54px;height:54px;border-radius:6px;object-fit:cover;background:var(--surface-2)}
.photo-upload-body{flex:1;min-width:0}
.photo-upload-name{font-size:13px;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.photo-upload-progress{height:4px;border-radius:2px;background:var(--surface-2);margin-top:6px;overflow:hidden}
.photo-upload-progress-bar{height:100%;background:var(--accent);width:0%;transition:width .2s ease}
.photo-upload-error{font-size:12px;color:var(--err-strong);margin-top:3px}
.photo-upload-dismiss{flex:none;border:0;background:transparent;color:var(--text-3);cursor:pointer;
  font-size:15px;line-height:1;padding:4px 6px;border-radius:var(--radius-md)}
.photo-upload-dismiss:hover{color:var(--text-1);background:var(--surface-2)}


/* ── Notas y solicitudes en la propiedad ── */
.note-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}
.note-del{margin-left:auto}

/* ── Página pública de propiedad ── */
body.pub{background:var(--surface-page)}
.pub-wrap{max-width:760px;margin:0 auto;padding:20px}
.pub-foot{text-align:center;padding:24px;color:var(--text-3);font-size:13px}

/* ── Banner de impersonación ── */
.imp-banner{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;background:var(--warn-soft);color:var(--warn-strong);padding:8px 16px;font-size:13px;font-weight:500;border-bottom:1px solid var(--warn)}
/* Sin `opacity:.8`: --warn-strong sobre --warn-soft ya está calculado para pasar AA, pero atenuarlo
   al 80% lo bajaba a 3,33:1. El peso normal (400) frente al 500 del resto de la franja alcanza para
   marcar que es la aclaración y no el mensaje — y es la franja que avisa "estás tocando datos
   reales de un cliente", justo la que no conviene que se lea a medias. */
.imp-banner .muted{color:inherit;font-weight:400}

/* ── Bottom-nav mobile (PWA) ── */
.bottom-nav{display:none}
@media (max-width:640px){
  .bottom-nav{display:flex;position:fixed;bottom:0;left:0;right:0;z-index:50;
    background:var(--surface);border-top:1px solid var(--border-default);
    padding:6px 4px calc(6px + env(safe-area-inset-bottom)) 4px;justify-content:space-around;align-items:center}
  /* `min-width:0`: con `flex:1` pero sin esto, cada ítem se planta en el ancho de su etiqueta
     ("Propiedades" es la más larga) y la barra entera se pasa de la pantalla. Se ve con el texto en
     A++ a 360px. La etiqueta se recorta con puntos suspensivos antes que romper el layout — el
     ícono, que es lo que de verdad guía, se sigue viendo entero. */
  .bn-item{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:2px;padding:4px;border:0;background:none;
    color:var(--text-3);font:500 12px var(--font-sans);cursor:pointer;text-decoration:none}
  .bn-item.on{color:var(--accent-soft-fg)}
  .bn-item span{white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
  .bn-add{flex:none;width:48px;height:48px;border-radius:999px;background:var(--accent);color:var(--accent-fg);
    display:flex;align-items:center;justify-content:center;text-decoration:none;margin-top:-16px;box-shadow:0 4px 12px rgba(34,211,166,.4)}
  .main .content{padding-bottom:76px}
  /* Nueva propiedad (parte H): a este ancho el bottom-nav YA está fijo al piso — la barra
     inferior del form (también fija desde 860px) tiene que apilarse arriba de él, no pisarlo. */
  .prop-bottombar{bottom:76px}
}

/* ── Estado de carga en botones ── */
.btn.is-loading{pointer-events:none}
.btn.is-loading::before{content:"";width:14px;height:14px;margin-right:6px;border:2px solid currentColor;
  border-top-color:transparent;border-radius:50%;display:inline-block;vertical-align:-2px;animation:btnspin .6s linear infinite}
@keyframes btnspin{to{transform:rotate(360deg)}}

/* ── Checklist de contraseña en vivo (refuerzo positivo) ── */
.pw-check{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-direction:column;gap:5px}
.pw-check li{font-size:12px;color:var(--text-3);display:flex;align-items:center;gap:8px;transition:color .15s}
.pw-check li::before{content:"";width:15px;height:15px;border-radius:50%;border:1.5px solid var(--border-strong);flex:none;transition:background .15s,border-color .15s}
.pw-check li.ok{color:var(--ok-strong)}
.pw-check li.ok::before{background:var(--ok);border-color:var(--ok)}

/* ── Paletazo de comandos (⌘K) ─────────────────────────────────────────── */
.cmdk-trigger{display:inline-flex;align-items:center;gap:8px;height:44px;min-width:44px;justify-content:center;padding:0 9px 0 11px;margin-right:4px;
  background:var(--surface-2);border:1px solid var(--border-default);border-radius:var(--radius-md);
  color:var(--text-3);font:inherit;font-size:13px;line-height:1;cursor:pointer;max-width:230px;
  transition:border-color .15s,color .15s}
.cmdk-trigger:hover{border-color:var(--border-strong);color:var(--text-2)}
.cmdk-trigger>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdk-kbd{margin-left:auto;font:500 12px var(--font-sans);color:var(--text-3);background:var(--surface);
  border:1px solid var(--border-default);border-radius:6px;padding:2px 6px;white-space:nowrap}
@media (max-width:640px){
  .cmdk-trigger{max-width:none;width:36px;justify-content:center;padding:0}
  .cmdk-trigger>span,.cmdk-kbd{display:none}
}

.cmdk{position:fixed;inset:0;z-index:120;display:none;align-items:flex-start;justify-content:center;
  padding:14vh 16px 16px;background:rgba(var(--overlay-rgb), .5);backdrop-filter:blur(2px)}
.cmdk.on{display:flex}
html.cmdk-open{overflow:hidden}
.cmdk-panel{width:100%;max-width:560px;background:var(--surface);border:1px solid var(--border-default);
  border-radius:var(--radius-lg);box-shadow:0 24px 60px rgba(0,0,0,.35);overflow:hidden;animation:cmdkin .13s ease-out}
@keyframes cmdkin{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.cmdk-head{display:flex;align-items:center;gap:10px;padding:12px 14px;border-bottom:1px solid var(--border-default)}
.cmdk-ico{color:var(--text-3);flex:none}
.cmdk-input{flex:1;min-width:0;border:0;background:transparent;color:var(--text-1);font:inherit;font-size:15px;outline:none}
.cmdk-input::placeholder{color:var(--text-3)}
.cmdk-esc{flex:none;font:500 12px var(--font-sans);color:var(--text-3);border:1px solid var(--border-default);border-radius:6px;padding:2px 6px}
.cmdk-list{max-height:52vh;overflow-y:auto;padding:6px}
.cmdk-group{font:500 12px var(--font-sans);letter-spacing:.04em;text-transform:uppercase;color:var(--text-3);padding:10px 10px 4px;margin:0}
.cmdk-item{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:var(--radius-md);text-decoration:none;color:var(--text-1)}
.cmdk-item.active{background:var(--accent-soft)}
.cmdk-item.active .cmdk-t{color:var(--accent-soft-fg)}
.cmdk-item-body{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}
.cmdk-t{font-size:14px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdk-s{font-size:12px;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdk-badge{flex:none;font-size:12px;font-weight:500;color:var(--text-2);background:var(--surface-2);border-radius:999px;padding:2px 9px}
.cmdk-meta{flex:none;font-size:12px;color:var(--text-3);white-space:nowrap}
.cmdk-empty{padding:26px 10px;text-align:center;color:var(--text-3);font-size:13px;margin:0}
/* 0826 — aviso de "corregimos lo que escribiste". Va con el acento del warning (no del error): no
   pasó nada malo, solo hay que notarlo antes de leer los resultados. */
.cmdk-corrected{margin:0;padding:8px 10px;font-size:12px;color:var(--text-2);background:var(--warn-soft);border-radius:8px}
.search-corrected{display:flex;gap:10px;align-items:baseline;flex-wrap:wrap}
.search-corrected a{white-space:nowrap}

/* ── Etiqueta de grupo (fichas, barrios) — igual que <label> ── */
.field-label{display:block;font-weight:500;margin:0 0 6px}

/* ── Fichas / radio-cards (Operación, Tipo): mismo tamaño SIEMPRE, grilla responsive ── */
.tile-group{display:grid;grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px}
.tile{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;
  text-align:center;height:74px;padding:8px 6px;border:1.5px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);color:var(--text-2);cursor:pointer;font:inherit;
  transition:border-color .12s,background .12s,color .12s;-webkit-tap-highlight-color:transparent}
.tile:hover{border-color:var(--border-strong);color:var(--text-1)}
/* La ficha elegible se lee como un botón: borde propio y una sombra mínima que la despega del
   fondo. Sin esto, sobre una superficie blanca con borde gris claro, no se distingue de un cartel. */
.tile:not(.is-off){border-color:var(--border-strong);box-shadow:0 1px 2px rgba(0,0,0,.05)}
.tile:not(.is-off):hover{border-color:var(--accent);background:var(--surface-2)}
.tile input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.tile-ico{display:flex;align-items:center;justify-content:center;color:var(--text-3);transition:color .12s}
.tile-ico svg{width:20px;height:20px}
.tile-ico img{display:block;width:20px;height:20px;object-fit:contain}   /* logos reales (Buscador): a color, sin tinte */
.tile-label{font-size:12px;font-weight:500;line-height:1.15;max-width:100%;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tile-hint{font-size:12px;color:var(--text-3)}
/* El look de "seleccionada" sale SOLO de :has(input:checked) — nunca de una clase estática (2026-07-28:
   una clase "on" escrita al renderizar se quedaba pegada para siempre si el agente clickeaba otra
   ficha con un click nativo, dejando dos fichas "prendidas" a la vez). */
.tile:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--accent-soft-fg)}
.tile:has(input:checked) .tile-ico{color:var(--accent-soft-fg)}
.tile:has(input:focus-visible),.tile-toggle:focus-visible{outline:2px solid var(--ring);outline-offset:1px}
.tile:has(input:checked)::after{content:"✓";position:absolute;top:4px;right:5px;width:14px;height:14px;border-radius:50%;
  background:var(--accent);color:var(--accent-fg);font-size:12px;line-height:14px;text-align:center;font-weight:500}
/* Ficha "+" fija (nunca cambia): despliega las opciones extra en la misma grilla, en fila
   nueva; la selección se ve en su propia ficha. El + rota a × mientras está abierta. */
.tile-ico-plus svg{transition:transform .15s}
.tile-group.more-open .tile-ico-plus svg{transform:rotate(45deg)}
.tile-more{display:contents}
.tile-more .tile:first-child{grid-column-start:1}   /* las opciones del "+" arrancan en fila nueva (abajo) */
.tile-group.js-collapsible:not(.more-open) .tile-more{display:none}

/* ── Comodidades adicionales (2026-07-27): subtítulo por grupo dentro de la sección ── */
.form-subheading{margin:18px 0 8px;font-size:13px;font-weight:500;color:var(--text-2)}
.form-subheading:first-of-type{margin-top:4px}

/* ── Subtipo de propiedad (2026-07-28): recuadro insertado dentro de Tipo — el fondo propio y el
   rótulo "pill" (en vez de un field-label genérico) comunican que es parte de Tipo, no una sección
   nueva del formulario (opción elegida tras revisar mockups). ── */
.subtype-inset{margin-top:14px;background:var(--surface-2);border:1px solid var(--border-default);
  border-radius:var(--radius-md);padding:12px 14px 14px}
.subtype-pill{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;
  color:var(--accent-soft-fg);background:var(--accent-soft);padding:4px 10px;border-radius:var(--radius-pill);
  margin-bottom:10px}
.subtype-pill svg{width:13px;height:13px}

/* ── Orientación (REVISION-UX-3 punto 6, 2026-07-29): un solo control circular — reemplaza la
   cruz de 8 fichas de 54px + rosa de los vientos gigante detrás (parecían 8 controles separados
   en vez de uno). La rosa ahora es parte visual del propio dial (no un adorno detrás de fichas
   ajenas), con los 8 sectores clickeables superpuestos y el valor elegido en el centro. ── */
.compass-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:6px}
.compass-head .field-label{margin:0}
.compass-head .hint{margin:0}
.orientation-dial{position:relative;width:180px;height:180px;margin:4px auto 0}
.orientation-dial-rose{position:absolute;inset:14px;color:var(--border-strong);pointer-events:none;z-index:0}
.orientation-dial-rose svg{width:100%;height:100%}
/* Centro: el valor elegido en texto, o "Sin elegir" — actualizado en vivo por bindOrientationDial (app.js). */
.orientation-dial-center{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:2;
  width:64px;height:64px;border-radius:50%;background:var(--surface);border:1px solid var(--border-default);
  display:flex;align-items:center;justify-content:center;text-align:center;padding:4px;
  font:500 12px/1.2 var(--font-sans);color:var(--text-2);pointer-events:none}
/* 8 sectores clickeables — posiciones fijas (siempre son los mismos 8 puntos cardinales, no hace
   falta calcularlas): objetivo táctil de 44px (WCAG), sobre la rosa (z-index:1). */
.orientation-pt{position:absolute;z-index:1;width:44px;height:44px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;transform:translate(-50%,-50%);
  cursor:pointer;background:transparent;border:1.5px solid transparent;
  transition:border-color .12s,background .12s}
.orientation-pt input{position:absolute;opacity:0;width:1px;height:1px;pointer-events:none}
.orientation-pt span{font:500 12px/1 var(--font-sans);color:var(--text-3)}
.orientation-pt:hover{background:var(--surface-2)}
.orientation-pt:has(input:checked){background:var(--accent-soft);border-color:var(--accent)}
.orientation-pt:has(input:checked) span{color:var(--accent-soft-fg)}
.orientation-pt:has(input:focus-visible){outline:2px solid var(--ring);outline-offset:1px}
.orientation-pt-n{top:2%;left:50%}
.orientation-pt-ne{top:14.6%;left:85.4%}
.orientation-pt-e{top:50%;left:98%}
.orientation-pt-se{top:85.4%;left:85.4%}
.orientation-pt-s{top:98%;left:50%}
.orientation-pt-so{top:85.4%;left:14.6%}
.orientation-pt-o{top:50%;left:2%}
.orientation-pt-no{top:14.6%;left:14.6%}

/* ── Precio por operación: nombre + moneda + importe + estado propio por fila ── */
.op-price-list{display:flex;flex-direction:column;gap:10px}
.op-price-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.op-price-row[hidden]{display:none}
.op-price-name{flex:0 0 88px;font-weight:500;font-size:13px}
.op-price-row .money-field{flex:1;min-width:150px;max-width:260px}

/* ── Importe: chips de moneda (izquierda, reusa .seg/.seg-opt) + valor con formato (derecha) ── */
.amount-row{display:flex;gap:8px;align-items:stretch}
.amount-row .seg{flex:none;align-items:center}
.amount-row .money-field{flex:1;min-width:0}
.money-field{display:flex;align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);transition:border-color .12s}
.money-field:focus-within{outline:2px solid var(--ring);outline-offset:0;border-color:var(--accent)}


.money-field .money-input{border:0;background:transparent;flex:1;min-width:0;font-variant-numeric:tabular-nums}
.money-field .money-input:focus-visible{outline:none;border:0}

/* ── Campo teléfono estándar: selector de país + número nacional (phone_field / lib/phone.php) ── */
/* Los dos controles ya son `.input` (borde/alto/radio propios); acá solo el layout: país a la
   izquierda con ancho acotado, número ocupando el resto. En pantallas angostas se apilan. */
.phone-field{display:flex;gap:8px;align-items:stretch}
.phone-field .phone-cc{flex:0 0 auto;width:auto;max-width:48%;padding-right:8px}
.phone-field .phone-input{flex:1;min-width:0;font-variant-numeric:tabular-nums}
@media (max-width:520px){.phone-field{flex-wrap:wrap}.phone-field .phone-cc{max-width:none;width:100%}}

/* ── Stepper −/+ (ambientes, dormitorios, baños, cocheras, superficies, antigüedad) ── */
.stepper{display:flex;align-items:stretch;max-width:190px}
.stepper-btn{flex:none;width:44px;border:1px solid var(--border-default);background:var(--surface-2);color:var(--text-1);
  font-size:20px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;user-select:none;
  transition:background .12s}
.stepper-btn:first-child{border-radius:var(--radius-md) 0 0 var(--radius-md);border-right:0}
.stepper-btn:last-child{border-radius:0 var(--radius-md) var(--radius-md) 0;border-left:0}
.stepper-btn:hover{background:var(--border-default)}
.stepper-btn:disabled{opacity:.4;cursor:not-allowed;background:var(--surface-2)}
.stepper-mid{flex:1;min-width:0;display:flex;align-items:center;border-top:1px solid var(--border-default);border-bottom:1px solid var(--border-default);background:var(--surface)}
.stepper-mid:focus-within{border-color:var(--accent)}
.stepper .stepper-input{border:0;border-radius:0;background:transparent;text-align:center;flex:1;min-width:0;font-variant-numeric:tabular-nums;-moz-appearance:textfield}
.stepper .stepper-input:focus-visible{outline:none}
.stepper .stepper-input::-webkit-outer-spin-button,.stepper .stepper-input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.stepper-unit{flex:none;padding:0 9px 0 2px;color:var(--text-3);font-size:12px;white-space:nowrap;font-variant-numeric:tabular-nums}

/* ── Precio + expensas lado a lado ── */
.price-cols{display:grid;grid-template-columns:1fr 1fr;gap:6px 24px}
@media (max-width:640px){.price-cols{grid-template-columns:1fr;gap:6px}}

/* Chip de moneda: mismo look neutro que Precio por operación (2026-07-28) — antes tenía acento
   propio acá y quedaban desparejos entre las dos secciones; ahora comparten el .seg-opt base. */

/* ── Campo con unidad fija a la derecha (m²) ── */
.unit-field{display:flex;align-items:center;border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);transition:border-color .12s}
.unit-field:focus-within{outline:2px solid var(--ring);outline-offset:0;border-color:var(--accent)}
.unit-field .unit-input{border:0;background:transparent;flex:1;min-width:0;font-variant-numeric:tabular-nums}
.unit-field .unit-input:focus-visible{outline:none;border:0}
.unit-suf{flex:none;padding:0 12px 0 4px;color:var(--text-3);font-weight:500;font-variant-numeric:tabular-nums}
/* Ícono A LA IZQUIERDA DEL TÍTULO (2026-07-28, corregido — antes vivía adentro del input): Superficie
   cubierta/semicubierta usan el ícono del Tipo elegido (se actualiza en vivo, ver bindUnitTypeIcon
   en app.js), Frente/Largo del terreno usan el ícono de Lote (fijo, no cambia con el Tipo). */
.field-label-row{display:flex;align-items:center;gap:6px}
.field-label-ico{display:inline-flex;flex:none;color:var(--text-3)}
.field-label-ico svg{width:16px;height:16px}

/* ── Superficie total: valor calculado, sin caja de input (Cubierta + Semicubierta) ── */
.sqm-total-stat{height:38px;display:flex;align-items:center;gap:8px;font-size:20px;font-weight:500;
  color:var(--text-1);font-variant-numeric:tabular-nums}
.sqm-total-ico{display:flex;flex:none;color:var(--text-3)}
.sqm-total-ico svg{width:20px;height:20px}
[data-land-section][hidden]{display:none}   /* .form-grid-3 es display:grid; sin esto el [hidden] no oculta */

/* Piso / N° de lote (REVISION-UX-3 punto 8) — según el Tipo, como mucho UNO de los dos se
   muestra nunca: el panel visible ocupa el ancho completo en vez de dejar la mitad de la fila
   vacía (antes quedaba un hueco al lado, sin ningún campo). */
[data-type-panel][hidden]{display:none}
.form-grid-2-solo:has([data-type-panel][hidden]) > [data-type-panel]:not([hidden]){grid-column:1 / -1}

/* ── Visualizador de conteo: un íconito por unidad (ambientes/dorm./baños/cocheras) ── */
.count-viz{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin-top:7px;min-height:18px}
/* Un solo lenguaje visual (REVISION-UX-3 punto 3): TODOS los íconos del viz — antes .cv-i (línea,
   accent) y .cv-obj (relleno, text-2) eran dos tratamientos distintos; ahora un solo selector. */
.count-viz .cv-i{width:16px;height:16px;color:var(--accent);flex:none}
.count-viz .cv-more{font-size:14px;font-weight:500;color:var(--text-3);line-height:1;padding-left:2px}

/* Separador liviano dentro de una .step-card entre dos subgrupos de campos (ej. "Cómo es":
   superficies/ambientes arriba, Comodidades abajo). */
.step-card-divider{height:1px;background:var(--border-default);margin:16px 0 14px}


/* ── Barra inferior pegajosa del form de propiedad (parte D, reemplaza .form-actions) ── */
/* Negative margin = bleed hasta el borde de .content (padding:22px 20px, style.css arriba) —
   así la barra queda pegada al piso del viewport en vez de flotar adentro del padding. */
.prop-bottombar{position:sticky;bottom:0;z-index:12;background:var(--surface);
  border-top:1px solid var(--border-default);box-shadow:0 -8px 24px -20px rgba(15,23,42,.5);
  margin:18px -20px -22px}
.prop-bottombar-inner{max-width:1060px;margin:0 auto;padding:8px 14px;display:flex;align-items:flex-end;gap:10px;flex-wrap:wrap}
/* REVISION-UX-4 punto B6 (2026-07-29): entre 861 y 1000px (todavía en modo "desktop" — el sticky
   de acá abajo, no el fixed de <860px) la línea de faltantes salta de renglón y la barra sticky
   crece a dos filas, corriendo todo el contenido de arriba cada vez que cambia de 1 a 2 líneas.
   Altura mínima fija para las dos filas: nunca más "salto" de layout en ese rango angosto. */
@media (min-width:861px) and (max-width:1000px){.prop-bottombar-inner{min-height:64px}}
.prop-bottombar-missing{display:flex;align-items:center;gap:9px;min-width:0;height:38px}
.prop-bottombar-dot{width:9px;height:9px;border-radius:50%;background:var(--warn);flex:none}
.prop-bottombar-dot.is-ok{background:var(--ok)}
.prop-bottombar-missing-label{font:500 13px/1.4 var(--font-sans)}
.prop-bottombar-missing-text{font:400 13px/1.4 var(--font-sans);color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.prop-bottombar-spacer{flex:1}
/* Botón invertido (fondo oscuro + texto acento). Solo funciona en tema CLARO: `--text-1` cambia
   con el tema, así que en oscuro quedaba teal sobre casi blanco (1,75:1) — el botón más
   importante del formulario era el menos legible de la barra. En oscuro se usa el par estándar
   del design system (fondo acento + `--accent-fg`), que el propio set de tokens garantiza AA. */
.btn-activate{background:var(--text-1);color:var(--accent);border-color:transparent;display:inline-flex;align-items:center;gap:8px}
.btn-activate:hover{background:var(--text-1);opacity:.9}
html.dark .btn-activate,html.dark .btn-activate:hover{background:var(--accent);color:var(--accent-fg)}
.prop-bottombar-count{font:500 12px/1 var(--font-sans);background:rgba(34,211,166,.18);padding:3px 6px;border-radius:5px}

/* ── REVISION-UX-6: riel conectado (botón Publicar → cable → tile por destino) ──────────────────
   Reemplaza a .prop-bottombar-portals/.pbp-* y a .prop-publish-status/.pps-*. Todo con tokens. */
.prop-bottombar-dot.is-publishing{animation:pbrBreathe 1s ease-in-out infinite}
@keyframes pbrBreathe{0%,100%{opacity:.5}50%{opacity:1}}

.pbb-autosave{display:inline-flex;align-items:center;height:38px;color:var(--text-3);flex:none}

.btn-icon-square{width:34px;height:34px;padding:0;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--border-default);border-radius:9px;background:transparent;color:var(--text-2);cursor:pointer;flex:none}
.btn-icon-square:hover{border-color:var(--border-strong);color:var(--text-1)}

.pbr-group{display:flex;align-items:flex-end;flex:none}
.pbr-group .btn-activate{height:38px;border-radius:var(--radius-md) 3px 3px var(--radius-md);display:inline-flex;align-items:center;gap:8px}
.pbr-stub{width:12px;height:2px;background:var(--accent);flex:none}

.pbr{display:flex;align-items:flex-end}
.pbr-seg{width:40px;position:relative;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;padding-bottom:7px}
/* El display propio le gana al [hidden] del navegador (mismo error de cascada ya visto 4 veces). */
.pbr-seg[hidden]{display:none}
.pbr-wire{position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--accent)}

.pbr-tile{width:28px;height:28px;border-radius:8px;padding:0;position:relative;display:inline-flex;
  align-items:center;justify-content:center;cursor:pointer;background:var(--surface-2);
  border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);transition:background .2s,border-color .2s,box-shadow .2s}
.pbr-tile img,.pbr-tile .logo-mark{width:19px;height:19px;object-fit:contain;transition:filter .2s,opacity .2s}
.pbr-tile[aria-disabled="true"]{cursor:default}

/* Objetivo tactil (H-28) en pantallas tactiles para lo que quedaba corto: tiles del riel de
   marketplaces, th ordenables (son operables: bindSortableTables), checkboxes de dependencias y
   chips de bloqueo en /screens. En desktop conservan su densidad.

   ANTES ESTE BLOQUE PREGUNTABA POR EL ANCHO (`max-width:860px`) Y ESO ESTABA MAL. El comentario
   siempre dijo "en pantallas tactiles", pero la consulta medía otra cosa: el iPad horizontal mide
   1024px de ancho Y se toca con el dedo, así que se caía de todas estas reglas y volvía a la
   densidad de escritorio justo donde no hay mouse. La matriz completa de tests/ux lo puso en
   números: de los 263 avisos de área táctil, 213 salían SOLO del ancho tablet-1024 — o sea que
   cuatro de cada cinco no eran controles sin arreglar, eran controles que el CSS ya creía
   arreglados y que ese ancho no alcanzaba. `pointer:coarse` pregunta por el dispositivo de
   entrada, que es lo que importa acá, y es lo que ya usaban home.css / global.css / filters.css
   (la cara pública, escrita después). Un teléfono angosto sigue entrando: también es puntero
   grueso. El precio es que un escritorio con el navegador angosto vuelve a la densidad compacta
   —correcta ahí, porque hay mouse—, y eso es justamente lo que se quería. */
@media (pointer:coarse){
  .pbr-tile{width:44px;height:44px}
  table.data th{padding-top:13px;padding-bottom:13px}
  .chip-check{min-height:44px}
  table.data .cell-check{min-width:44px;min-height:44px}
  /* Labels de linea que envuelven un radio/checkbox (ej. el alcance del bloqueo en /screens):
     el area que se toca es el label — que llegue al minimo tactil. */
  label.fw-400:has(> input[type="radio"]),label.fw-400:has(> input[type="checkbox"]){
    display:inline-flex;align-items:center;gap:6px;min-height:44px}
}
.pbr-tile:focus-visible{outline:2px solid var(--ring);outline-offset:2px}

.pbr-tile.is-off{background:transparent;border:1px dashed var(--border-strong)}
.pbr-tile.is-off img,.pbr-tile.is-off .logo-mark{filter:grayscale(1);opacity:.35}
.pbr-seg.is-off .pbr-wire{background:repeating-linear-gradient(90deg,var(--border-strong) 0 3px,transparent 3px 7px)}

.pbr-tile.is-wait{border-color:color-mix(in srgb,var(--accent) 20%,transparent)}
.pbr-tile.is-wait img{opacity:.45}
.pbr-seg.is-wait .pbr-wire{background:color-mix(in srgb,var(--accent) 22%,transparent)}

.pbr-tile.is-load{border-color:color-mix(in srgb,var(--accent) 90%,transparent);overflow:hidden}
/* Cable "en corriente": guiones de acento sobre huecos transparentes que se mueven. background-color
   transparente a propósito — si queda el accent sólido de base, los huecos no se ven y parece fijo. */
.pbr-seg.is-load .pbr-wire{background-color:transparent;
    background-image:linear-gradient(90deg,var(--accent) 0 7px,transparent 7px 14px);
    background-size:14px 2px;background-repeat:repeat-x;animation:pbrWire .5s linear infinite}

.pbr-tile.is-done{background:var(--ok-soft);border-color:var(--ok)}
.pbr-tile.is-err{background:var(--err-soft);border-color:var(--err)}
.pbr-seg.is-err .pbr-wire{background:linear-gradient(90deg,var(--accent),var(--err))}

/* Barrido de luz mientras publica (§4): una banda de acento cruza el tile, nada gira. */
.pbr-sweep{position:absolute;top:0;bottom:0;left:0;width:55%;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--accent) 50%,transparent),transparent);
  animation:pbrSweep .95s linear infinite}

.pbr-badge{position:absolute;right:-6px;bottom:-6px;width:16px;height:16px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;border:2px solid var(--surface);
  animation:pbrPop .3s cubic-bezier(.2,.9,.3,1.4) both}
.pbr-badge.is-done{background:var(--ok);color:var(--ok-soft)}
.pbr-badge.is-err{background:var(--err);color:var(--err-soft)}
.pbr-badge svg{width:10px;height:10px}

.pbr-btn-spin{width:14px;height:14px;border-radius:50%;flex:none;
  border:2px solid color-mix(in srgb,currentColor 25%,transparent);border-top-color:currentColor;animation:pbrSpin .7s linear infinite}
.pbr-btn-check{flex:none}
.pbr-btn-spin[hidden],.pbr-btn-check[hidden]{display:none}   /* que [hidden] gane a cualquier reset de svg/span */
/* Botón en fase "Publicado": verde suave, mismo par de tokens que el resto de los estados ok. */
.btn-activate.is-done{background:var(--ok-soft);color:var(--ok-strong);border-color:var(--ok)}
html.dark .btn-activate.is-done,html.dark .btn-activate.is-done:hover{background:var(--ok-soft);color:var(--ok-strong)}

@keyframes pbrSweep{from{transform:translateX(-110%)}to{transform:translateX(220%)}}
@keyframes pbrWire{from{background-position:0 0}to{background-position:14px 0}}
@keyframes pbrPop{0%{transform:scale(.3);opacity:0}60%{transform:scale(1.18)}100%{transform:scale(1);opacity:1}}
@keyframes pbrSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .prop-bottombar-dot.is-publishing,.pbr-seg.is-load .pbr-wire,.pbr-sweep,.pbr-badge,.pbr-btn-spin{animation:none}
  .pbr-sweep{display:none}
  .pbr-tile.is-load{border-color:color-mix(in srgb,var(--accent) 90%,transparent)}
  .pbr-seg.is-load .pbr-wire{background:color-mix(in srgb,var(--accent) 40%,transparent)}
}

/* Parte H (mobile <860px): la barra queda fija al viewport (no sticky-en-flujo) y el botón
   principal ("Revisar y activar") pasa a ocupar el ancho disponible en su propia fila.
   El indicador de autoguardado se oculta acá — ya vive también en .prop-journey (arriba),
   no se pierde información, solo el duplicado visual. .prop-form suma padding-bottom para que
   el último card nunca quede tapado por la barra fija (ver reserva exacta más abajo). */
@media (max-width:860px){
  /* REVISION-UX-4 punto B5 — tablet vertical (768px): a este ancho el form ocupaba el 100% de la
     pantalla sin límite (1060px nunca restringe por debajo de 860 igual), y las líneas de texto
     pasaban los ~90 caracteres cómodos de leer. max-width:720px, igual que cualquier columna de
     lectura — el resto de la maqueta (grillas de 1 columna, etc.) ya está resuelto arriba. */
  .prop-form{padding-bottom:190px;max-width:720px}
  /* Teclado virtual: con el foco en un campo de la mitad inferior, el navegador auto-scrollea
     el input a la vista pero no sabe que la barra inferior FIJA tapa esa franja — scroll-margin
     le reserva el mismo alto que ya reserva .prop-form (barra + safe-area) para que el campo
     quede realmente visible arriba de la barra, no tapado por ella. */
  .prop-form input,.prop-form select,.prop-form textarea{scroll-margin-bottom:100px}
  .prop-bottombar{position:fixed;left:0;right:0;bottom:0;margin:0;z-index:16;
    box-shadow:0 -8px 24px -14px rgba(15,23,42,.4);
    padding-bottom:env(safe-area-inset-bottom,0)}
  /* Dos filas — fila 1: punto + estado; fila 2: Cancelar · Borrador · [Publicar flex] · riel.
     Tiles a 34×34 con área táctil de 44px (§7). El autoguardado se oculta (vive también arriba). */
  .prop-bottombar-inner{max-width:none;padding:10px 14px;gap:8px}
  .prop-bottombar-missing{flex:1 1 100%;order:1;height:auto}
  .prop-bottombar-spacer,.pbb-autosave{display:none}
  .prop-bottombar .btn-ghost{order:2;flex:none;min-height:44px;display:inline-flex;align-items:center;padding:0 16px}
  .prop-bottombar .btn-icon-square{order:3;width:44px;height:44px}
  .prop-bottombar .pbr-group{order:4;flex:1 1 auto}
  .prop-bottombar .pbr-group .btn-activate{flex:1;justify-content:center;min-height:44px}
  .prop-bottombar .pbr-seg{width:44px;padding-bottom:9px}
  .prop-bottombar .pbr-tile{width:44px;height:44px}
  .prop-bottombar .pbr-tile img,.prop-bottombar .pbr-tile .logo-mark{width:22px;height:22px}
}

/* ── Switch compacto en línea (Sin expensas, Apto crédito): reusa .switch/.slider ── */
.switch-inline{display:inline-flex;align-items:center;gap:9px;cursor:pointer;font-size:13px;font-weight:500;height:38px}
.expenses-switch-wrap{display:flex;flex-direction:column;justify-content:center}

/* ── "Alquilada" + "Fin de contrato" (REVISION-UX-3 punto 7) — el switch y su campo van en su
   propia columna (no una grilla de 2 con un field-label vacío de relleno); "Fin de contrato" se
   revela recién al marcar "Alquilada", nunca queda como un campo gris sin contexto. ── */
.rented-field{margin-bottom:14px}
.rented-reveal{margin-top:10px;max-width:260px;animation:rentedReveal .15s ease}
.rented-reveal[hidden]{display:none}
@keyframes rentedReveal{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.rented-reveal{animation:none}}

/* ── Antigüedad: stepper + "A estrenar"/"En construcción" a la derecha (2026-07-28: se sacó la
   animación de la persona que envejece — no quedaba bien). ── */
.age-row{display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.age-row .stepper{flex:0 0 auto;width:150px}
.age-row .stepper-input:disabled{color:var(--text-3);cursor:not-allowed}
.age-switches{display:flex;gap:8px;flex-wrap:wrap}
.age-switch-btn{flex:none;min-height:44px;padding:0 13px;border:1.5px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);color:var(--text-2);font:500 13px var(--font-sans);cursor:pointer;
  transition:border-color .12s,background .12s,color .12s}
.age-switch-btn:hover{border-color:var(--border-strong);color:var(--text-1)}
.age-switch-btn.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-soft-fg)}
.age-switch-btn:focus-visible{outline:2px solid var(--ring);outline-offset:1px}

/* ── Estado: cabecera con ficha de color ── */
.st-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}
/* `--st-c` es la identidad del estado (tinte de fondo, borde y el punto del menú) y `--st-fg`, el
   texto. Son dos tokens porque el texto en el mismo color que su propio tinte no llega a AA nunca:
   son el mismo tono. Ver el bloque de tokens para los ratios medidos. */
.st-badge{--st-c:var(--st-default);--st-fg:var(--st-fg-default);
  display:inline-flex;align-items:center;font-size:12px;font-weight:500;padding:4px 12px;border-radius:999px;
  background:color-mix(in srgb,var(--st-c) 15%,transparent);color:var(--st-fg);border:1px solid color-mix(in srgb,var(--st-c) 32%,transparent)}
/* Los cinco estados de mt_44. El color codifica el EJE de visibilidad: verde = se publica,
   acento = interna (se comparte pero no se publica), gris claro = suspendida, gris oscuro =
   borrador y baja. Los tokens --st-* siguen siendo los mismos (colores fijos, independientes del
   tema, ver el bloque de tokens): lo que cambió son las claves que los usan. */
.st-badge[data-st="borrador"],.st-dot[data-st="borrador"]{--st-c:var(--st-borrador);--st-fg:var(--st-fg-borrador)}      /* gris oscuro */
.st-badge[data-st="publicada"],.st-dot[data-st="publicada"]{--st-c:var(--st-activo);--st-fg:var(--st-fg-activo)}        /* verde */
.st-badge[data-st="interna"],.st-dot[data-st="interna"]{--st-c:var(--info);--st-fg:var(--info-strong)}                   /* celeste */
/* Suspendida NO usa --warn-strong de texto en claro: #935F06 sobre el tinte ámbar al 15% da 4,8:1
   sobre blanco pero 4,4:1 sobre surface-2 (la tabla de /parametrizaciones) — justo abajo del AA.
   Un escalón más oscuro pasa en las dos superficies (5,9/6,4:1). En oscuro --warn-strong sí pasa. */
.st-badge[data-st="suspendida"],.st-dot[data-st="suspendida"]{--st-c:var(--warn);--st-fg:#7A4E05}                       /* ámbar */
html.dark .st-badge[data-st="suspendida"],html.dark .st-dot[data-st="suspendida"]{--st-fg:var(--warn-strong)}
.st-badge[data-st="baja"],.st-dot[data-st="baja"]{--st-c:var(--err);--st-fg:var(--err-strong)}                          /* rojo (terminal) */

/* ── Diagrama del flujo de estados (/parametrizaciones > Estados de propiedad, mt_44) ──
   Las cinco cajas en fila, cada una diciendo qué implica el estado. En pantalla angosta se apilan
   solas por el wrap del flex: son cajas independientes, no una secuencia que haya que leer en orden
   (justamente el punto del modelo es que NO hay un camino único). */
.st-flow{display:flex;flex-wrap:wrap;gap:10px}
.st-flow-box{flex:1 1 178px;min-width:0;display:flex;flex-direction:column;gap:6px;padding:12px;
  border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-2)}
.st-flow-box .st-badge{align-self:flex-start}
.st-flow-meta{font-size:12px;color:var(--text-2);line-height:1.4}
.st-flow-who{font-size:12px;color:var(--text-3);text-transform:uppercase;letter-spacing:.03em}
.st-flow-note{margin:14px 0 0;font-size:13px;color:var(--text-2);line-height:1.6}

/* Acá vivía `.table-scroll`, la clase gemela de `.table-wrap`: las dos hacían `overflow-x:auto`,
   pero esta venía sin fondo/borde y —el problema real— se usaba sin `tabindex`, así que la tabla
   se arrastraba con el dedo y NO con el teclado. Dos clases para lo mismo es de donde sale el doble
   mantenimiento: la menos usada fue la que se quedó atrás. Se unificó todo en `.table-wrap` (que ya
   cubría 42 de las 43 tablas) y esta se borró para que no haya dónde equivocarse.
   El invariante lo sostiene tests/unit/test_tablas_anchas.php. */

/* Ficha de filtro chica, para tildar opciones dentro de una celda de tabla. */
.fchip-sm{margin:0 4px 4px 0}
.fchip-sm span{font-size:12px;padding:4px 9px}

/* ── Motivo de la suspensión (mt_44): aparece solo con la operación suspendida ──
   OJO con el [hidden]: `.op-motivo{display:flex}` empata en especificidad con el `[hidden]
   {display:none}` del navegador y le gana por orden de cascada, así que el panel se vería SIEMPRE.
   Ya pasó cuatro veces en este proyecto (dropzone de fotos, paneles de Marketplaces, Tarjetas/Lista)
   — cualquier bloque que fije su propio `display` y se alterne con `hidden` necesita esta línea. */
.op-motivo{display:flex;align-items:center;gap:8px;flex:none}
.op-motivo[hidden]{display:none}
.op-motivo-lbl{font-size:12px;color:var(--text-2);white-space:nowrap}
.op-motivo-sel{width:auto;min-width:132px;padding:6px 10px;font-size:13px}

/* Los motivos, dentro del menú de estado, agrupados bajo su propio rótulo: "Suspendida" no es una
   opción suelta (exige motivo), así que se ofrece como un grupo de opciones concretas. */
.st-menu-group{border-top:1px solid var(--border-default);margin-top:4px;padding-top:4px}
.st-menu-group-lbl{display:block;padding:5px 12px 3px;font-size:12px;text-transform:uppercase;
  letter-spacing:.04em;color:var(--text-3)}

/* Aviso de baja próxima en la ficha: es un empujón, no una alarma — hasta que ya está vencida. */
.pf-baja-warn{display:inline-flex;align-items:center;font-size:12px;padding:3px 9px;border-radius:999px;
  background:var(--warn-soft);color:var(--warn-strong);white-space:nowrap}
.pf-baja-warn[data-due="1"]{background:var(--err-soft);color:var(--err-strong)}

/* Aclaración al pie de un grupo de filtros (por qué el listado no muestra las dadas de baja). */
.filters-hint{flex-basis:100%;font-size:12px;color:var(--text-3)}

/* ── Estado: el badge de la barra sticky es un desplegable para cambiarlo directo ── */
.st-menu{position:relative;flex:none}
.st-badge-btn{font-family:inherit;cursor:pointer;gap:7px;min-height:44px}
.st-badge-btn .st-caret{transition:transform .15s}
.st-badge-btn[aria-expanded="true"] .st-caret{transform:rotate(180deg)}
.st-menu-panel{min-width:185px}
.st-menu-panel .menu-item.current{background:var(--surface-2)}
.st-dot{--st-c:var(--st-default);width:10px;height:10px;border-radius:50%;background:var(--st-c);flex:none}

/* ── Indicador de autoguardado (las acciones del form viven en .prop-bottombar) ── */
.btn-success{background:var(--ok);color:var(--fixed-white);border-color:transparent}
.btn-success:hover{background:var(--ok-strong)}
.autosave-ind{font-size:12px;color:var(--text-3);min-height:16px}
.autosave-ind.saving{color:var(--text-3)}
.autosave-ind.saved{color:var(--ok-strong)}

/* ── Título espejo + ficha de estado dentro de .prop-journey (parte A del rediseño 2026-07-29;
   el contenedor .prop-sticky que las envolvía se reemplazó por .prop-journey más arriba en este
   archivo — estas dos reglas quedaron porque .prop-journey-title-row las sigue usando tal cual) ── */
.prop-sticky-title{font-size:15px;font-weight:500;color:var(--text-1);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.st-badge-lg{font-size:13px;padding:6px 15px;flex:none}

/* ── Importador de portales: progreso por tandas + resumen ── */
.import-bar{height:8px;border-radius:999px;background:var(--surface-2);overflow:hidden;margin-top:12px;position:relative}
.import-bar-fill{height:100%;background:var(--accent);border-radius:999px;transition:width .35s ease}
/* Efecto "En curso": rayas de ACENTO que se mueven sobre la PISTA (visibles aun con % en 0 —pasa al
   arrancar: ZonaProp propaga con demora—), rayas blancas sobre el relleno, y un barrido de acento.
   Se tiñe con el acento y NO con blanco: el barrido blanco sobre la pista clara (--surface-2) no se
   veía en modo CLARO (2026-08-10, a pedido de Máximo). */
.import-bar.is-running{
  background-image:linear-gradient(135deg,color-mix(in srgb,var(--accent) 30%,transparent) 25%,
    transparent 25%,transparent 50%,color-mix(in srgb,var(--accent) 30%,transparent) 50%,
    color-mix(in srgb,var(--accent) 30%,transparent) 75%,transparent 75%,transparent);
  background-size:18px 18px;animation:import-stripes .8s linear infinite}
.import-bar.is-running .import-bar-fill{
  background-image:linear-gradient(135deg,rgba(255,255,255,.4) 25%,transparent 25%,transparent 50%,
    rgba(255,255,255,.4) 50%,rgba(255,255,255,.4) 75%,transparent 75%,transparent);
  background-size:18px 18px;animation:import-stripes .8s linear infinite}
.import-bar.is-running::after{content:"";position:absolute;inset:0;border-radius:999px;
  background:linear-gradient(90deg,transparent,color-mix(in srgb,var(--accent) 45%,transparent),transparent);
  transform:translateX(-100%);animation:import-sweep 1.3s ease-in-out infinite}
@keyframes import-stripes{from{background-position:0 0}to{background-position:18px 0}}
@keyframes import-sweep{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
@media (prefers-reduced-motion:reduce){
  .import-bar.is-running,
  .import-bar.is-running .import-bar-fill{animation:none}
  .import-bar.is-running::after{animation:none;display:none}}
/* Título del preview de importación: Inmobiliaria «ORIGEN en Portal → DESTINO en Momix» */
.mkt-title{font-weight:700}
.mkt-title .mkt-src,.mkt-title .mkt-dst{font-weight:800}
.mkt-title .mkt-arrow{opacity:.65;padding:0 4px;font-weight:400}
/* Color de marca por portal en el título del preview — por data-portal (NO style inline: CSP). */
.mkt-src[data-portal="zonaprop"],.mkt-dst[data-portal="zonaprop"]{color:#00a19a}
.mkt-src[data-portal="mercadolibre"],.mkt-dst[data-portal="mercadolibre"]{color:#b89a00}
.mkt-src[data-portal="argenprop"],.mkt-dst[data-portal="argenprop"]{color:#e5384d}
.mkt-src[data-portal="metroprop"],.mkt-dst[data-portal="metroprop"]{color:#006aff}
.mkt-src[data-portal="buscadorprop"],.mkt-dst[data-portal="buscadorprop"]{color:#f7941e}

/* ── Marketplace SuperAdmin: link config + botonera de portal + orden/filtros del preview ── */
.mkt-link-hist-toggle{cursor:pointer;color:var(--accent-hover);font-size:13px;font-weight:500;list-style:none}
.mkt-link-hist-toggle::-webkit-details-marker{display:none}
.mkt-link-hist-toggle::marker{content:""}

.mkt-preview-tools{display:flex;align-items:flex-end;justify-content:space-between;gap:14px;flex-wrap:wrap}
.mkt-actions-top{flex:none}

/* ── Comodidades: selector unificado (item 5 / handoff), mapeado a tokens de Momix ── */
.amc{margin-top:8px}
.amc-bar{display:flex;flex-wrap:wrap;align-items:flex-end;gap:12px 16px;padding-bottom:14px}
.amc-bar-head{flex:1 1 240px;min-width:200px;display:flex;flex-direction:column;gap:2px}
.amc-sub{font-size:13px;color:var(--text-2)}
.amc-bar-tools{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.amc-count{display:inline-flex;align-items:center;height:34px;padding:0 13px;border-radius:999px;
  background:color-mix(in srgb,var(--accent) 14%,transparent);color:var(--text-1);font-size:13px;font-weight:600;white-space:nowrap}
.amc-clear{height:34px;padding:0 13px;border:1px solid var(--border-default);background:transparent;color:var(--text-2);
  font-size:13px;font-weight:600;border-radius:999px;cursor:pointer}
.amc-clear:hover{border-color:var(--err);color:var(--err);background:color-mix(in srgb,var(--err) 8%,transparent)}
.amc-search{height:38px;width:auto;min-width:200px;flex:1 1 200px}
.amc-seg{display:inline-flex;padding:3px;border-radius:999px;background:var(--surface-2);gap:3px}
.amc-seg-btn{height:32px;padding:0 15px;border:0;background:transparent;color:var(--text-2);font-size:13px;font-weight:600;border-radius:999px;cursor:pointer;white-space:nowrap}
.amc-seg-btn.is-on{background:var(--surface);color:var(--text-1);box-shadow:0 1px 3px rgba(0,0,0,.12)}
.amc-section{padding-top:18px}
.amc-section[hidden]{display:none}
.amc-sec-head{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}
.amc-sec-sel{font-size:12px;font-weight:600;color:var(--accent-text)}
.amc-sec-total{margin-left:auto;font-size:12px;color:var(--text-2)}
.amc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px}
.amc-chip{position:relative;display:flex;align-items:center;gap:10px;min-height:48px;padding:0 12px 0 14px;
  border:1.5px solid var(--border-default);border-radius:14px;background:var(--surface);color:var(--text-1);
  font-size:14px;cursor:pointer;transition:background .12s,border-color .12s}
.amc-chip[hidden]{display:none}
.amc-chip input{position:absolute;width:1px;height:1px;opacity:0;margin:0}
.amc-chip:hover{background:var(--surface-2);border-color:var(--accent)}
.amc-chip:has(input:checked){background:color-mix(in srgb,var(--accent) 12%,transparent);border-color:var(--accent)}
.amc-chip:has(input:focus-visible){outline:2px solid var(--accent);outline-offset:2px}
.amc-chip-ico{flex:none;width:20px;height:20px;display:inline-flex;align-items:center;justify-content:center;color:var(--text-3)}
.amc-chip-ico svg{width:20px;height:20px}
.amc-chip:has(input:checked) .amc-chip-ico{color:var(--accent)}
/* NORMA de fichas (2026-08-08): cuando el texto no entra, se recorta con "…" en UNA línea y el nombre
   completo se lee al pasar el mouse (tooltip [data-tip]). Mantiene las fichas de igual alto y prolijas. */
.amc-chip-label{flex:1;min-width:0;line-height:1.25;padding:6px 0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* No habitual para el Tipo/Operación actual (PROP_AMENITY_CONTEXT): borde punteado y un poco
   apagada. NUNCA se oculta ni se desmarca — solo baja al final de su grupo, donde el colapso
   la deja detrás de "Ver N más". Si está elegida se ve elegida igual: el dato manda. */
.amc-chip.is-unusual{border-style:dashed}
.amc-chip.is-unusual .amc-chip-ico{opacity:.55}
.amc-chip.is-unusual .amc-chip-label{color:var(--text-2)}
.amc-chip.is-unusual:has(input:checked){border-style:solid}
.amc-chip.is-unusual:has(input:checked) .amc-chip-ico,
.amc-chip.is-unusual:has(input:checked) .amc-chip-label{opacity:1;color:inherit}
.amc-chip-check{width:19px;height:19px;flex:none;color:var(--accent);opacity:0;transition:opacity .12s}
.amc-chip:has(input:checked) .amc-chip-check{opacity:1}
.amc-more{margin-top:12px;height:38px;padding:0 15px;border:1px solid var(--border-default);background:transparent;
  color:var(--accent-text);font-size:14px;font-weight:600;border-radius:12px;cursor:pointer}
.amc-more[hidden]{display:none}
.amc-more:hover{background:var(--surface-2);border-color:var(--accent)}
/* Objetivo tactil (H-28): en pantallas tactiles los controles del selector de comodidades
   suben a 44px, igual que hacia el componente anterior. En desktop quedan compactos.
   `pointer:coarse` y no `max-width:860px` — mismo motivo que el resto (ver `.pbr-tile`): en el
   iPad horizontal el buscador de comodidades quedaba en 38px y los segmentos en 32px. */
@media (pointer:coarse){
  .amc-clear,.amc-search,.amc-more{height:44px}
  .amc-seg-btn{height:44px}
  .amc-seg{padding:2px}
}
.amc-empty{text-align:center;color:var(--text-2);padding:40px 20px;font-size:14px}
.amc-empty[hidden]{display:none}

/* Modo mantenimiento (Pantallas → Propiedades) */
.maint-card.is-on{border-color:var(--warn);box-shadow:inset 3px 0 0 var(--warn)}
.maint-banner{display:flex;align-items:center;gap:10px;padding:12px 16px;margin-bottom:16px;border-radius:var(--radius-md);
  background:color-mix(in srgb,var(--warn) 14%,transparent);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);color:var(--text-1);font-size:14px}
.maint-banner svg{flex:none;color:var(--warn)}
.btn.is-disabled{opacity:.5;pointer-events:none;cursor:not-allowed}

/* Calendario propio de Momix (reemplaza el popup nativo del <input type=date>) */
input[type="date"]::-webkit-calendar-picker-indicator{display:none}
input[type="date"]{cursor:pointer}
.mdp{position:absolute;z-index:1200;width:284px;background:var(--surface);border:1px solid var(--border-default);
  border-radius:var(--radius-md);box-shadow:0 10px 34px rgba(0,0,0,.18);padding:12px;font-size:13px;user-select:none}
.mdp[hidden]{display:none}
.mdp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}
.mdp-title{font-weight:600;color:var(--text-1)}
.mdp-nav{width:32px;height:32px;border:0;background:none;border-radius:8px;cursor:pointer;color:var(--text-2);font-size:20px;line-height:1;display:flex;align-items:center;justify-content:center}
.mdp-nav:hover{background:var(--surface-2);color:var(--text-1)}
.mdp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.mdp-dow{text-align:center;font-size:11px;color:var(--text-3);padding:2px 0 6px;font-weight:600}
.mdp-empty{height:34px}
.mdp-day{height:34px;border:0;background:none;border-radius:8px;cursor:pointer;color:var(--text-1);font:inherit}
.mdp-day:hover:not(:disabled){background:var(--surface-2)}
.mdp-day.is-today{box-shadow:inset 0 0 0 1.5px var(--border-strong)}
.mdp-day.is-sel{background:var(--accent);color:var(--accent-fg);font-weight:600}
.mdp-day.is-sel:hover{background:var(--accent)}
.mdp-day:disabled{opacity:.3;cursor:not-allowed}
.mdp-foot{display:flex;justify-content:space-between;margin-top:8px;border-top:1px solid var(--border-default);padding-top:8px}
.mdp-link{border:0;background:none;cursor:pointer;color:var(--accent);font:inherit;font-weight:500;padding:4px 8px;border-radius:6px}
.mdp-link:hover{background:var(--surface-2)}
.mdp-hint{font-size:12px;color:var(--text-3)}
/* Rango: días entre desde/hasta resaltados; extremos en acento */
.mdp-day.is-in-range{background:color-mix(in srgb,var(--accent) 16%,transparent);border-radius:0}
.mdp-day.is-range-start{border-radius:8px 0 0 8px}
.mdp-day.is-range-end{border-radius:0 8px 8px 0}
.mkt-flog-range{display:inline-flex}
.mkt-flog-range [data-range-display]{cursor:pointer}

/* Paneles minimizables (Marketplace: Últimas corridas / Logs por aviso) — <details> con pinta de card. */
.mkt-collapse>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  font:600 15px/1.3 var(--font-sans);color:var(--text-1)}
.mkt-collapse>summary::-webkit-details-marker{display:none}
.mkt-collapse>summary::before{content:"▸";color:var(--text-3);font-size:12px;transition:transform .12s;flex:none}
.mkt-collapse[open]>summary::before{transform:rotate(90deg)}
.mkt-collapse-sum{margin-left:auto;font-weight:400;font-size:12.5px}
/* Origen de una corrida: reloj (automática desde Planificación) o "manual". */
.mkt-origin{display:inline-flex;align-items:center;justify-content:center}
.mkt-origin-auto{color:var(--accent)}
/* Pie de "Logs por aviso": pager + botones de descarga. */
.mkt-log-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:10px}
.mkt-log-pager{display:flex;align-items:center;gap:8px}
.mkt-log-pager .btn[disabled]{opacity:.4;pointer-events:none}
.mkt-log-export{display:flex;gap:8px}
/* "Sincronizar" (planificación): el aviso de que un modo puede PISAR datos de Momix va en el verde
   de la app (pedido de Máximo). */
.sync-pisa{color:var(--accent);font-weight:600}
.mkt-log-filters{display:flex;flex-wrap:wrap;gap:10px 12px;align-items:center;margin-bottom:12px}
.mkt-log-filters .input{width:auto}
.mkt-filter-chips{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.mkt-filter-lbl{font-size:12px;font-weight:600;color:var(--text-3);text-transform:uppercase;letter-spacing:.03em}
/* Barra "Filtrar por Estado" PEGADA a la tabla: se lee como control de la tabla (no config de import).
   Fondo tenue + borde, con la tabla flush debajo; la línea entre ambos separa "filtros" de "datos". */
.mkt-table-filterbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 12px;
  background:var(--surface-2);border:1px solid var(--border-default);
  border-radius:var(--radius-lg) var(--radius-lg) 0 0}
.mkt-table-attached{margin-top:0;border-top:0;border-top-left-radius:0;border-top-right-radius:0}
/* Chips de filtro (Sincronizar + Filtrar por Estado): MISMA pill Momix en las dos barras. Antes el
   estilo apuntaba solo a `.mkt-filter-chips .chip`, así que los de "Filtrar por Estado"
   (`.mkt-table-filterbar`) quedaban como botones pelados. Fondo `--surface` (claro) para que
   contrasten contra el fondo `--surface-2` de la barra. */
.mkt-filter-chips .chip,.mkt-table-filterbar .chip{
  display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border-default);
  background:var(--surface);color:var(--text-2);padding:7px 14px;border-radius:999px;
  font:600 13px/1.1 var(--font-sans);cursor:pointer;white-space:nowrap;
  transition:background .12s,border-color .12s,color .12s,box-shadow .12s}
.mkt-filter-chips .chip:hover,.mkt-table-filterbar .chip:hover{
  border-color:var(--accent);color:var(--text-1);box-shadow:0 1px 4px rgba(0,0,0,.07)}
.mkt-filter-chips .chip:focus-visible,.mkt-table-filterbar .chip:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
/* Seleccionado: relleno de acento + glow suave, para que se note claramente cuál está activo. */
.mkt-filter-chips .chip.is-on,.mkt-table-filterbar .chip.is-on{
  background:var(--accent);color:var(--accent-fg);border-color:var(--accent);
  box-shadow:0 1px 6px color-mix(in srgb,var(--accent) 38%,transparent)}
.mkt-filter-chips .chip.is-on:hover,.mkt-table-filterbar .chip.is-on:hover{color:var(--accent-fg)}
/* Chip de fotos: cuando está activo va en ámbar (--warn), para distinguirlo del resto de estados. */
.mkt-table-filterbar .chip.chip-photos.is-on{background:var(--warn);border-color:var(--warn);color:var(--warn-fg);
  box-shadow:0 1px 6px color-mix(in srgb,var(--warn) 42%,transparent)}
.mkt-filter-count{margin-left:auto;font-size:13px;font-weight:500;color:var(--text-3)}

/* Planificación */
.sectab-ico{vertical-align:-2px;margin-right:3px}
.sched-agencies{display:flex;flex-wrap:wrap;gap:8px;max-height:220px;overflow:auto;padding:4px 2px}
.chip-check{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--border-default);border-radius:999px;
  padding:5px 12px;font-size:13px;font-weight:400;cursor:pointer}
.chip-check:has(input:checked){background:color-mix(in srgb,var(--accent) 12%,transparent);border-color:var(--accent)}
.sched-times{display:flex;flex-direction:column;gap:6px}
.sched-time-row{display:flex;align-items:center;gap:6px}
.mkt-sched-actions{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.sched-creator{display:block;font-size:12px;color:var(--text-3);font-weight:400;margin-top:2px}
/* Botón-icono reutilizable (acciones de tabla). El 32px vale SOLO dentro de una fila de ≥44px
   (excepción H-40); cuando esta clase se redefinió acá (commit de Planificación, agosto) achicó
   también los botones de la TOPBAR, que el estándar fija en 40 como mínimo propio — la regla de
   abajo los restaura sin tocar los iconos de tabla. */
.icon-btn{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;flex:none;
  border:1px solid var(--border-default);background:var(--surface);border-radius:8px;color:var(--text-2);cursor:pointer;padding:0;transition:border-color .12s,color .12s,background .12s}
.topbar .icon-btn{width:40px;height:40px}
.icon-btn svg{width:17px;height:17px}
.icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--surface-2)}
.icon-btn-danger:hover{border-color:var(--err);color:var(--err)}
/* Tooltip propio (estética Momix, no el title nativo). Se activa con [data-tip] en hover/foco. */
[data-tip]{position:relative}
[data-tip]::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 7px);left:50%;
  transform:translateX(-50%) translateY(4px);background:var(--text-1);color:var(--surface);
  font-size:12px;font-weight:500;line-height:1.35;padding:6px 9px;border-radius:7px;
  /* NO `white-space:nowrap` (2026-08-25). El tooltip es `position:absolute` y, aunque esté en
     opacity:0, OCUPA LUGAR: una descripción larga en una sola línea estiraba el documento. En
     `/marketplace?tab=planificacion` los chips "Sincronizar" llevan descripciones de ~400px y a
     1024 la página medía 1185 — scroll horizontal de verdad, contra la regla dura del proyecto.
     Debajo de 860px no se veía porque `body{overflow-x:hidden}` lo tapaba: el mismo enmascarado
     que hizo parecer peor a E4. Acotado y con salto de línea, el tooltip se lee mejor además:
     400px en un renglón es un texto que nadie termina de leer.
     `max-width` en vw para que en pantalla chica tampoco pueda pasarse. */
  white-space:normal;overflow-wrap:anywhere;max-width:min(260px,84vw);width:max-content;text-align:center;
  box-shadow:0 6px 18px rgba(0,0,0,.22);opacity:0;pointer-events:none;transition:opacity .12s,transform .12s;z-index:1300}
[data-tip]::before{content:"";position:absolute;bottom:calc(100% + 2px);left:50%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:var(--text-1);opacity:0;pointer-events:none;transition:opacity .12s;z-index:1300}
[data-tip]:hover::after,[data-tip]:focus-visible::after{opacity:1;transform:translateX(-50%) translateY(0)}
[data-tip]:hover::before,[data-tip]:focus-visible::before{opacity:1}
.data-sub{margin:2px 0;font-size:12.5px;width:100%}
.data-sub th,.data-sub td{padding:6px 10px}
.data-sub tbody th{width:160px;color:var(--text-2);font-weight:600;white-space:nowrap;vertical-align:top}
.mkt-run-detail>td{background:var(--surface-2);padding:2px 10px}

/* ── Almacenamiento (SuperAdmin) ── */
.alm-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:16px}
.alm-big{display:flex;flex-direction:column;gap:4px}
.alm-big-lbl{font-size:12px;font-weight:600;color:var(--text-3);text-transform:uppercase;letter-spacing:.03em}
.alm-big-val{font-size:26px;font-weight:700;color:var(--text-1);line-height:1.1}
.alm-big-sub{font-size:12px}
.alm-total-row td{border-top:2px solid var(--border-strong)}
.alm-unit{display:inline-flex;gap:4px}
.alm-unit .chip{border:1px solid var(--border-default);background:var(--surface);color:var(--text-2);padding:4px 13px;border-radius:999px;font-size:13px;font-weight:500;cursor:pointer}
.alm-unit .chip.is-on{background:var(--accent);color:var(--accent-fg);border-color:transparent}

.mkt-scan-table th[data-sort]{padding:0}
.mkt-scan-table th .th-sort{width:100%;text-align:left;background:none;border:0;cursor:pointer;font:inherit;color:inherit;
  padding:10px 14px;display:inline-flex;align-items:center;gap:5px}
.mkt-scan-table th .th-sort::after{content:"↕";opacity:.35;font-size:11px}
.mkt-scan-table th[aria-sort="ascending"] .th-sort::after{content:"↑";opacity:.9}
.mkt-scan-table th[aria-sort="descending"] .th-sort::after{content:"↓";opacity:.9}
/* Precio del escaneo: alineado a la derecha y con cifras de ancho fijo (tabular-nums), que es lo que
   permite comparar importes de un vistazo — sin eso las columnas de dígitos no quedan alineadas.
   Un aviso puede tener venta Y alquiler: cada una en su renglón, con su operación adelante. */
.mkt-scan-table .mkt-price-col{text-align:right;white-space:nowrap}
.mkt-scan-table th.mkt-price-col .th-sort{text-align:right;justify-content:flex-end}
.mkt-price{display:block;font-variant-numeric:tabular-nums}
.mkt-price-op{color:var(--text-3);font-size:12px}
/* Orden por columnas (todas las listas): el encabezado se ve como TÍTULO (no botón/ficha). Se saca el
   relleno gris del thead. AFORDANCIA (2026-08-08): un ↕ TENUE SIEMPRE visible en toda columna ordenable
   avisa que se puede ordenar (antes la flecha solo salía al ordenar y nadie se daba cuenta); al pasar el
   mouse se realza y en la columna activa pasa a ↑/↓ en color de acento. */
/* OJO: data-sortable es un ATRIBUTO (<table class="data" data-sortable>), así que el selector es
   [data-sortable], NO .data-sortable (clase). Con el selector de clase la flecha NUNCA aparecía. */
[data-sortable] thead th{background:transparent;border-bottom:1.5px solid var(--border-default);text-transform:none;letter-spacing:0}
[data-sortable] thead th[data-sort]{cursor:pointer;user-select:none;white-space:nowrap}
[data-sortable] thead th[data-sort]:hover,[data-sortable] thead th[data-sort]:focus-visible{color:var(--accent)}
[data-sortable] thead th[data-sort]::after{content:"▾";font-size:13px;margin-left:5px;opacity:.35;display:inline-block;vertical-align:1px}
[data-sortable] thead th[data-sort]:hover::after,[data-sortable] thead th[data-sort]:focus-visible::after{opacity:.7}
[data-sortable] thead th[aria-sort="ascending"]::after{content:"▴";opacity:1;color:var(--accent)}
[data-sortable] thead th[aria-sort="descending"]::after{content:"▾";opacity:1;color:var(--accent)}
/* Campo de URL autocompletado (read-only): se ve claramente no-editable, pero se puede seleccionar/copiar */
.input[readonly]{background:var(--surface-2);color:var(--text-2);cursor:default}

.mkt-portal-bar{display:flex;gap:8px;margin-bottom:14px}
/* Botonera de portales: bordes más marcados + realce al pasar el mouse (el nombre del sitio va en title) */
.mkt-portal-btn{width:38px;height:38px;border-radius:9px;border:1.5px solid var(--border-strong);background:var(--surface);
  display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:border-color .12s,box-shadow .12s}
.mkt-portal-btn img{width:20px;height:20px;object-fit:contain}
.mkt-portal-btn:hover:not(:disabled){border-color:var(--accent)}
.mkt-portal-btn.is-on{border-color:var(--accent);box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 25%,transparent)}
.mkt-portal-btn:disabled{opacity:.4;cursor:not-allowed}
/* Uploader de marca del broker: zona para arrastrar / pegar / elegir, con vista previa. El input
   file real está oculto pero accesible; la zona lo dispara. Sin JS igual funciona (click al texto). */
.logo-drop{position:relative;border:2px dashed var(--border-strong);border-radius:12px;padding:18px;
  text-align:center;background:var(--surface-2);transition:border-color .15s,background .15s;cursor:pointer;max-width:420px}
.logo-drop.is-drag{border-color:var(--accent);background:var(--accent-soft)}
.logo-drop input[type=file]{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer}
.logo-drop-hint{font-size:13px;color:var(--text-2)}
.logo-drop-hint b{color:var(--accent-soft-fg)}
.logo-drop-preview{max-width:100%;max-height:90px;margin:0 auto 8px;display:block;object-fit:contain}
/* El preview arranca `hidden` y SIN src: sin esta regla, el `display:block` de arriba le ganaba por
   cascada al `[hidden]{display:none}` del navegador (el mismo error que ya se pisó tres veces en el
   proyecto) y se veía el ícono de "imagen rota" del <img> vacío dentro del dropzone. Al elegir un
   archivo, showPreview() pone el src y quita `hidden`, así que el preview real se ve igual. */
.logo-drop-preview[hidden]{display:none}

/* ── Conectores del broker (solapa "Conectores"): tarjetas de portales con chip de logo ── */
.conns{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px}
.conn-card{border:1px solid var(--border-default);border-radius:var(--radius-md);padding:16px;background:var(--surface)}
.conn-card.is-editing{border-color:var(--accent)}
.conn-top{display:flex;align-items:center;gap:12px;margin-bottom:8px}
.conn-logo{width:44px;height:44px;flex:none;border-radius:12px;display:flex;align-items:center;justify-content:center;
  font-weight:700;font-size:15px;letter-spacing:.5px;color:#fff}
/* Colores de marca PLACEHOLDER (chip con monograma) — reemplazables por el logo oficial de cada portal. */
.conn-logo-zonaprop{background:#00a19a}
.conn-logo-mercadolibre{background:#ffe600;color:#2d3277}
.conn-logo-argenprop{background:#e5384d}
.conn-logo-metroprop{background:#006aff}
/* Cuando hay logo oficial (archivo en /assets/connectors/): misma forma (cuadrado redondeado), pero
   fondo claro y el logo contenido adentro. Pisa el color de marca del monograma. */
.conn-logo.has-img{background:#fff;padding:6px}
.conn-logo.has-img img{width:100%;height:100%;object-fit:contain;display:block}
.conn-meta{display:flex;flex-direction:column;gap:4px;min-width:0}
.conn-meta strong{font-size:15px}
.conn-help{font-size:12.5px;margin:0 0 12px;line-height:1.4}
/* Portal que Momix todavía no integra: ocupa el lugar donde iría el formulario de credenciales, con
   el borde punteado que en el resto de la app significa "acá va a haber algo". */
.conn-soon{font-size:13px;margin:0;padding:10px 12px;line-height:1.45;
  border:1px dashed var(--border-strong);border-radius:var(--radius-md);background:var(--surface-2)}
.conn-edit .field{margin-bottom:10px}
/* Step-up con look de login de Momix (logo + email + contraseña). Cuando falta confirmar la
   contraseña, ocupa TODA la solapa (pantalla dedicada) para que el portal no se vea detrás. */
.conn-stepup-screen{display:flex;justify-content:center;padding:32px 16px}
.conn-stepup-screen .conn-stepup{border:1px solid var(--border-default);border-radius:var(--radius-md);
  background:var(--surface);padding:32px 28px;box-shadow:0 6px 24px rgba(0,0,0,.06)}
.conn-stepup{max-width:360px;margin:0 auto;text-align:center;padding:8px 0}
.conn-stepup-brand{display:flex;justify-content:center;margin-bottom:18px}
/* Logo Momix más grande en la confirmación (base: mark 30px / word 15px). */
.conn-stepup-brand .logo{gap:12px}
.conn-stepup-brand .logo-mark{width:52px;height:52px;border-radius:14px}
.conn-stepup-brand .logo-mark svg{width:28px;height:28px}
.conn-stepup-brand .logo-word{font-size:28px}
.conn-stepup-title{margin:0 0 6px;font-size:17px}
.conn-stepup .field{text-align:left;margin-bottom:12px}
.conn-stepup .inline-form{justify-content:center}
/* Credenciales cargadas (indicador en gris; la contraseña son puntitos, nunca la real). */
.conn-creds{font-size:13px;line-height:1.7}
.conn-cred-val{color:var(--text-2);font-variant-numeric:tabular-nums}
.conn-steps{margin:10px 0 0;padding-left:20px;color:var(--text-2);font-size:13.5px;line-height:1.5}
.conn-steps li{margin-bottom:9px}
.logo-drop kbd{font-size:11px;background:var(--surface);border:1px solid var(--border-default);border-radius:4px;padding:0 4px}
/* Config de la marca de agua (#5): preview a un lado, controles al otro. */
.wm-config{display:flex;gap:22px;flex-wrap:wrap;align-items:flex-start}
.wm-preview-wrap{flex:1 1 320px;min-width:0}
.wm-preview{width:100%;max-width:440px;border-radius:10px;border:1px solid var(--border-default);display:block}
.wm-form{flex:1 1 280px;min-width:0}
.wm-form input[type=range]{width:100%;accent-color:var(--accent);margin-top:4px}
.wm-pos{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:6px}
.wm-pos-opt{position:relative;border:1px solid var(--border-default);border-radius:8px;padding:8px 4px;text-align:center;cursor:pointer;font-size:12px;background:var(--surface-2);color:var(--text-2)}
.wm-pos-opt input{position:absolute;opacity:0;inset:0;cursor:pointer}
.wm-pos-opt:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--accent-soft-fg);font-weight:600}
.wm-check{display:flex;gap:8px;align-items:flex-start;font-size:13px;cursor:pointer;color:var(--text-1);margin-top:8px}
.wm-check input{margin-top:2px;flex:none}
@media (max-width:640px){.wm-pos{grid-template-columns:repeat(2,1fr)}}
/* Switch "marca de agua" en la subida de fotos de una propiedad (mt_98): miniatura de la marca +
   switch + estado. Al tocarlo se aplica/quita en todas las fotos (property-form.js). */
.photo-wm{display:flex;gap:10px;align-items:center;margin-top:10px;font-size:13px;color:var(--text-1)}
.photo-wm-thumb{flex:none;width:46px;height:34px;border:1px solid var(--border-default);border-radius:7px;
  background:var(--surface-2);display:grid;place-items:center;overflow:hidden}
.photo-wm-thumb img{max-width:100%;max-height:100%;object-fit:contain;display:block}
.photo-wm-status{font-size:12px}
/* Form "Invitar a un agente": el botón quedaba 14px más abajo que los inputs porque flexbox alinea
   la caja de MARGEN y cada .field arrastra su margin-bottom de 14px. Dentro de un inline-form
   alineado al pie, ese margen no aporta nada: se anula y todo queda sobre la misma línea. */
.inline-form.ai-flex-end > .field{margin-bottom:0}
/* ── Selección múltiple en Propiedades (#B1) ─────────────────────────────────────────
   El checkbox vive FUERA del <a> (un checkbox adentro de un ancla navega al clic): el wrapper es
   position:relative y el checkbox flota encima, en la esquina. Discreto hasta el hover o hasta que
   está tildado. La barra de acciones aparece fija abajo al haber selección. */
.prop-sel-wrap{position:relative}
/* Checkbox de selección: se apoya sobre la miniatura pero NO encima del badge de estado (que se
   corre a la derecha cuando la vista es seleccionable). Lleva un aro claro para verse sobre
   cualquier foto. Aparece al pasar el mouse o si ya está tildado. */
.prop-sel-wrap > .prop-sel{position:absolute;top:9px;left:9px;z-index:4;width:22px;height:22px;margin:0;
  cursor:pointer;accent-color:var(--accent);border-radius:6px;
  box-shadow:0 0 0 2px rgba(255,255,255,.85),0 1px 4px rgba(0,0,0,.35);
  opacity:0;transition:opacity .12s}
.prop-sel-wrap:hover > .prop-sel,.prop-sel-wrap > .prop-sel:checked,.prop-sel-wrap > .prop-sel:focus-visible{opacity:1}
.prop-sel-wrap > .prop-sel:checked ~ a{outline:2px solid var(--accent);outline-offset:-1px}
/* La ficha con selección corre su badge de estado para dejarle el lugar al checkbox. */
.prop-grid.selectable .prop-status{left:40px}
.prop-sel-wrap-row > .prop-sel{top:50%;transform:translateY(-50%);left:9px;box-shadow:0 0 0 2px rgba(255,255,255,.85),0 1px 4px rgba(0,0,0,.35)}
.prop-sel-wrap-row > .prop-sel:checked ~ a{outline:2px solid var(--accent);outline-offset:-1px;border-radius:12px}
.prop-list.selectable .prop-list-row{padding-left:40px}
/* "Seleccionar todo" al lado del contador. */
.sel-all{margin-left:12px;display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  color:var(--accent-text,var(--accent));font-weight:500}
.sel-all input{accent-color:var(--accent);cursor:pointer}
.bulk-bar{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:70;
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center;
  background:var(--surface);border:1px solid var(--border-strong);border-radius:999px;
  padding:8px 16px;box-shadow:0 8px 28px rgba(0,0,0,.22);max-width:calc(100vw - 24px)}
.bulk-bar[hidden]{display:none}
.bulk-count{font-size:13px;color:var(--text-1);margin-right:4px;white-space:nowrap}

/* Inbox del SuperAdmin (#B6): filas sin leer resaltadas + contador en el nav. */
.inbox-unread td{background:var(--accent-soft)}
.inbox-msg{max-width:420px;white-space:normal}
.ws-nowrap{white-space:nowrap}
.nav-badge{display:inline-block;min-width:18px;text-align:center;background:var(--accent);
  color:var(--brand-navy);border-radius:999px;font-size:11px;font-weight:700;padding:1px 6px;margin-left:6px}

/* Botón con apariencia de link (para acciones que deben ir por POST, como "Reenviar código"). */
.btn-link{background:none;border:0;padding:0;font:inherit;color:var(--accent-text,var(--accent));
  text-decoration:underline;cursor:pointer}
.btn-link:hover{opacity:.85}
.btn-link:disabled{opacity:.5;cursor:default;text-decoration:none}
/* Botón-ícono de copiar (mail, link): discreto, con feedback al copiar. */
.btn-copy{display:inline-flex;align-items:center;gap:5px;background:none;border:0;cursor:pointer;
  color:var(--accent-text,var(--accent));font:inherit;font-size:12.5px;padding:2px 4px;margin-left:8px;vertical-align:middle}
.btn-copy:hover{opacity:.8}
.btn-copy.is-copied{color:var(--ok-strong,var(--ok))}
.is-copied{color:var(--ok-strong,var(--ok))}

/* ── Campos calculados (Superficie total / del terreno) — REVISION-UX-4 punto A2 (2026-07-29):
   vuelve a un solo patrón "Ajustar manualmente" para los DOS campos (pisa la decisión de la
   revisión anterior, que en Superficie total sacaba el control por completo y en Superficie del
   terreno usaba una casilla con motivo propio) — el input a mano NUNCA está visible de entrada:
   se ve solo el valor calculado + la fórmula, y el link recién revela el input al hacer clic, ya
   precargado con el valor calculado (input siempre en el DOM, sincronizado en vivo por
   bindSqmTotal()/bindLandSection() aunque esté deshabilitado — "precargado" sale gratis de eso,
   no hace falta copiarlo a mano al abrir). Con el input abierto, el link cambia a "Volver al
   cálculo automático". */
.calc-head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}
.calc-toggle{background:none;border:0;padding:0;min-height:44px;font:inherit;font-size:13px;color:var(--text-2);
  cursor:pointer;text-decoration:underline;text-underline-offset:2px;display:inline-block;margin-top:4px}
.calc-toggle[hidden]{display:none}
.calc-toggle:hover{color:var(--accent-hover)}
.calc-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Leyenda bajo el valor calculado (ej. "Cubierta + semicubierta") — oculta en modo manual. */
.calc-formula-hint{margin:2px 0 0;font-size:12px;color:var(--text-3)}
.calc-formula-hint[hidden]{display:none}

/* ── Video de la propiedad (mt_25): iframe responsive 16:9 ── */
.video-embed{position:relative;width:100%;padding-top:56.25%;border-radius:var(--radius-md);
  overflow:hidden;background:var(--surface-2)}
.video-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* ═══════════════════ Ficha de propiedad — rediseño (FICHA-PROPIEDAD.md, 2026-07-29) ═══════════════════ */

/* ── A: barra de identidad pegajosa (misma familia visual que .prop-journey del alta) ── */
.pf-bar{position:sticky;top:0;z-index:15;background:var(--surface);border-bottom:1px solid var(--border-default)}
.pf-bar-inner{max-width:1120px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:14px}
.pf-bar-thumb,.pf-bar-thumb-ph{width:44px;height:34px;border-radius:8px;flex:none;background:var(--surface-2);object-fit:cover}
.pf-bar-thumb-ph{display:flex;align-items:center;justify-content:center;color:var(--text-3)}
.pf-bar-id{min-width:0;flex:1}
.pf-bar-title-row{display:flex;align-items:center;gap:9px;min-width:0}
/* La flecha de volver medía 17×17: el ícono y nada más. Eso queda por debajo incluso del mínimo de
   WCAG 2.2 (2.5.8, 24×24) — no del 44 "ideal", del piso. Y es el control de salida de la ficha, que
   en un celular se toca con el pulgar. Se le da área táctil sin moverla de lugar: el margen negativo
   compensa el padding, así que la flecha se sigue viendo donde estaba y solo crece la zona sensible. */
.pf-bar-back{color:var(--text-3);flex:none;display:flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;margin:-13px -13px -13px -14px}
.pf-bar-title{margin:0;font-size:15px;font-weight:500;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.pf-bar-meta{margin:2px 0 0;font-size:12px}
.pf-bar-price{font-size:20px;font-weight:500;font-variant-numeric:tabular-nums;flex:none;white-space:nowrap}
/* Varias operaciones en la cabecera (venta + alquiler + temporario): una por línea, más chicas. */
.pf-bar-price.is-multi{font-size:15px;line-height:1.3;text-align:right;display:flex;flex-direction:column;gap:2px}
.pf-bar-price.is-multi .pf-bar-price-op{white-space:nowrap}
.pf-bar-actions{display:flex;gap:8px;flex:none}

/* ── .pf-page envuelve TODA la ficha (barra + body + barra fija de mobile) y establece la
   containment UNA sola vez para las tres — Sección E: container queries, no viewport (el sidebar
   de 232px y el zoom de fs-2/3 hacen que el viewport mienta sobre el ancho real disponible, mismo
   criterio que .prop-form del alta).
   Dos bugs reales encontrados con un screenshot real de Chrome headless a 1440px (Máximo reportó
   la ficha "totalmente desfazada"), los dos por el mismo motivo raíz — un elemento no puede
   responder a @container sobre SÍ MISMO ni sobre algo que no sea su descendiente:
   1) .pf-body no puede ser a la vez el elemento con max-width:1120px Y el que establece el
      container-type — su ancho nunca supera 1120px, así que "@container pf (max-width:1200px)"
      quedaba SIEMPRE verdadero (1120 < 1200) sin importar cuán ancha fuera la pantalla real, y la
      ficha quedaba en una sola columna siempre. Fix: .pf-page (sin tope de ancho) es el
      contenedor; .pf-body es un simple descendiente centrado que solo pone el tope visual.
   2) .pf-bar (barra de identidad) y .pf-actions-fixed-bar (barra fija de mobile) vivían FUERA de
      ese contenedor (antes .pf-wrap, ahora .pf-page) — sus reglas "@container pf" (ocultar
      metadatos en mobile, mostrar la barra fija) nunca podían aplicar porque ninguna de las dos
      era descendiente del contenedor. Fix: las tres viven ahora dentro de .pf-page. */
.pf-page{container-type:inline-size;container-name:pf}
/* Columna vertical: el grid de dos columnas (.pf-grid) arriba, y Propietario/Actividad a lo ancho
   debajo. Están AFUERA del grid a propósito — ver la nota de .pf-side sobre el sticky. El gap hace
   de separación entre el grid y esas dos tarjetas (antes lo daba el gap del grid). */
.pf-body{max-width:1120px;margin:20px auto 0;padding:0 24px 40px;display:flex;flex-direction:column;gap:20px}
.pf-grid{display:grid;grid-template-columns:1fr 372px;gap:20px;align-items:start}
/* Ver la nota de .pgallery-strip: sin esto, cualquier hijo que scrollee por dentro (la tira de
   miniaturas, una tabla ancha) le pide a su pista el ancho completo de su contenido y el grid se lo
   da. Es la salvaguarda estructural del contenedor, no un parche de la galería: aplica a todo lo
   que se meta en estas columnas de acá en adelante. */
.pf-grid > *{min-width:0}
/* Las dos columnas son WRAPPERS flex independientes (pf-main / pf-side), no celdas de grid por
   fila: con celdas, cada fila del grid mide lo que su tarjeta más alta y la columna derecha
   terminaba "colgando" sola al final con un hueco enfrente (lo marcó el usuario dos veces). Con
   columnas de verdad cada lado apila lo suyo y las alturas no se contaminan. El sidebar es sticky:
   acompaña el scroll mientras la izquierda (galería+descripción+mapa) es más larga. */
.pf-main{grid-column:1;display:flex;flex-direction:column;gap:20px;min-width:0}
.pf-side{grid-column:2;display:flex;flex-direction:column;gap:20px;min-width:0;position:sticky;top:16px}
.pf-main > *,.pf-side > *{min-width:0}
/* Propietario y Actividad ya NO son items del grid: viven como hijos de .pf-body (columna flex), a lo
   ancho, debajo del grid. Se los sacó del grid porque el sidebar sticky de la derecha, cuyo bloque
   contenedor es el grid entero (Chrome no lo confina a su celda), les flotaba encima al scrollear
   (bug 2026-08-17). No necesitan reglas de columna: en la columna flex de .pf-body estiran solos. */

/* ── Precio y operaciones ── */
.pf-op-row{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;padding:8px 0}
.pf-op-row + .pf-op-row{border-top:1px solid var(--border-default)}
.pf-op-name{font-size:12px;color:var(--text-3);flex:none;width:100%}
.pf-op-price{font-size:24px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--text-1)}
.pf-op-row.is-secondary .pf-op-price{font-size:24px;font-weight:500;color:var(--text-2)}
.pf-op-unit{font-size:13px;color:var(--text-3)}
.pf-price-chip{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:500;padding:3px 10px;border-radius:999px}
.pf-price-chip.is-down{background:var(--ok-soft);color:var(--ok-strong)}
.pf-price-chip.is-up{background:var(--warn-soft);color:var(--warn-strong)}
.pf-price-history-btn{background:none;border:0;padding:0;font:inherit;font-size:12px;color:var(--text-2);cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.pf-price-history-btn:hover{color:var(--accent-hover)}
.pf-stats{display:flex;flex-wrap:wrap;gap:0;margin-top:14px;border-top:1px solid var(--border-default);padding-top:14px}
.pf-stat{padding:0 16px;border-left:1px solid var(--border-default);display:flex;flex-direction:column;gap:2px}
.pf-stat:first-child{padding-left:0;border-left:0}
.pf-stat-num{font-size:20px;font-weight:500;font-variant-numeric:tabular-nums;color:var(--text-1)}
.pf-stat-lbl{font-size:12px;color:var(--text-3)}
.pf-price-history-panel{margin-top:14px;padding-top:14px;border-top:1px solid var(--border-default)}
.pf-price-history-panel[hidden]{display:none}
.pf-ph-row{display:flex;align-items:center;gap:8px;font-size:13px;padding:5px 0;flex-wrap:wrap}
.pf-ph-chart{margin-bottom:10px}

/* ── Descripción y comodidades ── */
.pf-descr-text{white-space:pre-line;line-height:1.65;margin:0}
.pf-amenity-group + .pf-amenity-group{margin-top:14px}
.pf-amenity-group-title{margin:0 0 8px;font-size:12px;font-weight:500;color:var(--text-3);text-transform:uppercase;letter-spacing:.03em}
.pf-amenity-chips{display:flex;flex-wrap:wrap;gap:8px}
.pf-amenity-chip{display:inline-flex;align-items:center;gap:6px;font-size:13px;padding:6px 12px 6px 8px;border-radius:999px;
  background:var(--surface-2);color:var(--text-1)}
.pf-amenity-chip svg{width:15px;height:15px;color:var(--accent);flex:none}

/* ── Mapa de solo lectura ── */
.pf-map-addr{margin:0 0 8px;font-size:13px}
.pf-map-approx-note{margin:6px 0 0;font-size:12px;color:var(--text-3)}
.pf-map-readonly{height:220px;border-radius:var(--radius-md);overflow:hidden;background:var(--surface-2)}

/* ── Links compartidos con métrica ── */
.pf-share-item{padding:10px 0}
.pf-share-item + .pf-share-item{border-top:1px solid var(--border-default)}
.pf-share-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}
.pf-share-label{font-weight:500;font-size:13px}
.pf-share-metric{font-size:12px;color:var(--text-3)}
.pf-share-metric strong{color:var(--text-1);font-weight:500}

/* ── Propietario ── */
.pf-owner-name{font-weight:500;font-size:14px}
.pf-contact-gap{margin-top:10px;padding:8px 11px;font-size:12px;border-radius:var(--radius-md);
  background:var(--warn-soft);color:var(--warn-strong)}

/* ── C2: timeline unificado ── */
.pf-tl-tabs{display:flex;gap:4px;margin-bottom:12px;border-bottom:1px solid var(--border-default)}
.pf-tl-tab{background:none;border:0;border-bottom:2px solid transparent;padding:8px 4px;margin-right:14px;font:inherit;
  font-size:13px;font-weight:500;color:var(--text-3);cursor:pointer}
/* `--accent-text` para el TEXTO y `--accent` para el borde: es exactamente el caso que describe la
   nota de --accent-text ("links, solapa activa, botón ghost"). El teal de 12-momix está pensado
   para fondo — como texto sobre blanco da 1,92:1, menos de la mitad del AA. El mismo hallazgo
   (H-10) ya se había corregido en .dropzone-link; esta solapa quedó afuera porque la ficha no
   estaba en el catálogo de la suite de UX. El borde inferior sí sigue con --accent: es un borde,
   no texto, y ahí el token cumple su función. */
.pf-tl-tab.is-on{color:var(--accent-text);border-bottom-color:var(--accent)}
.pf-tl-tab-count{display:inline-flex;align-items:center;justify-content:center;min-width:16px;height:16px;padding:0 4px;
  margin-left:5px;border-radius:999px;background:var(--warn-soft);color:var(--warn-strong);font-size:12px;font-weight:500}
.pf-tl-list{list-style:none;margin:0;padding:0}
.pf-tl-item{display:flex;gap:10px;padding:9px 0;border-top:1px solid var(--border-default)}
.pf-tl-item:first-child{border-top:0;padding-top:0}
.pf-tl-item[hidden]{display:none}
.pf-tl-dot{width:9px;height:9px;border-radius:50%;flex:none;margin-top:5px;background:var(--text-3)}
.pf-tl-dot.is-accent{background:var(--accent)}
.pf-tl-dot.is-warn{background:var(--warn)}
.pf-tl-body{min-width:0;flex:1;font-size:13px}
.pf-tl-when{font-size:12px;color:var(--text-3)}
.pf-tl-more{width:100%;margin-top:10px}
.pf-tl-note-form{margin-top:14px;padding-top:14px;border-top:1px solid var(--border-default)}

/* ── B: galería y visor de fotos ── */
.pgallery-card{padding:0;overflow:hidden}
.pgallery-empty{display:flex;flex-direction:column;align-items:center;gap:8px;color:var(--text-3);padding:40px 20px}
/* Track de scroll nativo + scroll-snap: es lo que hace de swipe (B5) Y de mecanismo único de
   navegación (arrows/teclado/miniatura hacen scrollTo al slide destino) — cada foto vive en su
   propio <img> SIEMPRE presente en el DOM, así nunca hay un blanco/spinner intermedio (B2) sin
   necesitar una segunda capa de crossfade por opacidad. */
.pgallery-stage{position:relative;aspect-ratio:16/9;background:var(--surface-2);overflow:hidden}
.pgallery-track{display:flex;width:100%;height:100%;overflow-x:auto;scroll-snap-type:x mandatory;
  scroll-behavior:smooth;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.pgallery-track::-webkit-scrollbar{display:none}
/* El track es focusable (ver la nota de components/property_gallery.php), así que su parada de tab
   tiene que VERSE — un foco invisible cumple la regla de axe y sigue sin servirle a nadie.
   `outline-offset` negativo porque el stage tiene overflow:hidden y un outline por fuera se recorta. */
.pgallery-track:focus-visible{outline:2px solid var(--accent);outline-offset:-3px}
@media (prefers-reduced-motion:reduce){.pgallery-track{scroll-behavior:auto}}
/* `min-width:0` no es decorativo: un flex item arranca con `min-width:auto`, o sea que NUNCA se
   encoge por debajo del min-content de su contenido — y su contenido es un <img width="960">, cuyo
   min-content son esos 960px. Sin esto el slide empuja el ancho del track, el track empuja la
   tarjeta y la tarjeta empuja la columna del grid: a 360px la ficha pública terminaba midiendo
   444px y la página entera scrolleaba de costado (bug real, medido con Puppeteer a 360 y 390).
   En la ficha del equipo no se notaba porque las reglas de @container pf recortan antes. */
.pgallery-slide{flex:0 0 100%;width:100%;min-width:0;height:100%;scroll-snap-align:center;scroll-snap-stop:always;display:flex}
.pgallery-slide[hidden]{display:none}
.pgallery-slide img{width:100%;height:100%;object-fit:contain}
.pgallery-arrow{position:absolute;top:50%;transform:translateY(-50%);width:38px;height:38px;border-radius:50%;border:0;
  background:rgba(var(--overlay-rgb), .55);color:var(--fixed-white);display:flex;align-items:center;justify-content:center;cursor:pointer;
  opacity:0;transition:opacity .15s ease}
.pgallery-stage:hover .pgallery-arrow,.pgallery-arrow:focus-visible{opacity:1}
.pgallery-arrow-prev{left:10px}
.pgallery-arrow-next{right:10px}
.pgallery-arrow:hover{background:rgba(var(--overlay-rgb), .75)}
.pgallery-counter{position:absolute;top:10px;left:10px;background:rgba(var(--overlay-rgb), .6);color:var(--fixed-white);font-size:12px;
  font-weight:500;padding:4px 10px;border-radius:999px;font-variant-numeric:tabular-nums}
/* .55 → .72: el pill es semitransparente, así que su contraste real depende de la FOTO que tenga
   detrás. Con .55 daba 4,46:1 contra el blanco — a cuatro centésimas del AA sobre esta foto, y por
   debajo con cualquiera más clara. Subir la opacidad fija el piso sin importar la imagen, que es lo
   que hace que el número deje de ser una casualidad. */
.pgallery-tabs{position:absolute;top:10px;right:10px;display:flex;gap:2px;background:rgba(var(--overlay-rgb), .72);border-radius:999px;padding:3px}
/* Sin `opacity:.75`: el blanco atenuado sobre el pill semitransparente daba 3,28:1 (AA pide 4,5
   para 12px). La solapa activa ya se distingue por su fondo blanco relleno, así que la opacidad no
   era lo que sostenía la jerarquía — solo restaba legibilidad sobre una foto, que es justo el fondo
   menos predecible que hay. */
.pgallery-tab{background:none;border:0;color:var(--fixed-white);font:inherit;font-size:12px;font-weight:500;padding:4px 10px;
  border-radius:999px;cursor:pointer}
/* El fondo es `--fixed-white` (siempre blanco: flota sobre una foto, no sobre la superficie del
   tema), así que el texto de encima también tiene que ser INDEPENDIENTE del tema. Con `--text-1`
   seguía al tema y en oscuro valía #F1F5F9: casi blanco sobre blanco, 1,09:1 — el pill activo era
   literalmente ilegible. `--brand-navy` es el token fijo que corresponde para texto sobre claro. */
.pgallery-tab.is-on{background:var(--fixed-white);color:var(--brand-navy);opacity:1}
.pgallery-expand{position:absolute;bottom:10px;right:10px;width:32px;height:32px;border-radius:8px;border:0;
  background:rgba(var(--overlay-rgb), .55);color:var(--fixed-white);display:flex;align-items:center;justify-content:center;cursor:pointer}
.pgallery-expand:hover{background:rgba(var(--overlay-rgb), .75)}
/* 44×44 en pantallas táctiles: medía 32 y quedaba por debajo del mínimo de objetivo táctil
   (WCAG 2.5.5 / matriz de dispositivos de docs/05-testing.md). Solo donde importa: con mouse, 32px
   se aciertan sin problema y el botón flota sobre la foto, así que agrandarlo en escritorio le
   comería imagen sin motivo. Va DESPUÉS de la regla base a propósito — misma especificidad, así que
   el orden es lo único que decide cuál gana (declararlo antes fue exactamente por qué no aplicó). */
@media (pointer:coarse){.pgallery-expand{width:44px;height:44px}}
/* `min-width:0` acá y en los items del grid que la contiene (.pf-grid):
   la tira declara `overflow-x:auto` para scrollear, pero eso no sirve de nada si alguien le concede
   todo el ancho que pide. Un item de grid arranca con `min-width:auto`, o sea que su pista crece
   hasta el min-content del contenido — y el min-content de esta tira son las 33 miniaturas puestas
   una al lado de la otra. Resultado medido: la pista de .pf-grid quedaba en 2892px sobre un viewport
   de 768 y la ficha entera scrolleaba de costado, en TODOS los anchos ≥768 (a 360/390 no pasaba
   porque ahí la tira se oculta). Con esto la tira vuelve a hacer lo que dice su overflow: scrollear
   ella sola. Bug preexistente, no lo veía nadie porque la ficha no estaba en el catálogo de UX. */
.pgallery-strip{display:flex;gap:8px;padding:10px;overflow-x:auto;scroll-behavior:smooth;min-width:0}
@media (prefers-reduced-motion:reduce){.pgallery-strip{scroll-behavior:auto}}
.pgallery-thumb{position:relative;flex:none;width:78px;height:58px;padding:0;border-radius:8px;overflow:hidden;
  border:2px solid transparent;cursor:pointer;background:var(--surface-2)}
.pgallery-thumb.is-on{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.pgallery-thumb[hidden]{display:none}
.pgallery-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.pgallery-thumb-tag{position:absolute;bottom:2px;left:2px;background:rgba(var(--overlay-rgb), .75);color:var(--fixed-white);font-size:12px;padding:1px 4px;border-radius:4px}
.pgallery-dots{display:none;gap:5px;justify-content:center;padding:8px 0}
.pgallery-dot{width:6px;height:6px;border-radius:999px;background:var(--border-strong);flex:none;transition:width .15s ease,background .15s ease}
.pgallery-dot.is-on{width:16px;background:var(--accent)}
.pgallery-dot[hidden]{display:none}

/* Visor a pantalla completa: fondo fijo oscuro, no sigue el tema claro (es un visor, no la página) */
.pgallery-viewer{position:fixed;inset:0;z-index:200;background:var(--viewer-bg);display:flex;flex-direction:column}
.pgallery-viewer[hidden]{display:none}
.pgallery-viewer-bar{display:flex;align-items:center;gap:12px;padding:12px 16px;color:var(--fixed-white)}
.pgallery-viewer-counter{font-size:13px;font-weight:500;font-variant-numeric:tabular-nums}
.pgallery-viewer-chip{font-size:12px;color:rgba(255,255,255,.6);flex:1}
.pgallery-tabs-viewer{position:static;background:rgba(255,255,255,.1)}
.pgallery-viewer-close{background:none;border:0;color:var(--fixed-white);width:32px;height:32px;border-radius:8px;cursor:pointer;
  display:flex;align-items:center;justify-content:center}
.pgallery-viewer-close:hover{background:rgba(255,255,255,.12)}
.pgallery-viewer-stage{position:relative;flex:1;display:flex;align-items:center;justify-content:center;min-height:0;overflow:hidden}
.pgallery-viewer-img{max-width:100%;max-height:100%;object-fit:contain;cursor:zoom-in;touch-action:none;user-select:none}
.pgallery-viewer-img.is-zoomed{cursor:grab}
.pgallery-viewer-img.is-dragging{cursor:grabbing}
.pgallery-viewer-stage .pgallery-arrow{width:48px;height:48px}
.pgallery-strip-viewer{display:flex;gap:8px;padding:14px 16px;overflow-x:auto;justify-content:center}


/* ── E: responsive de la ficha — container queries sobre el CONTENEDOR de la ficha ──
   El doc pedía ">1200px de contenedor" para 2 columnas, pero .pf-wrap vive DENTRO de .content
   (compartido con toda la app), que tiene su propio max-width:1100px (border-box) — 1060px de
   ancho real disponible tras su padding, siempre, sin importar cuán ancha sea la pantalla. Pedir
   ">1200" era literalmente imposible de cumplir (bug real, encontrado con un screenshot real de
   Chrome headless a 1440px: la ficha quedaba en una sola columna siempre). 1000px dejan margen
   bajo ese techo de ~1060px: cualquier pantalla de escritorio normal (≥~1280px de viewport) llega
   a los 1060px reales y activa las 2 columnas; solo una ventana angosta o una tablet colapsa. */
@container pf (max-width:1000px){
  .pf-grid{grid-template-columns:1fr}
  /* Los wrappers de columna se DISUELVEN (display:contents): sus tarjetas vuelven a ser items
     directos del grid de 1 columna y el `order` de siempre decide la secuencia mobile (acciones
     arriba; links/propietario/actividad al final). El sticky del sidebar no aplica acá. */
  .pf-main,.pf-side{display:contents}
  /* Propietario/Actividad ya están afuera del grid (hijos de .pf-body flex). Su orden lo da .pf-body:
     el grid va primero (order 0) y ellas después. `order` de acá aplica en ese contexto flex. */
  .pf-c-actions{order:-1}
  .pf-c-shares{order:6}
  .pf-c-owner{order:7}
  .pf-c-activity{order:8}
}
@container pf (max-width:839px){
  .pf-body{padding:0 0 84px}
  .pf-c-gallery .card,.pgallery-card{border-radius:0;border-left:0;border-right:0}
  .pf-c-price,.pf-c-descr,.pf-c-map,.pf-c-shares,.pf-c-owner,.pf-c-activity{margin-left:16px;margin-right:16px}
  .pf-c-actions{margin-left:16px;margin-right:16px}
  /* Condensada a atrás+miniatura+título+estado (doc E, "<840: título+estado+un botón") — precio
     y acciones se sacan de acá: la barra fija de abajo YA tiene Editar/Compartir, y el precio ya
     está repetido en la tarjeta de Precio y operaciones — mantener los tres (miniatura+precio+
     acciones) en la misma fila que el título es lo que desbordaba el ancho real en un celular
     angosto (bug real, encontrado con un screenshot de Chrome headless a 390px: el botón "Editar"
     quedaba directamente fuera de la pantalla, sin scroll horizontal visible para llegar a él). */
  .pf-bar-meta,.pf-bar-price,.pf-bar-actions{display:none}
  .pf-map-readonly{height:180px}
  .pf-actions-fixed-bar{position:fixed;bottom:0;left:0;right:0;z-index:16;display:flex;gap:8px;padding:10px 16px;
    padding-bottom:calc(10px + env(safe-area-inset-bottom));background:var(--surface);border-top:1px solid var(--border-default)}
  .pf-actions-fixed-bar .btn{flex:1}
}
@container pf (min-width:840px){.pf-actions-fixed-bar{display:none}}
@container pf (min-width:1001px){.pgallery-dots{display:none !important}}

@container pf (max-width:1023px){
  .pgallery-thumb{width:88px;height:64px}
}
@container pf (max-width:639px){
  .pgallery-stage{aspect-ratio:4/3}
  .pgallery-strip{display:none}
  .pgallery-dots{display:flex}
}
/* Colisión real con el bottom-nav de la PWA (mismo problema ya resuelto para .prop-bottombar,
   ver el bloque "Bottom-nav mobile" más arriba): bottom-nav es fixed+z-index:50, y a este ancho
   .pf-actions-fixed-bar TAMBIÉN es fixed con bottom:0 — sin este ajuste quedaba completamente
   tapada debajo (bug real, encontrado con un screenshot de Chrome real vía Puppeteer a 390px: la
   barra de Editar/Compartir nunca se veía). Tiene que declararse DESPUÉS del bloque
   "@container pf (max-width:839px)" de más arriba (que fija bottom:0) para ganarle por orden de
   cascada — @media, no @container, porque bottom-nav en sí se activa por ancho de VIEWPORT. */
@media (max-width:640px){
  .pf-actions-fixed-bar{bottom:76px}
}


/* ── Utilidades del barrido de estilos inline (2026-07-29) ────────────────────────────
   Existen para poder sacar `'unsafe-inline'` de `style-src` en la CSP: un atributo
   `style="…"` en el HTML queda bloqueado por el navegador, y esa directiva era la única
   violación del piso del estándar que quedaba viva.

   El nombre de cada clase describe su VALOR, no su intención, a propósito: son la
   traducción uno a uno de lo que antes estaba inline, sin cambiar ni un pixel. Normalizar
   esos valores a la escala cerrada (4·8·12·16·18·22) es otra decisión —visual— y no se
   mezcló con esta, que es de seguridad. Una clase nueva acá se justifica solo si viene de
   un estilo inline que se está sacando; para algo nuevo, componente o clase semántica.

   OJO — DÓNDE NO APLICA: el HTML de un EMAIL. Un mail no carga style.css y muchos clientes
   descartan el <style>, así que ahí el estilo inline es obligatorio. El barrido convirtió por
   error dos casos dentro de cuerpos de mail (el código de 6 dígitos del acceso, que habría
   llegado sin formato) y se revirtieron a mano. Antes de convertir un style= a clase:
   preguntarse si ese HTML lo va a renderizar un NAVEGADOR o un cliente de correo. */
.ai-center{align-items:center}
.ai-end{align-items:end}
.ai-flex-end{align-items:flex-end}
.ai-flex-start{align-items:flex-start}
.ai-start{align-items:start}
.bc-accent{border-color:var(--accent)}
.bc-err{border-color:var(--err)}
.bc-warn{border-color:var(--warn)}
.bd-0{border:0}
.bg-none{background:none}
.bt-1{border-top:1px solid var(--border-default);border-top:1px solid var(--border)}
.c-err{color:var(--err-strong)}
.d-block{display:block}
.d-flex{display:flex}
.d-grid{display:grid}
.d-inline{display:inline}
.d-inline-block{display:inline-block}
.flex-1{flex:1}
.fs-115{font-size:12px}
.fs-12{font-size:12px}
.fs-125{font-size:12px}
.fw-400{font-weight:400}
.fw-wrap{flex-wrap:wrap}
.gap-10{gap:10px}
.gap-12{gap:12px}
.gap-16{gap:16px}
.gap-20{gap:20px}
.gap-8{gap:8px}
.gtc-2{grid-template-columns:1fr 1fr}
.gtc-2-2-1-auto{grid-template-columns:2fr 2fr 1fr auto}
.gtc-2-2-2-1-auto{grid-template-columns:2fr 2fr 2fr 1fr auto}
/* En un teléfono, tantas columnas dejaban los campos en ~40px — inutilizables.
   Detectado por la suite midiendo un 390px real, no a ojo. */
@media (max-width:640px){ .gtc-2-2-1-auto,.gtc-2-2-2-1-auto{grid-template-columns:1fr} }
@media (min-width:641px) and (max-width:980px){ .gtc-2-2-2-1-auto{grid-template-columns:1fr 1fr} }
.jc-between{justify-content:space-between}
.m-0{margin:0}
.m-0-0-12-18{margin:0 0 12px 18px}
.m-0-0-8{margin:0 0 8px}
.m-0-auto-40{margin:0 auto 40px}
.m-2-0-0{margin:2px 0 0}
.m-2-0-10{margin:2px 0 10px}
.m-2-0-6{margin:2px 0 6px}
.mb-0{margin-bottom:0}
.mb-10{margin-bottom:10px}
.mb-12{margin-bottom:12px}
.mb-14{margin-bottom:14px}
.mb-16{margin-bottom:16px}
.mb-18{margin-bottom:18px}
.mb-4{margin-bottom:4px}
.mb-8{margin-bottom:8px}
.minw-220{min-width:220px}
.minw-240{min-width:240px}
.minw-260{min-width:260px}
.ml-auto{margin-left:auto}
.mt-0{margin-top:0}
.mt-10{margin-top:10px}
.mt-12{margin-top:12px}
.mt-14{margin-top:14px}
.mt-16{margin-top:16px}
.mt-18{margin-top:18px}
.mt-22{margin-top:22px}
.mt-2{margin-top:2px}
.mt-6{margin-top:6px}
.mt-8{margin-top:8px}
.mt-4{margin-top:4px}
.mw-160{max-width:160px}
.mw-1120{max-width:1120px}
.mw-120{max-width:120px}
.mw-130{max-width:130px}
.mw-260{max-width:260px}
.mw-280{max-width:280px}
.mw-380{max-width:380px}
.mw-420{max-width:420px}
.mw-560{max-width:560px}
.mw-620{max-width:620px}
.my-14{margin:14px 0}
.my-16{margin:16px 0}
.nowrap{white-space:nowrap}
.p-10-12{padding:10px 12px}
.pt-60{padding-top:60px}
.px-24{padding:0 24px}
.shadow-none{box-shadow:none}
.tac{text-align:center}
.w-110{width:110px}
.w-140{width:140px}
.w-96{width:96px}
.w-160{width:160px}
.w-220{width:220px}
.w-34{width:34px}
.w-60{width:60px}
.w-90{width:90px}
.w-auto{width:auto}
.w-full{width:100%}

/* Zona de riesgo (security-hardening → "Zona de riesgo"): las acciones destructivas van en
   una tarjeta APARTE, con borde de error, separadas de lo cotidiano. La clase se usaba en
   profile.php pero nunca se había definido, así que el bloque se veía como texto suelto — y una
   acción irreversible que no se ve peligrosa es justo lo que el estándar quiere evitar. */
.danger-zone{border:1px solid var(--err);border-radius:var(--radius-lg);padding:16px;
  background:color-mix(in srgb, var(--err) 5%, transparent)}
.danger-zone h3{color:var(--err-strong);margin:0 0 8px}
.danger-zone ul{padding-left:0}

/* ── Actividad de seguridad: filtros y tabla con contexto (mt_52) ─────────── */
.ev-filtros-pie{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-top:14px}
.ev-total{font-size:13px;color:var(--text-2)}
.ev-export{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap}
.ev-cuando{white-space:nowrap}
.ev-hora{font-size:12px;color:var(--text-3)}
/* El evento ocupa tres renglones: qué pasó, qué significa y el dato puntual.
   `display:block` porque son <span> dentro de una celda: en línea, los tres
   quedarían pegados en un párrafo ilegible. */
.ev-titulo{display:block;font-weight:500}
.ev-que{display:block;margin-top:2px;font-size:12px;color:var(--text-2);max-width:52ch}
.ev-detalle{display:block;margin-top:4px;font-size:12px;color:var(--text-3);
            word-break:break-word;max-width:52ch}
.ev-mail,.ev-geo{display:block;font-size:12px;color:var(--text-3)}
.ev-paginas{display:flex;align-items:center;gap:12px;margin-top:14px}
@media (max-width:700px){
  .ev-export{margin-left:0;width:100%}
  .ev-export .btn{flex:1 1 auto}
}

/* ── Parametrizaciones: elegir cómo funciona "Empezá más rápido" ──────────── */
.qs-opt{display:flex;gap:11px;align-items:flex-start;padding:12px 14px;margin-bottom:8px;
        border:1px solid var(--border-field);border-radius:10px;cursor:pointer}
.qs-opt:hover{border-color:var(--accent)}
.qs-opt:has(input:checked){border-color:var(--accent);background:var(--accent-soft)}
.qs-opt input{margin-top:3px;flex:none}
/* SÓLO el título (hijo directo). Sin el `>`, esto agarra también las negritas de
   adentro de la descripción y las tira a un renglón propio. */
.qs-opt > span > strong{display:block;font-size:14px;font-weight:500;margin-bottom:2px}
.qs-opt .muted strong{font-weight:600;color:var(--text-2)}
.qs-opt .muted{font-size:12px;line-height:1.5}
.quickstart-activar{flex-basis:100%;margin:8px 0 0;font-size:12px;color:var(--text-3)}
.quickstart-activar a{color:var(--accent-soft-fg);font-weight:500}

/* ── Instalar el favorito que trae datos de un portal ─────────────────────── */
.bm-drop{border:2px dashed var(--accent);border-radius:12px;background:var(--accent-soft);
         padding:24px;text-align:center}
.bm-btn{display:inline-flex;align-items:center;gap:9px;background:var(--accent);color:var(--accent-fg);
        border-radius:999px;padding:13px 26px;font-size:15px;font-weight:600;text-decoration:none;
        cursor:grab;user-select:none;min-height:44px}
.bm-btn:active{cursor:grabbing}

/* ── Targets táctiles ≥44px (2026-08-04) ──────────────────────────────────────────────
   La suite de UX midió 246 controles por debajo de los 44px mínimos de la guía táctil en
   mobile/tablet: tabs de la galería (26px), tabs del timeline (38px), botones .btn-xs (36px),
   segmentado Fotos/Planos (32px) y la ✕ de borrar foto (37px de ancho). Solo con puntero
   grueso: en desktop el tamaño compacto es correcto y no se toca. min-width cubre los botones
   de solo ícono (✕); en los de texto no cambia nada porque ya son más anchos. */
@media (pointer: coarse) {
  .pgallery-tab, .pf-tl-tab, .btn-xs, .seg-btn { min-height: 44px; min-width: 44px; }
  /* El buscador del mapa mide 36px en desktop (correcto ahí); en táctil la caja entera sube a
     48px para que el input interior (height:100%, ~46px) pase los 44px mínimos. */
  .map-widget-search { height: 48px; }

  /* ── Segunda vuelta (2026-08-25), con la matriz ENTERA corrida por primera vez ───────────
     Los 263 avisos de área táctil de esa corrida no eran 263 problemas: eran 27 selectores, y
     213 de los 263 ya "estaban arreglados" en reglas que preguntaban por el ancho en vez de por
     el puntero (arriba está el detalle). Lo que sigue es lo que además faltaba de raíz.

     El menú lateral: 168 de los 263 avisos, uno por cada ítem del menú en cada pantalla del CRM.
     `padding:9px` + una línea de 21px da 39px, seis por debajo del piso. Es EL control más usado
     del panel y el más fácil de errarle con el pulgar. */
  .nav-item { min-height: 44px; }

  /* `.btn-sm` medía 36px: son los "Destacar / Quitar" de /destacadas, un botón por fila.
     VA ACOTADO A `.prop-list-row` A PROPÓSITO, no suelto. Los 36px de `.btn-sm` NO son un
     descuido: están documentados arriba (ver la regla del componente) como la única reducción
     que admite el estándar (H-40), válida DENTRO de una fila de tabla o lista que ya mida ≥44px,
     porque ahí el objetivo táctil real lo da la fila entera — es de lo que se apoya inbox.php,
     que lo dice en su propio comentario. Subir `.btn-sm` a 44 en todos lados le estiraría las
     filas al Inbox para no ganar nada.
     La fila de /destacadas es la excepción a esa excepción: está armada con <div>, no con
     <tr>/<li>, así que la fila no es un objetivo táctil y el botón queda solo. Ahí sí manda el
     piso de 44. `min-height` le gana al `height:36px` del componente sin repetirlo.
     Medido con 12 propiedades de prueba: 79 avisos, uno por botón por ancho táctil. El número
     crece con el inventario — por eso se arregla en el componente y no en la pantalla. */
  .prop-list-row .btn-sm { min-height: 44px; min-width: 44px; }

  /* Título de una fila de listado: en /destacadas es un <a> de verdad —abre la ficha pública— pero
     su caja es la del TEXTO, así que el alto es el del renglón (21px) y el ancho, el que dé el
     título: medido, de 13×21px el más corto a 284×21px el más largo. 62 avisos con 12 propiedades
     de prueba.
     Va SÓLO sobre el <a>: en /properties el mismo bloque es un <span> inerte, y engordarlo sería
     estirar las filas de la pantalla más transitada del CRM sin que nadie gane un objetivo táctil.

     Acá el área NO se puede robar con padding + margen negativo, que fue el primer intento: la caja
     del link se comía la línea de datos de abajo y la suite lo cantó como 50 SUPERPOSICIONES —
     cambiar 83 avisos por 50 errores es ir para atrás. Así que la fila crece de verdad, que es lo
     que corresponde en táctil: `line-height` de 44px da el alto y de paso centra el renglón.
     `display:block` (no `inline-flex`): un contenedor flex no elipsa su texto anónimo, y con
     inline-flex los títulos largos dejarían de cortarse con puntos suspensivos. */
  a.prop-list-title { display: block; flex: 1 1 0; min-width: 0; line-height: 44px; }


  /* Barra inferior del alta/edición de propiedad. Sus 44px vivían en el `max-width:860px` de más
     arriba JUNTO con el layout de dos filas, que sí es cosa del ancho y tiene que quedarse ahí:
     en una tablet apaisada la barra es de una fila sola, pero se toca igual. Sólo los tamaños. */
  .prop-bottombar .btn-icon-square { width: 44px; height: 44px; }
  .prop-bottombar .pbr-group .btn-activate { min-height: 44px; }
  /* El segmento tiene que crecer con el tile que contiene, si no lo recorta. */
  .prop-bottombar .pbr-seg { width: 44px; }

  /* El conmutador Tarjetas/Lista de /properties: `padding:7px` sobre una línea de 18px da 32px.
     Son dos botones fijos en la barra de la pantalla más transitada del CRM, presentes en los
     siete anchos táctiles (14 avisos). Sube el alto y no toca el ancho: ya son anchos de sobra
     porque llevan ícono + palabra. */
  .view-toggle-btn { min-height: 44px; }

  /* Checkbox/radio nativo SIN estilar: el navegador lo dibuja de 13×13 y no crece bien con CSS,
     así que lo que hay que agrandar es el <label> que lo envuelve — que es además el área que
     de verdad activa el control. Es la misma técnica que ya usaban `.cell-check` y `.chip-check`
     (y `label.fw-400` acá arriba), generalizada: aquellas cubrían las tres tablas donde alguien
     se acordó de ponerles clase, y dejaban afuera los checkboxes de tipos de campo de /screens y
     los sueltos de la barra de filtros.
     `:not([class])` acota la regla a los labels SIN clase, que son exactamente los que nadie
     diseñó: cualquier label con clase propia (los switches del form de propiedad, las fichas de
     comodidades) ya tiene su tamaño pensado y no se toca. */
  label:not([class]):has(> input[type="checkbox"]),
  label:not([class]):has(> input[type="radio"]) {
    display: inline-flex; align-items: center; gap: 6px; min-height: 44px;
  }
}

/* ── Mapa SIEMPRE claro (decisión de Máximo, 2026-08-05) ──────────────────────────────
   Se probó un modo oscuro del mapa (filtro invert+hue-rotate en el canvas) y se descartó: el
   mapa se mantiene claro estilo Google Maps también con la UI en tema oscuro — mismo criterio
   que usa Google en la web. Si algún día se quiere dark real, va por un style.json alternativo,
   no por filtro. */

/* ── Aviso de versión nueva ("Se actualizó Momix") ───────────────────────────────────
   Va al FINAL del archivo a propósito: la regla que lo despega del bottom-nav en mobile tiene que
   ganar por orden de cascada al `bottom` de arriba. Ya pasó con .prop-bottombar y con
   .pf-actions-fixed-bar — puesta en el lugar "obvio" (junto al resto de sus estilos) no servía de
   nada porque quedaba ANTES en el archivo.
   z-index 60: por encima del bottom-nav (50), que si no lo tapa justo en el ancho donde conviven. */
.release-toast{
  position:fixed; left:50%; transform:translateX(-50%); bottom:20px; z-index:60;
  display:flex; align-items:center; gap:12px; max-width:calc(100vw - 32px);
  padding:10px 10px 10px 18px; border-radius:var(--radius-lg);
  background:var(--surface); border:1px solid var(--border-strong);
  box-shadow:0 10px 30px rgba(8,17,31,.18); font-size:14px;
}
.release-toast .btn{flex:none}
.release-toast-x{
  flex:none; width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  background:none; border:0; cursor:pointer; color:var(--text-3); font-size:15px; border-radius:var(--radius-md);
}
.release-toast-x:hover{color:var(--text-1); background:var(--surface-2)}
@media (max-width:640px){
  /* Arriba del bottom-nav (76px es el mismo número que usa .main .content para reservarle espacio). */
  .release-toast{bottom:88px; left:16px; right:16px; transform:none; max-width:none}
}

/* Propietario cargado por otro broker (mt_82): se dice que existe y que no se toca, sin decir quién
   es. Mismo tratamiento visual que un campo deshabilitado, pero con texto legible — no es un error
   ni una falta de permiso a resolver, es cómo funciona. */
.owner-locked{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;font-size:13px;line-height:1.45;
  color:var(--text-2);background:var(--surface-2);border:1px dashed var(--border-field);border-radius:var(--radius-md)}
.owner-locked svg{flex:none;margin-top:2px;color:var(--text-3)}

/* Vínculo con el aviso del portal (mt_84): el postingId es la llave que impide publicar un
   duplicado, y hasta ahora no se veía en ninguna pantalla. */
.zp-link{display:grid;grid-template-columns:auto 1fr;gap:2px 10px;margin:0 0 12px;font-size:13px}
.zp-link dt{color:var(--text-3)}
.zp-link dd{margin:0;font-variant-numeric:tabular-nums}

/* Sugerencias por campo de ubicación (mt_85). Se posiciona sobre el campo, como el resto de los
   combobox del formulario; el contenedor .field ya es relativo. */
.place-suggest{position:absolute;z-index:30;left:0;right:0;top:100%;margin-top:2px;max-height:230px;
  overflow:auto;background:var(--surface);border:1px solid var(--border-strong);
  border-radius:var(--radius-md);box-shadow:0 10px 24px rgba(8,17,31,.14)}
.place-suggest-item{display:block;width:100%;text-align:left;padding:9px 12px;background:none;border:0;
  cursor:pointer;font:inherit;font-size:13px;color:var(--text-1)}
.place-suggest-item:hover,.place-suggest-item:focus-visible{background:var(--surface-2)}
.location-grid .field{position:relative}

/* ── Color por MOTIVO de suspensión (2026-08-11) ─────────────────────────────────────
   Antes los cinco motivos compartían el ámbar de "Suspendida" y había que LEER la palabra para
   saber si la propiedad se vendió o se archivó. Ahora el color lo dice primero:
     gris    Pausada / Reservada  — en pausa, puede volver
     celeste Vendida / Alquilada  — se concretó (lleva tilde, es una buena noticia)
     rojo    Archivada            — sale de circulación
   Se pinta sobre el mismo `.st-dot` y `.st-badge` que ya usan la ficha y la edición: un solo
   lenguaje visual para los dos widgets, que es lo que se pidió. */
.st-dot[data-tone="gris"]{background:var(--text-3)}
.st-dot[data-tone="ok"]{background:var(--info)}
.st-dot[data-tone="err"]{background:var(--err)}
.st-badge[data-tone="gris"]{background:var(--surface-2);color:var(--text-2)}
.st-badge[data-tone="ok"]{background:var(--info-soft);color:var(--info-strong)}
.st-badge[data-tone="err"]{background:var(--err-soft);color:var(--err-strong)}
/* La tilde de "se concretó": va en el propio badge, sin un elemento extra que haya que mantener
   sincronizado desde el server. */
.st-badge[data-tone="ok"] [data-st-label]::before{content:"✓ ";font-weight:600}
.menu-item .st-dot[data-tone="ok"]+*{font-weight:500}

/* Ubicación en mobile (bug reportado 2026-08-12, iPhone/Safari): los campos se salían del card.
   Causa: `.location-grid` es un grid y sus ítems tienen `min-width:auto` implícito — o sea que NO
   se achican por debajo del ancho de su contenido. Con la dirección larga que quedó cargada
   ("Larralde Crisologo, Ciudad Autónoma de Buenos Aires…") la columna se estiraba y arrastraba a
   todo el bloque fuera del card. Es el mismo error que ya apareció en los botones de la ficha.
   `min-width:0` es lo que le devuelve el permiso de encogerse; `max-width:100%` en los inputs
   evita que un `size` por defecto vuelva a empujar. */
.location-grid > *{min-width:0}
.location-grid .field,.location-grid .form-grid-2{min-width:0}
.location-grid input.input{max-width:100%}
/* El hint largo también empujaba: se le permite cortar palabra si no entra de otra forma. */
.location-grid .hint{overflow-wrap:anywhere}

/* ── Enrolamiento del 2FA: QR + clave escrita (2026-08-14) ────────────────────────────
   El QR es el camino normal y la clave queda como salida cuando no se puede escanear. En pantalla
   angosta se apilan; el QR NUNCA se achica por debajo de su tamaño natural, porque un QR chico dejа
   de leerse (cada módulo tiene que quedar de varios píxeles). */
.totp-setup{display:flex;flex-wrap:wrap;gap:22px;align-items:flex-start;margin-top:12px}
.totp-qr{flex:none;line-height:0;background:#FFFFFF;padding:10px;border-radius:var(--radius-md);
  border:1px solid var(--border-default)}
.totp-manual{flex:1 1 260px;min-width:0}
/* La alternativa a escanear va cerrada: el QR es el camino normal. 44px de alto porque es un
   control operable (H-28), aunque sea un <summary>. */
.totp-alt > summary{display:inline-flex;align-items:center;min-height:44px;cursor:pointer;
  color:var(--accent-soft-fg);font-size:13px;font-weight:500}
.totp-alt > summary:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:6px}

/* ============================================================================
   Momix Global — vidriera pública (/global, /inmobiliaria/[slug]) · 2026-08
   Marketplace de solo lectura, estética tipo Zillow (tarjetas grandes con foto,
   precio destacado, mucho aire) sobre el sistema de diseño de Momix. Todo por
   tokens → sirve igual si algún día estas páginas heredan modo oscuro. Los .mg-*
   conviven con los .pub-* de la ficha pública (body.pub) sin pisarlos.
   ============================================================================ */

/* ── Cabecera de la vidriera ── */
.mg-top{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:14px 20px;border-bottom:1px solid var(--border-default);background:var(--surface)}
.mg-brand{gap:8px}
.mg-brand-sub{margin-left:6px;padding:2px 7px;border-radius:var(--radius-pill);
  background:var(--accent-soft);color:var(--accent-soft-fg);font-size:11px;font-weight:600;
  letter-spacing:.04em;text-transform:uppercase;vertical-align:middle}
.mg-top-home{color:var(--text-2);font-size:13px;font-weight:500;text-decoration:none;white-space:nowrap}
.mg-top-home:hover{color:var(--text-1)}

.mg-wrap{max-width:1200px;margin:0 auto;padding:20px 20px 48px}

/* ── Hero (solo /global) ── */




/* ── Barra de filtros ── */
.mg-filters{background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-lg);
  padding:16px;margin:16px 0 24px}
.mg-filters .field{margin:0}
.mg-filters label,.mg-filters .field-label{display:block;font-size:12px;font-weight:600;color:var(--text-2);
  margin-bottom:5px}
.mg-filters-search{margin-bottom:12px}
.mg-filters-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px}
.mg-price-field{grid-column:1/-1}
.mg-price-inputs{display:flex;gap:8px;max-width:460px}
.mg-price-inputs .input{min-width:0;flex:1}
.mg-price-inputs .mg-cur{flex:0 0 auto;width:auto;min-width:96px}
.mg-filters-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px}

/* ── Inmobiliarias (franja en /global) ── */









/* ── Barra de resultados (cantidad + orden) ── */
.mg-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:16px}
.mg-count{font-size:14px;font-weight:600;color:var(--text-1);margin:0}
.mg-sort{display:flex;align-items:center;gap:8px}
.mg-sort-lbl{font-size:13px;color:var(--text-2)}
.mg-sort .input{height:40px;width:auto;min-width:170px}

/* ── Grilla de tarjetas (Zillow-like) ── */
.mg-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,250px),1fr));gap:20px}
.mg-card{display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border-default);
  border-radius:var(--radius-lg);overflow:hidden;transition:box-shadow .16s ease,transform .16s ease}
.mg-card:hover{box-shadow:0 10px 28px rgba(2,6,23,.12);transform:translateY(-2px)}
.mg-card-media{position:relative;display:block;aspect-ratio:4/3;background:var(--surface-2);overflow:hidden}
.mg-card-media img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .25s ease}
.mg-card:hover .mg-card-media img{transform:scale(1.03)}
.mg-noimg{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--text-3)}
.mg-noimg svg{width:44px;height:44px}
.mg-op-badge{position:absolute;top:10px;left:10px;background:rgba(15,23,42,.82);color:#fff;
  font-size:12px;font-weight:600;padding:4px 10px;border-radius:var(--radius-pill);backdrop-filter:blur(2px)}
.mg-card-body{display:flex;flex-direction:column;gap:7px;padding:14px 16px;flex:1}
.mg-price{font-size:20px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--text-1);margin:0}
.mg-card-title{font-size:15px;font-weight:500;line-height:1.35;margin:0}
.mg-card-title a{color:var(--text-1);text-decoration:none;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.mg-card-title a:hover{color:var(--accent-soft-fg)}
.mg-card-loc{font-size:13px;color:var(--text-2);margin:0}
.mg-chips{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:13px;color:var(--text-2);margin-top:1px}
.mg-chip{display:inline-flex;align-items:center;gap:5px}
.mg-chip svg{width:15px;height:15px;color:var(--text-3);flex:none}
.mg-card-agency{display:flex;align-items:center;gap:8px;margin-top:2px;padding-top:10px;
  border-top:1px solid var(--border-default);text-decoration:none}
.mg-card-agency-name{font-size:12px;font-weight:500;color:var(--text-2);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
a.mg-card-agency:hover .mg-card-agency-name{color:var(--text-1)}
/* flex-wrap + base de 120px: en tarjetas anchas van lado a lado; cuando no entran dos de ≥120px
   (tarjeta angosta, ~≤360px), envuelven a ancho completo apilados en vez de desbordar. min-width:0
   deja que el texto se achique antes de empujar. */
.mg-card-actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:auto;padding-top:12px}
.mg-cta{flex:1 1 120px;min-width:0;height:44px;padding:0 10px;font-size:13px;white-space:nowrap}
.mg-cta-wa svg{flex:none}

/* ── Estado vacío ── */
.mg-empty{text-align:center;padding:56px 20px;background:var(--surface);
  border:1px dashed var(--border-strong);border-radius:var(--radius-lg)}
.mg-empty h2{font-size:18px;margin:0 0 6px}

/* ── Paginación ── */
.mg-pagination{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;margin:32px 0 8px}
.mg-page-link{display:inline-flex;align-items:center;justify-content:center;min-width:40px;height:40px;
  padding:0 12px;border-radius:var(--radius-md);border:1px solid var(--border-default);background:var(--surface);
  color:var(--text-1);font-size:14px;font-weight:500;text-decoration:none;transition:border-color .15s,background .15s}
.mg-page-link:hover{border-color:var(--accent);color:var(--text-1)}
.mg-page-link.is-current{background:var(--accent);color:var(--accent-fg);border-color:var(--accent);cursor:default}
.mg-page-link.is-disabled{opacity:.45;pointer-events:none}
.mg-page-gap{color:var(--text-3);padding:0 2px}

/* ── Página de una inmobiliaria ── */
.mg-crumb{font-size:13px;color:var(--text-2);margin:4px 0 14px}
.mg-crumb a{color:var(--accent-soft-fg);text-decoration:none}
.mg-crumb a:hover{text-decoration:underline}
.mg-crumb-cur{color:var(--text-1);font-weight:500}
.mg-agency-head{display:flex;align-items:center;gap:16px;margin:0 0 20px;padding:20px;
  background:var(--surface);border:1px solid var(--border-default);border-radius:var(--radius-lg)}
.mg-agency-head-txt h1{font-size:24px;font-weight:600;margin:0 0 4px}
.mg-agency-head-txt p{margin:0}

.mg-foot{margin-top:16px}

/* Mobile: hero y títulos más chicos, una columna real */
@media (max-width:640px){

  .mg-agency-head-txt h1{font-size:20px}
  .mg-top-home{display:none}
}

/* ══ CARD ADMIN · Reconversión y metadatos (components/admin_reconvert_card.php) ═══════════════
   Zona compartida: este bloque toca SOLO clases con prefijo `adm-`, ninguna existente.
   Solo se renderiza con el Modo Admin prendido y para plataforma (regla dura 8), así que estos
   estilos nunca llegan a la pantalla de un cliente — pero igual respetan los tokens del tema,
   porque el SuperAdmin usa la app en claro y en oscuro como cualquiera. */
.adm-card { border-left:3px solid var(--warn, #F59E0B); }
.adm-sum { display:flex; align-items:center; gap:10px; cursor:pointer; font-weight:600;
  list-style:none; padding:2px 0; }
.adm-sum::-webkit-details-marker { display:none; }
.adm-sum::before { content:"▸"; opacity:.5; transition:transform .2s; }
.adm-card[open] .adm-sum::before { transform:rotate(90deg); }
.adm-tag { font:700 10px/1 ui-monospace,monospace; letter-spacing:.08em; padding:4px 7px;
  border-radius:5px; background:var(--warn, #F59E0B); color:#08111F; }
.adm-state { margin-left:auto; font:600 11px/1 ui-monospace,monospace; opacity:.7;
  border:1px solid var(--bd); border-radius:999px; padding:4px 9px; }
.adm-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:10px;
  margin:14px 0; }
.adm-k { display:block; font-size:11px; opacity:.65; }
.adm-v { display:block; font:600 13.5px ui-monospace,monospace; overflow-wrap:anywhere; }
.adm-avisos { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:14px;
  margin:14px 0; }
.adm-aviso { border:1px solid var(--bd); border-radius:10px; padding:12px; min-width:0; }
.adm-aviso h4 { margin:0 0 8px; font-size:13px; }
.adm-aviso > div { margin-bottom:6px; }
.adm-mini { font-weight:400; opacity:.6; font-size:11.5px; }
.adm-link { display:inline-block; margin-top:6px; font-size:12.5px; }
.adm-h { margin:16px 0 8px; font-size:13px; }
.adm-nota { font-size:13px; opacity:.75; margin:10px 0 0; }
.adm-err { font-size:13px; color:var(--danger, #EF4444); margin:10px 0 0; overflow-wrap:anywhere; }
/* Bitácora ZonaProp (card ADMIN de la ficha, components/admin_zpp_log_card.php). Mismo lenguaje que
   el PDF del camino Momix→ZonaProp: verde = el portal aceptó · rojo = rechazó o se cortó · ámbar =
   hubo reintento · gris = no hizo falta / no llegó. Todo con tokens: se ve igual en claro y oscuro. */
.zlog-intentos { list-style:none; margin:0; padding:0; display:grid; gap:10px; }
.zlog-intento { border:1px solid var(--bd); border-radius:10px; padding:0; background:var(--card, transparent); }
.zlog-intento-sum { display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px; padding:10px 12px; cursor:pointer;
  list-style:none; min-height:44px; font-size:13px; }
.zlog-intento-sum::-webkit-details-marker { display:none; }
.zlog-res { display:inline-grid; place-items:center; width:22px; height:22px; border-radius:50%; font:700 12px/1 ui-monospace,monospace; flex:none; }
.zlog-res.is-ok  { background:rgba(16,185,129,.15); color:var(--ok, #059669); }
.zlog-res.is-err { background:rgba(239,68,68,.15);  color:var(--danger, #DC2626); }
.zlog-cuando { font:600 12.5px ui-monospace,monospace; opacity:.8; }
.zlog-accion { font-weight:600; }
.zlog-resumen { opacity:.75; flex:1 1 260px; min-width:0; overflow-wrap:anywhere; }
.zlog-pasos { list-style:none; margin:0; padding:4px 12px 12px 12px; position:relative; }
.zlog-paso { display:grid; grid-template-columns:28px minmax(0,1fr); gap:0 10px; padding:6px 0; position:relative; }
.zlog-paso + .zlog-paso::before { content:""; position:absolute; left:13px; top:-6px; height:12px; border-left:2px solid var(--bd); }
.zlog-n { display:inline-grid; place-items:center; width:26px; height:26px; border-radius:50%; font:700 11.5px/1 ui-monospace,monospace;
  border:2px solid var(--bd); background:var(--card, transparent); color:inherit; }
.zlog-paso.is-ok .zlog-n       { border-color:var(--ok, #059669); background:rgba(16,185,129,.12); color:var(--ok, #059669); }
.zlog-paso.is-error .zlog-n    { border-color:var(--danger, #DC2626); background:rgba(239,68,68,.12); color:var(--danger, #DC2626); }
.zlog-paso.has-retry .zlog-n   { box-shadow:0 0 0 3px rgba(245,158,11,.25); }
.zlog-paso.is-aviso .zlog-n    { border-color:var(--warn, #B45309); background:rgba(245,158,11,.12); color:var(--warn, #B45309); }
.zlog-pill.is-aviso { color:var(--warn, #B45309); border-color:rgba(245,158,11,.5); }
.zlog-paso.is-aviso .zlog-motivo { border-left-color:var(--warn, #B45309); background:rgba(245,158,11,.08); }

.zlog-paso.is-omitido, .zlog-paso.is-no_llego { opacity:.55; }
.zlog-body { min-width:0; }
.zlog-head { display:flex; flex-wrap:wrap; align-items:center; gap:6px 10px; min-height:26px; }
.zlog-titulo { font-weight:600; font-size:13px; }
.zlog-pill { font:600 11px/1 ui-monospace,monospace; padding:4px 7px; border-radius:999px; border:1px solid var(--bd); opacity:.85; }
.zlog-pill.is-ok    { color:var(--ok, #059669); border-color:rgba(16,185,129,.4); }
.zlog-pill.is-error { color:var(--danger, #DC2626); border-color:rgba(239,68,68,.4); }
.zlog-pill.is-retry { color:var(--warn, #B45309); border-color:rgba(245,158,11,.5); }
.zlog-lineas { list-style:none; margin:4px 0 0; padding:0; font-size:12.5px; }
.zlog-lineas li { display:flex; gap:8px; padding:2px 0; min-width:0; }
.zlog-lineas li span { overflow-wrap:anywhere; min-width:0; }
.zlog-hora { font:500 11.5px/1.6 ui-monospace,monospace; opacity:.6; flex:none; }
.zlog-motivo { margin:6px 0 0; padding:8px 10px; border-left:3px solid var(--danger, #DC2626); background:rgba(239,68,68,.07);
  font-size:12.5px; overflow-wrap:anywhere; }
/* MEJORA-9: mientras publica, avisar que se puede salir. Discreto a propósito — es un permiso,
   no una alarma: lo que no tiene que pasar es que alguien se quede mirando la barra sin saberlo. */
.zpp-podes-salir { opacity:.75; font-style:italic; }
/* Contadores de la carga (MEJORA-8 descripción, MEJORA-10 fotos). Gris mientras falta, verde
   cuando alcanza: el color hace el trabajo de un vistazo y el texto dice el número exacto. */
.char-counter { margin:6px 0 0; font:12.5px/1.5 ui-monospace,Menlo,Consolas,monospace; opacity:.6; transition:color .2s, opacity .2s; }
/* `--ok-strong`, no `--ok`: el primero es el token PARA TEXTO (lo dice el comentario de la
   paleta), el segundo es para rellenos. Con `--ok` el contador en verde daba 2,53:1 sobre
   blanco —la mitad del 4,5:1 que pide WCAG AA— y era el último error de contraste que quedaba
   en toda la app (medido con axe, 06/09/2026). Su hermano ámbar ya usaba `--warn-strong`: esto
   lo pone a la par. */
.char-counter.is-ok { color:var(--ok-strong); opacity:1; font-weight:600; }
.zlog-desv { margin:12px 0 0; border:1px solid var(--bd); border-left:3px solid var(--danger, #DC2626); border-radius:8px; padding:0 12px; }
.zlog-desv > summary { cursor:pointer; min-height:44px; display:flex; align-items:center; font-weight:600; font-size:13px; color:var(--danger, #DC2626); }
.zlog-desv > summary::-webkit-details-marker { display:none; }
.zlog-desv form { padding:0 0 12px; }
.zlog-otros { margin:0 12px 12px; font-size:12.5px; }
.zlog-otros > summary { cursor:pointer; min-height:44px; display:flex; align-items:center; opacity:.8; }
.zlog-pre { margin:0; padding:10px; font:12px/1.5 ui-monospace,monospace; white-space:pre-wrap; overflow-wrap:anywhere;
  border:1px solid var(--bd); border-radius:8px; max-height:420px; overflow:auto; }
.adm-warn { border-left:3px solid var(--warn, #F59E0B); background:rgba(245,158,11,.08);
  border-radius:0 8px 8px 0; padding:12px 14px; margin:14px 0; font-size:13px; line-height:1.6; }
.adm-traza { margin:0; padding-left:18px; font-size:12.5px; max-height:280px; overflow:auto; }
.adm-traza li { margin-bottom:7px; }
.adm-t-at { font:400 11px ui-monospace,monospace; opacity:.6; margin-right:7px; }
.adm-t-paso { font-weight:600; margin-right:7px; }
.adm-t-det { opacity:.85; overflow-wrap:anywhere; }
/* Los controles operables mantienen los 44px de la norma aunque sean `btn-xs`. */
.adm-acciones { display:flex; flex-wrap:wrap; gap:9px; align-items:center; margin-top:16px; }
.adm-acciones .btn { min-height:44px; }
.adm-f { margin:0; }

/* ── /duplicados · comparación de duplicados (2026-08-27) ──────────────────────────────────────
   Fondo AMARILLO en los valores que difieren, portada, y "Ver todos los campos" sin JS (checkbox). */
td.dd-diff { background: #FDE68A; color: #1F2937; font-weight: 600; }
/* El override oscuro va DENTRO del @media: sin él, `:root:not([data-theme=light])` matchea también el
   modo "system" con preferencia CLARA y pintaba el amarillo de oliva (bug 2026-08-27). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) td.dd-diff { background: #6B5A1A; color: #FDE68A; }
}
[data-theme="dark"] td.dd-diff { background: #6B5A1A; color: #FDE68A; }
/* ── ¿Este campo se compara, y coincide? (2026-09-01) ──────────────────────────────────────────
   El amarillo de arriba dice "estos valores son distintos" y se pinta en cualquier campo, se use o
   no para detectar duplicados. Leído de corrido parecía decir "por esto son duplicados". Esta
   columna dice la otra mitad, y solo aparece en los campos que SÍ se comparan: ✓ coinciden,
   ✕ no coinciden. En los demás queda vacía, que es cómo se ve "este campo no interviene". */
td.dd-cmp, th.dd-cmp { width: 34px; text-align: center; padding-left: 2px; padding-right: 2px; }
.dd-cmp-ok, .dd-cmp-x { font-weight: 700; font-size: 15px; line-height: 1; cursor: help; }
.dd-cmp-ok { color: var(--ok, #059669); }
.dd-cmp-x  { color: var(--err, #DC2626); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .dd-cmp-ok { color: #34D399; }
  :root:not([data-theme="light"]) .dd-cmp-x  { color: #F87171; }
}
[data-theme="dark"] .dd-cmp-ok { color: #34D399; }
[data-theme="dark"] .dd-cmp-x  { color: #F87171; }
/* Diferencia en un campo que NO se compara: se ve, pero sin gritar. El amarillo queda reservado para
   los campos que de verdad deciden si dos fichas son la misma (Máximo, 02/09/2026: "que las que no
   se analizan estén con un gris, así no da la sensación de error").
   GRIS AZULADO y no el gris de la superficie (segunda vuelta, mismo día): con `--surface-2` la celda
   quedaba casi igual que el fondo de la tabla y la diferencia no se distinguía de un campo igual.
   Tiene que notarse que ahí hay algo distinto —para elegir cuál ficha conservar— sin competir con el
   amarillo. Va con borde izquierdo: el color solo, tan suave, se pierde en una fila con muchas celdas. */
td.dd-difsuave { background: #E2E8F5; color: #334155; box-shadow: inset 3px 0 0 #94A3B8; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) td.dd-difsuave { background: rgba(129,153,196,.22); color: #DBE3F0; box-shadow: inset 3px 0 0 #64748B; }
}
[data-theme="dark"] td.dd-difsuave { background: rgba(129,153,196,.22); color: #DBE3F0; box-shadow: inset 3px 0 0 #64748B; }
tr.dd-secondary { display: none; }
.dd-more-cb:checked ~ .table-wrap tr.dd-secondary { display: table-row; }
.dd-cover img { width: 104px; height: 78px; object-fit: cover; border-radius: 6px; display: block; }
/* /duplicados · panel de config (grilla de switches) y separación entre grupos (2026-08-28). */
.dd-fields-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 4px 18px; }
.dd-group + .dd-group { border-top: 1px solid var(--border-default); padding-top: 14px; }
/* Icono "i" de ayuda (usa el tooltip [data-tip] del proyecto). Chico, accesible por foco. */
.info-i{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;margin-left:5px;
  border-radius:50%;border:1px solid currentColor;font-size:10px;font-style:italic;font-weight:700;line-height:1;
  cursor:help;opacity:.6;vertical-align:middle}
.info-i:hover,.info-i:focus-visible{opacity:1}
/* Contador dentro del chip "Con cambios en foto". */
.mkt-table-filterbar .chip-badge{background:color-mix(in srgb,var(--warn) 22%,transparent);color:var(--warn-strong);
  font-size:11px;font-weight:700;padding:0 6px;border-radius:999px;min-width:16px;text-align:center}
.mkt-table-filterbar .chip.is-on .chip-badge{background:rgba(255,255,255,.28);color:var(--warn-fg)}

/* Marketplace · popup "ver diferencias" del scan (⇄) y links de la columna Link. */
.mkt-links{display:flex;flex-direction:column;gap:3px;align-items:flex-start}
.mkt-diff-btn{border:1px solid var(--border-default);background:var(--surface);color:var(--text-2);
  border-radius:7px;min-width:30px;min-height:30px;font-size:14px;line-height:1;cursor:pointer}
.mkt-diff-btn:hover{border-color:var(--accent);color:var(--text-1)}
.mkt-diff-btn:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
.mkt-diff-box{max-width:min(680px,94vw);text-align:left}
.mkt-diff-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}
.mkt-diff-pre{margin:0;max-height:56vh;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;
  font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:var(--surface-2);
  border:1px solid var(--border-default);border-radius:9px;padding:12px}
/* Tarjetas del listado: precio de las OTRAS operaciones debajo del principal (venta + alquiler). */
.prop-price-sub,.prop-list-price .prop-price-sub{display:block;font-size:12px;font-weight:500;color:var(--text-3);white-space:nowrap}

/* ── Número de cada foto, con el criterio con el que se publica (Máximo, 02/09/2026) ───────────
   ZonaProp muestra el aviso con una portada grande y las cuatro siguientes al costado, y respeta el
   orden que se le manda. Al publicar, Momix ordena la portada primero, así que acá la portada es la
   1 aunque en la grilla esté en el medio. Las cinco primeras van destacadas: son las que el portal
   muestra. El resto lleva su número en gris, para poder ordenar sin perder la cuenta. */
/* ARRIBA A LA IZQUIERDA, no abajo: abajo vive el botón "Portada" y el número se le montaba encima
   (visto en el navegador, 02/09/2026 — se leía "…ortada" detrás del círculo). Arriba comparte
   renglón con el cartel de Portada, que es su vecino natural: los dos dicen QUÉ es esta foto,
   mientras que los de abajo dicen qué se puede HACER con ella. */
.manage-num{position:absolute;left:6px;top:6px;z-index:3;min-width:20px;height:20px;padding:0 5px;
  display:inline-flex;align-items:center;justify-content:center;border-radius:999px;
  font:600 11px/1 var(--font-sans);background:rgba(15,23,42,.62);color:#fff;
  backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
/* El cartel de Portada corre a la derecha del número, en la misma línea. Sin `:has()` a propósito:
   el número lo pone el JS en TODAS las fotos y el cartel solo existe en una foto, así que siempre
   conviven — y si algún navegador no soportara `:has()`, volverían a pisarse. */
.manage-cover-tag{left:32px}
/* Las que ZonaProp muestra: con el color de acento, para que se vean como un grupo. */
.manage-num.is-zp{background:var(--accent);color:var(--accent-fg,#08111F);font-weight:700}

/* ── Así se va a ver la publicación (MX-068) ───────────────────────────────────────────────────
   Las CINCO PRIMERAS fotos de la grilla se dibujan como el portal arma la galería del aviso: la
   portada grande a la izquierda y las cuatro que la acompañan en 2×2 al costado. De la sexta en
   adelante siguen abajo, en la grilla de siempre.
   No es una vista aparte con copias —esa fue la primera versión y las cinco fotos aparecían dos
   veces, con la portada en dos lugares distintos—: son LAS MISMAS miniaturas, así que conservan su
   número, el cartel de Portada y sus botones.
   Cuatro columnas: la 1ª foto ocupa 2×2, la 2ª a la 5ª caen en las dos columnas restantes, y la 6ª
   arranca sola en la tercera fila. Cuando hay menos de cinco, el auto-placement rellena los huecos
   y no queda ningún espacio muerto. */
.manage-grid.zp-layout{grid-template-columns:repeat(4,1fr)}
.manage-grid.zp-layout>.manage-img:nth-child(1){grid-column:span 2;grid-row:span 2}
.manage-grid.zp-layout>.manage-img{aspect-ratio:1}
.photo-zp-note{margin:0 0 8px}
/* Aviso de "cambio deshecho" — corto y sin ocupar lugar cuando está vacío. */
.photo-aviso{margin:0 0 6px;min-height:1.2em;color:var(--accent-text,var(--accent));font-weight:600}
/* Mientras se arrastra: el resto se apaga y la foto que se mueve se ve como el HUECO donde va a
   caer. La numeración se recalcula en vivo, así que el número que se lee en cada miniatura es el
   que va a quedar al soltar. */
.manage-grid.is-reordenando>.manage-img{opacity:.55;transition:opacity .12s}
.manage-grid.is-reordenando>.manage-img.is-dragging{opacity:1}
.manage-img.is-dragging{outline:3px dashed var(--accent);outline-offset:-3px;border-radius:8px}
.manage-img.is-dragging img{filter:grayscale(.35) brightness(.9)}
@media (max-width:560px){
  .manage-grid.zp-layout{grid-template-columns:repeat(2,1fr)}
  .manage-grid.zp-layout>.manage-img:nth-child(1){grid-column:1 / -1;grid-row:auto;aspect-ratio:16/9}
}

/* ══ Formulario de alta/edición de propiedad — vuelta de UX del 2026-09-04 ═══════════════════
   Todo lo de acá sale de los dos videos que grabó Máximo cargando y modificando una propiedad.
   Va al final del archivo (antes de la guarda [hidden], que tiene que seguir siendo la última
   regla con `display`) porque style.css es zona compartida entre agentes: agregar no pisa a nadie.

   MEJORA-12 — el bloque de ambientes «aparece de una manera muy extraña… todo en una línea sería
   lo ideal». Los cinco contadores van en UNA fila que se acomoda sola: cinco columnas cuando el
   ancho da, y de a menos cuando no. `auto-fit` y no un número fijo porque el form vive adentro de
   un container query (con el sidebar puesto no mide lo mismo que sin él) y una cuenta escrita a
   mano se equivoca en la mitad de los casos. `min-width:0` porque un ítem de grid trae
   `min-width:auto` implícito y sin esto no se achica: es el desborde en mobile que ya pasó dos
   veces en este proyecto. */
.form-row-counts{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:0 16px}
.form-row-counts > *{min-width:0}
@container propform (max-width:640px){.prop-form .form-row-counts{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.form-row-counts{grid-template-columns:repeat(2,1fr)}}

/* MEJORA-13 — «poner un lápiz al lado de los metros que yo pueda hacer clic y eso lo habilite. Así
   queda más minimalista». Reemplaza al link «Ajustar manualmente», que ocupaba un renglón entero
   debajo del valor. La caja del valor pasa a `min-height` para que el botón entre con sus 44px de
   área táctil sin recortarse (el `height:38px` fijo de antes lo hubiera cortado). */
.calc-field .sqm-total-stat{height:auto;min-height:44px;gap:6px}
.calc-pencil{flex:none;width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;
  border:0;background:transparent;color:var(--text-3);cursor:pointer;border-radius:var(--radius-md);
  transition:color .12s,background .12s}
.calc-pencil svg{width:16px;height:16px}
.calc-pencil:hover{color:var(--accent);background:var(--surface-2)}
.calc-pencil:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}

/* MEJORA-14 — «Seleccionar todas» al lado de «Limpiar», y también por grupo, «con un ícono… más
   minimalista para que no ocupe tanto lugar». Misma altura y mismo borde que `.amc-clear` para que
   se lean como un par, pero cuadrado: es la mitad del ancho de un botón con texto, que es
   exactamente lo que pidió. `is-on` = el grupo ya está entero marcado (el botón desmarca). */
.amc-icon-btn{flex:none;width:44px;height:44px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--border-default);background:transparent;color:var(--text-2);
  border-radius:var(--radius-md);cursor:pointer;transition:color .12s,border-color .12s,background .12s}
.amc-icon-btn svg{width:16px;height:16px}
.amc-icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}
.amc-icon-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.amc-icon-btn.is-on{border-color:var(--accent);color:var(--accent)}
.amc-sec-all{margin-left:8px;align-self:center}
@media (max-width:640px){.amc-icon-btn{width:44px;height:44px}}

/* BUG-30 — los DOS NIVELES de mínimo, en color. Lo de Momix BLOQUEA y ya se pinta ámbar en el grupo
   «Necesario para activar»; lo del portal solo ADVIERTE, así que vive en «Recomendado» y recién se
   pinta cuando el destino elegido ES ese portal (`.is-portal-warn` en el contenedor). Sin destino
   elegido queda en gris como cualquier otra recomendación: no hay a quién culpar todavía. */
.quality-portal-warn{display:flex;align-items:flex-start;gap:7px;margin:8px 0 0;padding:8px 10px;
  border-radius:var(--radius-md);background:var(--warn-soft);border:1px solid rgba(245,158,11,.35);
  font:400 12px/1.45 var(--font-sans);color:var(--warn-strong)}
.quality-portal-warn-ico{flex:none;margin-top:1px;color:var(--warn-strong)}
.quality-portal-warn-ico svg{width:14px;height:14px}
.quality-checklist.is-portal-warn .quality-check-item.is-portal:not(.is-done){color:var(--warn-strong)}
.quality-checklist.is-portal-warn .quality-check-item.is-portal:not(.is-done) .quality-check-badge{
  border-color:var(--warn);background:var(--warn-soft)}

/* BUG-28 + MEJORA-18 — el contador de fotos y el de la descripción, con la misma escalera: gris
   mientras falta lo que bloquea, ámbar mientras falta lo que recomienda el portal, verde al llegar.
   El `hidden` de entrada lo pone el JS (no aparece hasta la primera letra / la primera foto). */
.char-counter.is-portal{color:var(--warn-strong);opacity:1}

/* MEJORA-16 — el aviso de duplicado deja de gritar: «no ponerlo en amarillo, ponerlo en gris,
   porque hay muchas publicaciones en la misma dirección… podés dejar el símbolo de advertencia pero
   en gris, así no alerta tanto». Se conserva el triángulo y el texto entero: es un dato que hay que
   mirar, no una alarma que hay que atender. */
.dup-warning.is-quiet{background:var(--surface-2);border-color:var(--border-default)}
.dup-warning.is-quiet .dup-warning-ico,
.dup-warning.is-quiet .dup-warning-title{color:var(--text-2)}
.dup-warning.is-quiet .dup-warning-text{color:var(--text-2)}

/* MEJORA-19 — «que el marco sea rojo en el título del aviso, no en toda la sección… para que no
   confunda». markFieldError() marcaba el campo Y le ponía el borde rojo a la `.step-card` entera:
   con una sección de veinte campos, el marco rojo no señala nada, y encima compite con la marca del
   campo que sí es la que hay que mirar. El campo sigue con su borde y su mensaje; la card, no. */
.step-card.has-error{border-color:var(--border-default)}

/* ── AUDITORÍA: resaltador gris del diff de texto (04/09/2026) ───────────────────────────────
   Pedido de Máximo mirando la tarjeta Auditoría: «siendo que se comparan dos textos, podría poner
   en letra con fondo gris (tipo resaltador gris) lo que está diferente». El diff lo calcula PHP
   (`property_audit_word_diff()`, lib/props_lib.php) y llega marcado con esta clase — el color va
   acá y nunca en un `style=`, que la CSP no admite.

   El gris sale de la rampa de la paleta (`--text-3`) mezclado con transparente, no de un hex
   inventado: así el mismo 32 % da un resaltador claro sobre fondo blanco y uno oscuro en tema
   oscuro, sin declarar dos valores que después se desincronizan. El `color:var(--text-1)` es
   necesario porque el lado viejo se dibuja dentro de un `.muted`: sin esto, gris sobre gris.

   Este bloque va DESPUÉS de todo lo demás pero ANTES de la guarda global `[hidden]`, que por
   invariante (test_hidden_global.php) tiene que ser la última regla con `display:` del archivo. */
.audit-diff{background:color-mix(in srgb, var(--text-3) 32%, transparent);color:var(--text-1);
  border-radius:3px;padding:0 2px}
/* La marca de recorte de un tramo largo sin cambios. Se distingue del texto a propósito: es una
   señal de la pantalla, no algo que alguien haya escrito en la propiedad. */
.audit-diff-cut{color:var(--text-3);font-style:normal;opacity:.75;padding:0 3px}
/* Una descripción larga o una URL sin espacios no pueden empujar la tarjeta de costado. */
.audit-changes li{overflow-wrap:anywhere}

/* ═════════════════════════════════════════════════════════════════════════════════════════
   BUG-A + BUG-B (Máximo, 04/09/2026) — barra inferior del form de propiedad y «Revisión final».

   ESTE BLOQUE VA AL FINAL DEL ARCHIVO A PROPÓSITO. Lo escribió un agente distinto del que estaba
   tocando esta misma hoja al mismo tiempo (regla de «trabajo en paralelo» de CLAUDE.md): puesto al
   final, el que llegue segundo rebasea sin conflicto de líneas y, ante un choque de reglas, gana
   esto por orden de cascada — que es lo que se busca, porque son correcciones de bugs reportados.
   Si al rebasear aparece un conflicto, el criterio es conservar este bloque entero.
   ═════════════════════════════════════════════════════════════════════════════════════════ */

/* ── BUG-A · la barra inferior tiene que ser INMUNE al largo del mensaje ────────────────────
   Reporte: «cuando corté parte de la Descripción, la barra inferior se desalineó. El error que marca
   se hizo muy largo» — el texto de estado empujaba a Publicar y al riel de portales fuera de la
   pantalla, y la fila quedaba torcida con el mensaje pisando el borde.

   POR QUÉ PASABA, dos causas que se suman:
     1. `.prop-bottombar-inner` era `flex-wrap:wrap`. En cuanto el contenido no entraba, el grupo de
        botones SALTABA de renglón — el desalineado de la captura no era un margen mal puesto, era
        la fila partiéndose.
     2. El texto no se podía achicar. Tenía `text-overflow:ellipsis` desde siempre, pero un ítem de
        flex arranca con `min-width:auto` (la trampa ya anotada en CLAUDE.md, y van tres): su tamaño
        mínimo era el de la frase entera, así que el recorte nunca llegaba a actuar y lo que sobraba
        se lo comía al resto.

   La regla es de LAYOUT y no depende de ningún mensaje en particular — mañana viene otro más largo:
     · la fila no se parte nunca (nowrap) y todo lo operable es `flex:none`: los botones no se mueven;
     · el mensaje es lo ÚNICO elástico — se achica hasta cero y se recorta con puntos suspensivos;
     · el bloque corta con `overflow:hidden`, así que ni un texto sin espacios se escapa de la barra;
     · lo recortado no se pierde: el `title` del bloque trae la lista completa (prop_bottombar.php
       en el primer pintado, barraEstado() de property-form.js después). */
@media (min-width:861px){
  .prop-bottombar-inner{flex-wrap:nowrap}
  /* `0 1 auto` = no crece (el que empuja los botones a la derecha sigue siendo el spacer) pero sí
     se achica; `min-width:0` desarma el mínimo implícito y `overflow:hidden` es el corte duro. */
  .prop-bottombar-missing{flex:0 1 auto;min-width:0;overflow:hidden}
  /* El rótulo («Falta para activar:») no se recorta a sí mismo: dejar «Falta para activ…» sería peor
     que recortar el detalle. Como el contenedor tiene overflow:hidden, en el caso extremo se clipea
     sin desbordar la barra. */
  .prop-bottombar-missing-label{flex:none;white-space:nowrap}
  .prop-bottombar-missing-text{flex:0 1 auto;min-width:0}
  /* base 0 → no aporta al reparto negativo: colapsa solo cuando el mensaje necesita el espacio. */
  .prop-bottombar-spacer{flex:1 1 0;min-width:0}
  .prop-bottombar .btn,.prop-bottombar .pbb-autosave,.prop-bottombar .pbr-group,
  .prop-bottombar .pbr,.prop-bottombar .pbr-stub{flex:none}
  /* El `min-height:64px` de 861–1000px existía para que el salto de 1 a 2 filas no corriera todo lo
     de arriba. Con nowrap ya no hay dos filas posibles: la reserva sobra y agrandaba la barra. */
  .prop-bottombar-inner{min-height:0}
}
/* En mobile (≤860px) la fila del mensaje YA es propia (`flex:1 1 100%`), así que nunca empuja a los
   botones. Lo que faltaba ahí era un TECHO: con `height:auto` un mensaje largo hacía crecer la barra
   sin límite y, como la reserva de `.prop-form` es un `padding-bottom` FIJO de 190px, terminaba
   tapando el formulario. Dos renglones y puntos suspensivos; el resto, en el `title`. */
@media (max-width:860px){
  .prop-bottombar-missing{min-width:0;overflow:hidden}
  .prop-bottombar-missing-text{flex:1 1 auto;min-width:0;white-space:normal;overflow:hidden;
    display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;max-height:2.9em}
}

/* ── BUG-B · «Ver los N cumplidos»: el contador y la lista hablan del mismo conjunto ────────
   Reporte: «la lista final no muestra todas las validaciones y por ejemplo ahí muestra que falta 1 y
   no dice cuál». El encabezado contaba los requisitos de las DOS listas y la pantalla, apenas no
   faltaba ningún bloqueante, ocultaba el grupo entero de bloqueantes: se veían 4 ítems y el número
   hablaba de 13. Ahora no se oculta nada: lo que falta se ve SIEMPRE, y lo cumplido se pliega detrás
   de este botón — que dice cuántos son, así la cuenta cierra a la vista sin llenar la tarjeta de
   tildes verdes. 44px de alto porque es un control operable (accesibilidad, CLAUDE.md). */
.quality-done-toggle{align-self:flex-start;margin:2px 0 0 25px;padding:0;border:0;background:none;
  font:500 12px/1.4 var(--font-sans);color:var(--text-3);cursor:pointer;text-align:left;
  display:inline-flex;align-items:center;gap:5px;min-height:44px}
.quality-done-toggle:hover{color:var(--text-1)}
.quality-done-toggle:focus-visible{outline:2px solid var(--ring);outline-offset:2px;border-radius:var(--radius-sm)}
.quality-done-toggle::before{content:"";width:6px;height:6px;flex:none;border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;transform:rotate(45deg);margin-bottom:2px;transition:transform .15s}
.quality-done-toggle[aria-expanded="true"]::before{transform:rotate(-135deg);margin-bottom:-2px}
.quality-done-list{display:flex;flex-direction:column;gap:8px}
/* Un grupo sin pendientes ni cumplidos quedaba mudo (una franja vacía que parece un error de
   render): se dice en palabras, nunca un hueco. */
.quality-check-empty{margin:0;font:400 12px/1.4 var(--font-sans);color:var(--text-3)}


/* MX-114 — el barrio cargado no está en la lista de ZonaProp. Un AVISO, no un error: no bloquea
   nada, así que va en el ámbar de «lo que recomienda el portal» (BUG-30) y no en el rojo de lo que
   está mal. Mismos tokens que .quality-portal-warn, en versión de una línea al pie del campo: es
   información sobre el dato cargado, no una alarma sobre el formulario. */
.field-portal-note{display:flex;align-items:flex-start;gap:7px;margin:2px 0 10px;padding:8px 10px;
  border-radius:var(--radius-md);background:var(--warn-soft);border:1px solid rgba(245,158,11,.35);
  font:400 12px/1.45 var(--font-sans);color:var(--warn-strong)}
.field-portal-note-ico{flex:none;margin-top:1px}
.field-portal-note-ico svg{width:14px;height:14px}
/* Ítem de flex sin min-width:0 no se achica: el texto largo desbordaría la columna en mobile. */
.field-portal-note>span:last-child{min-width:0;overflow-wrap:anywhere}

/* MX-114 — en el desplegable de Barrio/Zona, cuáles nombres los tiene el portal. Una marca SOBRIA:
   un punto y una etiqueta chiquita, en el color de acento y no en el de error — es una ventaja, no
   un problema. Los demás nombres siguen apareciendo, abajo y sin marca: no se esconde nada. */
.place-suggest-item.is-portal{display:flex;align-items:center;gap:7px}
.place-suggest-name{min-width:0;overflow-wrap:anywhere}
.place-suggest-badge{margin-left:auto;flex:none;display:inline-flex;align-items:center;gap:4px;
  font:500 11px/1 var(--font-sans);color:var(--text-3)}
.place-suggest-badge::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--accent)}

/* ============================================================================
   MX-121 · FICHA INTERNA DE PROPIEDADES — rediseño (05/09/2026)
   Handoff: design_handoff_ficha_propiedad (GUIA-FICHA-INTERNA v1 + v2).

   Bloque propio y al FINAL del archivo porque style.css es zona compartida entre
   frentes: así el que llega después agrega abajo y nadie se pisa. La única regla
   que TIENE que quedar por debajo es la guarda global [hidden].

   Sobre los colores: el handoff trae los hexes de la cara PÚBLICA (verde #22D3A6,
   navy, pasteles). Acá se usan los TOKENS de la app (--ok-soft, --info-soft, …)
   porque el archivo lo pide explícitamente ("los componentes usan SOLO nombres de
   variables, nunca hex") y porque así el modo oscuro sale gratis y correcto. La
   SEMÁNTICA del handoff se respeta entera: verde = publicada, azul = interna y
   vendida/alquilada, gris = pausada/reservada, rojo suave = archivada.
   ============================================================================ */

/* ── Barra de identidad: «Volver» con la palabra escrita ────────────────────── */
.pf-bar-back{display:inline-flex;align-items:center;gap:6px;flex:none;min-height:38px;padding:0 12px;margin:0;
  border:1px solid var(--border-default);border-radius:var(--radius-pill);background:var(--surface);
  color:var(--text-1);font:600 12px/1 var(--font-sans);text-decoration:none;white-space:nowrap}
.pf-bar-back:hover{background:var(--surface-2);text-decoration:none}
.pf-bar-back svg{color:var(--accent)}
.pf-bar-back:focus-visible{outline:2px solid var(--ring);outline-offset:2px}

/* ── Badge de estado (píldora) ──────────────────────────────────────────────── */
.pf-badge{--pf-bg:var(--surface-2);--pf-fg:var(--text-2);
  display:inline-flex;align-items:center;gap:5px;flex:none;
  padding:4px 10px;border-radius:var(--radius-pill);background:var(--pf-bg);color:var(--pf-fg);
  font:600 10.5px/1.35 var(--font-sans);letter-spacing:.01em;white-space:nowrap}
.pf-badge[data-badge="s-publicada"]{--pf-bg:var(--ok-soft);--pf-fg:var(--ok-strong)}
.pf-badge[data-badge="s-interna"]{--pf-bg:var(--info-soft);--pf-fg:var(--info-strong)}
.pf-badge[data-badge="s-borrador"]{--pf-bg:var(--surface-2);--pf-fg:var(--text-2)}
.pf-badge[data-badge="s-baja"]{--pf-bg:var(--err-soft);--pf-fg:var(--err-strong)}
.pf-badge[data-badge="s-suspendida"]{--pf-bg:var(--warn-soft);--pf-fg:var(--warn-strong)}
.pf-badge[data-badge="m-pausada"]{--pf-bg:var(--surface-2);--pf-fg:var(--text-2)}
.pf-badge[data-badge="m-reservada"]{--pf-bg:var(--border-strong);--pf-fg:var(--text-1)}
/* Vendida y Alquilada comparten familia (azul «éxito»): son las dos formas de cerrar. */
.pf-badge[data-badge="m-vendida"],
.pf-badge[data-badge="m-alquilada"]{--pf-bg:var(--info-soft);--pf-fg:var(--info-strong)}
.pf-badge[data-badge="m-retirada"]{--pf-bg:var(--err-soft);--pf-fg:var(--err-strong)}
.pf-badge[data-badge="mixto"]{--pf-bg:var(--warn-soft);--pf-fg:var(--warn-strong)}
.pf-badge[data-badge="privado"]{--pf-bg:var(--warn-soft);--pf-fg:var(--warn-strong)}
/* Un motivo que el SuperAdmin agregue y no tenga color propio cae al gris del default: nunca
   queda sin fondo (un badge transparente se lee como un error de render). */

/* El badge que ADEMÁS es un control. 44px de área táctil sin engordar la píldora: el alto
   lo pone un pseudo-elemento, no el padding (accesibilidad, CLAUDE.md). */
.pf-badge-btn{position:relative;border:0;font-family:inherit;cursor:pointer}
.pf-badge-btn::after{content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
.pf-badge-btn:hover{filter:brightness(.96)}
html.dark .pf-badge-btn:hover{filter:brightness(1.14)}
.pf-badge-btn:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
.pf-badge-btn .st-caret{transition:transform .15s;opacity:.7}
.pf-badge-btn[aria-expanded="true"] .st-caret{transform:rotate(180deg)}

/* ── Tarjeta «Operaciones» ──────────────────────────────────────────────────── */
.pf-ops-head{display:flex;align-items:center;justify-content:space-between;gap:10px;
  flex-wrap:wrap;margin-bottom:10px}
.pf-ops-title{margin:0}
.pf-ops-todas{position:relative}
.pf-op-st{position:relative;margin-left:auto}
/* Cada ítem del menú es la MISMA ficha de color del estado destino, todas del mismo ancho:
   así se lee «a qué voy a pasar», no una lista de textos sueltos (guía v2 §1). */
.pf-op-menu{min-width:214px;padding:6px}
.pf-op-menu form{display:block}
.pf-op-menu-item{width:100%;justify-content:center;padding:4px}
.pf-op-menu-item .pf-badge{width:100%;justify-content:center;padding:6px 12px;font-size:11.5px}
.pf-op-menu-item.current .pf-badge{box-shadow:0 0 0 1.5px var(--accent) inset}
.pf-op-menu-item:hover{background:transparent}
.pf-op-menu-item:hover .pf-badge{filter:brightness(.96)}
html.dark .pf-op-menu-item:hover .pf-badge{filter:brightness(1.14)}
.pf-op-menu-sep{margin:6px 4px 3px;padding-top:7px;border-top:1px solid var(--border-default);
  font:600 9.5px/1.4 var(--font-sans);letter-spacing:.06em;color:var(--text-3)}

/* ── Fila de acciones del panel derecho (sin tarjeta) ───────────────────────── */
.pf-acciones{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.pf-acciones > .st-menu{position:relative;min-width:0}
.pf-acc-btn{width:100%;min-height:42px;display:inline-flex;align-items:center;justify-content:center;gap:7px}
.pf-acc-menu{min-width:216px;left:0;right:auto}
.pf-acciones > .st-menu:last-child .pf-acc-menu{left:auto;right:0}
.pf-acc-menu .menu-item{font-size:12.5px;min-height:38px}

/* ── Tarjeta «Publicaciones» (acordeón) ─────────────────────────────────────── */
.pf-pubs{padding-left:0;padding-right:0}
.pf-pubs-title{margin:0 16px 6px;font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-3)}
.pf-pub + .pf-pub{border-top:1px solid var(--surface-2)}
.pf-pub-head{display:flex;align-items:center;gap:9px;width:100%;min-height:44px;padding:10px 16px;
  border:0;background:none;color:var(--text-1);font:600 13px/1.3 var(--font-sans);
  text-align:left;cursor:pointer}
.pf-pub-head:hover{background:var(--surface-2)}
.pf-pub-head:focus-visible{outline:2px solid var(--ring);outline-offset:-2px}
.pf-pub-logo{flex:none;width:auto;max-width:78px;object-fit:contain}
.pf-pub-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-pub-name-otros{font-weight:500;font-size:12px;color:var(--text-3)}
.pf-pub-chev{flex:none;color:var(--text-3);transition:transform .25s}
.pf-pub-head[aria-expanded="true"] .pf-pub-chev{transform:rotate(180deg)}
.pf-pub-body{padding:2px 16px 14px 45px}
.pf-pub-dl{display:grid;grid-template-columns:auto 1fr;gap:3px 12px;margin:0 0 8px;
  font-size:11.5px;line-height:1.5}
.pf-pub-dl dt{color:var(--text-3)}
.pf-pub-dl dd{margin:0;color:var(--text-1);display:flex;align-items:center;gap:6px;min-width:0}
.pf-pub-off .pf-pub-head{color:var(--text-3)}
.pf-pub-otros{list-style:none;margin:0 0 8px;padding:0;display:flex;flex-direction:column;gap:8px}
.pf-pub-otros li{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--text-2);
  opacity:.55;filter:grayscale(1)}
.pf-pub-soon{margin-left:auto;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-3)}
/* La marca de Momix es navy: en oscuro se invierte, igual que en la ficha pública. */
html.dark .pf-logo-momix{filter:brightness(0) invert(1)}

/* ── Botoncito de copiar / llamar al lado de un valor ───────────────────────── */
.pf-copy{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:26px;height:26px;padding:0;border:0;border-radius:var(--radius-md);
  background:none;color:var(--text-3);cursor:pointer}
.pf-copy:hover{background:var(--accent-soft);color:var(--accent-text);text-decoration:none}
.pf-copy:focus-visible{outline:2px solid var(--ring);outline-offset:1px}

/* ── Propietario + Actividad (panel derecho) ────────────────────────────────── */
.pf-owner-side-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
.pf-owner-side-head h2{margin:0}
.pf-owner-dl{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0;font-size:12px;line-height:1.5}
.pf-owner-dl dt{color:var(--text-3);align-self:center}
.pf-owner-dl dd{margin:0;display:flex;align-items:center;gap:2px;min-width:0;color:var(--text-1)}
.pf-owner-val{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-owner-hr{margin:14px 0 12px;border:0;border-top:1px solid var(--border-default)}
.pf-owner-act-title{margin:0 0 8px}
.pf-act-mini{list-style:none;margin:0 0 8px;padding:0;display:flex;flex-direction:column;gap:8px}
.pf-act-mini li{display:flex;align-items:flex-start;gap:8px;font-size:11.5px;line-height:1.45;min-width:0}
.pf-act-dot{flex:none;width:6px;height:6px;margin-top:5px;border-radius:var(--radius-pill);background:var(--accent)}
.pf-act-dot.is-warn{background:var(--warn)}
.pf-act-dot.is-err{background:var(--err)}
.pf-act-txt{flex:1;min-width:0;color:var(--text-1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-act-when{flex:none;color:var(--text-3);white-space:nowrap}

/* Link de texto punteado: es un botón que abre algo, no una navegación. */
.pf-link-dotted{padding:0;border:0;background:none;cursor:pointer;
  font:600 11.5px/1.4 var(--font-sans);color:var(--accent-text);
  border-bottom:1px dotted currentColor;align-self:flex-start}
.pf-link-dotted:hover{color:var(--accent-hover)}
.pf-link-dotted:focus-visible{outline:2px solid var(--ring);outline-offset:2px}

/* ── Popups (requisitos del portal, compartir) ──────────────────────────────── */
.pf-modal-card{width:min(480px,100%);max-height:86vh;overflow:auto;padding:20px;
  background:var(--surface);border-radius:var(--radius-lg);box-shadow:0 18px 44px rgba(8,17,31,.22)}
html.dark .pf-modal-card{box-shadow:0 18px 44px rgba(0,0,0,.5)}
.pf-modal-title{margin:0 0 14px;font-size:15px;font-weight:600;color:var(--text-1)}
.pf-modal-lbl{margin:0 0 8px;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}
.pf-modal-actions{display:flex;gap:8px;margin-top:16px}
.pf-modal-actions .btn{min-height:44px}
.pf-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}
.pf-chip{display:inline-flex;align-items:center;gap:6px;min-height:36px;padding:0 13px;
  border:1px solid var(--border-default);border-radius:var(--radius-pill);
  background:var(--surface);color:var(--text-2);font:600 12px/1 var(--font-sans);cursor:pointer}
.pf-chip .pf-chip-tick{opacity:0}
.pf-chip.is-on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent-text)}
.pf-chip.is-on .pf-chip-tick{opacity:1}
.pf-chip:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
/* La vista previa es el mensaje REAL: se muestra con sus saltos de línea y sin reformatear. */
.pf-share-prev{margin:0;padding:14px;max-height:38vh;overflow:auto;
  border:1px solid var(--border-default);border-radius:var(--radius-md);background:var(--surface-2);
  font:400 12px/1.6 var(--font-sans);color:var(--text-1);white-space:pre-wrap;word-break:break-word}
.pf-share-prev.is-wa{background:var(--accent-soft)}
.pf-reqs{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
.pf-req{display:flex;align-items:flex-start;gap:9px;font-size:12.5px;line-height:1.5}
.pf-req-mark{flex:none;width:16px;font-weight:700}
.pf-req.is-ok .pf-req-mark{color:var(--ok-strong)}
.pf-req.is-falta .pf-req-mark{color:var(--warn-strong)}
.pf-req-lbl{flex:1;min-width:0;color:var(--text-1)}
.pf-req-w{flex:none;font-weight:600;color:var(--warn-strong)}

/* ── Aviso corto ────────────────────────────────────────────────────────────── */
.pf-toast{position:fixed;left:50%;bottom:26px;transform:translate(-50%,14px);z-index:90;
  padding:11px 20px;border-radius:var(--radius-pill);background:var(--text-1);color:var(--surface);
  font:500 13px/1.3 var(--font-sans);box-shadow:0 14px 34px rgba(8,17,31,.35);
  opacity:0;pointer-events:none;transition:opacity .2s,transform .2s}
.pf-toast.is-on{opacity:1;transform:translate(-50%,0)}

/* ── Mobile ─────────────────────────────────────────────────────────────────── */
@media (max-width:1000px){
  /* En una columna, la tarjeta de propietario/actividad del panel derecho va después de las
     publicaciones y antes de los links, que es el orden en que se la consulta. */
  .pf-c-side-owner{order:5}
}
@media (max-width:640px){
  .pf-acciones{grid-template-columns:1fr}
  .pf-ops-head{align-items:flex-start}
  /* El menú de estados no puede desbordar la pantalla: en mobile se ancla a la derecha y se
     limita al ancho disponible (ítem de flex/grid con min-width:auto, trampa conocida). */
  .pf-op-menu{min-width:0;width:min(260px,calc(100vw - 32px))}
  .pf-pub-body{padding-left:16px}
  /* La barra fija de la PWA vive en bottom:0 z-50: el toast se sube para no quedar debajo. */
  .pf-toast{bottom:96px}
}


/* ── Avisos de la ficha ─────────────────────────────────────────────────────────
   Verde el «salió bien», ámbar el «no se pudo». El error NO va en rojo: casi todos
   los de esta pantalla son un «todavía no se puede» (falta suspenderla, el portal no
   contestó), no una falla de quien lo intentó (design-system). */
.pf-aviso{margin:0 auto 14px;max-width:1120px;padding:11px 13px;border-radius:var(--radius-md);
  font:400 13px/1.45 var(--font-sans)}
.pf-aviso.is-ok{background:var(--ok-soft);color:var(--ok-strong);
  border:1px solid color-mix(in srgb,var(--ok) 35%,transparent)}
.pf-aviso.is-err{background:var(--warn-soft);color:var(--warn-strong);
  border:1px solid color-mix(in srgb,var(--warn) 35%,transparent)}
/* Una propiedad PURGADA: perdió las fotos al cumplirse el plazo de archivada. Se ve como una
   advertencia (ámbar) pero NO es un error —es el final normal del ciclo—, así que el cartel va con
   role="status" y no con role="alert": nada que interrumpir, algo que explicar. */
.pf-aviso.is-purgada{background:var(--warn-soft);color:var(--warn-strong);
  border:1px solid color-mix(in srgb,var(--warn) 35%,transparent)}

/* ── La barra de identidad en mobile: el precio NO puede volver ─────────────────
   Arriba ya hay un `.pf-bar-price{display:none}` para <840px, pero
   `.pf-bar-price.is-multi{display:flex}` le gana por especificidad: en una propiedad
   con DOS operaciones el precio reaparecía y se ENCIMABA con el badge de estado y con
   el título (medido en Chrome a 390px, MX-121 — con el «Volver» ancho se nota más,
   pero el encimado ya estaba). Es la misma trampa de cascada de siempre; se cierra
   acá, al final del archivo, con la misma especificidad y ganando por orden. El
   precio no se pierde: está completo en la tarjeta Operaciones, dos dedos más abajo. */
@container pf (max-width:840px){
  .pf-bar-price,.pf-bar-price.is-multi{display:none}
  .pf-bar-back{padding:0 10px}
}

/* ============================================================================
   MX-121b · LA FICHA INTERNA, CONTRA EL MOCKUP (06/09/2026)

   Qué pasó: MX-121 implementó la estructura de datos y el panel derecho, pero el
   LAYOUT quedó como estaba. Máximo comparó la pantalla con
   `Ficha Interna Momix.dc.html` y marcó que «muchas cosas quedaron distintas,
   especialmente la UX». Este bloque cierra esas diferencias: mosaico de fotos,
   Operaciones como primera tarjeta del panel derecho y con cada operación en su
   caja, cifras con ícono, comodidades como chips de la descripción, y el panel
   del portal compactado a filas clave/valor.

   Va DESPUÉS del bloque MX-121 (misma especificidad, gana por orden) y ANTES de
   la guarda global [hidden], que tiene que seguir siendo la última regla con
   `display:` del archivo.
   ============================================================================ */

/* ── 1. La galería es un MOSAICO ─────────────────────────────────────────────
   Una foto grande a la izquierda (que sigue siendo el «stage» del componente de
   galería, con sus flechas, su contador y su visor) y cuatro chicas en 2×2 a la
   derecha. Solo aplica dentro de .pf-page: la ficha pública y los links /s/ usan
   el MISMO componente y no se tocan. */
.pf-gal.is-mosaico{display:grid;grid-template-columns:2fr 1fr 1fr;
  grid-template-rows:repeat(2,clamp(115px,13vw,170px));gap:6px}
.pf-gal.is-mosaico > .pf-gal-stage{grid-column:1;grid-row:1/3;min-width:0}
.pf-gal.is-mosaico .pgallery-card{height:100%;margin:0;border:0;border-radius:0;background:none;box-shadow:none}
.pf-gal.is-mosaico .pgallery{height:100%}
.pf-gal.is-mosaico .pgallery-stage{height:100%;aspect-ratio:auto;border-radius:var(--radius-lg) 0 0 var(--radius-lg)}
/* En el mosaico la foto grande RECORTA (como el mockup): la caja es casi cuadrada
   y `contain` la dejaba con dos franjas enormes. La foto entera sigue estando a un
   clic, en el visor, que sigue usando `contain` y nunca recorta. */
.pf-gal.is-mosaico .pgallery-slide img{object-fit:cover}
/* Las cuatro tiles reemplazan a la tira de miniaturas: tenerlas las dos es el mismo
   control dos veces. Los puntitos tampoco tienen sentido sin carrusel. */
.pf-gal.is-mosaico .pgallery-strip,
.pf-gal.is-mosaico .pgallery-dots{display:none}
/* …salvo la tira DEL VISOR, que vive dentro del mismo `.pgallery` y sí se usa. */
.pf-gal.is-mosaico .pgallery-viewer .pgallery-strip{display:flex}
.pf-gal-tiles{grid-column:2/4;grid-row:1/3;display:grid;grid-template-columns:1fr 1fr;
  grid-template-rows:1fr 1fr;gap:6px;min-width:0}
.pf-gal-tile{position:relative;padding:0;border:0;min-width:0;overflow:hidden;cursor:pointer;
  background:var(--surface-2)}
.pf-gal-tile:nth-child(2){border-radius:0 var(--radius-lg) 0 0}
.pf-gal-tile:nth-child(4){border-radius:0 0 var(--radius-lg) 0}
.pf-gal-tile img{width:100%;height:100%;object-fit:cover;display:block}
.pf-gal-tile:hover img{filter:brightness(.92)}
.pf-gal-tile:focus-visible{outline:2px solid var(--accent);outline-offset:-3px}
.pf-gal-more{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:7px;
  background:rgba(var(--overlay-rgb),.55);color:var(--fixed-white);
  font:600 13px/1 var(--font-sans)}
.pf-gal-tile:hover .pf-gal-more{background:rgba(var(--overlay-rgb),.68)}
/* Con la solapa «Planos» puesta, el mosaico de FOTOS no corresponde: ficha-interna.js
   marca la galería y las tiles se van, dejando la foto grande a todo el ancho. */
.pf-gal.is-mosaico.is-solo{grid-template-columns:1fr}
.pf-gal.is-mosaico.is-solo > .pf-gal-stage{grid-column:1/-1}
.pf-gal.is-mosaico.is-solo .pgallery-stage{border-radius:var(--radius-lg)}
.pf-gal.is-mosaico.is-solo .pgallery-strip{display:flex}

/* ── 2. Las cifras, con su ícono ─────────────────────────────────────────────
   Eran números pelados separados por líneas verticales. Ahora cada una es un tile
   con el ícono arriba, como el mockup y como la ficha pública. */
.pf-c-cifras .pf-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(84px,1fr));
  gap:10px;margin-top:0;padding-top:0;border-top:0}
.pf-c-cifras .pf-stat{align-items:center;text-align:center;gap:6px;padding:8px 2px;border-left:0;min-width:0}
.pf-stat-ico{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:46px;height:46px;border-radius:14px;background:var(--surface);color:var(--text-1);
  box-shadow:0 6px 16px rgba(8,17,31,.14)}
html.dark .pf-stat-ico{background:var(--surface-2);box-shadow:0 6px 16px rgba(0,0,0,.42)}
.pf-c-cifras .pf-stat-num{font-size:16px;font-weight:600;line-height:1.2}
.pf-c-cifras .pf-stat-lbl{font-size:10.5px;line-height:1.3}

/* ── 3. «Operaciones», primera tarjeta del panel derecho ─────────────────────
   Cada operación es su propia caja: etiqueta arriba a la izquierda, badge de
   estado arriba a la derecha, precio grande abajo. El título de la tarjeta pasa a
   micro-etiqueta, igual que «PUBLICACIONES», porque ahora son vecinas. */
.pf-c-ops .card{display:flex;flex-direction:column;gap:10px}
.pf-ops-title{font:600 10.5px/1.4 var(--font-sans);letter-spacing:.07em;text-transform:uppercase;
  color:var(--text-3)}
.pf-ops-head{margin-bottom:0}
.pf-op-row{display:block;padding:12px 14px;border:1px solid var(--border-default);
  border-radius:var(--radius-md);background:var(--surface)}
.pf-op-top{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}
.pf-op-name{width:auto;flex:1;min-width:0;font:600 11px/1.3 var(--font-sans);letter-spacing:.09em;
  text-transform:uppercase;color:var(--text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pf-op-money{display:flex;align-items:baseline;flex-wrap:wrap;gap:6px;margin-top:4px;min-width:0}
.pf-op-price,.pf-op-row.is-secondary .pf-op-price{font-size:22px;font-weight:600;color:var(--text-1)}
.pf-op-row .pf-price-chip{margin-top:8px}
/* El menú de estados cuelga de una tarjeta de 372px pegada al borde derecho: se
   ancla a la derecha (el default de .menu-panel) y nunca más ancho que su columna. */
.pf-c-ops .pf-op-menu,.pf-c-ops .st-menu-panel{left:auto;right:0;max-width:min(260px,calc(100vw - 32px))}

/* ── 4. Las comodidades son chips de la DESCRIPCIÓN ──────────────────────────
   Sin `<h2>` ni línea divisoria: el mockup no tiene una sección aparte. Los grupos
   quedan como micro-etiquetas —una propiedad real trae cuarenta comodidades y sin
   agrupar no se leen—, pero ya no como títulos de sección. */
/* Una descripción sin un solo espacio (pasa: texto pegado de un portal, o una prueba con
   «dddddd…») no tiene dónde cortar y se sale de la tarjeta por la derecha, encima del panel.
   Se vio en dev con la propiedad 1016 al medir esto en Chrome real. */
.pf-descr-text{overflow-wrap:anywhere}
.pf-amenities{margin-top:14px;display:flex;flex-direction:column;gap:12px}
.pf-amenities .pf-amenity-group + .pf-amenity-group{margin-top:0}
.pf-amenities .pf-amenity-group-title{margin:0 0 6px;font-size:9.5px;font-weight:600;
  letter-spacing:.09em;color:var(--text-3)}
.pf-amenities .pf-amenity-chips{gap:6px}
.pf-amenities .pf-amenity-chip{font-size:11.5px;padding:5px 11px 5px 8px}

/* ── 5. El panel del portal, compacto ────────────────────────────────────────
   Filas clave/valor y dos acciones como links de texto (teal / rojo), en lugar del
   anillo de 60px + párrafo + botones a lo ancho.

   Los links miden 44px de alto (el padding, no el texto): el mockup los dibuja como
   texto suelto y el estándar de accesibilidad pide 44px de área operable — gana el
   estándar, y visualmente se ve igual. */
.pf-pub-falta{font-weight:600;color:var(--warn-strong)}
.pf-pub-acts{display:flex;flex-wrap:wrap;align-items:center;gap:18px;margin-top:2px}
.pf-pub-acts form{display:inline-flex}
.pf-pub-act{display:inline-flex;align-items:center;min-height:44px;padding:0;border:0;background:none;
  cursor:pointer;font:600 12px/1.3 var(--font-sans);color:var(--accent-text)}
.pf-pub-act:hover{color:var(--accent-hover);text-decoration:underline}
.pf-pub-act.is-danger{color:var(--err-strong)}
.pf-pub-act.is-danger:hover{color:var(--err-strong)}
.pf-pub-act:focus-visible{outline:2px solid var(--ring);outline-offset:2px}
.pf-pub-dl dd a{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}

/* ── 6. Responsive ───────────────────────────────────────────────────────────
   Las clases de gancho cambiaron (.pf-c-price se partió en .pf-c-ops, que se fue al
   panel, y .pf-c-cifras, que se quedó en la columna izquierda): las reglas de mobile
   que las nombraban por la clase vieja se reescriben acá, o quedaban sin márgenes
   laterales y pegadas al borde de la pantalla. */
@container pf (max-width:1000px){
  /* En una columna el orden es el de la lectura: fotos, cuánto sale y cómo está,
     los atajos y dónde está publicada (.pf-c-actions, que ya venía con order:-1 y
     arrastra la tarjeta Publicaciones porque la envuelve), y recién después los
     datos. Las cifras NO llevan `order`: quedan en el grupo 0, donde el DOM ya las
     pone antes de la descripción. Ponerles un `order` positivo las mandaba al final
     de todo, detrás del mapa — medido en Chrome a 390px. */
  .pf-c-gallery{order:-3}
  .pf-c-ops{order:-2}
}
@container pf (max-width:1000px) and (min-width:840px){
  /* Fuera del sidebar la tarjeta Operaciones deja de ser angosta: las cajas se
     reparten el ancho en vez de apilarse una debajo de la otra. */
  .pf-c-ops .card{display:block}
  .pf-c-ops .pf-op-row{display:inline-block;width:calc(50% - 10px);vertical-align:top;margin-top:10px}
  .pf-c-ops .pf-op-row + .pf-op-row{margin-left:8px}
}
@container pf (max-width:839px){
  .pf-c-ops,.pf-c-cifras{margin-left:16px;margin-right:16px}
  /* El mosaico a 390px con cinco cajas es una grilla de estampillas: la foto grande
     va sola, a todo el ancho, y las cuatro chicas debajo en una fila de cuatro. */
  .pf-gal.is-mosaico{grid-template-columns:1fr;grid-template-rows:auto auto}
  .pf-gal.is-mosaico > .pf-gal-stage{grid-column:1;grid-row:1}
  .pf-gal.is-mosaico .pgallery-stage{border-radius:0}
  .pf-gal-tiles{grid-column:1;grid-row:2;grid-template-columns:repeat(4,1fr);grid-template-rows:66px;
    padding:0 16px}
  .pf-gal-tile,.pf-gal-tile:nth-child(2),.pf-gal-tile:nth-child(4){border-radius:var(--radius-md)}
  .pf-gal-more{font-size:11px;gap:4px}
  .pf-gal-more svg{display:none}
}

/* ── Mensajes compartidos en la Actividad (mt_133, 06/09/2026) ──────────────────────────────
   Cada mensaje que se copió para WhatsApp o para mail queda registrado y se lee en la Actividad de
   la propiedad. La línea de arriba es el resumen; el texto entero va plegado en un <details>,
   porque son quince renglones y la Actividad es una LISTA — desplegado, un solo mensaje taparía el
   resto de lo que le pasó a la ficha. El <pre> reusa .pf-share-prev, que ya da el monoespaciado y
   el fondo del popup de compartir: es el mismo texto, se ve igual en los dos lados. */
.pf-tl-msg{margin-top:6px}
.pf-tl-msg>summary{cursor:pointer;font-size:12px;color:var(--text-3);
  /* 44px de área táctil como todo control operable del estándar, sin engordar la línea. */
  min-height:24px;padding:10px 0;list-style:none}
.pf-tl-msg>summary::-webkit-details-marker{display:none}
.pf-tl-msg>summary::before{content:'▸ ';display:inline-block;transition:transform .12s ease}
.pf-tl-msg[open]>summary::before{content:'▾ '}
.pf-tl-msg>summary:hover{color:var(--text-1)}
.pf-tl-msg .pf-share-prev{margin:4px 0 0;max-height:260px;overflow:auto}


/* ── CARGANDO en el listado de propiedades (12/09/2026) ─────────────────────────────────────
   De los cuatro estados de pantalla (vacío, cargando, error, con datos), «cargando» era el que
   faltaba en toda la app: el JS ya ponía `is-loading` al filtrar, pero ninguna regla la usaba, así
   que tocar un filtro no cambiaba nada y los resultados viejos seguían ahí como si fueran los
   nuevos. Atenuar + cursor de espera + `pointer-events:none` dice las tres cosas: está trabajando,
   esto que ves ya no vale, no hace falta que vuelvas a clickear. */
[data-auto-filters].is-loading{cursor:progress}
#propResults[aria-busy="true"]{opacity:.5;pointer-events:none;transition:opacity .12s ease}
@media (prefers-reduced-motion:reduce){#propResults[aria-busy="true"]{transition:none}}

/* ── La barra de selección múltiple no puede tapar el menú de abajo (12/09/2026) ─────────────
   `.bulk-bar` es fija a 18px del piso y con z-index 70; el `.bottom-nav` de la PWA es fijo al piso
   con z-index 50 y ocupa 76px. O sea que en el celular la píldora «N seleccionadas · Publicar ·
   Suspender…» quedaba ENCIMA de Inicio / Propiedades / [+] / Contactos, justo mientras se opera en
   masa desde el teléfono: se pierde la navegación y se aprieta «Suspender» queriendo apretar [+].
   Era la única barra fija sin su colchón — las otras cuatro (.prop-bottombar, .pf-actions-fixed-bar,
   .release-toast y .pf-toast) ya lo tenían.
   Va al final del archivo y en su propio @media a propósito: la regla base está más arriba y una
   declaración posterior con la misma especificidad es la que gana (trampa de cascada ya anotada). */
@media (max-width:640px){
  .bulk-bar{bottom:88px}
}

/* ── Paginador del listado de propiedades (12/09/2026) ──────────────────────────────────────
   El listado traía 200 filas y no había más: con 250 propiedades, 50 no existían para la pantalla.
   Ahora hay páginas, y son links de verdad (conservan filtros, se comparten, el Atrás anda). */
.pager{display:flex;align-items:center;justify-content:center;gap:12px;margin:18px 0 4px;flex-wrap:wrap}

/* ── Controles chicos de las pantallas internas (12/09/2026) ────────────────────────────────
   El chequeo de navegador marcó 36 controles por debajo de los 44px de la norma. Revisados uno por
   uno, en las pantallas que el agente usa todo el día quedan TRES casos reales:

     · «Ver toda la actividad» (.pf-link-dotted): 110×17px y sin área de toque ampliada;
     · «Volver» de la ficha (.pf-bar-back): 38px de alto, le faltan 6;
     · los campos de ambientes/dormitorios/baños/cocheras: el ALTO ya es 44px — el problema es el
       ANCHO. La caja está topeada en 190px y los dos botones se llevan 88px, así que en un teléfono
       de 320px el campo donde se tipea queda en 24px.

   Lo que NO se toca, a propósito: el badge de estado (.pf-badge-btn) YA tiene su área táctil de 44px
   con un ::after invisible — el medidor lo marca por su tamaño visual, pero el dedo le acierta.
   «Arreglarlo» sería agrandar un badge que está bien. Los controles del mapa son de MapLibre y los de
   las fichas públicas son otra superficie (otra escala tipográfica, decisión del manual).

   El alto va bajo `pointer:coarse` —criterio que el proyecto ya usa para el buscador de la home—: es
   cuestión de dedo, no de ancho de pantalla, así que también alcanza al iPad apaisado. */
@media (pointer:coarse){
  /* Área de toque de 44px SIN cambiar cómo se ve: el mismo truco del badge de estado. */
  .pf-link-dotted{position:relative}
  .pf-link-dotted::after{content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
  .pf-bar-back{min-height:44px}
}
/* El campo numérico necesita ancho, no alto: sin el tope, el input usa lo que haya. */
@media (max-width:380px){
  .stepper{max-width:none}
}

/* Cartel de ambiente QA (14/09/2026). Lo pinta components/logo.php SOLO con APP_ENV=qa, al lado de
   la palabra «momix». Usa el par ámbar del sistema: está calculado para contraste AA en tema claro y
   oscuro, así que no hace falta una regla por tema, y el ámbar se lee como «atención, no es
   producción». No es un control operable, así que no le corresponde el mínimo de 44px; 12px está en
   la escala tipográfica cerrada del proyecto. */
.logo-qa {
  font-size:12px; font-weight:700; line-height:1; letter-spacing:.04em;
  padding:3px 6px; border-radius:6px;
  background:var(--warn-soft); color:var(--warn-strong); border:1px solid var(--warn-strong);
}

/* ── /agency-view: la lista de propiedades de una inmobiliaria ───────────────────
   Es una lista de consulta, no el listado de propiedades: una línea por ficha, con
   lo que la identifica a la izquierda y lo que le va a pasar a la derecha. No reusa
   `.prop-card` a propósito — esa tarjeta trae foto, precio y acciones, y acá no hay
   ninguna de las tres. */
.av-list{list-style:none;margin:10px 0 0;padding:0;display:flex;flex-direction:column;gap:2px}
.av-item{display:flex;align-items:center;gap:12px;padding:9px 10px;border-radius:var(--radius-md)}
.av-item + .av-item{border-top:1px solid var(--border)}
.av-item:hover{background:var(--surface-2)}
.av-item-main{flex:1;min-width:0;display:block;text-decoration:none;color:inherit}
.av-item-title{display:block;font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.av-item-meta{display:block;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.av-item-side{flex:0 0 auto;font-size:12px}
/* Una ya borrada se lee distinto de una que está por borrarse: su título va apagado. */
.av-item.is-purgada .av-item-title{color:var(--text-3)}

/* ── GUARDA GLOBAL [hidden] (02/09/2026) ────────────────────────────────────────────────────
   Cualquier `.clase{display:…}` le gana por cascada al [hidden] del navegador, y el elemento oculto
   se sigue viendo. Pasó CINCO veces más en el testing de hoy (botones, m² manual, chips, selector de
   inmobiliarias) después de haber pasado tres antes: la solución por caso no escala. Con esto,
   `hidden` significa oculto, siempre. Va al FINAL del archivo y con !important a propósito. */
[hidden]{display:none!important}
