/* ════════════════════════════════════════
     UTILIDADES — variables CSS globales
     Incluir PRIMERO en cualquier página
     ════════════════════════════════════════ */
 
/* ── Reset base ── */
  * { box-sizing: border-box; margin: 0; padding: 0; }
 
  /* ── Micro-interacción global: todo botón se "hunde" al presionar ──
     Especificidad baja (0-0-1), así que cualquier regla más específica
     ya existente en cada módulo la sobreescribe sin conflicto. */
  button:not(:disabled) {
    transition: transform 0.15s var(--ease-apple);
  }
  button:not(:disabled):active {
    transform: scale(0.97);
  }
 
  /* ── Scroll morado: nativo del documento + interno de cualquier
        contenedor con overflow propio (aparece solo cuando hay contenido) ──
        Selectores que se suman acá con el tiempo, cada uno porque tenía
        su propia versión gris/suelta en otro archivo (index.html,
        notas.css, graficos.css) en vez de usar esta regla:
        - .autocomplete-lista: cliente/empresa/equipo.
        - .vit-sidebar: filtros de catálogo/gráficos/registro/agenda/admin.
        - .vit-carrito-col: columnas del carrito flotante (Persona+
          Dispositivo | Carrito).
        - .notas-modal-body, #muro-notas: módulo de Notas.
        - .chart-wrap-scroll: scroll horizontal del gráfico temporal.
        - .admin-panel: contenido de cada pestaña del Panel Admin.
        - .tab-panel: contenido interno de cada pestaña (Catálogo,
          Registro, Gráficos, Notas, Agenda, Planilla).
        - .kpi-detalle-lista: listas del detalle de una tarjeta en Gráficos.
        - #reg-grid-wrap: grilla de Comprobantes.
        Si aparece un contenedor nuevo con scroll propio, se suma ACÁ,
        no se le inventa su propia regla en otro archivo.

        OJO con el "contain" de abajo: corta la cadena de scroll en los
        DOS ejes. En un contenedor que solo scrollea en horizontal (una
        tira de imágenes, un gráfico ancho), eso deja la rueda vertical
        muerta encima de él en vez de pasarla al panel que sí scrollea.
        Esos casos declaran overscroll-behavior-x:contain y -y:auto
        aparte (ver .chart-wrap-scroll y #np-imagen-referencia). ── */
  /* La lista va una sola vez con :is(): antes estaba repetida en las seis
     reglas de abajo, y sumar un contenedor nuevo obligaba a acordarse de
     agregarlo en las seis — cualquier olvido dejaba ese scroll con la
     barra gorda del navegador. Así se agrega en un solo lugar. */
  html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col,
  .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel,
  .vit-login-dropdown, #selector-empresas,
  .kpi-detalle-lista, #reg-grid-wrap {
    scrollbar-width: thin;
    scrollbar-color: var(--color-accent-dark) #1d1d1d;
    /* Sin esto, al llegar al borde de un scroll interno (esta lista,
       el sidebar, etc.) el scroll "se pasa" al contenedor de afuera —
       la página entera, u otro panel — en vez de quedarse quieto acá. */
    overscroll-behavior: contain;
  }
  html { overflow-y: auto; }
  :is(html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col, .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel, .vit-login-dropdown, #selector-empresas, .kpi-detalle-lista, #reg-grid-wrap)::-webkit-scrollbar { width: 8px; height: 8px; }
  :is(html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col, .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel, .vit-login-dropdown, #selector-empresas, .kpi-detalle-lista, #reg-grid-wrap)::-webkit-scrollbar-track { background: #1d1d1d; border-radius: 4px; }
  :is(html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col, .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel, .vit-login-dropdown, #selector-empresas, .kpi-detalle-lista, #reg-grid-wrap)::-webkit-scrollbar-thumb { background: var(--color-accent-dark); border-radius: 4px; border: 2px solid #1d1d1d; transition: background 0.2s var(--ease-apple); }
  :is(html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col, .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel, .vit-login-dropdown, #selector-empresas, .kpi-detalle-lista, #reg-grid-wrap)::-webkit-scrollbar-thumb:hover { background: var(--color-accent); }
  :is(html, .scroll-morado, .autocomplete-lista, .vit-sidebar, .vit-carrito-col, .notas-modal-body, #muro-notas, .chart-wrap-scroll, .admin-panel, .tab-panel, .vit-login-dropdown, #selector-empresas, .kpi-detalle-lista, #reg-grid-wrap)::-webkit-scrollbar-corner { background: transparent; }

  /* ── Alto del sidebar de filtros: reutilizado por las dos variantes
     de .vit-sidebar (sticky en Catálogo vs fixed en Gráficos/Registro/
     Agenda/Admin, ver vitrina.css). Siempre llega hasta el fondo del
     viewport aunque el contenido sea corto (ej. Agenda con pocos
     filtros) — antes solo Catálogo tenía height fijo y las otras
     páginas se achicaban al alto de su contenido, así que las cuatro
     se veían distintas entre sí.
     Le resta --vit-nav-alto (alto completo del navbar: fila de menú +
     barra de filtros) porque su "top" (ver vitrina.css) también le
     suma ese mismo valor. Restarle TAMBIÉN el alto del footer acá se
     probó y se descartó — dejaba el sidebar chico todo el tiempo (no
     solo cerca del final del scroll, que es cuando en verdad hace
     falta), incluso arriba de todo con miles de píxeles de productos
     de por medio antes de llegar al footer. Ese ajuste fino, dependiente
     de la posición real del scroll (no solo del alto de viewport), vive
     en JS — ver _clampSidebarCatalogo() en vitp-paginas.js, que solo
     achica el sidebar (con max-height inline) cuando de verdad está por
     taparse con la cabecera o el footer, y lo deja en su alto normal
     el resto del tiempo. ── */
  @media (min-width: 701px) {
    .vit-sidebar {
      height: calc(100vh - var(--vit-nav-alto, 62px) - 20px);
      max-height: calc(100vh - var(--vit-nav-alto, 62px) - 20px);
      padding: 4px 0 4px 0;
      z-index: 270;
    }
  }

  /* ── Espacio reservado para el sidebar fijo dentro de una página vitp
     (Comprobantes/Gráficos/Agenda/Admin/Registro-tabla, todas con class
     "vitp-page") — el <aside> vive "suelto" con position:fixed
     (.vitp-sidebar-fijo en vitrina.css), así que el contenido necesita
     este padding-left o queda tapado debajo. Antes esta misma regla
     vivía copiada por id en cada módulo (core-registro.css ×3,
     planilla.css) — un módulo nuevo la olvidaba y su contenido quedaba
     tapado (pasó con Admin, y de nuevo con Registro-tabla). Ahora un
     módulo nuevo solo necesita agregar la clase "vitp-page" a su
     contenedor (ya la agrega vitp-paginas.js al crearlo) y esto
     funciona solo. ── */
  .vitp-page.vitp-con-sidebar {
    flex-direction: row;
  }
  .vitp-page.vitp-con-sidebar > .vitp-body {
    flex: 1;
    min-width: 0;
    min-height: 0;
  }
  @media (min-width: 701px) {
    .vitp-page.vitp-con-sidebar {
      padding-left: 292px; /* 272px del sidebar + 10px + 10px de márgenes */
      transition: padding-left 0.35s var(--ease-apple);
    }
    .vitp-page.vitp-con-sidebar.vitp-sidebar-cerrado {
      padding-left: 0;
    }
  }

  body {
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    letter-spacing: -0.01em;
    background: transparent;
    color: var(--color-text);
  }
 
  :root {
    /* Grises NEUTROS, con los tres canales IGUALES. Estos fondos venían
       de una paleta vieja con el azul unos puntos más arriba (18/18/31,
       26/26/46, 42/42/74), y aunque parezca poco, sobre un gris puro al
       lado se nota: en el carrito, donde conviven los dos, se veía el
       salto de tono de un cuadro al de al lado. Un gris con el azul
       +3 TAMBIÉN se ve azul — si hay que agregar un fondo acá, los tres
       canales van iguales. El color de la marca es el lila, no el azul. */
    --color-bg-deep:      #0f0f0f;
    --color-bg-card:      rgba(15, 15, 15, 0.82);
    --color-bg-input:     rgba(21, 21, 21, 0.9);
    --color-bg-input-solid: #151515; /* misma base que --color-bg-input, pero opaca (sin alpha) */
    --color-bg-panel:     #1d1d1d; /* paneles/filas secundarias dentro del panel clásico interno */
    --color-bg-placeholder: #101010; /* fondo de miniaturas/placeholders de imagen */
    --color-border:       #303030;
    /* Fondo y borde de los cuadros de texto (.form-group input/select/
       textarea). Van aparte de --color-bg-input a propósito: ese token lo
       remapea vitp-shell.css por contexto, así que el mismo campo cambiaba
       de tono según la página. Estos son grises neutros, fijos, para que un
       campo se vea igual en la vitrina y en el panel de administración. El
       fondo es más oscuro que el panel que lo contiene, así el campo se ve
       hundido y se distingue dónde se escribe. */
    --color-bg-campo:     #141414;
    --color-border-campo: rgba(255, 255, 255, 0.14);
    /* Borde del campo enfocado. Es el lila de marca rebajado y sin halo:
       el halo de 3px hacía crecer el campo al pisarlo y saltaba a la vista
       más que el texto que se estaba escribiendo. */
    --color-border-campo-foco: rgba(206, 147, 216, 0.45);
    --color-border-card:  rgba(206, 147, 216, 0.18);
    --color-accent:       #ce93d8;
    --color-accent-dark:  #6a1b9a;
    /* Familia paralela de la app interna: mismo trabajo que --txt-* pero
       sobre blanco puro y con el tope en 0.9, así que el mismo texto se
       veía de un tono en la vitrina y de otro en la consola. Ahora son
       nombres de la misma rampa, no una segunda escala. */
    --color-text:         var(--txt-cuerpo);
    --color-text-dim:     var(--txt-sub);
    --color-text-bright:  var(--txt-titulo);
    --color-verde:        #81c784;
    --color-rojo:         #ff5252;
    --color-rojo-suave:   #ff8a80; /* rojo de "limpiar/borrar" — más claro que --color-rojo */
    --color-amarillo:     #f9a825;
    --ease-apple:          cubic-bezier(0.4, 0, 0.2, 1);
    --shadow-focus-soft:   0 0 0 3px rgba(206, 147, 216, 0.08);
  }
 
/* ── Entrada compartida para menús flotantes, dropdowns y modales:
   un pequeño "asomarse" (fade + leve escala/desplazamiento) en vez de
   aparecer de golpe con display:block. Se usa en vitrina.css, boletas.css,
   carrito.css, etc. ── */
@keyframes asomarse {
  from { opacity: 0; transform: translateY(-6px) scale(0.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
 
/* ── Y su inversa para el cierre: antes desaparecían de golpe
   (display:none no se puede transicionar), así que el JS ahora agrega
   esta clase, espera a que termine, y recién ahí agrega .oculto. Ver
   _cerrarConAnimacion() en vitrina.js / notas.js. ── */
@keyframes despedirse {
  from { opacity: 1; transform: translateY(0) scale(1); }
  to   { opacity: 0; transform: translateY(-6px) scale(0.96); }
}
.cerrando {
  animation: despedirse 0.14s var(--ease-apple) forwards;
  pointer-events: none;
}
 
/* ── Entrada compartida para grillas de tarjetas (boletas, admin,
   selector de empresas, gráficos, pendientes): fade + leve subida en
   vez de aparecer de golpe con innerHTML. ── */
@keyframes surgir {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
 

 
/* ── Modal de selección de comprobante (cliente-datos.js: mostrarModalSeleccion).
   Antes era 100% inline (sin animación de entrada/salida, hover roto por
   JS inline con un bug de comillas). Se lleva al mismo lenguaje visual que
   el resto de los modales: fondo con fade y caja con "asomarse"/"despedirse". ── */
.modal-seleccion-overlay {
  position: fixed;
  inset: 0;
  background: rgba(11, 11, 11, 0.45);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  animation: modalSeleccionFadeIn 0.2s var(--ease-apple) forwards;
}
@keyframes modalSeleccionFadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal-seleccion-overlay.oculto { display: none; }

.modal-seleccion-overlay.cerrando {
  animation: modalSeleccionFadeOut 0.18s var(--ease-apple) forwards;
  pointer-events: none;
}
@keyframes modalSeleccionFadeOut { from { opacity: 1; } to { opacity: 0; } }
 
.modal-seleccion-caja {
  animation: asomarse 0.22s var(--ease-apple) both;
}
.modal-seleccion-overlay.cerrando .modal-seleccion-caja {
  animation: despedirse 0.18s var(--ease-apple) forwards;
}
 
.modal-seleccion-cerrar {
  color: #ff8a80;
  transition: background 0.2s var(--ease-apple), color 0.2s var(--ease-apple), transform 0.15s var(--ease-apple);
  border-radius: 6px;
}
.modal-seleccion-cerrar:hover {
  background: rgba(255, 82, 82, 0.15);
  color: #ff5252;
  transform: scale(1.1);
}
 
.modal-seleccion-fila {
  transition: background 0.15s var(--ease-apple);
  animation: surgir 0.25s var(--ease-apple) both;
  animation-delay: min(calc(var(--i, 0) * 25ms), 200ms);
}
.modal-seleccion-fila:hover { background: #232323; }
 
.modal-seleccion-btn-todo,
.modal-seleccion-btn-cliente {
  padding: 5px 12px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.2s var(--ease-apple), transform 0.15s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
}
.modal-seleccion-btn-todo {
  background: rgba(106, 27, 154, 0.4);
  border: 1px solid #9c27b0;
  color: #e1bee7;
  margin-right: 6px;
}
.modal-seleccion-btn-todo:hover {
  background: rgba(106, 27, 154, 0.6);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(106, 27, 154, 0.35);
}
.modal-seleccion-btn-cliente {
  background: rgba(33, 150, 243, 0.2);
  border: 1px solid #1565c0;
  color: #90caf9;
}
.modal-seleccion-btn-cliente:hover {
  background: rgba(33, 150, 243, 0.35);
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(33, 150, 243, 0.25);
}
.modal-seleccion-btn-todo:active,
.modal-seleccion-btn-cliente:active { transform: scale(0.97); }
 
/* ── Botones utilitarios ── */
  .btn-sm {
    padding: 3px 10px;
    font-size: 15px;
    border-radius: 8px;
    border: none;
    cursor: pointer;
    font-weight: 500;
    transition: all 0.25s var(--ease-apple);
    white-space: nowrap;
    flex-shrink: 0;
  }
 
  .btn-sm:active,
  .btn-verde-bg:active,
  .btn-rojo-bg:active,
  .btn-azul-bg:active,
  .btn-amarillo-bg:active,
  .btn-morado-bg:active {
    transform: scale(0.97);
  }
 
  .btn-verde-bg    { --btn-rgb: 76, 175, 80; }
  .btn-rojo-bg     { --btn-rgb: 244, 67, 54; }
  .btn-azul-bg     { --btn-rgb: 33, 150, 243; }
  .btn-amarillo-bg { --btn-rgb: 255, 193, 7; }
  .btn-morado-bg   { --btn-rgb: 206, 147, 216; }
 
  .btn-verde-bg, .btn-rojo-bg, .btn-azul-bg, .btn-amarillo-bg, .btn-morado-bg {
    background: rgba(var(--btn-rgb), 0.2);
    border: 1px solid rgba(var(--btn-rgb), 0.4);
    color: var(--color-text);
    transition: background 0.2s var(--ease-apple), box-shadow 0.2s var(--ease-apple), transform 0.15s var(--ease-apple), color 0.2s var(--ease-apple);
  }
  .btn-verde-bg:hover, .btn-rojo-bg:hover, .btn-azul-bg:hover,
  .btn-amarillo-bg:hover, .btn-morado-bg:hover {
    background: rgba(var(--btn-rgb), 0.35);
    color: var(--color-text-bright);
    /* Antes: transform:translateY(-1px) + box-shadow con 14px de blur.
       Eso necesita bastante espacio libre AFUERA del botón, y si queda
       pegado al borde de un contenedor con overflow:hidden/auto (como
       #formulario-scroll), el halo se corta — ver .scroll-area más abajo.
       Con un box-shadow chico (2px/6px, sin offset vertical) el efecto
       casi no necesita espacio extra, así que no se corta en ningún
       contenedor sin tener que parchar cada uno por separado. */
    box-shadow: 0 0 6px 1px rgba(var(--btn-rgb), 0.3);
  }
 
  .btn-sm:disabled,
  .btn-verde-bg:disabled,
  .btn-rojo-bg:disabled,
  .btn-azul-bg:disabled,
  .btn-amarillo-bg:disabled,
  .btn-morado-bg:disabled {
    background: rgba(255, 255, 255, 0.05) !important;
    border: 1px solid rgba(255, 255, 255, 0.1) !important;
    color: rgba(255, 255, 255, 0.3) !important;
    cursor: not-allowed !important;
  }

  /* Mismo look que los ":disabled" de arriba, pero para botones que
     necesitan seguir siendo clickeables aunque falten campos
     obligatorios — ej. el botón "Procesar" del carrito interno
     (carrito.js, actualizarEstadoBtnProcesar): un <button disabled>
     nativo no dispara "click" en absoluto, y ahí es procesarAccion()
     quien valida todo y marca en rojo el campo puntual que falta, así
     que no puede quedar con disabled nativo. Esta clase solo cambia
     el aspecto, sin agregar pointer-events:none ni nada que bloquee
     el clic. */
  .btn-luce-deshabilitado {
    background: rgba(255, 255, 255, 0.05) !important;
    border: 1px solid rgba(255, 255, 255, 0.1) !important;
    color: rgba(255, 255, 255, 0.3) !important;
    cursor: not-allowed !important;
  }

 
/* ── Tarjetas / secciones ── */
  .form-section {
    margin-bottom: 20px;
    border: 1px solid var(--color-border-card);
    border-radius: 14px;
    padding: 18px 20px;
    background: var(--color-bg-card);
    backdrop-filter: blur(20px) saturate(180%);
    -webkit-backdrop-filter: blur(20px) saturate(180%);
    transition: all 0.25s var(--ease-apple);
    width: 100%;
  }
 
  .form-section-title {
    font-size: 17px;
    font-weight: 600;
    letter-spacing: -0.015em;
    color: var(--color-text-bright);
    margin-bottom: 10px;
    padding-bottom: 6px;
    border-bottom: 2px solid var(--color-accent);
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 10px;
  }
 
  .form-section-title .titulo-texto { flex-shrink: 0; }

/* ── Panel genérico tipo tarjeta (ej. "Monto abonado" / "Forma de pago"
   del carrito) — mismo fondo sólido que .equipo-cliente-item (ver
   cliente-equipo.css), no el traslúcido de --color-bg-card que se veía
   muy claro/celeste. Vivía duplicada en carrito.css: al ser un estilo de
   panel genérico (no algo propio del carrito), se centraliza acá para
   que cualquier módulo la pueda reusar sin repetirla. ── */
.panel-metodo-extra {
  padding: 12px 14px;
  background: var(--color-bg-input-solid);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  min-width: 0;
  align-self: start;
  animation: asomarse 0.15s var(--ease-apple);
}

.panel-metodo-extra-titulo {
  font-size: 13px;
  font-weight: 600;
  color: var(--color-text-bright);
  margin-bottom: 8px;
}

.panel-metodo-extra select,
.panel-metodo-extra input[type="text"] {
  background: var(--color-bg-panel);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  color: var(--color-text);
  padding: 7px 10px;
  transition: border-color 0.2s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
}

/* El tamaño va aparte: lo que se ESCRIBE usa --fs-input (16px), porque
   bajo eso Safari en iOS hace zoom al enfocar. El <select> se queda en
   13px — no dispara el zoom (abre su propia ruedita) y crecer le
   desordena el panel. Ver la escala en vitrina.css. */
.panel-metodo-extra input[type="text"] { font-size: var(--fs-input); }
.panel-metodo-extra select { font-size: 13px; }


.panel-metodo-extra select:focus,
.panel-metodo-extra input[type="text"]:focus {
  outline: none;
  border-color: var(--color-accent);
  box-shadow: var(--shadow-focus-soft);
}

.panel-metodo-extra-fila {
  display: flex;
  gap: 6px;
  align-items: center;
}

.btn-metodo-extra-add {
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border: none;
  border-radius: 8px;
  background: var(--color-accent-dark);
  color: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  transition: background 0.2s var(--ease-apple), transform 0.15s var(--ease-apple);
}
.btn-metodo-extra-add:hover  { background: var(--color-accent); transform: scale(1.05); }
.btn-metodo-extra-add:active { transform: scale(0.94); }

.mixto-fila {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-top: 6px;
}

.mixto-fila-metodo {
  flex: 1;
  font-size: 13px;
  color: var(--color-text); overflow-wrap: anywhere;
}

.mixto-fila-monto {
  width: 80px;
  flex-shrink: 0;
  text-align: right;
}

.mixto-fila-quitar {
  cursor: pointer;
  color: var(--color-rojo);
  font-size: 12px;
  padding: 2px 4px;
  flex-shrink: 0;
  border-radius: 4px;
  transition: background 0.15s var(--ease-apple);
}
.mixto-fila-quitar:hover { background: rgba(255, 82, 82, 0.15); }

.metodo-extra-resumen {
  margin-top: 8px;
  font-size: 12px;
  text-align: right;
  color: var(--color-text-dim);
}

/* ── Toast ── */
  #toast {
    position: fixed;
    bottom: 20px;
    right: 20px;
    background: #1d1d1d;
    color: var(--color-text);
    padding: 12px 20px;
    border-radius: 8px;
    font-size: 15px;
    z-index: 1000;
    opacity: 0;
    transition: opacity 0.3s var(--ease-apple);
    pointer-events: none;
    border: 1px solid var(--color-border);
    box-shadow: 0 4px 12px rgba(0,0,0,0.5);
  }
  #toast.visible { opacity: 1; }
  #toast.success { background: rgba(76, 175, 80, 0.9);  border-color: #4caf50; }
  #toast.error   { background: rgba(244, 67, 54, 0.9);  border-color: #f44336; }

/* ── Toast de "cargando" — mismo look que #toast, pero con spinner y sin
   autocierre (dura mientras dure la operación, ver mostrarCargando en
   utilidades.js). Va un poco más arriba que #toast para que, si llegan a
   coincidir un instante, no se tapen entre sí. ── */
  #toast-cargando {
    position: fixed;
    bottom: 70px;
    right: 20px;
    display: flex;
    align-items: center;
    gap: 10px;
    background: #1d1d1d;
    color: var(--color-text);
    padding: 12px 18px;
    border-radius: 8px;
    font-size: 14px;
    z-index: 1000;
    opacity: 0;
    transform: translateY(6px);
    transition: opacity 0.25s var(--ease-apple), transform 0.25s var(--ease-apple);
    pointer-events: none;
    border: 1px solid var(--color-border);
    box-shadow: 0 4px 12px rgba(0,0,0,0.5);
  }
  #toast-cargando.visible { opacity: 1; transform: translateY(0); }
  /* Las rayitas del ti-loader, como en todo el resto de la aplicación:
     un solo dibujo y una sola velocidad, y cada lugar le pone su tamaño.
     Antes acá había un anillo con borde, girando además más rápido. */
  .toast-cargando-spinner {
    flex-shrink: 0;
    font-size: 16px;
    line-height: 1;
    color: var(--color-accent);
    animation: fh-cargando-spin 1s linear infinite;
  }

/* ── Bloque genérico de "cargando" (ver htmlCargando en utilidades.js) ── */
  .fh-cargando {
    display: flex; align-items: center; justify-content: center;
    gap: 10px; padding: 24px; color: var(--txt-sub); font-size: 13px;
  }
  .fh-cargando i { animation: fh-cargando-spin 1s linear infinite; }
  @keyframes fh-cargando-spin { to { transform: rotate(360deg); } }

  /* La misma ruedita, suelta, para ponerle a cualquier ícono que tenga
     que girar mientras se espera algo — ej. el visto del botón Guardar
     del producto, que pasa a ti-loader mientras dura el guardado. */
  .girando { animation: fh-cargando-spin 1s linear infinite; }

/* ── Input de edición inline en tabla ── */
  .input-edicion {
    width: 100%;
    border: none;
    background: transparent;
    text-align: center;
    font-size: 12px;
    color: var(--color-text);
    outline: none;
  }
 
/* ── Panel de información de registro ── */
  .info-titulo {
    font-weight: 500;
    font-size: 17px;
    color: var(--color-text);
    margin-bottom: 10px;
    padding-bottom: 6px;
    border-bottom: 2px solid var(--color-accent);
  }
 
  .info-fila {
    display: flex;
    justify-content: space-between;
    padding: 3px 0;
    border-bottom: 1px solid var(--color-border);
  }
 
  .info-label {
    color: var(--color-text);
    font-size: 15px;
    font-weight: 500;
    flex-shrink: 0;
    padding-right: 8px;
  }
 
  .info-valor {
    font-weight: normal;
    font-size: 15px;
    text-align: right;
    max-width: 200px;
    white-space: normal;
    word-break: break-word;
    color: var(--color-text);
  }
 
/* ── Quitar elemento de lista (archivos, equipos, etc.) ── */
  .archivo-quitar,
  .equipo-quitar {
    cursor: pointer;
    color: #ff8a80;
    font-weight: 500;
    padding: 0 3px;
    flex-shrink: 0;
    transition: color 0.15s var(--ease-apple), transform 0.15s var(--ease-apple);
  }
  .archivo-quitar:hover,
  .equipo-quitar:hover {
    color: #ffb3ab;
    transform: scale(1.15);
  }
 
/* ── Lista de archivos adjuntos ── */
  .archivo-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 3px 6px;
    margin-bottom: 3px;
    background: #151515;
    border-radius: 8px;
    font-size: 15px;
    border: 1px solid var(--color-border);
    color: var(--color-text);
  }
 
/* ── Progreso de subida ── */
  .progreso-box {
    display: none;
    background: #151515;
    border-radius: 8px;
    padding: 6px;
    flex-shrink: 0;
    border: 1px solid var(--color-border);
  }
 
  .progreso-texto {
    font-size: 15px;
    color: var(--color-text);
    margin-bottom: 3px;
    text-align: center;
  }
 
  .progreso-barra-bg {
    width: 100%;
    background: var(--color-border);
    border-radius: 20px;
    height: 10px;
    overflow: hidden;
  }
 
  .progreso-barra {
    height: 100%;
    width: 0%;
    background: #4caf50;
    border-radius: 20px;
    transition: width 0.3s var(--ease-apple);
  }
 
  .progreso-ok {
    display: none;
    margin-top: 4px;
    text-align: center;
    color: var(--color-verde);
    font-weight: 500;
    font-size: 15px;
  }
 
/* ── Preview de archivos en grid ── */
  .lista-archivos {
    max-height: 40px;
    overflow-y: auto;
    flex-shrink: 0;
  }
 
/* ── Preview de archivos adjuntos de la venta (con miniatura) ── */
.archivos-venta-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  max-height: 180px;
  overflow-y: auto;
  padding-top: 8px;
}
 
.archivo-venta-item {
  position: relative;
  width: 64px;
  flex-shrink: 0;
  text-align: center;
}
 
.archivo-venta-miniatura {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: #151515;
  display: block;
  cursor: pointer;
  transition: transform 0.15s var(--ease-apple), border-color 0.15s var(--ease-apple);
}
.archivo-venta-miniatura:hover { transform: scale(1.05); border-color: var(--color-accent); }
 
.archivo-venta-icono {
  width: 64px;
  height: 64px;
  border-radius: 8px;
  border: 1px solid var(--color-border);
  background: #151515;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  cursor: pointer;
  transition: transform 0.15s var(--ease-apple), border-color 0.15s var(--ease-apple);
}
.archivo-venta-icono:hover { transform: scale(1.05); border-color: var(--color-accent); }
 
.archivo-venta-nombre {
  font-size: 11px;
  color: var(--color-text-dim); overflow-wrap: anywhere;
  margin-top: 2px;
}
 
.archivo-venta-quitar {
  position: absolute;
  top: -6px;
  right: -6px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #ff8a80;
  color: #151515;
  font-size: 12px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  line-height: 1;
  z-index: 1;
  transition: transform 0.15s var(--ease-apple);
}
.archivo-venta-quitar:hover { transform: scale(1.15); }
 
  .preview-vacio {
    color: var(--color-text-dim);
    font-style: italic;
    font-size: 15px;
    text-align: center;
    padding: 6px 0;
    flex-shrink: 0;
  }
 
  .preview-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(75px, 100%), 1fr));
    gap: 6px;
  }
 
  .preview-item {
    position: relative;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--color-border);
    cursor: pointer;
    transition: transform 0.15s var(--ease-apple);
    background: #151515;
  }
 
  .preview-item:hover { transform: scale(1.05); }
  .preview-item img   { width: 100%; height: 100%; object-fit: cover; }
 
  .file-icon {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    background: #151515;
    color: var(--color-text);
  }
 
  .file-name {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    background: rgba(0,0,0,0.85);
    color: var(--color-text-bright);
    font-size: 15px;
    padding: 2px 4px; overflow-wrap: anywhere;
  }
 
/* ════════════════════════════════════════
   CONTENEDOR DE TABLA COMPARTIDO
   ════════════════════════════════════════ */
  #tabla-wrapper,
  #tabla-container {
    width: 100%;
    height: 450px;
    overflow-x: auto;
    overflow-y: auto;
    background: #151515;
    border: 1px solid var(--color-border-card);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.25);
    position: relative;
    z-index: 10;
  }
 
