/* ============================================================
   Nexel Panel — Sistema de diseño
   Implementación de DESIGN.md. Todos los estilos del proyecto
   viven en este único archivo.
   ============================================================ */

/* ---------- 1. Tokens ---------- */
:root {
  color-scheme: light;
  --bg:        #f4f6fb;
  --canvas:    #e5e7eb;
  --surface:   #ffffff;
  --border:    #e2e8f0;
  --primary:   #1877f2;
  --primary-h: #1569d5;
  /* Lavado del color de la cuenta. Dice "esto está marcado" sin pintar la
     superficie entera. Lo comparten el ítem activo del sidebar y la fila
     sin leer de las bandejas, que tienen que verse EXACTAMENTE iguales —
     por eso es un token y no la mezcla repetida en cada regla.
     Va contra `transparent` (o sea: alpha) y no contra un token de fondo,
     así sirve igual sobre el chrome y sobre las tarjetas, y en los dos
     temas, sin necesidad de una variante por tema. */
  --primary-wash:   color-mix(in srgb, var(--primary) 20%, transparent);
  --primary-wash-h: color-mix(in srgb, var(--primary) 30%, transparent);
  --danger:    #ef4444;
  --success:   #22c55e;
  --warn:      #f59e0b;
  --info:      #3b82f6;
  --purple:    #8b5cf6;
  --text:      #1e293b;
  --muted:     #64748b;
  --radius:    10px;
  --shadow:    0 1px 4px rgba(0,0,0,.08);
  --shadow-lg: 0 8px 32px rgba(0,0,0,.12);
  --row-hover: #f1f5f9;

  /* Borde inferior de la banda de marca (§3 y §4): el mismo primary un
     escalón más oscuro. Va como token porque lo comparten las dos mitades
     de la banda y tienen que dar exactamente igual. */
  --primary-edge: color-mix(in srgb, var(--primary) 82%, #000);

  /* Pines y globos de grupo de la vista Mapa. Es el ROJO DE GOOGLE MAPS
     (#ea4335) y no `--primary`, y es la única cosa del panel pintada con
     un color que no sale de la paleta: adentro del mapa el rojo no es "lo
     destructivo" del sistema sino "acá hay algo", que es lo que cualquiera
     que haya usado un mapa ya sabe leer. Un pin azul obliga a aprender de
     nuevo algo que ya estaba aprendido.
     Va como token igual —y no como un hex suelto en panel.js— porque el
     color lo comparten el pin y el globo de grupo, y tienen que dar
     exactamente igual. NO cambia con el tema: el mapa se ve igual en los
     dos. Lo lee `mapaInmueblesPinColor()`. */
  --map-pin: #ea4335;

  /* Chrome. El cuerpo del sidebar es una superficie NEUTRA; la banda de
     marca de arriba (bloque del logo + topbar, §4 y §5) es la única parte
     del chrome pintada con `--primary`. */
  --chrome-bg:       #ffffff;
  --chrome-text:     #1e293b;
  --chrome-muted:    #64748b;
  --chrome-border:   #e2e8f0;
  --chrome-hover-bg: color-mix(in srgb, var(--primary) 10%, transparent);
  --chrome-sub-bg:   transparent;

  /* Badges. El par bg/fg se resuelve por tema: en claro van pasteles
     sólidos; en oscuro, translúcidos sobre la superficie gris. */
  --badge-info-bg:    #eff6ff; --badge-info-fg:    #2563eb;
  --badge-success-bg: #dcfce7; --badge-success-fg: #16a34a;
  --badge-danger-bg:  #fef2f2; --badge-danger-fg:  #dc2626;
  --badge-warn-bg:    #fef9c3; --badge-warn-fg:    #a16207;
  --badge-muted-bg:   #f1f5f9; --badge-muted-fg:   #64748b;
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg:        #1a1a1a;
  --canvas:    #1a1a1a;
  --surface:   #242526;
  --border:    #383838;
  --primary:   #1877f2;
  --primary-h: #1569d5;
  --danger:    #ef4444;
  --success:   #22c55e;
  --warn:      #f59e0b;
  --text:      #f0f0f0;
  --muted:     #9ca0a4;
  --shadow:    0 1px 4px rgba(0,0,0,.45);
  --shadow-lg: 0 8px 32px rgba(0,0,0,.65);
  --row-hover: #2d2e2f;

  /* El chrome queda un escalón por encima del canvas y otro por debajo de
     las tarjetas: tres planos de profundidad (contenido → chrome →
     tarjetas) en vez de la "L" de color institucional que había antes. */
  --chrome-bg:       #202225;
  --chrome-text:     #f0f0f0;
  --chrome-muted:    #9ca0a4;
  --chrome-border:   #2e3033;
  --chrome-hover-bg: color-mix(in srgb, var(--primary) 22%, transparent);
  --chrome-sub-bg:   transparent;

  --badge-info-bg:    rgba(59,130,246,.18);  --badge-info-fg:    #93c5fd;
  --badge-success-bg: rgba(34,197,94,.18);   --badge-success-fg: #86efac;
  --badge-danger-bg:  rgba(239,68,68,.20);   --badge-danger-fg:  #f5a8a8;
  --badge-warn-bg:    rgba(245,158,11,.18);  --badge-warn-fg:    #fcd34d;
  --badge-muted-bg:   rgba(255,255,255,.08); --badge-muted-fg:   #9ca0a4;
}

/* ---------- 2. Reset & base ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--bg); color: var(--text); min-height: 100vh;
}

/* ---------- 3. Layout principal ---------- */
.layout  { display: flex; min-height: 100vh; }
.sidebar { width: 220px; background: var(--chrome-bg);
           border-right: 1px solid var(--chrome-border);
           display: flex; flex-direction: column; flex-shrink: 0; }
.main    { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
/* La topbar y el bloque del logo forman UNA banda de marca continua a lo
   ancho de la pantalla: mismo `--primary` y mismo alto (60px), para que
   el escalón entre el sidebar y el contenido no se note. */
.topbar  { background: var(--primary); border-bottom: 1px solid var(--primary-edge);
           padding: 0 24px; height: 60px;
           display: flex; align-items: center; justify-content: space-between;
           box-shadow: var(--shadow); }
.content { flex: 1; padding: 24px; overflow-y: auto; background: var(--canvas); }

/* ---------- 4. Sidebar ---------- */
/* Banda de marca — es la mitad izquierda de la franja que sigue en la
   topbar (§3). El alto va clavado en 60px (el de la topbar) y no librado
   al contenido: con el alto natural del texto la banda quedaba 2px más
   baja y el escalón se veía justo en el borde entre el sidebar y el
   contenido. */
.sidebar-logo  { padding: 12px 20px 9px; height: 60px; flex-shrink: 0;
                 background: var(--primary); border-bottom: 1px solid var(--primary-edge);
                 display: flex; align-items: center; gap: 8px; }
/* El logotipo se ajusta al alto disponible de la banda y nunca se estira:
   `width:auto` + `object-fit:contain` lo dejan respirar contra los 60px de
   la franja sin importar la relación de aspecto del archivo. */
.sidebar-logo img { max-width: 100%; max-height: 28px;
                    width: auto; height: auto; object-fit: contain;
                    display: block; }
.sidebar-nav   { padding: 8px 0 12px; flex: 1; }

.nav-item { display: flex; align-items: center; gap: 10px;
            padding: 10px 20px; font-size: .9rem; color: var(--chrome-muted);
            cursor: pointer; border-left: 3px solid transparent;
            transition: background .15s, color .15s; text-decoration: none; }
.nav-item:hover  { background: var(--chrome-hover-bg); color: var(--chrome-text); }
/* El ítem activo se marca con una barra lateral en el color de la cuenta y
   un lavado del mismo color — no con el primary sólido de fondo. El chrome
   es neutro; el primary entra como acento. */
.nav-item.active { background: var(--primary-wash);
                   color: var(--chrome-text);
                   border-left-color: var(--primary); font-weight: 600; }
.nav-icon { font-size: 1.1rem; width: 20px; text-align: center; flex-shrink: 0; }
/* Los glifos de FA rinden más grandes que un emoji al mismo tamaño. */
.nav-icon i { font-size: 1rem; }
.nav-item.active .nav-icon i { color: var(--primary); }

.nav-group-wrap .nav-sub      { display: none; background: var(--chrome-sub-bg); }
.nav-group-wrap.open .nav-sub { display: block; }
/* La categoría es un encabezado de sección, no un link más: va en
   versalitas chicas para que los sub-ítems se lean como el nivel
   navegable y la categoría como su rótulo. */
.nav-group-toggle             { justify-content: space-between;
                                font-size: .72rem; font-weight: 700;
                                text-transform: uppercase; letter-spacing: .08em;
                                padding-top: 14px; padding-bottom: 8px; }
.nav-group-toggle:hover       { background: none; color: var(--chrome-text); }
.nav-group-arrow              { font-size: .95rem; font-weight: 700;
                                margin-left: auto; transition: transform .2s;
                                color: var(--chrome-muted); line-height: 1; }
.nav-group-wrap.open .nav-group-arrow { transform: rotate(45deg); }
.nav-sub-item                 { padding-left: 34px; background: var(--chrome-sub-bg);
                                font-size: .88rem; }

/* ---------- 5. Topbar ---------- */
/* Sobre la banda de marca los hijos van en blanco y en opacidades de
   blanco — nunca en `--chrome-text` / `--chrome-muted` / `--border`, que
   están calibrados contra el gris del chrome y sobre el azul se ensucian.
   Misma regla que rige la barra de título de los modales (§14). */
.topbar-title    { font-size: 1rem; font-weight: 600; flex: 1; color: #fff; }
.topbar-user     { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
/* Píldora con avatar: el botón de usuario es el único control anclado a la
   topbar, así que se lee como una pastilla y no como texto suelto. */
.topbar-username { background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.28);
                   cursor: pointer;
                   font-size: .85rem; color: #fff; font-family: inherit;
                   display: flex; align-items: center; gap: 8px;
                   padding: 4px 12px 4px 6px; border-radius: 99px;
                   transition: background .15s, border-color .15s; }
.topbar-username:hover { background: rgba(255,255,255,.24); border-color: rgba(255,255,255,.45); }
.topbar-avatar   { font-size: 1.5rem; line-height: 1; color: #fff; }
.topbar-username .fa-caret-down { color: rgba(255,255,255,.75); font-size: .8rem; }
/* La píldora dice QUIÉN opera y SOBRE QUÉ CUENTA — con accesos a varias
   cuentas, el nombre solo no alcanza para saber dónde se está escribiendo.
   Las dos mitades van en el MISMO blanco: son un solo rótulo y ninguna es
   secundaria de la otra; la que separa es la barra, que sí va atenuada.
   Cada mitad se recorta por su cuenta: un nombre largo no puede empujar la
   topbar ni comerse al otro. */
.topbar-username-texto   { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.topbar-username-persona,
.topbar-username-cuenta  { overflow: hidden; text-overflow: ellipsis;
                           white-space: nowrap; max-width: 20ch; color: #fff; }
.topbar-username-sep     { color: rgba(255,255,255,.45); flex-shrink: 0; }
.user-dropdown   { display: none; position: absolute; right: 0; top: calc(100% + 6px);
                   background: var(--surface); border: 1px solid var(--border);
                   border-radius: 10px; box-shadow: var(--shadow-lg);
                   min-width: 160px; overflow: hidden; z-index: 200; }
.user-dropdown.open { display: block; }

/* ---------- 6. Botones ---------- */
.btn { padding: 8px 16px; border-radius: var(--radius); border: none;
       font-size: .88rem; font-weight: 600; cursor: pointer;
       display: inline-flex; align-items: center; gap: 6px;
       transition: background .15s, transform .1s; }
.btn:active           { transform: scale(.97); }
.btn:disabled         { opacity: .65; cursor: not-allowed; }
.btn-primary          { background: var(--primary); color: #fff; }
.btn-primary:hover    { background: var(--primary-h); }
.btn-danger           { background: var(--danger); color: #fff; }
.btn-danger:hover     { background: #dc2626; }
/* Resolver algo a favor. Existe por las dos bandejas que el panel decide
 * —Visitas agendadas y Ofertas—, donde aprobar y rechazar son la razón de
 * ser de la pantalla y van como botones directos de la barra del modal,
 * uno en success y otro en danger (ver DESIGN.md §14). Fuera de ese par no
 * se usa: un "Guardar" es primary, no success. */
.btn-success          { background: var(--success); color: #fff; }
.btn-success:hover    { background: color-mix(in srgb, var(--success) 85%, #000); }
.btn-secondary        { background: var(--surface); color: var(--text);
                        border: 1px solid var(--border); }
.btn-secondary:hover  { background: var(--bg); }
.btn-ghost            { background: transparent; color: var(--muted);
                        border: 1px solid var(--border); }
.btn-ghost:hover      { background: var(--bg); color: var(--text); }
.btn-sm               { padding: 5px 12px; font-size: .8rem; }
.btn-icon-sm          { background: none; border: none; cursor: pointer;
                        padding: 4px 8px; border-radius: 6px; font-size: .85rem; }
.btn-icon-sm:hover    { background: var(--bg); }

/* ---------- 7. Inputs ---------- */
input[type=text], input[type=number], input[type=url], input[type=tel],
input[type=email], input[type=date], input[type=time], input[type=datetime-local],
input[type=password], input[type=search], select, textarea {
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px 12px; font-size: .88rem; background: var(--surface);
  color: var(--text); outline: none; transition: border .15s; font-family: inherit;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
}
input:disabled, select:disabled, textarea:disabled,
input[readonly], textarea[readonly] {
  color: var(--muted); background: var(--bg); cursor: not-allowed; opacity: .75;
}
textarea { resize: vertical; min-height: 60px; }

.field-error   { margin-top: 4px; font-size: .78rem; color: var(--danger); }
.field-hint    { margin-top: 4px; font-size: .78rem; color: var(--muted); }
.input-invalid { border-color: var(--danger) !important;
                 box-shadow: 0 0 0 2px rgba(239,68,68,.18); }

.password-wrap        { position: relative; display: block; }
.password-wrap input  { padding-right: 38px; width: 100%; }
.password-toggle      { position: absolute; top: 50%; right: 6px;
                        transform: translateY(-50%);
                        background: none; border: none; cursor: pointer;
                        color: var(--muted); padding: 4px 8px;
                        border-radius: 6px; font-size: .95rem;
                        transition: color .15s, background .15s; }
.password-toggle:hover { color: var(--text); background: var(--bg); }

/* ---------- 8. Formularios ---------- */
.form-row    { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.form-row-3  { grid-template-columns: repeat(3, 1fr); }
.form-row-4  { grid-template-columns: repeat(4, 1fr); }
.form-group  { display: flex; flex-direction: column; gap: 5px; }
.form-group label { font-size: .8rem; font-weight: 600; color: var(--muted); }
.form-group input, .form-group select, .form-group textarea { width: 100%; }

/* ---------- 10. Tablas ---------- */
.table-card { background: var(--surface); border: 1px solid var(--border);
              border-radius: var(--radius); box-shadow: var(--shadow);
              overflow-x: auto; overflow-y: hidden; }
table        { width: 100%; border-collapse: collapse; font-size: .88rem; }
thead tr     { background: var(--bg); }
th           { padding: 10px 14px; text-align: left;
               font-size: .75rem; text-transform: uppercase; letter-spacing: .05em;
               color: var(--muted); font-weight: 600;
               border-bottom: 1px solid var(--border); white-space: nowrap; }
td           { padding: 10px 14px; border-bottom: 1px solid var(--border);
               vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--row-hover); }
.actions      { display: flex; gap: 4px; }
.table-empty  { text-align: center; padding: 48px 24px; color: var(--muted); }

/* Headers de tabla que actúan como atajo del filtro de orden. Clic
   sobre el título alterna asc/desc; el estado activo se refleja con la
   flechita ↑ / ↓. */
th.th-sortable            { cursor: pointer; user-select: none; transition: color .15s; }
th.th-sortable:hover      { color: var(--primary); }
th.th-sortable::after     { content: ""; display: inline-block; width: 10px;
                            margin-left: 6px; opacity: 0; transition: opacity .15s; }
th.th-sortable.sorted-asc,
th.th-sortable.sorted-desc { color: var(--primary); }
th.th-sortable.sorted-asc::after  { content: "↑"; opacity: 1; }
th.th-sortable.sorted-desc::after { content: "↓"; opacity: 1; }
.td-img       { width: 44px; height: 44px; object-fit: cover;
                border-radius: 8px; border: 1px solid var(--border); }
/* Columna identificatoria del listado (título / nombre del registro). Va en
   el color de la cuenta: es el ancla visual de la fila y lo que el operador
   busca al barrer la tabla con la vista. */
.td-nombre    { font-weight: 600; color: var(--primary); }
.td-id        { color: var(--muted); font-size: .8rem; }

/* Fila SIN LEER de las bandejas (Visitas, Ofertas, Conversaciones). Lleva el
   MISMO lavado que el ítem activo del sidebar (`--primary-wash`): las dos
   cosas dicen "esto está marcado con el color de la cuenta", así que salen
   del mismo token y no se pueden desincronizar. La fila leída no lleva
   ninguna marca: renglón común, como en cualquier otro listado.
   Al ser un tinte y no un relleno, el contenido NO se fuerza a blanco —
   conserva sus colores normales y se lee igual en los dos temas. Lo que
   refuerza la señal es la negrita: tinte + `font-weight: 700`.
   Corolario que sigue vigente: dentro de estos listados ningún texto ni
   ícono se pinta con `--primary` ni con `--danger`; el color es de la
   fila, no del contenido. */
tbody tr.row-unread       { background: var(--primary-wash); font-weight: 700; }
/* El hover sube un escalón para que la fila siga acusando el mouse: con el
   mismo tono que en reposo, la fila sin leer parecería no responder. */
tbody tr.row-unread:hover { background: var(--primary-wash-h); }

/* ---------- 11. Badges ---------- */
.badge         { display: inline-block; padding: 2px 10px;
                 border-radius: 99px; font-size: .75rem; font-weight: 600; }
.badge-info    { background: var(--badge-info-bg);    color: var(--badge-info-fg); }
.badge-success { background: var(--badge-success-bg); color: var(--badge-success-fg); }
.badge-danger  { background: var(--badge-danger-bg);  color: var(--badge-danger-fg); }
.badge-warn    { background: var(--badge-warn-bg);    color: var(--badge-warn-fg); }
.badge-muted   { background: var(--badge-muted-bg);   color: var(--badge-muted-fg); }

/* ---------- 12. Stat cards ---------- */
.stats-bar  { display: flex; gap: 14px; margin-bottom: 20px; flex-wrap: wrap; }
.stat-card  { background: var(--surface); border: 1px solid var(--border);
              border-radius: var(--radius); padding: 16px 22px;
              display: flex; flex-direction: column; gap: 4px;
              flex: 1; min-width: 120px; }
.stat-label { font-size: .75rem; color: var(--muted); font-weight: 600;
              text-transform: uppercase; letter-spacing: .06em; }
.stat-value { font-size: 1.9rem; font-weight: 700; line-height: 1.1; }
.stat-value.green  { color: var(--success); }
.stat-value.blue   { color: var(--primary); }
.stat-value.red    { color: var(--danger); }
.stat-value.amber  { color: var(--warn); }

.dash-link { cursor: pointer; transition: opacity .15s; }
.dash-link:hover { opacity: .75; }
.stat-card.dash-link:hover { background: var(--bg); }

/* ---------- 13. Dashboard grid ---------- */
.dash-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 4px; }
@media (max-width: 768px) { .dash-grid { grid-template-columns: 1fr; } }
/* Si un dash-grid queda con una sola tarjeta (por ejemplo, el bloque
   de Contenidos con Posts pero sin Noticias) la tarjeta se estira al
   ancho completo — así el widget solo no queda pegado a la izquierda. */
.dash-grid > .table-card:only-child { grid-column: 1 / -1; }

.dash-table-header { padding: 14px 20px 10px;
                     font-weight: 600; font-size: .95rem;
                     border-bottom: 1px solid var(--border);
                     display: flex; align-items: center; justify-content: space-between;
                     gap: 12px; }
.dash-ver-mas      { font-size: .78rem; font-weight: 500; color: var(--primary);
                     text-decoration: none; cursor: pointer; }
.dash-ver-mas:hover{ text-decoration: underline; }

/* ---------- 13-bis. Pendientes de respuesta (dashboard) ----------
   Lo que entró por el portal y todavía nadie contestó: visitas, ofertas
   y conversaciones. Es lo único del dashboard que pide una acción hoy
   —el resto es información— así que va arriba de todo y se ve distinto
   del resto de la pantalla.

   Va en `--warn` y no en `--primary` por §12 del DESIGN: lo pendiente es
   ámbar. Además es lo que lo separa visualmente del resto del dashboard,
   que es todo el color de la cuenta, y lo que hace que se lea como un
   pedido y no como una métrica más. El lavado se mezcla contra
   `--surface` (no contra un hex) para que el tono siga solo al tema. */
.pendientes-bar    { background: color-mix(in srgb, var(--warn) 10%, var(--surface));
                     border: 1px solid color-mix(in srgb, var(--warn) 35%, var(--border));
                     border-left: 4px solid var(--warn);
                     border-radius: var(--radius); box-shadow: var(--shadow);
                     padding: 14px 18px; margin-bottom: 16px;
                     display: flex; flex-direction: column; gap: 12px; }
.pendientes-head   { display: flex; align-items: center; gap: 14px; }
.pendientes-icono  { font-size: 1.6rem; line-height: 1; color: var(--warn); }
.pendientes-titulo { font-weight: 700; font-size: .95rem; }
.pendientes-sub    { font-size: .82rem; color: var(--muted); margin-top: 2px; }

/* Una tarjeta por bandeja, auto-fill: con una sola pendiente ocupa la
   fila entera y con las tres se reparten. Cada una es un link al módulo
   —no un contador— así que se pinta como tarjeta clickeable sobre el
   lavado ámbar, no como texto suelto. */
.pendientes-grid   { display: grid; gap: 10px;
                     grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.pendientes-item   { display: flex; align-items: center; gap: 12px;
                     background: var(--surface); border: 1px solid var(--border);
                     border-radius: var(--radius); padding: 12px 14px;
                     text-decoration: none; color: var(--text); cursor: pointer;
                     transition: border-color .15s, background .15s; }
.pendientes-item:hover { border-color: var(--warn); background: var(--row-hover); }
.pendientes-item > i   { font-size: 1.15rem; color: var(--warn);
                         width: 22px; text-align: center; flex-shrink: 0; }
.pendientes-num    { font-size: 1.5rem; font-weight: 700; line-height: 1;
                     color: var(--warn); }
.pendientes-texto  { font-size: .82rem; color: var(--muted); line-height: 1.3; flex: 1; }
.pendientes-flecha { margin-left: auto; color: var(--muted); font-size: .95rem;
                     line-height: 1; flex-shrink: 0; }

/* ---------- 9. Toolbar (búsqueda + filtros + acciones) ---------- */
.toolbar       { display: flex; align-items: center; gap: 12px;
                 flex-wrap: wrap; margin-bottom: 20px; }
.toolbar-left  { display: flex; align-items: center; gap: 10px;
                 flex: 1; flex-wrap: wrap; }
.toolbar-right { display: flex; gap: 10px; }

.search-wrap        { position: relative; display: inline-flex; align-items: center; }
.search-wrap .search-input { width: 260px; padding-right: 28px; }
.search-clear       { position: absolute; right: 6px; background: none; border: none;
                      cursor: pointer; color: var(--muted); font-size: 1.1rem;
                      padding: 2px 6px; border-radius: 50%; transition: color .15s; }
.search-clear:hover { color: var(--text); }

.filter-chip        { padding: 6px 12px; border-radius: 20px;
                      border: 1.5px solid var(--border);
                      font-size: .8rem; font-weight: 600; cursor: pointer;
                      white-space: nowrap; background: var(--surface);
                      color: var(--muted); transition: all .15s; }
.filter-chip:hover  { border-color: var(--primary); color: var(--primary); }
.filter-chip.active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* Botones solo-ícono con badge opcional */
.btn-icon            { position: relative; padding: 7px 11px; }
.btn-icon.active     { border-color: var(--primary); color: var(--primary); }
.btn-icon-badge      { position: absolute; top: -4px; right: -4px;
                       background: var(--primary); color: #fff;
                       font-size: .65rem; font-weight: 700;
                       min-width: 16px; height: 16px; padding: 0 4px;
                       border-radius: 99px; display: inline-flex;
                       align-items: center; justify-content: center;
                       line-height: 1; }

/* ---------- 14. Modales ---------- */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.45);
                  display: flex; align-items: center; justify-content: center;
                  z-index: 100; opacity: 0; pointer-events: none;
                  transition: opacity .2s; }
.modal-backdrop.open { opacity: 1; pointer-events: all; }
/* El scroll es del CUERPO, no del modal entero: la barra de título y la
   barra de acciones quedan fijas. Sin `min-height:0` en el cuerpo el
   `overflow-y` nunca se activa (un hijo flex no baja de su alto de
   contenido) y la cabecera se iría hacia arriba con el scroll. */
.modal          { background: var(--surface); border-radius: 14px;
                  width: 100%; max-width: 520px; max-height: 90vh;
                  overflow: hidden; display: flex; flex-direction: column;
                  box-shadow: var(--shadow-lg);
                  transform: scale(.96) translateY(12px);
                  transition: transform .2s; margin: 16px; }
.modal-backdrop.open .modal { transform: scale(1) translateY(0); }
.modal-header   { padding: 20px 24px 16px; border-bottom: 1px solid var(--border);
                  display: flex; align-items: center; justify-content: space-between;
                  gap: 12px; }
.modal-title    { font-size: 1rem; font-weight: 700;
                  display: inline-flex; align-items: center; gap: 8px; }
.modal-body     { padding: 20px 24px;
                  display: flex; flex-direction: column; gap: 16px;
                  flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.modal-header, .modal-menubar { flex: 0 0 auto; }

/* Barra de título pintada en primary. Va JUNTO a `.modal-header`, no en su
   lugar. Sobre el color de marca los hijos usan `#fff` y opacidades de
   blanco: `--text` / `--muted` / `--border` están calibrados contra el gris
   del modal y acá se ensucian. */
.modal-header-primary {
    background: var(--primary);
    color: #fff;
    border-bottom: none;          /* con el fondo pintado, la divisoria sobra */
    border-radius: 14px 14px 0 0; /* mismo radio que el `.modal` */
    padding: 10px 24px;           /* más baja que el header normal (20/24/16) */
}
.modal-header-primary .modal-title       { color: #fff; }
.modal-header-primary .modal-subtitle    { color: rgba(255,255,255,.75); }
.modal-header-primary .btn-icon-sm,
.modal-header-primary .btn-ghost         { color: #fff; background: transparent;
                                           border-color: rgba(255,255,255,.35); }
.modal-header-primary .btn-icon-sm       { border: none; }
.modal-header-primary .btn-icon-sm:hover,
.modal-header-primary .btn-ghost:hover   { background: rgba(255,255,255,.18); color: #fff; }

/* Barra de acciones, pegada debajo del título. Lleva TODOS los botones del
   modal — no hay `.modal-footer`. Los 14px verticales no son los 10 del
   título: acá hay botones de ~30px y con 10 quedan tocando el primary de
   arriba y la divisoria de abajo. */
.modal-menubar {
    padding: 14px 24px;
    border-bottom: 1px solid var(--border);
    /* Un escalón MÁS OSCURO que el cuerpo: gris intermedio entre los dos
     * tokens que ya conviven en el modal. La mezcla es ENTRE TOKENS, no
     * contra #000, así el tono sigue solo al tema. */
    background: color-mix(in srgb, var(--surface) 40%, var(--bg));
    display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* La barra normaliza el tamaño sola: ningún call site se olvida el `btn-sm`. */
.modal-menubar .btn { white-space: nowrap; padding: 5px 12px; font-size: .8rem; }
/* La salida va RELLENA con el gris del cuerpo, no transparente: sobre la
 * franja más oscura un ghost sin fondo se lee como un hueco al lado de los
 * primary. Y su hover SUBE a `--row-hover` en vez de bajar al color de la
 * franja, que lo haría desaparecer bajo el mouse. */
.modal-menubar .btn-ghost       { background: var(--surface); color: var(--text); }
.modal-menubar .btn-ghost:hover { background: var(--row-hover); color: var(--text); }
.modal-menubar-end  { margin-left: auto; }   /* empuja un botón al borde derecho */
.menubar-caret      { font-size: .7em; opacity: .65; margin-left: 2px; }

/* Intercambiador de rol (dropdown de la topbar → Cambiar de rol). Cada
   cuenta es un botón de ancho completo y no un radio + submit: son pocas
   opciones y un clic tiene que alcanzar para saltar. Mismo criterio y
   mismo look que la pantalla `elegir-cuenta.php`, que resuelve lo mismo
   durante el ingreso. */
.rol-lista     { display: flex; flex-direction: column; gap: 8px; }
.rol-opcion    { display: flex; align-items: center; gap: 12px; width: 100%;
                 padding: 12px 14px; border: 1px solid var(--border);
                 border-radius: var(--radius); background: var(--bg);
                 color: var(--text); font-size: .9rem; font-weight: 600;
                 cursor: pointer; text-align: left;
                 transition: border-color .15s, background .15s; }
.rol-opcion:hover      { border-color: var(--primary); background: var(--surface); }
.rol-opcion > i        { color: var(--muted); width: 18px; text-align: center; flex-shrink: 0; }
.rol-opcion:hover > i  { color: var(--primary); }
.rol-opcion-nombre     { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rol-opcion-rol        { margin-left: auto; font-size: .72rem; font-weight: 500;
                         color: var(--muted); text-transform: uppercase;
                         letter-spacing: .04em; flex-shrink: 0; }
/* El rol activo se muestra igual que el resto pero sin acción: elegirlo
   sería reemitir el mismo token. */
.rol-opcion.actual     { border-color: var(--primary); cursor: default; }
.rol-opcion.actual:hover { background: var(--bg); }
.rol-opcion.actual > i { color: var(--primary); }
.rol-opcion.actual .rol-opcion-rol { color: var(--primary); }

/* Modal `formInmuebleBackdrop` (Nuevo / Editar / Revisar inmueble
   importado): comparte el mismo layout de pestañas para los tres modos.
   Las pestañas (`.form-tabs`) van pegadas debajo del header del modal
   y cada `.form-tab-panel` ocupa el ancho completo del body. */
.form-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--border);
  padding: 0 12px;
  overflow-x: auto;
  flex-wrap: nowrap;
  flex-shrink: 0;
}
.form-tab {
  background: none;
  border: none;
  padding: 12px 16px;
  font-size: .85rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  transition: color .15s, border-color .15s, background .15s;
  white-space: nowrap;
  font-family: inherit;
}
.form-tab:hover  { color: var(--text); background: var(--bg); }
.form-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
/* Dentro de un modal el cuerpo va sin padding (las pestañas y la
   divisoria tienen que llegar a los dos bordes), así que la sangría la
   pone la tira: los botones arrancan en la misma columna de 24px que las
   tarjetas y el encabezado de la ficha. Con los 12px de la tira genérica
   el subrayado del activo quedaba casi tocando el lateral, corrido de
   todo lo que tiene debajo. Fuera del modal —la tira de Facturas /
   Recibos dentro de una `.table-card`— el aire ya lo da la tarjeta. */
.modal .form-tabs { padding: 0 24px; }
.form-tab-panel  { display: none; padding: 20px 24px; }
.form-tab-panel.active {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* Encabezado de registro: de qué ficha se trata. Va entre la barra de
   acciones y las pestañas, y FUERA del `.modal-body` para no irse con el
   scroll — es el contexto que hace falta en todas las pestañas, no sólo
   en la primera.

   Es una CAJA y no una franja a todo el ancho: mismo fondo, radio e
   inset que las tarjetas de consulta (ver ABM.md). Pegada a los bordes y
   con divisoria abajo se leía como una segunda barra de menú abrochada a
   la de acciones; recuadrada se lee como la portada de la ficha. Adentro
   son dos columnas: la identidad del registro a la izquierda, su estado
   y sus fechas a la derecha. */
.modal-rechead        { flex: 0 0 auto; margin: 16px 24px 12px;
                        padding: 16px 20px;
                        background: color-mix(in srgb, var(--surface) 90%, #000);
                        border-radius: var(--radius);
                        display: flex; align-items: flex-start;
                        justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.modal-rechead-main   { flex: 1; min-width: 0; }
/* De qué tipo de registro es la ficha. Versalitas chicas: rotula lo que
   viene abajo, no compite con él. */
.modal-rechead-kicker { font-size: .72rem; font-weight: 700; color: var(--muted);
                        text-transform: uppercase; letter-spacing: .06em;
                        margin-bottom: 4px; }
/* El renglón grande: cómo se llama el registro. Va en `--text` y al mismo
   cuerpo que el dato destacado de más abajo — los dos son lo que se lee
   de la portada, y al mismo tamaño se leen como un par. En el color de la
   cuenta, que es lo que usa el `.td-nombre` del listado, un título largo
   de aviso se convertía en un bloque rojo de dos renglones. */
.modal-rechead-title  { font-size: 1.15rem; font-weight: 700; color: var(--text);
                        line-height: 1.25; word-break: break-word; }
/* El dato destacado de la portada (quién preguntó, quién ofertó): el
   rótulo arriba y el valor en cuerpo grande, como el total de un
   comprobante. */
.modal-rechead-hi        { margin-top: 10px; }
.modal-rechead-hi-label  { font-size: .72rem; color: var(--muted);
                           text-transform: uppercase; letter-spacing: .06em; }
.modal-rechead-hi-value  { font-size: 1.15rem; font-weight: 700; line-height: 1.25;
                           color: var(--text); word-break: break-word; }
/* El estado, arriba a la derecha de la caja. */
.modal-rechead-badges { display: flex; align-items: center; gap: 6px;
                        flex-wrap: wrap; justify-content: flex-end; flex-shrink: 0; }

/* Editor WYSIWYG minimo (contenteditable + toolbar con execCommand).
   Se usa en el modal Editar casilla (pestaña Firma). No usar libreria:
   vanilla JS + los estilos base de la app. */
.wysiwyg-toolbar {
  display: flex; align-items: center; gap: 2px; flex-wrap: wrap;
  padding: 6px 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-bottom: none;
  border-top-left-radius: var(--radius);
  border-top-right-radius: var(--radius);
}
.wysiwyg-btn {
  background: none; border: none; cursor: pointer;
  padding: 6px 9px; border-radius: 6px;
  color: var(--muted); font-size: .85rem;
  transition: background .12s, color .12s;
  font-family: inherit;
}
.wysiwyg-btn:hover  { background: var(--surface); color: var(--text); }
.wysiwyg-btn.active { background: var(--surface); color: var(--primary); }
.wysiwyg-sep {
  display: inline-block; width: 1px; height: 18px;
  background: var(--border); margin: 0 4px;
}
.wysiwyg-editor {
  min-height: 180px; max-height: 320px; overflow-y: auto;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
  font-size: .9rem; line-height: 1.45; color: var(--text);
  outline: none;
}
.wysiwyg-editor:focus { border-color: var(--primary);
                        box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.wysiwyg-editor:empty::before {
  content: attr(data-placeholder);
  color: var(--muted); pointer-events: none;
}
.wysiwyg-editor img       { max-width: 100%; height: auto; }
.wysiwyg-editor a         { color: var(--primary); text-decoration: underline; }
.wysiwyg-editor ul,
.wysiwyg-editor ol        { padding-left: 20px; margin: 4px 0; }
.wysiwyg-source {
  min-height: 180px; max-height: 320px; width: 100%;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
  font-family: monospace; font-size: .82rem; line-height: 1.4;
  color: var(--text); resize: vertical;
}

/* Tarjetas de campos del modal de Consultar (10% más oscuro que --surface) */
.consult-grid   { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.consult-card   { background: color-mix(in srgb, var(--surface) 90%, #000);
                  border: none; border-radius: var(--radius);
                  padding: 10px 14px; display: flex; flex-direction: column; gap: 2px; }
.consult-card.full { grid-column: 1 / -1; }
/* Rótulo con sello: el nombre del campo a la izquierda y, contra el borde
   opuesto, cuándo pasó. Es para las tarjetas donde la fecha es del TEXTO
   y no del registro —el mensaje y su respuesta—, así el
   dato queda pegado a lo que fecha en vez de en el encabezado. */
.consult-cardhead { display: flex; align-items: baseline;
                    justify-content: space-between; gap: 10px; }
.consult-stamp    { font-size: .72rem; color: var(--muted); white-space: nowrap; }
/* Cifra de portada dentro de una tarjeta: el dato por el que se abre la
   ficha. Lo usan las dos cifras de una oferta —lo ofrecido y el precio
   publicado—, que son un par que se compara y por eso van al mismo
   cuerpo. Se reserva para eso: si todas las tarjetas gritan, ninguna
   resalta. */
.consult-value.big { font-size: 1.35rem; font-weight: 700; line-height: 1.25; }
.consult-label  { font-size: .72rem; color: var(--muted);
                  text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.consult-value  { font-size: .9rem; color: var(--text); word-break: break-word; }
@media (max-width: 540px) { .consult-grid { grid-template-columns: 1fr; } }

/* ---------- El boleto de compraventa (módulo Operaciones) ----------
   El documento que el comprador imprimió desde el portal, guardado entero
   en `avisos_operaciones.boleto` y pintado tal cual dentro de la pestaña
   Boleto de su ficha.

   El HTML lo escribe `www/lib/boleto.php` y trae sus propias clases
   `boleto-*`, que allá las estila un bloque `@media print`. Acá hay que
   estilarlas para PANTALLA, y encima hay que DESHACER lo que el CSS
   global del panel le hace a un `<table>` suelto: ancho completo,
   `thead` con fondo, `th` en versalitas y `td` con divisoria. Sin ese
   reset, la tabla de datos de las partes se vería como un listado del
   admin y no como la grilla de un contrato.

   Va con los tokens del tema y no con tinta negra sobre papel blanco: es
   un documento de TEXTO, no la foto de una hoja, y el panel arranca en
   oscuro. La hoja blanca aparece al imprimir, que es donde corresponde
   (ver el bloque `@media print` de más abajo).

   La FAMILIA y el aire entre párrafos son los del papel: el documento se
   imprime en sans y corrido, sin renglones en blanco entre cláusulas
   (ver `appBoletoCss()` en www/lib/boleto.php), y esto es ese mismo
   documento en pantalla. Que la vista previa se lea distinta del papel
   que sale abajo es justamente lo que no puede pasar. */
.boleto-doc {
  background: color-mix(in srgb, var(--surface) 90%, #000);
  border-radius: var(--radius);
  padding: 26px 30px;
  color: var(--text);
  font-family: Calibri, Carlito, 'Segoe UI', Arial, Helvetica, sans-serif;
  font-size: .82rem;
  line-height: 1.5;
}
.boleto-doc p  { margin: 0; text-align: justify; }
.boleto-doc h1 { margin: 0 0 20px; font-size: 1.05rem; letter-spacing: .04em;
                 text-align: center; text-transform: uppercase; }
.boleto-doc h2 { margin: 66px 0 3px; font-size: .92rem; letter-spacing: .03em;
                 text-transform: uppercase; }

/* El cartel de borrador, con marco: lo que se está mirando es un MODELO
   y eso tiene que leerse antes que el título, igual que en el papel. */
.boleto-doc .boleto-borrador {
  margin: 0 0 10px; padding: 5px 10px;
  border: 1px solid currentColor; border-radius: 6px;
  font-family: inherit; font-size: .72rem; font-weight: 700;
  letter-spacing: .06em; text-align: center; text-transform: uppercase;
  color: var(--warn);
}
.boleto-doc .boleto-lugar { text-align: right; margin-bottom: 8px; }

/* Las rayas de lo que el portal NO sabe —dominio, matrícula, DNI— y hay
   que completar a mano. Van explícitas y no omitidas: quien firma tiene
   que ver qué le falta al documento. */
.boleto-doc .boleto-raya {
  display: inline-block; min-width: 4.5cm;
  border-bottom: 1px solid var(--muted);
}

/* Reset de lo que el CSS global le hace a cualquier `<table>`. */
.boleto-doc table    { width: 100%; border-collapse: collapse; font-size: inherit; }
.boleto-doc thead tr { background: none; }
.boleto-doc th,
.boleto-doc td {
  padding: 0 10px 0 0; border-bottom: none; white-space: normal;
  font-size: .86rem; font-weight: normal; text-transform: none;
  letter-spacing: normal; color: inherit; text-align: left;
  vertical-align: baseline;
}

/* Ficha del bien (cláusula SEGUNDA). Desde 2026-09-18f los pares van en
   el párrafo de la cláusula, separados por comas y a continuación del
   rótulo, así que hereda todo del `p` de arriba y lo único propio es la
   raya: en el mismo párrafo puede haber tres —dominio, motor y chasis— y
   con los 4.5cm de la suelta cada una se comía un renglón. */
.boleto-doc p.boleto-ficha .boleto-raya { min-width: 2.6cm; }

/* Los documentos CONGELADOS antes de esa fecha traen la cláusula en
   TABLA y se siguen mirando acá tal como se imprimieron: rótulo a ancho
   fijo para que la columna de valores arranque siempre a la misma altura
   —"Año" y "Nomenclatura catastral" están en la misma tabla—. No se
   borran aunque ningún documento nuevo las use. */
.boleto-doc table.boleto-ficha { margin: 0 0 12px; }
.boleto-doc table.boleto-ficha th { width: 5.2cm; color: var(--muted); }

/* Datos de las partes: dos columnas iguales, el encabezado en versalitas
   chicas. Acá SÍ se recupera algo del `th` del panel, porque "EL
   VENDEDOR" / "EL COMPRADOR" son encabezados de columna de verdad. */
.boleto-doc .boleto-partes    { margin-top: 20px; }
.boleto-doc .boleto-partes th,
.boleto-doc .boleto-partes td { width: 50%; vertical-align: top; }
.boleto-doc .boleto-partes th { font-size: .74rem; font-weight: 700;
                                letter-spacing: .05em; color: var(--muted); }
.boleto-doc .boleto-partes .boleto-raya { min-width: 2.8cm; }

.boleto-doc .boleto-ejemplares { margin-top: 10px; }

.boleto-doc .boleto-firmas    { margin-top: 110px; }
.boleto-doc .boleto-firmas td { width: 50%; padding: 0 14px; text-align: center;
                                font-size: .74rem; letter-spacing: .05em;
                                color: var(--muted); }
.boleto-doc .boleto-firma-linea { display: block; margin-bottom: 6px;
                                  border-top: 1px solid var(--muted); }

.boleto-doc .boleto-pie { margin-top: 14px; font-family: inherit;
                          font-size: .7rem; text-align: center; color: var(--muted); }

/* Layout en dos columnas para el modal Consultar (ficha + galería) */
.consult-layout  { display: grid; grid-template-columns: 1fr 1fr;
                   gap: 20px; align-items: start; }
.consult-gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.consult-foto    { position: relative; display: block; aspect-ratio: 4/3;
                   overflow: hidden; border-radius: var(--radius);
                   background: var(--bg); border: 1px solid var(--border); }
.consult-foto img { width: 100%; height: 100%; object-fit: cover; display: block; }
.consult-gallery-empty,
.consult-gallery-loading,
.consult-gallery-error { grid-column: 1 / -1; text-align: center;
                         padding: 16px; color: var(--muted); font-size: .85rem; }
.consult-gallery-error { color: var(--danger); }
@media (max-width: 900px) {
  .consult-layout  { grid-template-columns: 1fr; }
  .consult-gallery { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- 15. Confirm dialog ---------- */
.confirm-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.5);
                    display: flex; align-items: center; justify-content: center;
                    z-index: 150; opacity: 0; pointer-events: none;
                    transition: opacity .15s; }
.confirm-backdrop.open { opacity: 1; pointer-events: all; }
.confirm-box     { background: var(--surface); border-radius: 14px;
                   padding: 28px 28px 20px; max-width: 380px;
                   width: calc(100% - 32px); box-shadow: var(--shadow-lg);
                   transform: scale(.95); transition: transform .15s; }
.confirm-backdrop.open .confirm-box { transform: scale(1); }
.confirm-title   { font-weight: 700; margin-bottom: 8px; }
.confirm-msg     { font-size: .88rem; color: var(--muted); margin-bottom: 20px;
                   line-height: 1.45; }
.confirm-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* ---------- Config cards (sección Configuración) ---------- */
.config-grid       { display: grid; gap: 16px;
                     grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
.config-card       { background: var(--surface); border: 1px solid var(--border);
                     border-radius: var(--radius); box-shadow: var(--shadow);
                     display: flex; flex-direction: column; overflow: hidden; }
.config-card-header { display: flex; gap: 14px; padding: 16px 18px; align-items: flex-start; }
/* Ancho fijo para que el texto de todas las tarjetas arranque en la misma
   columna: los glifos de FA no miden todos lo mismo. */
.config-card-icon  { font-size: 1.8rem; line-height: 1; flex-shrink: 0;
                     width: 28px; text-align: center; }
/* Los glifos de FA rinden más grandes que un emoji al mismo cuerpo, y son
   monocromos: bajan un escalón y se pintan en la marca, como la tarjeta de
   ayuda de cada módulo. */
.config-card-icon i { font-size: 1.5rem; color: var(--primary); }
.config-card-info  { display: flex; flex-direction: column; gap: 4px; }
.config-card-title { font-weight: 700; font-size: .95rem; color: var(--text); }
.config-card-desc  { font-size: .82rem; color: var(--muted); line-height: 1.4; }
.config-card-body  { padding: 14px 18px; border-top: 1px solid var(--border); }

/* ---------- Ayuda: título de grupo entre grillas de tarjetas ---------- */
.ayuda-group-title { font-size: .78rem; text-transform: uppercase;
                     letter-spacing: .06em; color: var(--muted);
                     font-weight: 700; margin: 22px 0 12px; }
.ayuda-group-title:first-of-type { margin-top: 8px; }

/* ---------- 17. Toggle switches ---------- */
.toggle-switch { display: inline-flex; align-items: center; gap: 12px;
                 cursor: pointer; user-select: none; }
.toggle-switch input { display: none; }
.toggle-track  { width: 44px; height: 24px; border-radius: 12px;
                 background: var(--border); position: relative;
                 transition: background .2s; flex-shrink: 0; }
.toggle-switch input:checked + .toggle-track { background: var(--primary); }
.toggle-thumb  { position: absolute; top: 3px; left: 3px;
                 width: 18px; height: 18px; border-radius: 50%;
                 background: #fff; transition: transform .2s;
                 box-shadow: 0 1px 4px rgba(0,0,0,.2); }
.toggle-switch input:checked + .toggle-track .toggle-thumb { transform: translateX(20px); }
.toggle-label  { font-size: .9rem; color: var(--text); }

/* ---------- 20. Menú contextual ---------- */
.ctx-menu { position: fixed; background: var(--surface);
            border: 1px solid var(--border); border-radius: 10px;
            box-shadow: var(--shadow-lg); min-width: 180px;
            padding: 6px; z-index: 250; display: none; }
.ctx-menu.open { display: block; }
.ctx-menu button { display: flex; align-items: center; gap: 10px;
                   width: 100%; padding: 8px 12px; border: none;
                   background: none; cursor: pointer;
                   font-size: .88rem; color: var(--text);
                   border-radius: 6px; text-align: left; }
.ctx-menu button i { width: 16px; text-align: center; color: var(--muted); }
.ctx-menu button:hover { background: var(--bg); }
.ctx-menu .ctx-menu-sep { height: 1px; background: var(--border); margin: 6px 4px; }
.ctx-menu .ctx-menu-danger { color: var(--danger); }
.ctx-menu .ctx-menu-danger i { color: var(--danger); }
.ctx-menu .ctx-menu-danger:hover { background: rgba(239,68,68,.08); }
.ctx-menu .ctx-menu-label { font-size: .68rem; font-weight: 700;
                            text-transform: uppercase; letter-spacing: .06em;
                            color: var(--muted); padding: 8px 12px 4px;
                            pointer-events: none; user-select: none; }
.ctx-menu button i.ctx-check { opacity: 0; color: var(--primary); }
.ctx-menu button[data-active="1"] { color: var(--primary); font-weight: 700; }
.ctx-menu button[data-active="1"] i.ctx-check { opacity: 1; }

/* Filas clickeables del listado */
.row-clickable { cursor: pointer; }

/* ---------- 16. Toasts ---------- */
.toast { position: fixed; bottom: 24px; left: 50%;
         transform: translateX(-50%) translateY(16px);
         background: #1e293b; color: #fff;
         padding: 10px 20px; border-radius: 99px;
         font-size: .88rem; opacity: 0; pointer-events: none;
         transition: opacity .2s, transform .2s;
         z-index: 200; white-space: nowrap; }
.toast.show  { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.error { background: var(--danger); }

/* ---------- 17. Banner de nueva versión ----------
   El banner es una notificación de la plataforma, no de la cuenta: avisa
   que cambió `version.txt` en el server. Por eso va SIEMPRE en un verde
   fijo (#15803d) e independiente del `--primary` del panel — así se lee
   como un aviso del sistema y no como un elemento del chrome de marca.

   El verde es el MISMO hex que en cloud/assets/css/cloud.css (sección
   17): es la misma notificación de la misma plataforma, y quien salta de
   una solapa a la otra tiene que reconocerla sin releerla. Si cambia
   acá, cambia allá.

   No sale de `--success` ni de ningún token a propósito: los tokens
   siguen al tema y a la jerarquía de acción, y este aviso no es ninguna
   de las dos cosas. El tono es oscuro (y no el `#22c55e` de `--success`)
   porque encima va texto blanco: a 5:1 contra el blanco se lee igual que
   se leía el azul original, y el verde claro caía a 2,3:1.

   Los botones de los mails (api/auth.php) siguen en el azul heredado de
   dex — son otra pieza y no se tocaron acá. */
.version-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 9999;
  background: #15803d; color: #fff;
  padding: 10px 20px;
  display: flex; align-items: center; justify-content: center; gap: 16px;
  font-size: .9rem; font-weight: 500;
  box-shadow: 0 2px 8px rgba(0,0,0,.25);
}
.version-banner button {
  background: #fff; color: #15803d;
  border: none; border-radius: 6px;
  padding: 5px 14px; font-size: .85rem; font-weight: 600;
  cursor: pointer;
}
.version-banner button:hover { background: #dcfce7; }

/* ---------- 18. Spinner ---------- */
.spin { display: inline-block; width: 28px; height: 28px;
        border: 3px solid var(--border); border-top-color: var(--primary);
        border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.ai-pulse { color: var(--primary); animation: ai-pulse 1.6s ease-in-out infinite; }
@keyframes ai-pulse {
  0%, 100% { color: var(--muted);   opacity: .75; }
  50%      { color: var(--primary); opacity: 1;   }
}

/* ---------- Debug de sesión (modal F2) ---------- */
.debug-section       { display: flex; flex-direction: column; gap: 6px; }
.debug-section-title { font-size: .82rem; font-weight: 700; color: var(--text); }
.debug-pre {
  margin: 0; padding: 12px 14px;
  background: color-mix(in srgb, var(--surface) 90%, #000);
  color: var(--text); border-radius: var(--radius);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .78rem; line-height: 1.5;
  white-space: pre-wrap; word-break: break-all;
  max-height: 240px; overflow: auto;
}

/* ---------- 19. Responsive / mobile ---------- */
/* Vive dentro de la topbar, así que va en blanco sobre la banda de marca. */
.hamburger        { display: none; background: none; border: none; cursor: pointer;
                    font-size: 1.4rem; color: #fff;
                    padding: 4px 8px; margin-right: 8px; line-height: 1; }
.sidebar-overlay  { display: none; position: fixed; inset: 0;
                    background: rgba(0,0,0,.45); z-index: 199; }
.sidebar-overlay.active { display: block; }

@media (max-width: 768px) {
  .hamburger { display: inline-flex; align-items: center; }
  .sidebar   { position: fixed; top: 0; left: 0; height: 100vh; z-index: 200;
               transform: translateX(-100%); transition: transform .25s; }
  .sidebar.open { transform: translateX(0); }
  .form-row, .form-row-3, .form-row-4 { grid-template-columns: 1fr; }
  /* La píldora comparte la topbar con el hamburguesa y el título: acá las
     dos mitades se recortan antes para que nada se salga de la banda. */
  .topbar-username-persona,
  .topbar-username-cuenta { max-width: 9ch; }
}

/* ---------- Stepper de precio (modal "Cambiar precio") ----------
   Un número grande flanqueado por − y +. El cuerpo es grande a propósito:
   el modal existe para mirar UN dato y moverlo, así que el precio es la
   pantalla, no un campo más de un formulario.

   El campo sigue siendo un `<input>` de texto y no un `type=number`: los
   spinners nativos del navegador saltan de a 1 (o de a lo que diga `step`
   sólo con el teclado), se ven distintos en cada navegador y compiten
   visualmente con los dos botones grandes que ya hacen ese trabajo. El
   valor se tipea igual. */
.precio-stepper        { display: flex; align-items: stretch; gap: 10px; }
.precio-stepper-campo  { flex: 1; min-width: 0;
                         display: flex; align-items: baseline;
                         justify-content: center; gap: 8px;
                         border: 1px solid var(--border); border-radius: var(--radius);
                         background: var(--surface); padding: 10px 12px;
                         transition: border-color .15s, box-shadow .15s; }
.precio-stepper-campo:focus-within {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
}
.precio-stepper-moneda { font-size: .9rem; font-weight: 700; color: var(--muted);
                         flex-shrink: 0; }
/* Sin borde ni fondo propios: el marco es `.precio-stepper-campo`, que
   además lleva el focus ring por el `:focus-within` de arriba. */
.precio-stepper-input  { border: none !important; background: none !important;
                         box-shadow: none !important; padding: 0 !important;
                         width: 100%; min-width: 0; text-align: center;
                         font-size: 1.9rem; font-weight: 700; line-height: 1.1;
                         color: var(--text); font-family: inherit; }
.precio-stepper-btn    { flex-shrink: 0; width: 52px;
                         border: 1px solid var(--border); border-radius: var(--radius);
                         background: var(--surface); color: var(--text);
                         font-size: 1.1rem; cursor: pointer;
                         display: flex; align-items: center; justify-content: center;
                         transition: background .15s, border-color .15s, transform .1s; }
.precio-stepper-btn:hover  { background: var(--row-hover); border-color: var(--primary); }
.precio-stepper-btn:active { transform: scale(.95); }

/* ---------- 20. Editor Quill (cuerpo de noticias) ---------- */
.ql-toolbar.ql-snow {
  border: 1px solid var(--border);
  border-top-left-radius: var(--radius);
  border-top-right-radius: var(--radius);
  background: var(--bg);
}
.ql-container.ql-snow {
  border: 1px solid var(--border);
  border-top: 0;
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: .92rem;
}
.ql-editor.ql-blank::before { color: var(--muted); font-style: normal; }
.ql-editor { min-height: 240px; }
[data-theme="dark"] .ql-snow .ql-stroke    { stroke: var(--text); }
[data-theme="dark"] .ql-snow .ql-fill      { fill:   var(--text); }
[data-theme="dark"] .ql-snow .ql-picker    { color:  var(--text); }
[data-theme="dark"] .ql-snow .ql-picker-options {
  background: var(--surface); border-color: var(--border);
}

/* ============================================================
   Conversaciones de avisos — el hilo con quien pregunta
   ============================================================
   Un chat de verdad entre dos personas (`avisos_conversaciones`,
   2026-09-16e) que se contesta desde el mismo modal, así que tiene
   compositor y burbujas de dos lados.

   Las clases van con prefijo corto (`.chat-b`) porque hasta el
   2026-09-27 convivían con las del historial del asistente de IA
   (`.chat-log` / `.chat-msg`), que se eliminó junto con su módulo. */

/* Alto fijo y layout flex: el único scroll vertical tiene que ser el del
   hilo. Acá además hay un compositor clavado al pie, y con alto variable
   se movería hacia abajo con cada mensaje — justo debajo del cursor de
   quien escribe. */
#avisosConversacionBackdrop .modal {
  display: flex;
  flex-direction: column;
  height: 82vh;
  overflow: hidden;
}
/* `min-height: 0` no es opcional: sin eso un hijo flex no baja de su alto
   de contenido y el `overflow-y` nunca se activa (DESIGN.md §14). */
#avisosConversacionBackdrop .modal-body.chat-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  /* 14px de canalón y no los 24 del `.modal-body` de siempre: adentro
     hay una conversación, y en un chat el aire de los costados es ancho
     que le falta a las burbujas. Es el mismo canalón que la cabecera y
     el compositor — las tres piezas tienen que coincidir o la caja de
     arriba queda corrida respecto de los mensajes. */
  padding: 8px 14px;
}

/* La cabecera del hilo: portada + título + con quién, todo en cuerpo
   reducido.

   Los tamaños se bajan SÓLO acá y no en `.modal-rechead` a secas porque
   en los otros modales (Consultar oferta, favorito, visita)
   esa caja ES la ficha del registro y tiene que mandar. En éste compite
   con el hilo por el alto del modal, y lo que se vino a leer está abajo:
   la caja sólo tiene que decir de qué aviso y con quién, de un vistazo.

   La foto va a la izquierda, como la tira del modal del portal — los dos
   lados de la conversación ven la misma portada. */
/* El canalón es de 14px en las tres piezas —cabecera, hilo y
   compositor— y no los 24 del resto de los modales: acá adentro hay una
   conversación, y en un chat el aire de los costados es ancho que le
   falta a las burbujas. Las tres tienen que coincidir o la caja de la
   cabecera queda corrida respecto de los mensajes de abajo. */
#avisosConversacionBackdrop .modal-rechead { margin: 12px 14px 8px; padding: 10px 12px; gap: 12px; }

/* La portada es un botón: abre la ficha del aviso. El reset lo deja sin
   nada de botón salvo el cursor — lo que se ve es la foto.

   La señal de que se puede tocar es el hover (la foto se aclara y el
   borde toma el color de la cuenta) más el `title`. Un ícono encima
   taparía justamente lo que identifica al aviso, que es de lo poco que
   entra en una caja de 64px. */
.rechead-foto-btn {
  flex-shrink: 0;
  padding: 0; border: none; background: none; line-height: 0;
  border-radius: var(--radius);
  cursor: pointer;
  box-shadow: 0 0 0 1px transparent;   /* el anillo se pinta en hover, sin mover el layout */
  transition: box-shadow .15s, opacity .15s;
}
.rechead-foto-btn:hover  { box-shadow: 0 0 0 2px var(--primary); }
.rechead-foto-btn:hover .rechead-foto { opacity: .85; }
.rechead-foto-btn:active { transform: scale(.97); }
.rechead-foto-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.rechead-foto-btn[hidden] { display: none !important; }

.rechead-foto {
  display: block;
  width: 64px; height: 64px;
  border-radius: var(--radius);
  object-fit: cover;          /* las fotos de aviso son panorámicas: recorta, no deforma */
  background: var(--border);  /* mientras carga, un bloque y no un hueco */
  transition: opacity .15s;
}

/* Un modal abierto DESDE otro modal.
   `.modal-backdrop` es z-index 100 para todos, así que entre dos abiertos
   gana el que esté más abajo en el HTML. La ficha del aviso está mucho
   antes que el modal de la conversación, y sin esto se abriría por
   debajo: se vería el velo y nada más.
   120 y no 200: tiene que quedar por debajo del `confirmDialog` (150),
   que es una pregunta y siempre va arriba de todo lo que interrumpe. */
.modal-backdrop.modal-encima { z-index: 120; }

/* Disparador de un selector que vive en OTRO modal. Hoy lo usa un solo
   lugar: las Características del filtro de Inmuebles, que son ~50 y como
   nube de chips ocupaban media pantalla.
   Se dibuja como un input —mismo borde, mismo radio, mismo padding que
   §7 de DESIGN.md— y no como un botón: está adentro de un `.form-group`
   con su label arriba, así que la fila tiene que leerse como un campo
   más del formulario. Con filtro puesto se marca en `--primary`, igual
   que un `.filter-chip.active`. */
.picker-field {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; text-align: left; cursor: pointer; font-family: inherit;
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 8px 12px; font-size: .88rem;
  background: var(--surface); color: var(--text);
  transition: border-color .15s, color .15s;
}
.picker-field:hover          { border-color: var(--primary); }
.picker-field > span         { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker-field > i            { color: var(--muted); font-size: .85rem; flex-shrink: 0; }
.picker-field.active         { border-color: var(--primary); color: var(--primary); font-weight: 600; }
.picker-field.active > i     { color: var(--primary); }

/* ---------- Campo de autocompletar (`.autocompletar`) ----------

   Un <input> que busca contra el servidor y guarda el id elegido en un
   <input hidden>. Es el reemplazo del <select> cuando la lista es larga —
   hoy el Propietario de una unidad, cuya agenda son cientos de personas.

   Se dibuja como un input normal (§7) y no como un control aparte: está
   dentro de un `.form-group` con su label arriba y tiene que leerse como
   un campo más del formulario. Lo único que lo delata es la flecha, que
   anuncia que además se despliega.

   La LISTA va `position: fixed` y la ubica el JS. No es capricho: estos
   campos viven dentro de un `.modal`, que lleva `overflow: hidden` y
   recortaría cualquier hijo absoluto que se pase de su caja (§14). El
   z-index va sobre el modal (100) y debajo del confirm (150), igual que
   el `.ctx-menu`. */
.autocompletar { position: relative; display: block; }

.autocompletar-input { width: 100%; padding-right: 32px; }

/* Decorativa y sin eventos: el click tiene que llegar al input de abajo. */
.autocompletar-flecha {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  color: var(--muted); font-size: .75rem; pointer-events: none;
}

.autocompletar-lista {
  position: fixed; z-index: 250; display: none;
  max-height: 260px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-lg); padding: 6px;
}
.autocompletar-lista.open { display: block; }

.autocompletar-opcion {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 8px 12px; border-radius: 6px; cursor: pointer;
  font-size: .88rem; color: var(--text); white-space: nowrap;
}
/* La opción marcada se pinta con el lavado del primary — el mismo del ítem
   activo del sidebar y de la fila sin leer (§10.1). Es un tinte y no un
   relleno, así el texto conserva su color y se lee igual en los dos temas.
   NO se distingue hover de marcada: con el teclado bajando y el mouse
   quieto encima, dos resaltados a la vez no dicen cuál se elige con Enter. */
.autocompletar-opcion.activa { background: var(--primary-wash); font-weight: 600; }

/* El neutro ("Sin asignar") es la forma de volver a sin-elegir, así que es
   una opción más — pero en cursiva y atenuada, porque no es una persona. */
.autocompletar-opcion.es-neutro { color: var(--muted); font-style: italic; }

.autocompletar-doc {
  color: var(--muted); font-size: .78rem; flex-shrink: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.autocompletar-vacio {
  padding: 10px 12px; font-size: .82rem; color: var(--muted); line-height: 1.4;
  white-space: normal;
}

/* ---------- Vista Mapa de un listado ----------
   Hoy la usa Inmuebles: la alternativa al `.table-card`, con un pin por
   registro. Hereda la caja de la tabla —misma superficie, mismo borde,
   mismo radio y misma sombra— porque es la MISMA pantalla en otra forma;
   si se dibujara distinto, alternar se leería como haber navegado a otro
   lado. */
.mapa-wrap {
  position: relative;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow);
  overflow: hidden;
}
/* Alto de viewport menos lo que ocupan el chrome, la ayuda, las stats y
   la toolbar. El `min-height` es el piso: en una pantalla corta el mapa
   no puede quedar en una banda de 100px donde no entra ni un barrio. */
.mapa-lienzo { width: 100%; height: calc(100vh - 430px); min-height: 420px; }

/* Aviso discreto sobre el mapa — hoy: cuántos inmuebles del listado
   quedaron afuera por no tener coordenadas. Va arriba a la izquierda, que
   es el único borde que Google deja libre de controles. */
.mapa-aviso {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 99px; box-shadow: var(--shadow);
  padding: 6px 14px; font-size: .78rem; color: var(--muted);
  display: flex; align-items: center; gap: 7px; max-width: calc(100% - 24px);
}
.mapa-aviso i { color: var(--warn); }

/* Tarjeta del pin clickeado. Se para JUSTO ARRIBA del pin y lo acompaña
   mientras el mapa se acomoda —el zoom, y el paneo con el que ella misma
   se hace lugar al abrirse—. Si en cambio la persona ARRASTRA el mapa, se
   cierra: ahí ya terminó con ese aviso y se fue a mirar otra zona.
   `left` y `top` los escribe el JS en cada `draw()` de un
   `google.maps.OverlayView` — es el único que sabe traducir lat/lng a
   píxeles del contenedor. Acá van sólo el `position` y las medidas.
   La tarjeta es HTML nuestro y NO un `InfoWindow`: el de Google trae su
   propia caja blanca con su pico y su tipografía, imposible de llevar a
   los tokens sin pisarle clases internas que cambian entre versiones de
   la API — y en modo oscuro quedaba un parche blanco en el medio del
   panel. Vive fuera del `.mapa-lienzo`, como hermana suya, así que ningún
   panel del mapa la recorta. */
.mapa-card {
  position: absolute; left: 0; top: 0; z-index: 3; width: 420px;
  max-width: calc(100% - 24px);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  overflow: hidden;
  cursor: pointer;   /* la tarjeta entera abre el Consultar */
  transition: border-color .15s;
}
.mapa-card:hover { border-color: var(--primary); }
/* Su pin quedó absorbido por un globo de grupo al alejar el zoom: la
   tarjeta se apaga pero NO se cierra —`visibility` y no `display`—, así
   vuelve sola al acercar sin que haya que clickear de nuevo. */
.mapa-card.is-sin-pin,
.mapa-card-pico.is-sin-pin { visibility: hidden; }

/* El pico que ata la tarjeta a SU pin. Con dos avisos a pocos píxeles uno
   del otro, la tarjeta sola no dice de cuál salió; el pico sí.
   `left` y `top` los escribe el JS, y el `left` sigue al PIN, no al
   centro de la tarjeta: contra el borde del mapa la tarjeta se corre y el
   pico se queda señalando. */
.mapa-card-pico {
  position: absolute; z-index: 4;
  width: 18px; height: 10px;
  pointer-events: none;   /* es decoración: no se clickea ni tapa el mapa */
}
/* Dos triángulos apilados, que es la única forma de darle a un pico el
   mismo filete de 1px que tiene la tarjeta: el de atrás en el color del
   borde, el de adelante —un poco más chico y 1px más arriba— en el color
   del pie, que es donde el pico se enchufa. */
.mapa-card-pico::before,
.mapa-card-pico::after {
  content: ''; position: absolute;
  border-left-color: transparent; border-right-color: transparent;
  border-left-style: solid; border-right-style: solid;
  border-top-style: solid;
}
.mapa-card-pico::before {
  top: 0; left: 0;
  border-left-width: 9px; border-right-width: 9px;
  border-top-width: 10px; border-top-color: var(--border);
}
/* El MISMO color que el fondo de la tarjeta, que es de donde el pico
   sale: cualquier otro tono se vería como un escalón en la unión. */
.mapa-card-pico::after {
  top: -1px; left: 1px;
  border-left-width: 8px; border-right-width: 8px;
  border-top-width: 9px;
  border-top-color: var(--surface);
}
.mapa-card-cerrar {
  position: absolute; top: 6px; right: 6px; z-index: 1;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 50%; width: 26px; height: 26px; line-height: 1;
  cursor: pointer; color: var(--muted); font-size: .8rem;
}
.mapa-card-cerrar:hover { color: var(--text); background: var(--row-hover); }
/* Foto a la IZQUIERDA y datos a la derecha, no la foto arriba a todo lo
   ancho. El mapa es una caja apaisada —mucho más ancha que alta— y una
   tarjeta vertical le come el alto, que es lo escaso: con la foto al
   costado la tarjeta mide ~120px en vez de ~290px y deja ver el mapa que
   está tapando.
   La foto es CUADRADA, y de ahí sale el alto de la tarjeta: el lado se
   declara una sola vez en `--foto` y lo usan el ancho de la imagen y el
   alto del cuerpo, así no se pueden desincronizar y la foto no se puede
   deformar. `overflow:hidden` es la red: el título ya está cortado en dos
   líneas, pero si algún día la columna creciera, se recorta en vez de
   estirar la tarjeta y romper el cuadrado. */
.mapa-card      { --foto: 120px; }
.mapa-card-body { display: flex; align-items: stretch;
                  height: var(--foto); overflow: hidden; }
.mapa-card-foto {
  display: block; flex-shrink: 0;
  width: var(--foto); align-self: stretch; object-fit: cover;
  background: var(--bg); border-right: 1px solid var(--border);
}
.mapa-card-datos {
  flex: 1; min-width: 0;   /* min-width:0 o el título largo estira la tarjeta */
  padding: 11px 13px; display: flex; flex-direction: column; gap: 5px;
  /* Con el título en una sola línea la columna queda más baja que la foto:
     centrada, el bloque se apoya contra el medio de la imagen en vez de
     dejar un hueco abajo. */
  justify-content: center;
}
/* La fila de chapitas deja lugar a la ✕, que flota sobre esta esquina. */
.mapa-card-chips  { display: flex; align-items: center; gap: 6px;
                    flex-wrap: wrap; padding-right: 26px; }
/* La operación (Venta / Alquiler). No es un `.badge` porque no informa un
   estado sino de qué clase de aviso se trata: va en el lavado del primary
   —el mismo token del ítem activo del sidebar— y en versalitas. */
.mapa-card-op     { background: var(--primary-wash); color: var(--primary);
                    border-radius: 99px; padding: 2px 9px;
                    font-size: .68rem; font-weight: 700;
                    text-transform: uppercase; letter-spacing: .05em;
                    white-space: nowrap; }
/* Dos líneas y corta: el título de un aviso puede tener 120 caracteres y
   entero haría crecer la tarjeta más que la foto que tiene al lado. */
.mapa-card-titulo { font-size: .86rem; font-weight: 600; color: var(--text); line-height: 1.32;
                    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                    overflow: hidden; }
.mapa-card:hover .mapa-card-titulo { color: var(--primary); }
.mapa-card-precio { font-size: 1.02rem; font-weight: 700; line-height: 1.2; }

@media (max-width: 768px) {
  .mapa-lienzo { height: 60vh; min-height: 320px; }
  /* El ancho baja, pero la posición la sigue escribiendo el JS: la
     tarjeta se pega al pin también en el teléfono, y de que no se salga
     del mapa se encarga el clampeo de `posicionarTarjetaMapaInmuebles()`. */
  /* La foto sigue cuadrada, sólo que más chica: se cambia `--foto` y el
     alto del cuerpo la acompaña solo. */
  .mapa-card       { width: 300px; --foto: 104px; }
  .mapa-card-datos { padding: 9px 11px; }
  .mapa-card-titulo { -webkit-line-clamp: 1; }
}

/* Encabezado de cada categoría dentro del selector de Características.
   Mismo rótulo en versalitas que el de la pestaña Características del
   modal de alta/edición, para que las dos pantallas agrupen igual. */
.picker-grupo-titulo {
  font-weight: 600; font-size: .78rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  margin: 14px 0 8px;
}
.picker-grupo-titulo:first-child { margin-top: 0; }
.picker-chips { display: flex; gap: 6px; flex-wrap: wrap; }

/* Los dos renglones que quedaron (ver el markup en index.php): el título
   del aviso y con quién es el hilo. MISMO CUERPO los dos —son los dos
   datos de la portada, y en dos medidas distintas el segundo se lee como
   un pie de foto— y lo que los jerarquiza es el peso y el color. */
#avisosConversacionBackdrop .modal-rechead-title {
  font-size: .95rem; line-height: 1.3;
  font-weight: 700; color: var(--text);
}
#avisosConversacionBackdrop .modal-rechead-hi-value {
  font-size: .95rem; line-height: 1.3;
  font-weight: 400; color: var(--muted);
  margin-top: 2px;
}
/* Los badges acompañan la baja de cuerpo: al tamaño normal quedaban más
   pesados que el título que tienen al lado. */
#avisosConversacionBackdrop .modal-rechead-badges .badge {
  font-size: .68rem; padding: 2px 8px;
}

/* Hilo sin mensajes. Centrado en el alto disponible —`margin: auto`
   dentro de un flex column— para que no quede colgando arriba en un modal
   que mide 82vh. */
.chat-vacio-msg {
  margin: auto;
  color: var(--muted);
  font-size: .88rem;
}

/* Separador de día. La línea se dibuja con dos bordes que crecen a los
   costados del rótulo, que es lo que lo mantiene centrado sea cual sea el
   largo del texto. */
.chat-sep {
  display: flex; align-items: center; gap: 10px;
  margin: 10px 0 4px;
}
.chat-sep::before,
.chat-sep::after { content: ''; flex: 1; height: 1px; background: var(--border); }
.chat-sep span {
  font-size: .7rem; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em; font-family: monospace;
}

/* La burbuja. `max-width: 78%` y no 100%: lo que dice de qué lado viene
   cada mensaje es que no llegue al otro borde — el color solo no alcanza
   para quien no lo distingue.

   El fondo del mensaje entrante es el mismo `color-mix` de las tarjetas
   del modal de Consultar (§Modales de ABM.md): un escalón más oscuro que el modal,
   sin borde. El propio va en el lavado del color de la cuenta, que es el
   mismo token con el que el panel marca todo lo suyo (DESIGN.md §1.1) —
   pintarlo de `--primary` pleno obligaría a texto blanco y rompería la
   regla de no rellenar superficies con el color de marca. */
.chat-b {
  align-self: flex-start;
  max-width: 78%;
  background: color-mix(in srgb, var(--surface) 90%, #000);
  border-radius: var(--radius);
  /* La esquina del lado del que habla va cuadrada: es la "colita" de la
     burbuja, dibujada sin pseudo-elemento. */
  border-bottom-left-radius: 4px;
  padding: 9px 12px 6px;
}
.chat-b-mio {
  align-self: flex-end;
  background: var(--primary-wash);
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: 4px;
}
/* Quién contestó. Sólo en las burbujas propias y sólo cuando el nombre
   dice algo: en una cuenta con varios operadores es el dato que falta
   para entender el hilo. */
.chat-b-autor {
  display: block;
  font-size: .72rem; font-weight: 700; color: var(--primary);
  margin-bottom: 2px;
}
/* `pre-wrap` conserva los saltos que escribió la persona sin convertirlos
   a `<br>`; el texto entra escapado desde el JS. */
.chat-b p {
  margin: 0;
  font-size: .9rem; line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;   /* una URL larga no ensancha la burbuja */
}
.chat-b time {
  display: block; margin-top: 2px; text-align: right;
  font-size: .68rem; color: var(--muted); font-family: monospace;
}

/* El compositor, clavado al pie del modal — fuera del `.modal-body` para
   que el hilo scrollee por detrás. El fondo repite el de la
   `.modal-menubar`: las dos son franjas de control, una arriba y otra
   abajo del contenido. */
.chat-compose {
  flex: 0 0 auto;
  display: flex; align-items: flex-end; gap: 10px;
  padding: 12px 14px;   /* el mismo canalón que la cabecera y el hilo */
  border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 40%, var(--bg));
}
/* Arranca de un renglón y crece con lo que se escribe hasta este tope,
   donde empieza a scrollear (lo estira el JS, ver
   `avisosConversacionAjustarCampo()`). `resize: none` a propósito: el
   alto lo maneja el JS y una manija de arrastre pelearía con él. */
.chat-compose-campo {
  flex: 1; min-width: 0;
  max-height: 140px;
  resize: none;
  min-height: 0;   /* gana al `min-height: 60px` de los textarea del tema */
  overflow-y: auto;
}
.chat-compose .btn { flex-shrink: 0; }

/* ============================================================
   Impresión de Comprobantes (Facturas / Recibos)
   Vista previa sobre el fondo `assets/img/fondo.jpg` (A4 vertical).
   Portado desde cloud/assets/css/cloud.css — mismo layout que la
   vista de "Imprimir" del proyecto cloud, para que el cliente vea
   idéntico el comprobante en Mi cuenta.
   ============================================================ */

.print-modal .print-body { padding: 20px; background: #eef2f7; }

.print-page {
  position: relative;
  width: 210mm;
  height: 297mm;
  margin: 0 auto;
  background: #fff url('../img/fondo.jpg') no-repeat center / 100% 100%;
  box-shadow: var(--shadow-lg);
  color: #111;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 10pt;
  overflow: hidden;
}

.pp-header-right {
  position: absolute;
  top: 2.2%; left: 50%; width: 39.5%; height: calc(14.8% - 25px);
  display: flex; flex-direction: column; justify-content: flex-start;
  color: #555;
  transform: translate(50px, -5px);
}
.pp-header-right .pp-hr-top    { display: flex; align-items: flex-start; gap: 5px; }
.pp-letra-block {
  position: absolute;
  top: 2.2%; left: 50%;
  transform: translate(-50%, -12px);
  width: 30px;
  display: flex; align-items: center; justify-content: center;
  color: #555;
}
.pp-letra-block .pp-letra {
  font-size: 22pt; font-weight: 800; line-height: 1;
  color: #555;
}
.pp-header-right .pp-hr-titles { display: flex; flex-direction: column; min-width: 0; }
.pp-header-right .pp-tipo      { font-size: 16pt; font-weight: 800; letter-spacing: .04em; color: #555; line-height: 1.05; margin-top: 5px; }
.pp-header-right .pp-nro       { font-size: 11pt; font-weight: 700; line-height: 1.15; margin-top: 6px; }
.pp-header-right .pp-emisor    { margin-top: 13px; }
.pp-header-right .pp-emisor-line { font-size: 8pt; line-height: 1.25; color: #7a7a7a; }

.pp-cliente {
  position: absolute;
  top: 18.7%; left: 3%; width: calc(94% - 5px); height: calc(12% - 45px);
  display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
  color: #555;
  transform: translateY(-8px);
}
.pp-cli-left,
.pp-cli-right              { display: flex; flex-direction: column; }
.pp-cli-right              { text-align: right; }
.pp-cli-row                { font-size: 8pt; line-height: 1.35; color: #555; }
.pp-cli-row strong         { color: #555; }

.pp-items {
  position: absolute;
  top: 33.5%; left: 3%; width: calc(94% - 5px); height: 600px;
  overflow: hidden;
  display: flex; flex-direction: column;
  color: #555;
  transform: translateY(-45px);
}
.pp-items-table {
  width: 745px; table-layout: fixed; border-collapse: collapse; font-size: 8pt;
  background: transparent;
}
.pp-items-table thead tr           { background: transparent !important; }
.pp-items-table thead th {
  padding: 6px 8px; text-align: left;
  font-size: 8pt; font-weight: 700; color: #555;
  background: transparent !important;
  border-bottom: 1px solid transparent;
  text-transform: none;
  letter-spacing: 0;
}
.pp-items-table tbody tr           { background: transparent !important; }
.pp-items-table tbody tr:hover     { background: transparent !important; }
.pp-items-table tbody td {
  padding: 4px 8px; vertical-align: top; color: #555;
  background: transparent;
  border-bottom: none;
}
.pp-items-table tbody tr:last-child td { padding-bottom: 14px; }
.pp-items-table .pp-c-cant    { width: 55px;  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pp-items-table .pp-c-desc    { width: 460px; text-align: justify; }
.pp-items-table .pp-c-pu      { width: 90px; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pp-items-table .pp-c-monto   { width: 90px; text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.pp-items-table thead .pp-c-cant  { text-align: left; }
.pp-items-table thead .pp-c-pu,
.pp-items-table thead .pp-c-monto { text-align: right; }

.pp-total-row {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 3px;
  font-size: 8pt;
  color: #555;
}
.pp-total-line {
  display: flex; justify-content: space-between; gap: 24px;
  min-width: 240px;
}
.pp-total-line-final {
  border-top: 1px solid #7a7a7a;
  margin-top: 3px;
  padding-top: 4px;
  font-weight: 700;
}
.pp-total-row .pp-total-label { color: #555; }
.pp-total-row .pp-total-value { font-variant-numeric: tabular-nums; color: #555; }
.pp-total-row .pp-total-line-final .pp-total-value { font-weight: 800; }

.pp-total-block {
  position: absolute;
  top: 980px; right: 3%;
  width: 260px;
}

.pp-totales {
  position: absolute;
  top: 980px; left: 3%; width: calc(55% - 85px); height: calc(9% - 13px);
  display: flex; align-items: flex-start;
  color: #555;
  overflow: hidden;
}
.pp-totales .pp-observ-inline {
  font-size: 8pt; line-height: 1.4; color: #555;
  text-align: justify;
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  line-clamp: 6;
}

@media (max-width: 960px) {
  .print-modal { max-width: 96vw !important; }
  .print-page  { transform-origin: top left; }
}

/* ---------- Impresión real (@media print) ----------
   Portado desde cloud: activar `body.printing-comprobante` deja
   solamente la `.print-page` visible al momento de imprimir. */
@media print {
  @page { size: A4 portrait; margin: 0; }

  html, body {
    width: 210mm !important; height: 297mm !important;
    margin: 0 !important; padding: 0 !important;
    background: #fff !important;
    overflow: hidden !important;
  }

  body.printing-comprobante > *:not(#imprimirComprobanteBackdrop) { display: none !important; }

  body.printing-comprobante #imprimirComprobanteBackdrop {
    position: static !important; inset: auto !important;
    background: #fff !important; opacity: 1 !important; pointer-events: auto !important;
    display: block !important;
    overflow: visible !important;
    width: 210mm !important; height: 297mm !important;
    padding: 0 !important; margin: 0 !important;
  }

  body.printing-comprobante .print-modal {
    max-width: none !important; width: 210mm !important; height: 297mm !important;
    max-height: none !important; min-height: 0 !important;
    margin: 0 !important; padding: 0 !important;
    box-shadow: none !important; border: none !important; border-radius: 0 !important;
    background: #fff !important;
    transform: none !important;
    overflow: visible !important;
    display: block !important;
  }
  body.printing-comprobante .no-print { display: none !important; }
  body.printing-comprobante .print-body {
    padding: 0 !important; margin: 0 !important;
    background: #fff !important;
    overflow: visible !important;
    height: auto !important; max-height: none !important;
  }
  body.printing-comprobante .print-page {
    width: 210mm !important; height: 297mm !important;
    margin: 0 !important; padding: 0 !important;
    box-shadow: none !important; border: none !important;
    overflow: hidden !important;
    page-break-after: avoid;
    page-break-inside: avoid;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* ---------- El boleto de compraventa (módulo Operaciones) ----------
     Mismo mecanismo que el comprobante de arriba y que el del portal:
     `#imprimirBoletoHoja` es hija directa de `<body>` y la regla esconde
     a todos sus HERMANOS de un saque, sin tener que enumerarlos.

     Lo que NO comparte con el comprobante es el alto: aquél es una página
     A4 exacta y por eso el bloque de arriba clava `height: 297mm` +
     `overflow: hidden` en `html, body`. Un boleto puede ocupar dos
     páginas, así que las dos cosas se deshacen acá. La regla sobre `html`
     va con `:has()` para no tocar el camino del comprobante — si el
     navegador no la soporta, la de `body` alcanza en la práctica. */
  html:has(body.printing-boleto),
  body.printing-boleto {
    width: auto !important; height: auto !important;
    overflow: visible !important;
  }

  /* Y la página en blanco, como en el portal. El panel arranca en oscuro y
     su `body` lleva el fondo del tema: con "Gráficos de fondo" tildado en
     el diálogo de impresión, el mismo boleto salía sobre papel blanco
     desde el portal y sobre una hoja gris oscura desde acá.

     El `color-scheme` va con el fondo y no es de más: con el tema oscuro
     declarado, el navegador pinta el lienzo de la página con SU gris
     (#121212 en Chrome) antes de que ningún fondo nuestro entre a jugar,
     así que sin esta línea la hoja seguía saliendo oscura por debajo. */
  html:has(body.printing-boleto),
  body.printing-boleto { background: #fff !important; color-scheme: light; }

  body.printing-boleto > *:not(#imprimirBoletoHoja) { display: none !important; }

  /* Y NADA MÁS QUE MOSTRARLA. El ancho, la tipografía, la tinta negra
     sobre blanco, la geometría de las tablas y el margen de la hoja los
     trae el propio documento, en el `<style>` que le escribe
     `appBoletoCss()` (www/lib/boleto.php) y que viaja guardado con él en
     `avisos_operaciones.boleto`.

     Acá había una copia a mano de ese formato, y era una copia
     incompleta: tenía la tipografía y los colores pero nunca tuvo el
     título centrado en versalitas, el marco del cartel de borrador, el
     lugar de firma a la derecha ni la geometría de las tablas de partes y
     firmas. Encima el CSS global del panel —`table` a ancho completo,
     `th` en versalitas grises, `td` con divisoria— se le metía adentro,
     así que la cláusula de identificación del bien salía impresa como un
     listado del admin. El mismo boleto impreso desde el portal y desde
     acá eran dos papeles distintos; ahora es el mismo string con su
     propio formato y el que imprime no decide nada.

     El margen ya no va como padding: el documento declara una `@page` CON
     NOMBRE (`@page boleto-nexel`, A4 y medio centímetro), que se acota a
     esta hoja y por lo tanto convive con el `@page { margin: 0 }` global
     que el comprobante necesita para ocupar el A4 completo.

     El `page` que la elige va acá, sobre la hoja, y no adentro del
     documento: el navegador toma el nombre de la página de la PRIMERA
     caja que la ocupa, que es ésta. Puesto sobre el `.boleto-hoja` de
     adentro no lo lee nadie y la hoja se imprime con el margen 0 del
     comprobante, a papel lleno. Es la única palabra que este proyecto
     comparte con la plantilla, y es la misma en app.css. */
  body.printing-boleto #imprimirBoletoHoja {
    display: block !important;
    width: auto !important;
    background: #fff !important;
    page: boleto-nexel;
  }
}

/* Fuera de la impresión la hoja no existe: nace vacía y sólo la llena
   `imprimirBoletoOperacion()` un instante antes de `window.print()`. */
#imprimirBoletoHoja { display: none; }