/* ════════════════════════════════════════
   TABLA COMPARTIDA
   ════════════════════════════════════════
   "Compartida" = look por defecto para cualquier <table> propio de la
   app que no traiga su propia clase (tabla-stock-bajo, la de
   cliente-datos.js, etc.) — de ahí que estén
   escritas como selectores de elemento sueltos, sin una clase que las
   límite a un solo módulo.

   Eso mismo las hacía chocar con widgets de terceros que también
   renderizan un <table> propio con SU look ya definido (el editor de
   .xlsx de Jspreadsheet, tabla "jss_worksheet" — ver hojas.css). La
   excepción ":not(.jss_worksheet *)" es la forma correcta de resolver
   ese choque: en vez de pelear después con !important en el lado del
   editor (como se hizo antes), acá se excluye explícitamente a ese
   widget de la convención "tabla compartida" — sigue aplicando igual
   que siempre a todo lo demás. */
  table:not(.jss_worksheet) {
    width: 100%;
    border-collapse: collapse;
    font-size: 15px;
    table-layout: auto;
    position: relative;
    z-index: 10;
  }

  th:not(.jss_worksheet *) {
    background: rgba(29, 29, 29, 0.7);
    position: sticky;
    top: 0;
    z-index: 20;
    padding: 6px 4px;
    text-align: center;
    border-bottom: 2px solid var(--color-accent-dark);
    border-right: 2px solid var(--color-accent-dark);
    vertical-align: middle; overflow-wrap: anywhere;
    font-weight: 500;
    font-size: 15px;
    cursor: pointer;
    user-select: none;
    line-height: 1.2;
    min-width: 60px;
    color: var(--color-accent);
    transition: background 0.15s var(--ease-apple);
  }

  th:not(.jss_worksheet *):first-child { border-top-left-radius: 11px; }
  th:not(.jss_worksheet *):last-child  { border-top-right-radius: 11px; border-right: none; }

  th:not(.jss_worksheet *):hover       { background: var(--color-border); }

  td:not(.jss_worksheet *) {
    padding: 5px 4px;
    text-align: center;
    border-bottom: 1px solid var(--color-border);
    border-right: 1px solid #1d1d1d;
    vertical-align: middle;
    white-space: normal;
    word-break: normal;
    overflow-wrap: anywhere;
    min-width: 60px;
    max-width: 180px;
    color: var(--color-text);
  }

  td:not(.jss_worksheet *):last-child  { border-right: none; }

  tr:not(.jss_worksheet *):nth-child(odd)  { background: rgba(21, 21, 21, 0.4); }
  tr:not(.jss_worksheet *):nth-child(even) { background: rgba(29, 29, 29, 0.4); }
  tr:not(.jss_worksheet *):hover           {
    background: #2a1a3a;
    box-shadow: inset 3px 0 0 var(--color-accent);
    transition: background 0.2s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
    cursor: pointer;
  }
  td.editando:not(.jss_worksheet *) { background: #3a2a0a !important; box-shadow: inset 0 0 0 2px var(--color-amarillo); }
 
  .no-results {
    text-align: center;
    padding: 40px;
    color: var(--color-text-dim);
    font-style: italic;
  }
 
/* ════════════════════════════════════════
   INPUT / SELECT BASE (compartidos)
   ════════════════════════════════════════ */
  .input-base {
    padding: 10px 14px;
    font-size: 17px;
    border: 2px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-bg-input);
    color: var(--color-text);
    width: 100%;
    transition: border-color 0.2s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
  }
 
  .input-base::placeholder { color: var(--color-text-dim); }
 
  .input-base:focus {
    border-color: var(--color-border-campo-foco);
    outline: none;
    box-shadow: none;
    color: var(--color-text-bright);
  }
 
  .select-base {
    padding: 8px 4px;
    font-size: 14px;
    border: 2px solid var(--color-border);
    border-radius: 8px;
    background: var(--color-bg-input);
    cursor: pointer;
    min-width: 0;
    color: var(--color-text);
    transition: border-color 0.2s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
  }
 
  .select-base:focus {
    border-color: var(--color-border-campo-foco);
    outline: none;
    box-shadow: none;
    color: var(--color-text-bright);
  }
  .select-base:hover { border-color: var(--color-accent-dark); }
 
  .select-base option {
    background: #151515;
    color: var(--color-text);
  }
 
/* Layout vertical scrolleable (para páginas tipo Inventario) */
.layout-scroll-y {
  overflow-y: auto;
}
 
/* ── Layout de página estándar (compartido entre todas las páginas) ── */
.layout-pagina {
  display: flex;
  flex-direction: column;
  padding: 15px;
  gap: 10px;
  box-sizing: border-box;
}
 
/* Dentro de .layout-pagina el gap maneja la separación; anulamos el margin-bottom de .form-section */
.layout-pagina > .form-section,
.layout-pagina > * > .form-section {
  margin-bottom: 0;
}
 
/* ════════════════════════════════════════
   PANELES INFERIORES COMPARTIDOS
   ════════════════════════════════════════ */
 
/* ── Fila inferior: contenedor de columnas ── */
.panel-inferior {
  display: flex;
  gap: 10px;
}
 
/* ── Columna genérica (Archivos, Imágenes, Acciones, Carrito…) ── */
.panel-subida {
  flex: 1;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-card);
  border-radius: 14px;
}
 
/* ── Columna Pendientes (igual a panel-subida pero sin gap interno) ── */
.panel-pendientes {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: var(--color-bg-card);
  border: 1px solid var(--color-border-card);
  border-radius: 14px;
}
 
#pendientes-scroll {
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
 
#buscar-pendientes { flex-shrink: 0; }
 
/* ── Área scrolleable interna ── */
.scroll-area {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}
 
 
/* ── Fila de archivo: Elegir (izq) + botones acción (der) ── */
.archivo-fila {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-bottom: 8px;
}
 
.archivo-fila .archivo-acciones {
  margin-left: auto;
  display: flex;
  gap: 6px;
}
 
input[type="file"] {
  width: 100%;
  font-size: 15px;
  color: var(--color-text);
}
 
/* ── Preview "Ver más" ── */
.preview-ver-mas {
  display: none;
  text-align: center;
  font-size: 17px;
  color: var(--color-accent);
  cursor: pointer;
  flex-shrink: 0;
  transition: color 0.15s var(--ease-apple);
}
.preview-ver-mas:hover { color: var(--color-text-bright); }
 
/* ── Badge de notificaciones sobre botón de pestaña ── */
.reloj-badge {
  position: absolute;
  top: -6px;
  right: -6px;
  background: #ef4444;
  color: white;
  font-size: 10px;
  font-weight: 700;
  min-width: 18px;
  height: 18px;
  border-radius: 9px;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  border: 2px solid #1d1d1d;
  pointer-events: none;
  z-index: 100;
  font-family: sans-serif;
}
 
/* ── Alarma: agitación del botón de notas ── */
@keyframes alarmaShake {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  10%, 50%  { transform: translateX(-4px) rotate(-4deg); }
  30%, 70%  { transform: translateX(4px) rotate(4deg); }
  90%       { transform: translateX(-2px) rotate(-2deg); }
}
 
.alarma-shake {
  animation: alarmaShake 0.5s ease infinite;
  box-shadow: 0 0 0 3px rgba(239,68,68,0.6), 0 0 20px rgba(239,68,68,0.4) !important;
  border-color: #ef4444 !important;
  color: #fca5a5 !important;
}
/* ── Formularios ── */
.form-row {
  display: -webkit-flex;
  display: flex;
  gap: 10px;
  width: 100%;
}
 
.form-group {
  -webkit-flex: 1;
  flex: 1;
  min-width: 0;
  display: -webkit-flex;
  display: flex;
  -webkit-flex-direction: column;
  flex-direction: column;
}
 
.form-group label {
  font-size: 15px;
  font-weight: 500;
  color: rgba(255, 255, 255, 0.7);
  margin-bottom: 3px;
}
 
.form-group input,
.form-group select,
.form-group textarea {
  width: 100%;
  min-width: 0;
  max-width: 100%;
  padding: 6px 8px;
  border: 2px solid var(--color-border-campo);
  border-radius: 8px;
  /* Un <textarea> sin font-family cae en la monoespaciada del navegador,
     así que los campos que son <textarea> se veían con otra letra que los
     <input> de al lado. */
  font-family: inherit;
  background: var(--color-bg-campo);
  color: rgba(255, 255, 255, 0.7);
}

/* Mismo criterio que arriba: lo que se escribe, con el token. */
.form-group input,
.form-group textarea { font-size: var(--fs-input); }
.form-group select { font-size: 15px; }


/* Ítem del buscador de giros del SII. .autocomplete-nombre viene con
   nowrap + ellipsis (pensado para nombres de cliente, que son cortos);
   las descripciones del SII son frases largas y cortadas no se pueden
   comparar entre sí, así que acá se dejan envolver en varias líneas. */
.giro-item {
  align-items: flex-start;
}
.autocomplete-nombre.giro-nombre {
  display: block;
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  line-height: 1.3;
}
.giro-rubro {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  font-weight: 400;
  color: rgba(255, 255, 255, 0.40);
}

/* Sin esto, las flechitas nativas de type="number" se dibujan con el
   estilo claro del navegador (negras sobre este fondo oscuro, casi
   invisibles) — con color-scheme:dark el navegador las redibuja en su
   variante oscura, a juego con el resto del campo. */
.form-group input[type="number"] {
  color-scheme: dark;
}

/* Flechitas ▲▼ a mano para campos de texto que necesitan stepper pero no
   pueden ser type="number" (ej. "Stock disponible", que acepta "NO
   APLICA") — mismo lugar/tamaño que las nativas de un input number. */
.np-stepper-wrap {
  position: relative;
}
.np-stepper-wrap input {
  padding-right: 26px !important;
}
.np-stepper-btns {
  position: absolute;
  top: 2px;
  right: 2px;
  bottom: 2px;
  display: flex;
  flex-direction: column;
  width: 20px;
}
.np-stepper-btns button {
  flex: 1;
  min-height: 0;
  padding: 0;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.55);
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  border-radius: 4px;
}
.np-stepper-btns button:hover {
  background: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.85);
}

/* Con "No aplica" marcado el campo de stock no se escribe: se apaga y se
   le esconden las flechitas, para que se vea que el número lo mandó a
   callar el checkbox y no que la caja se rompió. */
.np-stepper-wrap input:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.np-stepper-wrap input:disabled + .np-stepper-btns {
  display: none;
}

/* El checkbox "No aplica" que va bajo el campo de stock. El .chk-dark
   normal está pensado para listas de filtros (15px); acá va colgando de
   un campo del formulario, así que se achica para no competir con el
   label "Stock disponible" que tiene encima. */
.np-check-sin-conteo {
  margin-top: 7px;
}
.np-check-sin-conteo .chk-dark + label {
  font-size: 12px;
  gap: 6px;
}
.np-check-sin-conteo .chk-dark + label::before {
  width: 15px;
  height: 15px;
}
.np-check-sin-conteo .chk-dark + label::after {
  left: 5px;
  width: 4px;
  height: 8px;
}

/* Sin esto, el placeholder heredaba el mismo color casi opaco que un
   valor real tipeado (rgba(255,255,255,0.7) de arriba) — un ejemplo
   como "49.990" se veía igual de "escrito" que un número real, y
   confundía si el campo estaba realmente vacío o no. */
.form-group input::placeholder,
.form-group textarea::placeholder {
  color: rgba(255, 255, 255, 0.28);
}

/* Un campo de solo lectura no se escribe: lo llena otro campo (la región
   sale de la comuna que se elige) o lo genera el sistema. Se apaga un
   poco para no perder tiempo intentando escribir adentro. */
.form-group input[readonly],
.form-group textarea[readonly] {
  opacity: 0.65;
  cursor: default;
}

/* Borde lila al enfocar cualquier campo de .form-group — centralizado
   acá para que cualquier sección/página que use .form-group lo herede
   gratis (antes solo lo tenía ".equipo-cliente-item", ver
   cliente-equipo.css, definido aparte con la misma variable
   --color-border-campo-foco; si algún día cambia ese color, alcanza con
   tocarla en un solo lugar). */
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  border-color: var(--color-border-campo-foco);
  outline: none;
  box-shadow: none;
}

/* Estado deshabilitado — centralizado acá para que cualquier campo de
   .form-group (Persona/Empresa/Dispositivo) o .detalles-group
   (Tipo/Método/Documento/Seguimiento) lo herede gratis. Antes estaba
   duplicado tal cual en carrito.css (.detalles-group select:disabled) y
   en cliente-equipo.css (.equipo-cliente-item select/input:disabled) —
   y como ese segundo apuntaba a "input[type=text]" nada más, al pasar
   Marca/Modelo a <textarea> se quedaron sin este estilo (se veían
   habilitados aunque no lo estuvieran). Con esto cubre los 3 tipos de
   campo de una vez, en cualquier lugar de la app. */
.form-group input:disabled,
.form-group select:disabled,
.form-group textarea:disabled,
.detalles-group input:disabled,
.detalles-group select:disabled,
.detalles-group textarea:disabled {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.3);
  cursor: not-allowed;
}

/* Borde rojo temporal en campos obligatorios que quedaron sin llenar al
   apretar "Procesar" (ver _marcarCampoInvalido en carrito.js). Se quita
   solo apenas el usuario toca ese campo. El halo "respira" lentamente
   (sube y baja de intensidad) en vez de quedar fijo y grueso, para
   llamar la atención sin ser agresivo. Centralizado acá (antes vivía
   en carrito.css) para poder reusarlo en cualquier formulario de la
   app, no solo en el carrito. */
@keyframes campoInvalidoPulso {
  0%, 100% {
    border-color: rgba(255, 82, 82, 0.35);
    box-shadow: 0 0 2px 0 rgba(255, 82, 82, 0.15);
  }
  50% {
    border-color: rgba(255, 82, 82, 0.65);
    box-shadow: 0 0 4px 1px rgba(255, 82, 82, 0.35);
  }
}

.campo-invalido {
  border-width: 1px !important;
  animation: campoInvalidoPulso 2.4s ease-in-out infinite;
}
 
/* El único corte que queda en un <select>: lo que muestra un desplegable
   cerrado es una línea que el navegador no sabe envolver — no es texto
   nuestro, es el control nativo. Al abrirlo se ve la opción completa. */
.form-group select {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}
 
.form-group select option {
  background: #0d0d0d;
  color: rgba(255, 255, 255, 0.7);
}
 
.form-group textarea {
  resize: none;
  min-height: 50px;
  overflow: hidden;
}
 
/* Textareas que reemplazan a un <input> de una sola línea (nombre, correo)
   y deben verse igual que un input hasta que el texto no quepa, momento
   en el que crecen hacia abajo.

   field-sizing:content (probado antes acá) no terminó funcionando en la
   práctica — Safari lo reconoce como propiedad válida (pasa @supports)
   pero no la aplicaba de verdad sobre el <textarea>. En vez de seguir
   dependiendo de que el navegador mida bien en el momento justo (por
   JS, con scrollHeight — eso fue lo primero que falló, dos veces) o de
   soporte de una feature CSS nueva poco confiable, se usa la técnica
   "mirror" de toda la vida: un ::after invisible, con el MISMO texto,
   apilado en la misma celda de un grid que el textarea real — el alto
   de la fila del grid lo determina el más alto de los dos, y eso lo
   calcula el motor de layout del navegador solo, sin JS midiendo nada
   ni "momento correcto" que adivinar. El wrapper ".autogrow-wrap" se
   arma solo la primera vez que se llama a autoResizeTextarea() sobre un
   campo (ver utilidades.js) — no hace falta tocar el HTML de cada
   textarea uno por uno. */
.autogrow-wrap {
  display: grid;
  width: 100%;
  min-width: 0;
}
.autogrow-wrap::after {
  content: attr(data-autogrow-value) ' ';
  visibility: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  /* El alto sale de comparar este espejo contra el campo real: si cada uno
     usa otra letra, otro padding u otro borde, el mismo texto ocupa
     distinto y el alto queda mal calculado. Las medidas las copia del
     campo _copiarMedidasAlEspejo (utilidades.js) al envolverlo; los
     valores de acá son el respaldo, los de .form-group, para cuando esa
     copia no llegó a correr. */
  padding: var(--autogrow-padding, 6px 8px);
  border: var(--autogrow-borde, 2px) solid transparent;
  font-family: var(--autogrow-familia, inherit);
  font-size: var(--autogrow-fuente, 15px);
  font-weight: var(--autogrow-peso, inherit);
  font-style: var(--autogrow-estilo, inherit);
  /* Sin esto el espejo hereda el espaciado de la página y el campo no:
     escribe más angosto, corta en otro lado y mide un renglón de menos. */
  letter-spacing: var(--autogrow-espaciado, normal);
  word-spacing: var(--autogrow-espaciado-palabra, normal);
  line-height: var(--autogrow-interlinea, 1.3);
  overflow-wrap: break-word;
  min-height: 33px;
}
.autogrow-wrap > textarea.autogrow-input,
.autogrow-wrap::after {
  grid-area: 1 / 1 / 2 / 2;
}
.form-group textarea.autogrow-input {
  min-height: 33px;
  height: 33px;
  line-height: 1.3;
  resize: none;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
}
.autogrow-wrap > textarea.autogrow-input {
  height: auto;
}

/* Un placeholder tampoco se puede envolver: un <input> es de una sola
   línea por definición. Es texto de ejemplo, no contenido — lo que la
   persona escriba se ve entero (el campo crece, ver .autogrow-wrap).

   El alto lo decide el TEXTO ESCRITO, no el de ejemplo: el espejo mide
   data-autogrow-value, que es el valor. Un placeholder más largo que una
   línea se envolvía y se salía de una caja que no había crecido por él —
   un <input> lo mostraba en una línea recortada y así se habían escrito
   estos textos de ejemplo. Se le devuelve ese comportamiento.

   No se resuelve haciendo que el espejo mida el placeholder: eso dejaría
   los formularios vacíos con cajas de dos y tres renglones que se achican
   al empezar a escribir. */
.autogrow-input::placeholder {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
 
/* ── Toggle switch ── */
.chk-toggle {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
 
.chk-toggle + label {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
  background: #303030;
  border-radius: 11px;
  cursor: pointer;
  transition: background 0.3s var(--ease-apple), box-shadow 0.2s var(--ease-apple);
  flex-shrink: 0;
}
 
.chk-toggle + label::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #888;
  transition: all 0.3s var(--ease-apple);
}
 
.chk-toggle + label:hover   { background: #3c3c3c; }
.chk-toggle:checked + label { background: var(--color-accent); }
 
.chk-toggle:checked + label::after {
  left: 20px;
  background: #fff;
}
 
.chk-toggle:focus-visible + label {
  box-shadow: 0 0 0 3px rgba(206, 147, 216, 0.3);
}
 
/* ── Checkbox cuadrado ── */
.chk-dark {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
 
.chk-dark + label {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 15px;
  color: rgba(255, 255, 255, 0.7);
  user-select: none;
}
 
.chk-dark + label::before {
  content: '';
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  border: 2px solid #303030;
  border-radius: 4px;
  background: #0d0d0d;
  transition: all 0.2s var(--ease-apple);
}
 
.chk-dark + label:hover::before {
  border-color: var(--color-accent);
  background: #1d1d1d;
}
 
.chk-dark:checked + label::before {
  background: var(--color-accent);
  border-color: var(--color-accent);
}
 
.chk-dark + label::after {
  content: '';
  position: absolute;
  left: 6px;
  top: 50%;
  width: 5px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: translateY(-65%) rotate(45deg) scale(0);
  transition: transform 0.15s var(--ease-apple);
}
 
.chk-dark:checked + label::after {
  transform: translateY(-65%) rotate(45deg) scale(1);
}
 
.chk-dark:focus-visible + label::before {
  box-shadow: 0 0 0 3px rgba(206, 147, 216, 0.3);
}
 
/* ── Tarjeta búsqueda + tabla (base compartida) ── */
.tarjeta-busqueda-tabla {
  margin: 0;
  min-width: 0;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
  position: relative;
  z-index: 10;
}
 

/* ── Item de navegación reutilizable: botón con ícono + texto, cajita
   redondeada resaltada cuando está activo/abierto. Antes esto vivía
   duplicado casi idéntico en dos lugares: .admin-tab (admin.css, nav
   del panel Admin) y .vit-sb-section-btn (vitrina.css, headers de
   sección en los sidebars con filtros). Ahora ambos heredan de acá y
   solo agregan lo que les es propio (ver admin.css/vitrina.css).
   Usa las variables --va/--vag/--vag2/--vtx2/--vtx3/--r definidas en
   vitrina.css: si una página usa esta clase sin cargar vitrina.css,
   hay que definir esas variables (o pasarles equivalentes) ahí. ── */
.nav-item-pill {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 12px;
  margin: 2px 0;
  border: 1px solid transparent;
  border-radius: var(--r);
  background: transparent;
  color: var(--vtx2);
  font-size: 14px;
  font-weight: 400;
  text-align: left;
  cursor: pointer;
  transition: all 0.15s var(--ease-apple);
}
.nav-item-pill i { font-size: 16px; flex-shrink: 0; color: inherit; }
.nav-item-pill:hover { background: var(--vag); color: var(--vtx); }
.nav-item-pill.activo {
  color: var(--va);
  font-weight: 700;
}

/* ── Filas de opción del sidebar ───────────────────────────────────
   Las opciones que van dentro de un .vit-sb-section-body, en los cuatro
   sidebars: el catálogo, los filtros de Registro, el admin y la consola.
   Viven acá y no en vitrina.css/admin.css porque tenerlas duplicadas fue
   justamente lo que hizo que los sidebars terminaran viéndose distintos —
   el catálogo llegó a tener su propia copia (.vit-filter-item) con estas
   mismas medidas escritas a mano al lado.

   Va en mayúsculas por CSS: las etiquetas del catálogo ya vienen en
   mayúscula desde la base (son nombres de categoría, marca, etc.) y las
   del admin son texto fijo que escribimos nosotros. Sin esto se veían
   distintas al lado.

   .sb-opcion sirve tanto a un <button> como a un <label>; un <button> no
   hereda ni la tipografía ni el letter-spacing, así que van explícitos. */
.sb-opcion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 10px;
  padding: 2px 6px;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(255,255,255,0.03);
  border-radius: 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: -0.16px;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--vtx2);
  text-align: left;
  cursor: pointer;
  transition: background 0.12s var(--ease-apple), color 0.12s var(--ease-apple);
}
.sb-opcion:last-child { border-bottom: none; }
.sb-opcion:hover { background: var(--vag); border-radius: 6px; color: var(--vtx); }

/* Variante con casilla (los sidebars de filtros del catálogo y de
   Registro, ver _htmlOpcionSidebar en utilidades.js). El texto se lleva
   el espacio sobrante para que la casilla quede pegada a la izquierda:
   sin esto el space-between de arriba las manda a cada extremo. */
.sb-opcion input[type="checkbox"] {
  width: 15px;
  height: 15px;
  border-radius: 5px;
  accent-color: var(--va);
  cursor: pointer;
  flex-shrink: 0;
}
.sb-opcion-texto { flex: 1; }

/* Tildada se marca igual que .activo de arriba: es la misma idea de
   "esta opción está elegida", solo que acá lo dice la casilla. */
.sb-opcion:has(input:checked) {
  color: var(--vtx);
  font-weight: 700;
}

/* Seleccionada: blanco y en negrita. A propósito NO en color de acento —
   ese ya lo usa el título de la sección, y con los dos marcados en morado
   no se distinguía cuál era la sección y cuál la opción elegida. */
.sb-opcion.activo {
  color: var(--vtx);
  font-weight: 700;
}

/* Contador al costado de una opción (ej. cuántas empresas hay en cada
   estado). Sigue a la búsqueda, así se ve dónde quedó el resultado. */
.sb-opcion-cuenta {
  flex-shrink: 0;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--vag);
  color: var(--txt-sub);
  font-size: 11px;
  font-weight: 700;
}
.sb-opcion.activo .sb-opcion-cuenta { background: var(--vag2); color: var(--vtx); }


/* Celdas de las tablas que arma el JS (ej. el modal que lista los
   comprobantes de un cliente). Eran las mismas medidas escritas en cada
   <th> y cada <td>. */
/* Con el elemento adelante a propósito: más arriba hay un
   "th:not(.jss_worksheet *) { padding: 6px 4px }" que le gana a una clase
   sola. Cuando esto era un estilo en línea ganaba siempre —los inline le
   ganan a todo— así que al pasarlo a clase el padding se caía a 6px sin
   que nada avisara. */
th.fx-tabla-th {
  padding: 8px;
  text-align: left;
  border-bottom: 2px solid var(--color-accent-dark);
}
td.fx-tabla-td {
  padding: 8px;
  border-bottom: 1px solid var(--color-border);
}
.fx-tabla-centro { text-align: center; }

/* ── Dos textos chicos que se repetían escritos a mano ────────────
   De 261 atributos "style" largos en el JS, 35 patrones cubrían 102
   apariciones; el resto son únicos y quedan como están. Estos dos son los
   que más se repetían, y son los que un cambio de color o de tamaño
   obligaba a tocar en un montón de lugares.

   .fx-etiqueta: el rótulo en mayúsculas que encabeza una sección.
   .fx-nota: la aclaración chica debajo de un campo. */
.fx-etiqueta {
  display: block;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--txt-sub);
  margin-bottom: 5px;
}
.fx-nota {
  font-size: 12px;
  color: var(--txt-sub);
  margin: 0;
}
.fx-nota-chica { font-size: 11px; }

/* ── "Acá no hay nada" ────────────────────────────────────────────
   El mismo cartel para una lista vacía en todo el programa. Vivía en
   admin.css con nombre de panel, pero la agenda, el carrito público y la
   consola muestran lo mismo — y los que no la usaban repetían sus
   medidas con estilos en línea, cada uno con su padding y su gris. */
.admin-vacio {
  text-align: center;
  color: var(--txt-sub);
  padding: 40px 20px;
  font-size: 13px;
}

/* ── Confirmación compartida (ver confirmarAccion en utilidades.js) ──
   Mismo fondo difuminado que el resto de los modales del programa. */
.fx-confirmar {
  position: fixed;
  inset: 0;
  z-index: 4000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  animation: asomarse 0.15s var(--ease-apple);
}
.fx-confirmar.oculto { display: none; }
.fx-confirmar-caja {
  width: 100%;
  max-width: 380px;
  padding: 22px 22px 18px;
  background: var(--color-bg-panel, #1b1b1b);
  border: 1px solid var(--color-border, rgba(255,255,255,0.14));
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.7);
}
.fx-confirmar-caja h2 {
  margin: 0 0 8px;
  font-size: 17px;
  font-weight: 700;
  color: var(--txt-titulo);
}
.fx-confirmar-caja p {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--txt-cuerpo);
  /* El mensaje suele traer un salto de línea con la consecuencia de
     borrar ("Las citas ya agendadas..."), y se escribe con textContent. */
  white-space: pre-line;
}
.fx-confirmar-botones {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}
.fx-confirmar-btn {
  padding: 8px 16px;
  border-radius: 10px;
  border: 1px solid var(--color-border, rgba(255,255,255,0.14));
  background: transparent;
  color: var(--txt-cuerpo);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.15s var(--ease-apple);
}
.fx-confirmar-btn:hover { background: rgba(255, 255, 255, 0.06); color: var(--txt-titulo); }
.fx-confirmar-si.peligro {
  border-color: rgba(255, 138, 128, 0.45);
  color: var(--color-rojo-suave, #ff8a80);
}
.fx-confirmar-si.peligro:hover {
  background: rgba(255, 138, 128, 0.12);
  color: var(--color-rojo-suave, #ff8a80);
}

/* ── Resultado de una operación: el visto (o la cruz) que se dibuja ──
   Lo comparten el alta pública ("Solicitud enviada") y el cierre del
   carrito público (pago confirmado / rechazado). Antes vivía en
   admin.css con nombres de una sola pantalla (.reg-emp-listo-*).

   Antes era un emoji de 48px que aparecía de golpe junto con el texto.
   La marca ahora se dibuja: primero el círculo, después el trazo, y el
   texto entra detrás. El tiempo que toma es el punto — el formulario
   desaparece en el mismo instante, y sin algo que ocupe ese momento la
   pantalla cambia tan rápido que no queda claro si salió bien o si algo
   se rompió.

   Se dibuja con stroke-dashoffset: el trazo se define del largo exacto
   de la línea y se corre hasta que la descubre entera. La variante
   .malo cambia el color y usa la cruz; el largo del trazo lo declara
   cada marca, porque una cruz de dos segmentos mide distinto que un
   visto. Colores y curva son los del resto de la app. */
.fx-resultado {
  text-align: center;
  padding: 60px 20px;
}
.fx-resultado-anim {
  position: relative;
  width: 72px;
  height: 72px;
  margin: 0 auto 18px;
}
.fx-resultado-svg {
  width: 100%;
  height: 100%;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.fx-resultado-circulo {
  stroke: var(--va);
  stroke-width: 2;
  opacity: 0.55;
  stroke-dasharray: 145;
  stroke-dashoffset: 145;
  animation: fxResultadoCirculo 0.55s var(--ease-apple) forwards;
}
.fx-resultado-marca {
  stroke: var(--va);
  stroke-width: 3.5;
  stroke-dasharray: 34;
  stroke-dashoffset: 34;
  animation: fxResultadoMarca 0.35s var(--ease-apple) 0.45s forwards;
}
/* La cruz son dos segmentos en un mismo path, así que el trazo total es
   más largo que el del visto. */
.fx-resultado.malo .fx-resultado-marca { stroke-dasharray: 46; stroke-dashoffset: 46; }
.fx-resultado.malo .fx-resultado-circulo,
.fx-resultado.malo .fx-resultado-marca,
.fx-resultado.malo .fx-resultado-aro { stroke: var(--color-rojo-suave); border-color: var(--color-rojo-suave); }

/* Halo que se expande una vez, como el eco del trazo al cerrarse. */
.fx-resultado-aro {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 2px solid var(--va);
  opacity: 0;
  animation: fxResultadoAro 0.7s var(--ease-apple) 0.5s forwards;
}
@keyframes fxResultadoCirculo { to { stroke-dashoffset: 0; } }
@keyframes fxResultadoMarca   { to { stroke-dashoffset: 0; } }
@keyframes fxResultadoAro {
  from { opacity: 0.5; transform: scale(1); }
  to   { opacity: 0;   transform: scale(1.45); }
}

.fx-resultado h2 {
  font-size: 20px; color: var(--vtx); margin: 0 0 8px;
  animation: surgir 0.4s var(--ease-apple) 0.55s backwards;
}
.fx-resultado p {
  font-size: 13px; color: var(--txt-cuerpo); margin: 0 0 20px; line-height: 1.6;
  animation: surgir 0.4s var(--ease-apple) 0.68s backwards;
}
.fx-resultado button {
  animation: surgir 0.4s var(--ease-apple) 0.8s backwards;
}

/* Quien pidió menos movimiento ve la marca ya dibujada, sin trazo ni
   halo: el resultado no depende de la animación para entenderse. */
@media (prefers-reduced-motion: reduce) {
  .fx-resultado-circulo,
  .fx-resultado-marca { stroke-dashoffset: 0; animation: none; }
  .fx-resultado-aro { display: none; }
  .fx-resultado h2,
  .fx-resultado p,
  .fx-resultado button { animation: none; }
}

/* ── Anillo de foco de la app ──────────────────────────────────────
   Un campo enfocado se marca siempre igual, en todo el sitio. Esta
   pareja de propiedades estaba repetida en ocho selectores de cinco
   hojas distintas (vitrina, admin, selector-empresas, vitp-shell,
   fx-datepicker); acá va una sola vez.

   Se listan los selectores de cada componente en vez de pedir una clase
   nueva en el marcado porque varios de estos campos los genera JS: una
   clase habría que acordarse de ponerla en cada uno, y olvidarla no
   rompe nada visible hasta que alguien enfoca ese campo.

   Quedan afuera a propósito .select-rol:focus y
   .sel-emp-hero-chips select:focus, que usan el borde de acento
   (var(--va)) en lugar de --vbor2: ahí el foco es más marcado a
   propósito. */
:is(.vit-search-wrap, .sel-emp-hero-buscador, .fx-dp-trigger):focus-within,
.vit-sort-select:focus,
:is(.vit-login-dropdown, .admin-form, .vitp-filtros-bar) :is(input, select):focus {
  border-color: var(--vbor2);
  box-shadow: var(--shadow-focus-soft);
}

/* ── Panel flotante que no puede salirse de la ventana ──────────────
   Ver _publicarAnchoVentana en utilidades.js: --ancho-ventana es el
   ancho REAL utilizable (sin la barra de scroll), que es lo que 100vw
   no da. Cualquier desplegable, popover o panel anclado a un borde
   debería llevar esta clase o copiar su max-width. ── */
.fx-flotante-acotado {
  max-width: calc(var(--ancho-ventana, 100vw) - 24px);
}

/* ── Menú flotante ⋮ de una tarjeta ─────────────────────────────────
   Estaba escrito dos veces, idéntico declaración por declaración: en
   vitrina.css (#vit-card-menu-dropdown, el de las cards del catálogo) y
   en boletas.css (.reg-card-menu-dropdown-flotante, los que crea
   _asegurarMenuCardRegistro acá mismo). El comentario del segundo ya
   decía "mismo patrón que vit-card-menu-dropdown"; ahora lo es de
   verdad y no por coincidencia.

   Va "fixed" y no "absolute" a propósito: cuelga de <body> para que no
   lo recorte el overflow de la tarjeta ni de la grilla. ── */
.fx-menu-card-flotante {
  position: fixed;
  min-width: 150px;
  background: var(--vs2);
  border: 1px solid var(--vbor2);
  border-radius: var(--r);
  padding: 6px;
  z-index: 500;
  box-shadow: 0 12px 32px rgba(0,0,0,0.55);
  display: flex;
  flex-direction: column;
  gap: 2px;
  animation: asomarse 0.15s var(--ease-apple);
}
.fx-menu-card-flotante.cerrando {
  animation: despedirse 0.14s var(--ease-apple) forwards;
}
.fx-menu-card-flotante.oculto { display: none; }

/* ── Menú anclado a su botón ────────────────────────────────────────
   El otro sabor de menú del proyecto. A diferencia de
   .fx-menu-card-flotante —que va "fixed" y cuelga de <body> para que no
   lo recorte el overflow de una tarjeta—, este va "absolute" pegado
   debajo y a la derecha de su disparador.

   Estaba escrito tres veces: .admin-card-menu-dropdown (admin.css),
   .hoj-nuevo-menu (hojas.css) y .plan-descargar-menu (planilla.css).
   Diez de sus declaraciones eran idénticas en los tres; solo cambiaban
   el fondo, el ancho mínimo y el z-index, que quedan en cada uno.

   Ojo con el selector al usarla: estas tres reglas viven en archivos que
   se cargan DESPUÉS de utilidades.css, así que cualquier declaración que
   dejen puesta le gana a esta base aunque la clase venga primero en el
   atributo. Es lo que se busca acá, pero es la misma trampa que hizo
   caer un padding de 8px a 6px al mover estilos en línea a clases. ── */
.fx-menu-anclado {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  border: 1px solid var(--vbor2);
  border-radius: var(--r);
  padding: 6px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Aviso de código de barra repetido, bajo el campo del formulario de
   producto. Rojo como .campo-invalido, pero sin el pulso: acá el texto
   ya explica el problema y un parpadeo permanente mientras se corrige
   molesta más de lo que ayuda. */
/* Miniaturas de la galería del formulario de producto. Estaban en
   atributos style dentro de _renderGaleriaImagenesNP (vitrina.js), donde
   ganaban por especificidad a cualquier regla: se trajeron acá para
   poder agrandarlas en la página de producto, que tiene sitio de sobra. */
.np-img-item {
  position: relative;
  width: 72px;
  height: 72px;
  flex-shrink: 0;
}
.np-img-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid var(--vbor);
}
/* Morado: la foto todavía no está guardada. */
.np-img-item-nueva img { border-color: var(--va); }

.np-img-quitar {
  position: absolute;
  top: -6px;
  right: -6px;
  background: #d33;
  color: #fff;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
}

.np-aviso-repetido {
  margin-top: 6px;
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: #ff8a80;
}

/* ── Página-formulario ─────────────────────────────────────────────
   El armazón de las pantallas que son un formulario largo y nada más:
   hoy el alta pública de empresa (/registro-empresa) y el editor de
   producto (/producto/nuevo y compañía). Vivía en admin.css con el
   prefijo reg-emp-, de cuando lo usaba una sola de las dos.

   El scroll va acá y no en .vitp-body: esa se queda en overflow:hidden
   a propósito y cada página hace scrollear su propio contenedor (igual
   que .admin-panel). Sin esto el formulario queda cortado — mide más
   que la caja que lo contiene y no hay forma de llegar al final.

   Ocupa el ancho completo, y el que centra es .fx-form-cols adentro: si
   el max-width estuviera acá, la barra de scroll se dibujaría en el
   borde de esa caja, o sea flotando en medio de la pantalla en vez de
   pegada a la derecha. scroll-morado le da la barra fina del resto de
   la aplicación. */
.fx-form-pagina {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  width: 100%;
  padding: 28px 20px 60px;
}
/* Es esto lo que centra y acota el contenido, no el que scrollea. */
.fx-form-cols {
  max-width: 720px;
  margin: 0 auto;
}
.fx-form-titulo {
  font-size: 24px;
  font-weight: 800;
  color: var(--vtx);
  margin: 0 0 6px;
}
.fx-form-bajada {
  font-size: 13px;
  color: var(--txt-sub);
  margin: 0 0 20px;
  line-height: 1.6;
}

/* Confirmación por escrito, para lo que no tiene vuelta atrás. */
.fx-confirmar-escribir {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 4px 0 2px;
  font-size: 13px;
  color: var(--txt-sub);
}
.fx-confirmar-escribir input {
  background: var(--vs2);
  border: 1px solid var(--vbor);
  border-radius: var(--rm, 10px);
  color: var(--vtx);
  padding: 9px 12px;
  font: inherit;
  font-size: var(--fs-input);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.fx-confirmar-escribir input:focus {
  outline: none;
  border-color: var(--color-accent);
}
/* Apagado hasta que la palabra calce: que se vea que falta algo, no que
   el botón está roto. */
.fx-confirmar-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* ── "¿A qué taller entras?" ──
   Reusa la caja de confirmarAccion: es el mismo tipo de interrupción y no
   tiene sentido inventarle otra apariencia. */
.fx-elegir-lista {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 6px 0 2px;
}
.fx-elegir-taller-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  background: var(--vs2);
  border: 1px solid var(--vbor);
  border-radius: var(--rm, 10px);
  color: var(--vtx);
  font: inherit;
  cursor: pointer;
  text-align: left;
}
.fx-elegir-taller-item:hover:not(:disabled) {
  border-color: var(--color-accent);
  background: var(--vag2);
}
/* Apagado y con el motivo al lado, no escondido: saber por qué no puedes
   entrar a un taller es tan útil como poder entrar a otro. */
.fx-elegir-taller-item:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.fx-elegir-nombre { font-weight: 600; }
.fx-elegir-rol {
  font-size: 12px;
  color: var(--txt-sub);
  text-align: right;
  flex-shrink: 0;
  max-width: 55%;
}

/* ── "Mis dispositivos" ──
   Misma caja que las dos de arriba. Las filas no son botones: la fila
   entera no hace nada, el único gesto es el "Cerrar" de la derecha, y
   que se vea clickeable algo que no lo es confunde. */
.fx-disp-lista {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 6px 0 18px;
}
.fx-disp-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* En un teléfono el nombre del aparato y el botón no caben en la misma
     línea, y partir el nombre en dos ("Windows ·" / "Chrome") se lee mal.
     Con wrap el botón se baja solo y el nombre se queda entero y de
     corrido, que es lo que importa. */
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 11px 13px;
  background: var(--vs2);
  border: 1px solid var(--vbor);
  border-radius: var(--rm, 10px);
}
/* El de uno, marcado: es el que NO se puede cerrar desde acá. */
.fx-disp-item.es-este { border-color: var(--color-accent); }
.fx-disp-datos {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  /* Sin esto el nombre del aparato no deja encoger la fila y el botón
     "Cerrar" se sale de la caja. */
  min-width: 0;
}
.fx-disp-nombre {
  display: flex;
  align-items: center;
  gap: 7px;
  font-weight: 600;
  color: var(--vtx);
}
.fx-disp-actual {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--vag2);
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
  font-size: 11px;
  font-weight: 600;
}
.fx-disp-cuando {
  font-size: 12px;
  color: var(--txt-sub);
}
.fx-disp-cerrar {
  flex-shrink: 0;
  margin-left: auto;
  padding: 7px 13px;
  border-radius: var(--rm, 10px);
  border: 1px solid rgba(255, 138, 128, 0.45);
  background: transparent;
  color: var(--color-rojo-suave, #ff8a80);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s var(--ease-apple);
}
.fx-disp-cerrar:hover { background: rgba(255, 138, 128, 0.12); }

/* ── Garantía de un comprobante (/garantia/FHR-XXXX) ──
   Página pública con el mismo aire que el alta de empresa: título,
   bajada y tarjetas. Las clases son propias (gar-*) y no las de
   admin.css a propósito — esta página la abre gente sin sesión. */
.gar-estado {
  display: flex; align-items: center; gap: 10px;
  padding: 16px 18px;
  border-radius: var(--rl);
  border: 1px solid var(--vbor2);
  background: var(--vs);
  font-size: 16px; font-weight: 650; color: var(--vtx);
  margin-bottom: 14px;
}
.gar-punto { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; background: var(--txt-tenue); }
.gar-ok   .gar-punto { background: var(--vgrn); }
.gar-mal  .gar-punto { background: var(--vred); }
.gar-ok   { border-color: rgba(74, 222, 128, 0.35); }
.gar-mal  { border-color: rgba(248, 113, 113, 0.35); }

.gar-caja {
  background: var(--vs);
  border: 1px solid var(--vbor2);
  border-radius: var(--rl);
  padding: 18px;
  margin-bottom: 14px;
}
.gar-caja h3 { color: var(--va); font-size: 15px; font-weight: 700; margin: 0 0 10px; }
/* Lo que solo ve el taller: se distingue del resto de la página, que es
   lo que también está viendo el cliente. */
.gar-caja-interna { border-color: rgba(206, 147, 216, 0.4); }
.gar-nota { font-size: 13px; color: var(--txt-sub); line-height: 1.5; margin: 0; }

.gar-linea { padding: 14px 0; border-top: 1px solid var(--vbor); }
.gar-linea:first-of-type { border-top: none; padding-top: 0; }
.gar-linea-que { font-size: 15px; font-weight: 500; color: var(--vtx); }
.gar-linea-detalle { font-size: 12px; color: var(--txt-sub); margin-top: 2px; line-height: 1.5; }
.gar-linea-fechas { font-size: 13px; color: var(--txt-cuerpo); margin-top: 8px; }

.gar-chip {
  display: inline-block; margin-top: 8px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 99px;
}
.gar-chip-ok   { background: rgba(74, 222, 128, 0.14); color: var(--vgrn); }
.gar-chip-mal  { background: rgba(248, 113, 113, 0.14); color: var(--vred); }
.gar-chip-nada { background: var(--vag); color: var(--txt-sub); }

/* El botón "Guardar" de los datos del cliente necesita aire: pegado al
   campo de arriba se lee como parte del cuadro de texto. */
.gar-caja-interna .btn-sm { margin-top: 16px; }

/* Título de la página + Descargar/Compartir a un costado */
.gar-encabezado {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.gar-acciones { display: flex; gap: 8px; flex-shrink: 0; }
/* El color y la forma los pone .btn-azul-bg (el mismo botón que el del
   carrito); acá solo el ícono al lado del texto. */
.gar-btn-exportar { display: inline-flex; align-items: center; gap: 6px; }

/* ── El comprobante de garantía (previsualización + PDF) ──
   La hoja va en blanco y negro a propósito: es un documento para
   guardar o imprimir, no una pantalla. Lo que se ve acá es exactamente
   lo que html2canvas convierte en PDF. */
/* El visor del comprobante de garantía reusa la caja del visor de
   comprobantes de la aplicación (.modal-contenido / .modal-header /
   .modal-cerrar, en carrito.css): la barra morada con el título y los
   botones es la misma que al imprimir una venta. Acá solo va el fondo
   y el tamaño, que ahí están atados a #modal-preview-archivo. */
/* El visor del comprobante de garantía: la misma caja y las mismas
   medidas que el de una venta (320px de ancho, cuerpo oscuro, el ticket
   adentro de un <iframe> que ocupa todo). Las clases .modal-contenido /
   .modal-header / .modal-cerrar / .modal-body vienen de carrito.css;
   acá solo va lo que allá está atado a #modal-preview-archivo. */
#modal-comprobante-garantia {
  display: none;
  position: fixed; inset: 0; z-index: 2000;
  background: rgba(0, 0, 0, 0.75);
  padding: 24px 12px;
  align-items: center; justify-content: center;
}
#modal-comprobante-garantia.activo { display: flex; }
#modal-comprobante-garantia .cg-marco { width: 320px; max-width: 90vw; height: auto; max-height: 92vh; }
.cg-titulo-modal {
  flex: 1; min-width: 0;
  font-size: 14px; font-weight: 500; overflow-wrap: anywhere;
}
#modal-comprobante-garantia .cg-hoja {
  width: 100%;
  height: 80vh;
  max-height: 100%;
  border: none;
  background: #fff;
  display: block;
}

/* Fotos y videos del reclamo */
.gar-btn-adjuntar {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 6px; padding: 8px 12px;
  background: var(--vag); border: 1px dashed var(--vbor2);
  border-radius: var(--r); color: var(--txt-sub);
  font: inherit; font-size: 13px; cursor: pointer;
}
.gar-btn-adjuntar:hover { border-color: var(--color-accent); color: var(--vtx2); }
.gar-archivos-lista { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.gar-archivo-chip {
  display: inline-flex; align-items: center; gap: 8px;
  max-width: 240px; padding: 5px 10px;
  background: var(--vs); border: 1px solid var(--vbor);
  border-radius: var(--r); font-size: 12px; color: var(--vtx2);
}
.gar-archivo-nombre { overflow-wrap: anywhere; }
.gar-archivo-peso { color: var(--txt-sub); flex: 0 0 auto; }
.gar-archivo-quitar {
  background: none; border: none; padding: 0; line-height: 1;
  color: var(--txt-sub); cursor: pointer; font-size: 13px;
}
.gar-archivo-quitar:hover { color: var(--vred); }
.gar-archivo-abrir { cursor: pointer; font-family: inherit; }
.gar-archivo-abrir:hover { border-color: var(--color-accent); color: var(--va); }

/* Cada producto pide su propia garantía: el chip de vigencia a la
   izquierda y el botón a la derecha, en la misma fila. */
.gar-linea-pie {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-top: 8px;
}
.gar-linea-pie .gar-chip { margin-top: 0; }
.gar-btn-pedir {
  background: var(--vag); border: 1px solid var(--vbor2);
  color: var(--vtx2); border-radius: var(--r);
  padding: 6px 12px; font-size: 12px; font-weight: 600;
  cursor: pointer; font-family: inherit;
  transition: border-color 0.18s var(--ease-apple), color 0.18s var(--ease-apple);
}
.gar-btn-pedir:hover { border-color: var(--color-accent); color: var(--vtx); }
.gar-form-linea {
  margin-top: 12px; padding: 14px;
  background: var(--vs2); border: 1px solid var(--vbor);
  border-radius: var(--r);
}
.gar-form-linea.oculto { display: none; }
.gar-form-linea .btn-sm { margin-top: 12px; }
.gar-nota-suelta { margin-top: 12px; }

.gar-reclamos { margin-top: 14px; }
.gar-reclamo { padding: 12px 0; border-top: 1px solid var(--vbor); }
.gar-reclamo-cabecera {
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; color: var(--vtx); font-weight: 600;
}
.gar-reclamo-cabecera .gar-chip { margin-top: 0; }

/* Los campos del formulario: mismo aspecto que los del alta de empresa,
   sin depender de .admin-form (que vive en admin.css, el paquete que
   solo se carga con sesión). */
.gar-caja .form-group label { display: block; font-size: 12px; color: var(--txt-sub); margin-bottom: 6px; }
.gar-caja input,
.gar-caja textarea {
  width: 100%;
  background: var(--vs2);
  border: 1px solid var(--vbor);
  color: var(--vtx2);
  border-radius: var(--r);
  padding: 10px 12px;
  font: inherit;
  font-size: var(--fs-input);
  outline: none;
  resize: vertical;
}
.gar-caja input:focus,
.gar-caja textarea:focus { border-color: var(--color-accent); }
.gar-caja #gar-enviar { margin-top: 14px; }
.gar-esperando { padding: 30px 0; }

/* ── Los planes en la página de alta ──
   Tarjetas para elegir con cuál entrar, arriba del formulario. Se
   comportan como un grupo de radios: una sola queda marcada. */
.reg-planes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr));
  gap: 12px;
  margin: 0 0 26px;
}
.reg-planes-titulo {
  grid-column: 1 / -1;
  font-size: 15px;
  font-weight: 650;
  color: var(--vtx);
  margin: 0;
}
.reg-planes-bajada {
  grid-column: 1 / -1;
  font-size: 13px;
  color: var(--txt-sub);
  margin: -6px 0 2px;
}
.reg-plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
  padding: 16px;
  border-radius: var(--rl);
  border: 1px solid var(--vbor2);
  background: var(--vs);
  color: var(--vtx);
  cursor: pointer;
  font: inherit;
  transition: border-color 0.18s var(--ease-apple), background 0.18s var(--ease-apple);
}
.reg-plan:hover { border-color: var(--color-accent); }
.reg-plan:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
/* El elegido: borde y fondo, no solo un tilde. Con cuatro tarjetas
   iguales, un tilde chico en la esquina se pierde. */
.reg-plan.elegido {
  border-color: var(--color-accent);
  background: var(--vag2);
}
.reg-plan-cinta {
  /* La franja de color del plan, arriba de la tarjeta. */
  height: 4px;
  border-radius: 99px;
  width: 38px;
}
.reg-plan-nombre {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.reg-plan-precio {
  font-size: 21px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.reg-plan-precio small {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--txt-sub);
  letter-spacing: 0.02em;
}
.reg-plan-usuarios {
  font-size: 13px;
  color: var(--txt-sub);
  border-top: 1px solid var(--vbor);
  padding-top: 10px;
  margin-top: auto;
}
.reg-plan-tilde {
  position: absolute;
  top: 12px; right: 12px;
  width: 18px; height: 18px;
  border-radius: 50%;
  display: grid; place-items: center;
  background: var(--color-accent);
  color: #1d1d1d;
  font-size: 11px;
  font-weight: 800;
  opacity: 0;
  transition: opacity 0.18s var(--ease-apple);
}
.reg-plan.elegido .reg-plan-tilde { opacity: 1; }
