/* =========================================================
   Componentes de UI — Estándar Hermes
   -----------------------------------------------------------
   BOTONES NEUTROS:
     .btn-save, .btn-pdf, .btn-add, .btn-edit, .btn-archive,
     .btn-refresh, .btn-download, .btn-upload,
     .btn-open-tab, .btn-link, .btn-multi-select, .btn-history

   BOTONES DESTRUCTIVOS:
     .btn-cancel, .btn-delete

   BOTÓN DE IA: .btn-ai (+ .btn-ai--solido, .btn-ai--menu, .is-pensando)
     Tiene acento PROPIO — ver su bloque al final del fichero.

   BARRA DE BÚSQUEDA: .search-bar

   FORMULARIO:
     .form-field (wrapper común)
     .form-input, .form-textarea, .form-password,
     .form-date, .form-color, .form-switch

   Estilo: outline, sin fondo, color base var(--primary-dark)
   ========================================================= */

:root {
  /* --- Colores neutros (base var(--primary-dark)) --- */
  --btn-action-border:           var(--primary-dark);
  --btn-action-border-hover:     #2d3748;
  --btn-action-border-active:    #000000;
  --btn-action-border-disabled:  #a0aec0;

  --btn-action-icon:             var(--primary-dark);
  --btn-action-icon-hover:       #2d3748;
  --btn-action-icon-active:      #000000;
  --btn-action-icon-disabled:    #a0aec0;

  --btn-action-bg-hover:         rgba(26, 32, 44, 0.06);
  --btn-action-bg-active:        rgba(26, 32, 44, 0.12);
  --btn-action-focus-ring:       rgba(26, 32, 44, 0.30);

  /* --- Colores destructivos --- */
  --btn-danger-border:           #c53030;
  --btn-danger-border-hover:     #9b2c2c;
  --btn-danger-border-active:    #742a2a;
  --btn-danger-border-disabled:  #feb2b2;

  --btn-danger-icon:             #c53030;
  --btn-danger-icon-hover:       #9b2c2c;
  --btn-danger-icon-active:      #742a2a;
  --btn-danger-icon-disabled:    #feb2b2;

  --btn-danger-bg-hover:         rgba(197, 48, 48, 0.08);
  --btn-danger-bg-active:        rgba(197, 48, 48, 0.16);
  --btn-danger-focus-ring:       rgba(197, 48, 48, 0.30);

  /* --- Acento de IA (violeta) ---------------------------------------
     El violeta NO se usa para nada más en el ERP: identifica lo que
     consume inteligencia artificial, que es lo único que se le factura al
     cliente por uso. Un control que gasta dinero no puede parecerse a uno
     que no gasta — ver el bloque `.btn-ai` al final del fichero. */
  --btn-ia-border:            var(--violet);
  --btn-ia-ink:               #5b21b6;
  --btn-ia-bg:                #f1eafe;
  --btn-ia-bg-hover:          #e4d6fd;
  --btn-ia-on-solid:          #ffffff;
  --btn-ia-focus-ring:        rgba(124, 58, 237, 0.32);

  /* --- Estado activo / "seleccionado" (toggle) --- */
  --btn-action-bg-pressed:       rgba(26, 32, 44, 0.10);
  --btn-action-border-pressed:   var(--primary-dark);

  /* --- Tamaño y forma de botones --- */
  --btn-action-size:             36px;
  --btn-action-icon-size:        18px;
  --btn-action-radius:           8px;
  --btn-action-border-width:     1.5px;
  --btn-action-transition:       all 0.18s var(--ease);

  /* --- Tokens de la barra de búsqueda --- */
  --search-bar-height:           36px;
  --search-bar-min-width:        240px;
  --search-bar-padding-x:        12px;
  --search-bar-icon-size:        16px;
  --search-bar-gap:              8px;
  --search-bar-font-size:        14px;
  --search-bar-placeholder:      #718096;

  /* --- Tokens de formularios --- */
  --field-height:                36px;
  --field-padding-x:              12px;
  --field-font-size:              14px;
  --field-radius:                 8px;
  --field-border:                 1.5px;
  --field-label-color:            var(--primary-dark);
  --field-label-size:             13px;
  --field-label-weight:           600;
  --field-help-color:             var(--gray-500);
  --field-help-size:              12px;
  --field-error:                  #c53030;
  --field-gap:                    6px;

  /* --- Switch --- */
  --switch-width:                 40px;
  --switch-height:                22px;
  --switch-thumb:                 16px;
  --switch-bg-off:                #cbd5e0;
  --switch-bg-on:                 var(--primary-dark);
}

/* Táctil: 36px es cómodo con ratón y demasiado justo con el dedo (el mínimo
   de referencia es 44px). Se sube el TOKEN, no cada botón, así que vale para
   todos los `.btn-action`/`.btn-edit`/`.btn-delete`… del ERP a la vez; el
   icono se queda en su tamaño y lo que crece es el área pulsable. Solo en
   punteros gruesos: en escritorio la densidad de las barras no cambia. */
@media (pointer: coarse) {
  :root {
    --btn-action-size: 44px;
  }
}

/* ===========================================================
   MODO OSCURO — los botones outline y los campos usan
   var(--primary-dark) como tinta; en oscuro eso desaparece, así
   que aquí se reescriben a tintas claras/luminosas. Ver base.css.
   =========================================================== */
html[data-theme="dark"] {
  /* En oscuro la rampa del violeta se invierte: el acento se aclara para
     mantener el contraste sobre superficie oscura, y el texto que va ENCIMA
     del violeta pleno pasa a ser oscuro. */
  --btn-ia-border:            var(--violet);
  --btn-ia-ink:               #c4b5fd;
  --btn-ia-bg:                rgba(167, 139, 250, 0.14);
  --btn-ia-bg-hover:          rgba(167, 139, 250, 0.24);
  --btn-ia-on-solid:          #1a1229;
  --btn-ia-focus-ring:        rgba(167, 139, 250, 0.40);

  --btn-action-border:           var(--gray-500);
  --btn-action-border-hover:     var(--gray-400);
  --btn-action-border-active:    var(--gray-300);
  --btn-action-border-disabled:  var(--gray-700);

  --btn-action-icon:             var(--text-color);
  --btn-action-icon-hover:       var(--text-strong);
  --btn-action-icon-active:      var(--text-strong);
  --btn-action-icon-disabled:    var(--gray-600);

  --btn-action-bg-hover:         rgba(255, 255, 255, 0.06);
  --btn-action-bg-active:        rgba(255, 255, 255, 0.12);
  --btn-action-focus-ring:       rgba(91, 143, 214, 0.40);

  --btn-action-bg-pressed:       rgba(255, 255, 255, 0.10);
  --btn-action-border-pressed:   var(--gray-400);

  --btn-danger-border:           var(--danger);
  --btn-danger-border-hover:     #f87171;
  --btn-danger-border-active:    #fca5a5;
  --btn-danger-border-disabled:  #7f1d1d;
  --btn-danger-icon:             var(--danger);
  --btn-danger-icon-hover:       #f87171;
  --btn-danger-icon-active:      #fca5a5;
  --btn-danger-icon-disabled:    #7f1d1d;
  --btn-danger-bg-hover:         rgba(240, 82, 82, 0.12);
  --btn-danger-bg-active:        rgba(240, 82, 82, 0.20);
  --btn-danger-focus-ring:       rgba(240, 82, 82, 0.35);

  --search-bar-placeholder:      var(--gray-500);
  --field-label-color:           var(--text-strong);
  --field-error:                 var(--danger);
  --switch-bg-off:               var(--gray-300);
  --switch-bg-on:                var(--primary-light);
}

/* =========================================================
   BOTONES — Base compartida (geometría + comportamiento)
   ========================================================= */
.btn-action,
.btn-save,
.btn-pdf,
.btn-add,
.btn-edit,
.btn-archive,
.btn-refresh,
.btn-download,
.btn-open-tab,
.btn-upload,
.btn-link,
.btn-multi-select,
.btn-history,
.btn-cancel,
.btn-delete {
  display: inline-flex;
  align-items: center;
  justify-content: center;

  width: var(--btn-action-size);
  height: var(--btn-action-size);
  padding: 0;
  background-color: transparent;
  border-width: var(--btn-action-border-width);
  border-style: solid;
  border-radius: var(--btn-action-radius);

  cursor: pointer;
  user-select: none;
  transition: var(--btn-action-transition);
}

.btn-action .icon,
.btn-save .icon,
.btn-pdf .icon,
.btn-add .icon,
.btn-edit .icon,
.btn-archive .icon,
.btn-refresh .icon,
.btn-download .icon,
.btn-open-tab .icon,
.btn-upload .icon,
.btn-link .icon,
.btn-multi-select .icon,
.btn-history .icon,
.btn-cancel .icon,
.btn-delete .icon {
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
  flex-shrink: 0;
}

/* =========================================================
   BOTONES NEUTROS
   ========================================================= */
.btn-action,
.btn-save,
.btn-pdf,
.btn-add,
.btn-edit,
.btn-archive,
.btn-refresh,
.btn-download,
.btn-open-tab,
.btn-upload,
.btn-link,
.btn-multi-select,
.btn-history {
  border-color: var(--btn-action-border);
  color: var(--btn-action-icon);
}

.btn-action:hover,
.btn-save:hover,
.btn-pdf:hover,
.btn-add:hover,
.btn-edit:hover,
.btn-archive:hover,
.btn-refresh:hover,
.btn-download:hover,
.btn-open-tab:hover,
.btn-upload:hover,
.btn-link:hover,
.btn-multi-select:hover,
.btn-history:hover {
  background-color: var(--btn-action-bg-hover);
  border-color: var(--btn-action-border-hover);
  color: var(--btn-action-icon-hover);
}

.btn-action:active,
.btn-save:active,
.btn-pdf:active,
.btn-add:active,
.btn-edit:active,
.btn-archive:active,
.btn-refresh:active,
.btn-download:active,
.btn-open-tab:active,
.btn-upload:active,
.btn-link:active,
.btn-multi-select:active,
.btn-history:active {
  background-color: var(--btn-action-bg-active);
  border-color: var(--btn-action-border-active);
  color: var(--btn-action-icon-active);
}

.btn-action:focus-visible,
.btn-save:focus-visible,
.btn-pdf:focus-visible,
.btn-add:focus-visible,
.btn-edit:focus-visible,
.btn-archive:focus-visible,
.btn-refresh:focus-visible,
.btn-download:focus-visible,
.btn-open-tab:focus-visible,
.btn-upload:focus-visible,
.btn-link:focus-visible,
.btn-multi-select:focus-visible,
.btn-history:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

.btn-action:disabled, .btn-action[aria-disabled="true"],
.btn-save:disabled, .btn-save[aria-disabled="true"],
.btn-pdf:disabled, .btn-pdf[aria-disabled="true"],
.btn-add:disabled, .btn-add[aria-disabled="true"],
.btn-edit:disabled, .btn-edit[aria-disabled="true"],
.btn-archive:disabled, .btn-archive[aria-disabled="true"],
.btn-refresh:disabled, .btn-refresh[aria-disabled="true"],
.btn-download:disabled, .btn-download[aria-disabled="true"],
.btn-open-tab:disabled, .btn-open-tab[aria-disabled="true"],
.btn-upload:disabled, .btn-upload[aria-disabled="true"],

.btn-link:disabled, .btn-link[aria-disabled="true"],
.btn-multi-select:disabled, .btn-multi-select[aria-disabled="true"],
.btn-history:disabled, .btn-history[aria-disabled="true"] {
  border-color: var(--btn-action-border-disabled);
  color: var(--btn-action-icon-disabled);
  background-color: transparent;
  cursor: not-allowed;
}

/* Toggle "seleccionado" — para botones que activan un modo
   (ej. .btn-multi-select, .btn-history). Marcar con
   aria-pressed="true" o clase .is-active. */
.btn-action[aria-pressed="true"], .btn-action.is-active,
.btn-multi-select[aria-pressed="true"], .btn-multi-select.is-active,
.btn-history[aria-pressed="true"], .btn-history.is-active,
.btn-link[aria-pressed="true"], .btn-link.is-active {
  background-color: var(--btn-action-bg-pressed);
  border-color: var(--btn-action-border-pressed);
  color: var(--btn-action-icon-active);
}

/* =========================================================
   BOTONES DESTRUCTIVOS — .btn-cancel, .btn-delete
   ========================================================= */
.btn-cancel,
.btn-delete {
  border-color: var(--btn-danger-border);
  color: var(--btn-danger-icon);
}

.btn-cancel:hover,
.btn-delete:hover {
  background-color: var(--btn-danger-bg-hover);
  border-color: var(--btn-danger-border-hover);
  color: var(--btn-danger-icon-hover);
}

.btn-cancel:active,
.btn-delete:active {
  background-color: var(--btn-danger-bg-active);
  border-color: var(--btn-danger-border-active);
  color: var(--btn-danger-icon-active);
}

.btn-cancel:focus-visible,
.btn-delete:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--btn-danger-focus-ring);
}

.btn-cancel:disabled, .btn-cancel[aria-disabled="true"],
.btn-delete:disabled, .btn-delete[aria-disabled="true"] {
  border-color: var(--btn-danger-border-disabled);
  color: var(--btn-danger-icon-disabled);
  background-color: transparent;
  cursor: not-allowed;
}

/* =========================================================
   LOADING (botones)
   ========================================================= */
.btn-action.is-loading,
.btn-save.is-loading,
.btn-pdf.is-loading,
.btn-add.is-loading,
.btn-edit.is-loading,
.btn-archive.is-loading,
.btn-refresh.is-loading,
.btn-download.is-loading,
.btn-open-tab.is-loading,
.btn-upload.is-loading,
.btn-link.is-loading,
.btn-multi-select.is-loading,
.btn-history.is-loading,
.btn-cancel.is-loading,
.btn-delete.is-loading {
  cursor: wait;
  pointer-events: none;
  position: relative;
}

.btn-action.is-loading .icon,
.btn-save.is-loading .icon,
.btn-pdf.is-loading .icon,
.btn-add.is-loading .icon,
.btn-edit.is-loading .icon,
.btn-archive.is-loading .icon,
.btn-refresh.is-loading .icon,
.btn-download.is-loading .icon,
.btn-open-tab.is-loading .icon,
.btn-upload.is-loading .icon,
.btn-link.is-loading .icon,
.btn-multi-select.is-loading .icon,
.btn-history.is-loading .icon,
.btn-cancel.is-loading .icon,
.btn-delete.is-loading .icon {
  visibility: hidden;
}

.btn-action.is-loading::after,
.btn-save.is-loading::after,
.btn-pdf.is-loading::after,
.btn-add.is-loading::after,
.btn-edit.is-loading::after,
.btn-archive.is-loading::after,
.btn-refresh.is-loading::after,
.btn-download.is-loading::after,
.btn-open-tab.is-loading::after,
.btn-upload.is-loading::after,
.btn-link.is-loading::after,
.btn-multi-select.is-loading::after,
.btn-history.is-loading::after {
  content: "";
  position: absolute;
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
  border: 2px solid rgba(26, 32, 44, 0.20);
  border-top-color: var(--btn-action-icon);
  border-radius: 50%;
  animation: btn-action-spin 0.6s linear infinite;
}

.btn-cancel.is-loading::after,
.btn-delete.is-loading::after {
  content: "";
  position: absolute;
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
  border: 2px solid rgba(197, 48, 48, 0.20);
  border-top-color: var(--btn-danger-icon);
  border-radius: 50%;
  animation: btn-action-spin 0.6s linear infinite;
}

@keyframes btn-action-spin {
  to { transform: rotate(360deg); }
}

/* =========================================================
   BARRA DE BÚSQUEDA — .search-bar
   ========================================================= */
.search-bar {
  display: inline-flex;
  align-items: center;
  gap: var(--search-bar-gap);

  height: var(--search-bar-height);
  min-width: var(--search-bar-min-width);
  padding: 0 var(--search-bar-padding-x);

  background-color: transparent;
  border: var(--btn-action-border-width) solid var(--btn-action-border);
  border-radius: var(--btn-action-radius);

  color: var(--btn-action-icon);
  transition: var(--btn-action-transition);
}

.search-bar:hover { border-color: var(--btn-action-border-hover); }

.search-bar:focus-within {
  border-color: var(--btn-action-border-active);
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

.search-bar__icon {
  width: var(--search-bar-icon-size);
  height: var(--search-bar-icon-size);
  flex-shrink: 0;
  color: var(--btn-action-icon);
}

.search-bar__input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--btn-action-icon);
  font: inherit;
  font-size: var(--search-bar-font-size);
  line-height: 1.2;
}

.search-bar__input::placeholder { color: var(--search-bar-placeholder); }

.search-bar__input::-webkit-search-decoration,
.search-bar__input::-webkit-search-cancel-button,
.search-bar__input::-webkit-search-results-button,
.search-bar__input::-webkit-search-results-decoration {
  -webkit-appearance: none;
  appearance: none;
}

.search-bar__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: 4px;
  color: var(--btn-action-icon);
  cursor: pointer;
  transition: var(--btn-action-transition);
  flex-shrink: 0;
}
.search-bar__clear:hover { background-color: var(--btn-action-bg-hover); }
.search-bar__clear[hidden] { display: none; }

/* El display:inline-flex de los botones del estándar gana al [hidden] del
   navegador: todo botón del estándar que se muestre/oculte por JS con
   `el.hidden` necesita esta reafirmación (precedente: .search-bar__clear).
   Se declara para TODA la familia y no botón a botón: la reafirmación se venía
   añadiendo cuando alguien tropezaba con ella (primero .btn-ai, después el
   footer por pasos del modal de IA de WordPress, donde cinco botones aparecían
   a la vez), y hasta entonces lo único que lo tapaba era el
   `[hidden]{display:none!important}` del reboot de Bootstrap — que llega por
   CDN y no debería ser lo que sostiene la interfaz. */
.btn-action[hidden], .btn-save[hidden], .btn-pdf[hidden], .btn-add[hidden],
.btn-edit[hidden], .btn-archive[hidden], .btn-refresh[hidden],
.btn-download[hidden], .btn-open-tab[hidden], .btn-upload[hidden],
.btn-ai[hidden], .btn-link[hidden], .btn-multi-select[hidden],
.btn-history[hidden], .btn-cancel[hidden], .btn-delete[hidden] { display: none; }
.search-bar__clear .icon { width: 14px; height: 14px; }

.search-bar.is-disabled,
.search-bar[aria-disabled="true"] {
  border-color: var(--btn-action-border-disabled);
  color: var(--btn-action-icon-disabled);
  cursor: not-allowed;
}
.search-bar.is-disabled .search-bar__input {
  cursor: not-allowed;
  color: var(--btn-action-icon-disabled);
}

/* ---------------------------------------------------------------
   Variante COLAPSABLE — .search-bar--collapsible
   Arranca como botón cuadrado (solo la lupa) y se despliega a la
   derecha al pulsar. core.js (initSwapSearchBars) alterna .is-expanded.
   --------------------------------------------------------------- */
.search-bar--collapsible {
  min-width: 0;
  width: var(--search-bar-height);          /* cuadrado 36×36 colapsado */
  padding: 0;
  gap: 0;
  overflow: hidden;
  transition: width 0.25s var(--ease), padding 0.25s var(--ease), gap 0.25s var(--ease);
}
.search-bar--collapsible.is-expanded {
  width: var(--search-bar-expanded-width, var(--search-bar-min-width));
  padding-right: var(--search-bar-padding-x);
  gap: var(--search-bar-gap);
}

/* La lupa pasa a ser un botón: ancla el control en estado colapsado. */
.search-bar__toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--btn-action-icon);
  cursor: pointer;
}
.search-bar__toggle .search-bar__icon { color: inherit; }
/* Colapsado: el toggle llena la caja → lupa centrada en el cuadrado 36×36. */
.search-bar--collapsible .search-bar__toggle { flex: 1 1 auto; width: auto; }
/* Expandido: cuadrado fijo a la izquierda, el input ocupa el resto. */
.search-bar--collapsible.is-expanded .search-bar__toggle {
  flex: 0 0 auto;
  width: var(--search-bar-height);
}

/* Input oculto (ancho 0) mientras está colapsado; fluido al expandir. */
.search-bar--collapsible .search-bar__input {
  width: 0;
  flex: 0 0 auto;
  opacity: 0;
  transition: opacity 0.18s var(--ease);
}
.search-bar--collapsible.is-expanded .search-bar__input {
  width: auto;
  flex: 1 1 auto;
  opacity: 1;
}

/* =========================================================
   FORMULARIO — .form-field (wrapper común)
   -----------------------------------------------------------
   Estructura HTML esperada:
     <div class="form-field">
       <label class="form-field__label" for="...">Etiqueta</label>
       <div class="form-field__control">
         <!-- input / textarea / etc. -->
       </div>
       <span class="form-field__help">Texto de ayuda</span>
       <span class="form-field__error" hidden>Mensaje de error</span>
     </div>
   ========================================================= */
.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--field-gap);
  /* Sin ancho mínimo propio: lo decide el contenedor. Con `min-width: 240px`
     dos campos en una rejilla de dos columnas pedían ~500px, y en cualquier
     modal más estrecho (modal-sm, tablet, el `.form-modal-grid` de 2 columnas
     por encima de 600px de VIEWPORT pero dentro de un diálogo de 500px) los
     campos se salían de su celda y se MONTABAN uno sobre otro. Se arregló
     siete veces en local (viajes, embudo, llavero, contratos, wordpress,
     documentación, admin) antes de arreglarlo aquí. Los campos sueltos en una
     barra de flex-wrap fijan su ancho con `flex-basis`, no con este mínimo. */
  min-width: 0;
}

.form-field__label {
  font-size: var(--field-label-size);
  font-weight: var(--field-label-weight);
  color: var(--field-label-color);
}

.form-field__label .required {
  color: var(--field-error);
  margin-left: 2px;
}

.form-field__control {
  position: relative;
  display: flex;
  align-items: center;
}

.form-field__help {
  font-size: var(--field-help-size);
  color: var(--field-help-color);
  line-height: 1.4;
}

.form-field__error {
  font-size: var(--field-help-size);
  color: var(--field-error);
  line-height: 1.4;
}

.form-field__error[hidden] { display: none; }

/* Estado de error: aplicar .has-error al .form-field */
.form-field.has-error .form-input,
.form-field.has-error .form-textarea,
.form-field.has-error .form-password,
.form-field.has-error .form-date,
.form-field.has-error .form-color {
  border-color: var(--field-error);
}
.form-field.has-error .form-input:focus,
.form-field.has-error .form-textarea:focus,
.form-field.has-error .form-password:focus,
.form-field.has-error .form-date:focus,
.form-field.has-error .form-color:focus {
  box-shadow: 0 0 0 3px rgba(197, 48, 48, 0.25);
}

/* =========================================================
   INPUT DE TEXTO — .form-input
   ========================================================= */
.form-input,
.form-textarea,
.form-password,
.form-date {
  width: 100%;
  /* Un input tiene tamaño intrínseco (~20 caracteres): como hijo de flex o
     grid, `min-width: auto` le deja desbordar la celda aunque diga width 100%. */
  min-width: 0;
  height: var(--field-height);
  padding: 0 var(--field-padding-x);

  background-color: transparent;
  border: var(--field-border) solid var(--btn-action-border);
  border-radius: var(--field-radius);

  color: var(--btn-action-icon);
  font: inherit;
  font-size: var(--field-font-size);
  line-height: 1.2;

  transition: var(--btn-action-transition);
  outline: none;
}

.form-input::placeholder,
.form-textarea::placeholder,
.form-password::placeholder,
.form-date::placeholder {
  color: var(--search-bar-placeholder);
}

.form-input:hover,
.form-textarea:hover,
.form-password:hover,
.form-date:hover {
  border-color: var(--btn-action-border-hover);
}

.form-input:focus,
.form-textarea:focus,
.form-password:focus,
.form-date:focus {
  border-color: var(--btn-action-border-active);
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

.form-input:disabled,
.form-textarea:disabled,
.form-password:disabled,
.form-date:disabled {
  border-color: var(--btn-action-border-disabled);
  color: var(--btn-action-icon-disabled);
  cursor: not-allowed;
}

/* ---------------------------------------------------------
   CAMPO DERIVADO / SOLO LECTURA — .form-input--readonly
   -----------------------------------------------------------
   Para campos `readonly` cuyo valor lo CALCULA el sistema (la
   administración competente se deduce de la comunidad autónoma, el
   próximo número de factura del correlativo, el NIF de TicketBAI de la
   ficha de empresa). Tiene que verse ANTES de intentar escribir en él,
   y sin convertirlo en `disabled`: `disabled` lo saca del tabulador,
   del lector de pantalla y del envío del formulario. `readonly` ya
   anuncia «solo lectura» por sí mismo, así que aquí no hace falta ARIA
   añadido; lo que faltaba era la señal VISUAL.

   No se estiliza `input[readonly]` a secas a propósito: el datepicker
   del proyecto marca readonly TODOS sus campos (`ppdate-dom._decorate`),
   así que un selector por atributo teñiría de gris todas las fechas del
   ERP, que sí se editan (por el calendario).

   Las tres señales, y por qué cada una funciona en los DOS temas:
   · Fondo: el campo se HUNDE respecto a la tarjeta, nunca se ilumina.
     «Gris» en tema claro es oscurecer un poco el blanco (`--gray-100`);
     hacer eso mismo en oscuro dejaría el campo MÁS luminoso que la
     tarjeta, o sea llamando la atención justo el que no hay que tocar.
     Por eso el valor va en una variable local que cada tema resuelve con
     SU token: `--gray-100` en claro y `--surface-sunken` en oscuro. Los
     dos son tokens; no hay ningún color escrito a mano.
   · Borde `--border-color` en vez del casi negro `--btn-action-border`:
     el campo pierde su marco de control y se lee como dato mostrado.
   · Texto un escalón por debajo del editable (`--gray-700`, que se
     invierte solo). El texto NO se apaga más que eso a propósito: un
     campo derivado sigue siendo un dato que hay que poder leer, y AA
     exige 4,5:1 también aquí. Medido sobre su fondo: 9,5:1 en claro y
     15:1 en oscuro. `--text-muted` se queda en 4,3-4,5:1 en claro, al
     filo del mínimo, por eso no se usa.

   El foco SÍ se sigue viendo (el campo es tabulable y su valor se puede
   seleccionar y copiar); lo que se retira es la reacción al hover, que
   prometía edición. */
.form-input--readonly,
.form-textarea--readonly,
.form-date--readonly {
  --field-readonly-bg: var(--gray-100);
  background-color: var(--field-readonly-bg);
  border-color: var(--border-color);
  color: var(--gray-700);
  cursor: default;
}

html[data-theme="dark"] .form-input--readonly,
html[data-theme="dark"] .form-textarea--readonly,
html[data-theme="dark"] .form-date--readonly {
  /* En oscuro el hundido de verdad es el fondo de página, más OSCURO que
     la tarjeta; `--gray-100` allí queda por encima de la superficie. */
  --field-readonly-bg: var(--surface-sunken);
}

.form-input--readonly::placeholder,
.form-textarea--readonly::placeholder,
.form-date--readonly::placeholder {
  /* Un escalón por debajo del valor para que «vacío» no se confunda con
     «relleno», y aun así legible: el placeholder de estos campos EXPLICA
     por qué están vacíos («Territorio común — no se indica…»).
     6,9:1 en claro, 12,6:1 en oscuro. */
  color: var(--gray-600);
}

.form-input--readonly:hover,
.form-textarea--readonly:hover,
.form-date--readonly:hover {
  border-color: var(--border-color);
}

.form-input--readonly:focus,
.form-textarea--readonly:focus,
.form-date--readonly:focus {
  border-color: var(--gray-500);
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

/* =========================================================
   TEXTAREA — .form-textarea
   ========================================================= */
.form-textarea {
  height: auto;
  min-height: calc(var(--field-height) * 2.5);
  padding-top: 10px;
  padding-bottom: 10px;
  resize: vertical;
  font-family: inherit;
  line-height: 1.5;
}

/* =========================================================
   CAMPO CONTRASEÑA — .form-password (input + toggle ojo)
   -----------------------------------------------------------
   Estructura:
     <div class="form-password-wrapper">
       <input type="password" class="form-password">
       <button type="button" class="form-password__toggle">...</button>
     </div>
   ========================================================= */
.form-password-wrapper {
  position: relative;
  width: 100%;
}

.form-password-wrapper .form-password {
  padding-right: 40px;   /* deja espacio para el botón ojo */
}

.form-password__toggle {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: transparent;
  border-radius: 4px;
  color: var(--btn-action-icon);
  cursor: pointer;
  transition: var(--btn-action-transition);
}

/* Área táctil del ojo. Mide 28px, que con el dedo no se acierta — y es el único
   control DENTRO del input, que además solo aparece cuando hay algo escrito, o
   sea justo cuando hace falta darle. El cuadrado de 44px lo pone un
   pseudo-elemento para no deformar un campo de 36px de alto. */
@media (pointer: coarse) {
    .form-password__toggle { width: 40px; height: 40px; }
    .form-password-wrapper .form-password { padding-right: 46px; }
    .form-password__toggle::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 44px;
        height: 44px;
    }
}

.form-password__toggle:hover { background-color: var(--btn-action-bg-hover); }

.form-password__toggle .icon { width: 16px; height: 16px; }

/* =========================================================
   DATEPICKER — .form-date (usa input[type=date] nativo)
   ========================================================= */
.form-date {
  /* Hereda todos los estilos de .form-input.
     El picker nativo se renderiza por el navegador. */
  font-variant-numeric: tabular-nums;
}

/* Icono de calendario nativo (Webkit): teñirlo para coherencia */
.form-date::-webkit-calendar-picker-indicator {
  cursor: pointer;
  opacity: 0.7;
  filter: invert(11%) sepia(15%) saturate(800%) hue-rotate(178deg) brightness(95%) contrast(90%);
}
.form-date:hover::-webkit-calendar-picker-indicator { opacity: 1; }

/* =========================================================
   COLORPICKER — .form-color
   -----------------------------------------------------------
   Estructura:
     <div class="form-color-wrapper">
       <input type="color" class="form-color" value="var(--primary-dark)">
       <span class="form-color__hex">var(--primary-dark)</span>
     </div>
   ========================================================= */
.form-color-wrapper {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: var(--field-height);
  padding: 0 var(--field-padding-x);
  border: var(--field-border) solid var(--btn-action-border);
  border-radius: var(--field-radius);
  background: transparent;
  transition: var(--btn-action-transition);
}

.form-color-wrapper:hover { border-color: var(--btn-action-border-hover); }
.form-color-wrapper:focus-within {
  border-color: var(--btn-action-border-active);
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

.form-color {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--btn-action-border);
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  flex-shrink: 0;
}

/* Quitar el cuadro feo nativo en webkit */
.form-color::-webkit-color-swatch-wrapper { padding: 0; }
.form-color::-webkit-color-swatch { border: none; border-radius: 3px; }
.form-color::-moz-color-swatch    { border: none; border-radius: 3px; }

.form-color__hex {
  font-family: 'Inter', system-ui, monospace;
  font-size: var(--field-font-size);
  color: var(--btn-action-icon);
  font-variant-numeric: tabular-nums;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Variante editable: el hex como <input> se integra en el wrapper sin
   borde ni fondo propios (el foco lo dibuja .form-color-wrapper:focus-within). */
input.form-color__hex {
  width: 8ch;
  padding: 0;
  border: none;
  background: transparent;
  outline: none;
  cursor: text;
}
input.form-color__hex::placeholder { color: var(--btn-action-icon-disabled); }

.form-color-wrapper.is-disabled,
.form-color-wrapper[aria-disabled="true"] {
  border-color: var(--btn-action-border-disabled);
  cursor: not-allowed;
}
.form-color-wrapper.is-disabled .form-color { cursor: not-allowed; opacity: 0.5; }
.form-color-wrapper.is-disabled .form-color__hex { color: var(--btn-action-icon-disabled); }

/* =========================================================
   SWITCH — .form-switch
   -----------------------------------------------------------
   Estructura:
     <label class="form-switch">
       <input type="checkbox" class="form-switch__input">
       <span class="form-switch__track"></span>
       <span class="form-switch__label">Texto</span>
     </label>
   ========================================================= */
.form-switch {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
  font-size: var(--field-font-size);
  color: var(--field-label-color);
}
/* En táctil, toda la etiqueta es zona de toque de 44px (M5): el track solo
   mide ~20px y era lo único pulsable a esa altura en todo el ERP. */
@media (pointer: coarse) {
  .form-switch { min-height: 44px; }
}

.form-switch__input {
  /* Ocultar visualmente, mantener accesibilidad */
  position: absolute;
  opacity: 0;
  pointer-events: none;
  width: 0;
  height: 0;
}

.form-switch__track {
  position: relative;
  width: var(--switch-width);
  height: var(--switch-height);
  background-color: var(--switch-bg-off);
  border-radius: 999px;
  transition: var(--btn-action-transition);
  flex-shrink: 0;
}

.form-switch__track::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  transform: translateY(-50%);
  width: var(--switch-thumb);
  height: var(--switch-thumb);
  background-color: var(--surface);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  transition: var(--btn-action-transition);
}

.form-switch__input:checked + .form-switch__track {
  background-color: var(--switch-bg-on);
}

.form-switch__input:checked + .form-switch__track::after {
  /* width − thumb − margen lateral × 2 */
  left: calc(var(--switch-width) - var(--switch-thumb) - 3px);
}

.form-switch__input:focus-visible + .form-switch__track {
  box-shadow: 0 0 0 3px var(--btn-action-focus-ring);
}

.form-switch__input:disabled + .form-switch__track {
  opacity: 0.5;
  cursor: not-allowed;
}
.form-switch:has(.form-switch__input:disabled) {
  cursor: not-allowed;
  color: var(--btn-action-icon-disabled);
}

.form-switch__label { line-height: 1.2; }

/* =========================================================
   BOTÓN DE APROBACIÓN — check verde (TicketBAI: aprobar/emitir)
   Mismo tamaño/forma que el resto de acciones; tinta verde para
   destacar la confirmación positiva. .btn-approve--retry vira a
   ámbar cuando el envío previo falló (reintento).
   ========================================================= */
.btn-approve {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-action-size);
  height: var(--btn-action-size);
  padding: 0;
  background-color: transparent;
  border-width: var(--btn-action-border-width);
  border-style: solid;
  border-radius: var(--btn-action-radius);
  border-color: var(--success);
  color: var(--success);
  cursor: pointer;
  user-select: none;
  transition: var(--btn-action-transition);
}
.btn-approve .icon {
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
  flex-shrink: 0;
}
.btn-approve:hover {
  background-color: rgba(22, 163, 74, 0.10);
  border-color: var(--success);
  color: var(--success);
}
.btn-approve:active { background-color: rgba(22, 163, 74, 0.18); }
.btn-approve:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.30);
}
.btn-approve:disabled, .btn-approve[aria-disabled="true"] {
  border-color: var(--btn-action-border-disabled);
  color: var(--btn-action-icon-disabled);
  cursor: not-allowed;
}
.btn-approve--retry { border-color: #d97706; color: #d97706; }
.btn-approve--retry:hover { background-color: rgba(217, 119, 6, 0.10); }
/* En oscuro, el ámbar oscuro se pierde sobre fondo negro: tinta clara. */
html[data-theme="dark"] .btn-approve--retry { border-color: #fbbf24; color: #fbbf24; }
html[data-theme="dark"] .btn-approve--retry:hover { background-color: rgba(251, 191, 36, 0.14); }

/* Sello "emitida en TicketBAI": indicador no interactivo, mismo gálibo. */
.tbai-emitida {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-action-size);
  height: var(--btn-action-size);
  color: var(--success);
}
.tbai-emitida .icon {
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
}
/* Comunicándose a Hacienda (TicketBAI/Veri*Factu) ahora mismo: ni emitida ni
   borrador. Mismo azul que el sello propio: es un estado, no un aviso. */
.tbai-enviando { color: var(--info); }
/* Emitida en el entorno de PRUEBAS: no es una factura real (ámbar, no verde). */
.tbai-emitida--test {
  color: var(--warning);
}
/* Expedida y sellada en el registro de facturación PROPIO (sin TicketBAI).
   Comparte gálibo con el sello de arriba pero no su verde: aquí no hay nada
   comunicado a Hacienda, solo un documento que ya no se puede cambiar, y dos
   candados del mismo color harían pensar que la factura está presentada.
   Tampoco gris: en tema oscuro `--text-muted` (#94a3b8) es MÁS apagado que el
   gris de un botón deshabilitado (--gray-600, #cbd5e1), así que el sello se leía
   como «botón apagado» en vez de como estado. `--info` mantiene la distinción
   frente al verde sin caer por debajo de lo inactivo. */
.emitida-sellada {
  color: var(--info);
}

/* Contenedor de acciones de fila (editar/borrar/…): promovido a global al
 * tercer uso (contratos, productos, generador de contratos). */
.btn-row-actions {
    display: inline-flex;
    gap: .35rem;
    justify-content: flex-end;
}

/* Hueco de un botón de fila que NO aplica en esa fila (p. ej. borrar en la
 * empresa por defecto): reserva el mismo ancho para que los botones homólogos
 * de todas las filas queden alineados en columna. */
.btn-slot-empty {
    display: inline-flex;
    width: var(--btn-action-size);
    height: var(--btn-action-size);
    visibility: hidden;
}


/* =========================================================
   BOTÓN DE IA — .btn-ai
   ---------------------------------------------------------
   ÚNICA puerta visual para toda acción que consuma inteligencia
   artificial. Si el clic dispara una llamada que se sella en
   `ai_usage_log`, el botón lleva esta clase; si no la dispara, NO la
   lleva aunque el módulo se llame «IA».

   Por qué tiene acento propio y no es un botón neutro más (lo era hasta
   la 2.7.0): el ingreso principal de la plataforma es el margen sobre el
   consumo de IA, así que cada uno de estos botones GASTA DINERO del
   cliente y cuenta contra su tope mensual. Mientras compartió color con
   «guardar» y «descargar», nada distinguía el control que cobra del que
   no cobra — en Dossiers, «Generar productos con IA» y «Añadir producto»
   eran el mismo botón verde en la misma fila.

   El violeta no se usa para nada más en el ERP. El módulo de Viajes ya lo
   había intuido con su orbe de espera; aquí pasa a ser regla.

   Tres formas, un solo componente:
     .btn-ai                 icono solo (barra de acciones)
     .btn-ai + rótulo        icono + texto (formulario, modal)
     .btn-ai--solido         relleno, para la acción principal de un modal
     .btn-ai--menu           fila de un menú «Acciones»

   El icono es SIEMPRE el destello triple (`UI_ICONS.IA`), a
   `currentColor`. Un módulo no tiene icono de IA propio, igual que no
   tiene color de marca propio.
   ========================================================= */

.btn-ai {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;

  height: var(--btn-action-size);
  min-width: var(--btn-action-size);
  /* Sin rótulo queda cuadrado; con rótulo, el padding lo ensancha. El
     `:has()` no hace falta: un botón icon-only no tiene nodo de texto y
     el `min-width` ya lo cuadra. */
  padding: 0 0.75rem;

  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1;

  color: var(--btn-ia-ink);
  background-color: var(--btn-ia-bg);
  border: var(--btn-action-border-width) solid var(--btn-ia-border);
  border-radius: var(--btn-action-radius);

  cursor: pointer;
  user-select: none;
  position: relative;
  transition: var(--btn-action-transition);
}

/* Icono solo: sin padding lateral, para que el gálibo case exactamente con
   el de los demás botones de una barra de acciones. */
.btn-ai:not(:has(span)):not(:has(.btn-ai__txt)) { padding: 0; }
.btn-ai--icono { padding: 0; }

.btn-ai .icon {
  width: var(--btn-action-icon-size);
  height: var(--btn-action-icon-size);
  flex: 0 0 auto;
}

.btn-ai:hover {
  background-color: var(--btn-ia-bg-hover);
  border-color: var(--btn-ia-border);
}

.btn-ai:active { transform: translateY(1px); }

.btn-ai:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--btn-ia-focus-ring);
}

.btn-ai:disabled,
.btn-ai[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  color: var(--btn-action-icon-disabled);
  background-color: transparent;
  border-color: var(--btn-action-border-disabled);
  transform: none;
}

/* --- Variante SÓLIDA: acción principal de un modal --------------- */
.btn-ai--solido {
  color: var(--btn-ia-on-solid);
  background-color: var(--violet);
  border-color: var(--violet);
}
.btn-ai--solido:hover {
  background-color: color-mix(in srgb, var(--violet) 82%, black);
  border-color: color-mix(in srgb, var(--violet) 82%, black);
  color: var(--btn-ia-on-solid);
}
html[data-theme="dark"] .btn-ai--solido:hover {
  background-color: color-mix(in srgb, var(--violet) 82%, white);
  border-color: color-mix(in srgb, var(--violet) 82%, white);
}

/* --- Variante MENÚ: fila de un desplegable «Acciones» ------------- */
/* --- Variante MENÚ: fila de un desplegable «Acciones» -------------
   Aquí el acento va SOLO en el icono, y el resto se comporta como
   cualquier otro ítem de la lista: mismo color de texto, mismo peso,
   mismo hover, mismo alto.

   La primera versión traía el texto morado, en negrita y con fondo
   morado al pasar el ratón, y quedaba mal: un menú es una LISTA, y un
   ítem con color y peso propios se sale de ella — parece un aviso, no
   una opción más. El destello violeta ya dice que esa opción consume IA;
   pintar además la fila era decirlo dos veces y a gritos.

   El contraste con la variante de barra es deliberado: allí el botón
   compite con otros botones y necesita distinguirse; aquí convive con
   opciones hermanas y necesita alinearse. */
.btn-ai--menu {
  width: 100%;
  height: auto;
  min-height: 40px;
  justify-content: flex-start;
  gap: 0.55rem;
  padding: 0.45rem 0.65rem;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 400;
  line-height: 1.3;
  text-align: left;
  color: var(--text-color);
  background-color: transparent;
  border: 0;
  border-radius: 6px;
}
.btn-ai--menu:hover,
.btn-ai--menu:focus-visible {
  background-color: color-mix(in srgb, var(--primary) 10%, transparent);
  color: var(--text-strong);
  border-color: transparent;
  outline: none;
}
.btn-ai--menu:active { transform: none; }

/* =========================================================
   EL SÍMBOLO — .icon-ia
   ---------------------------------------------------------
   El destello es del MISMO violeta esté donde esté: en un botón de una
   barra, en una fila de menú, en el título de un modal o suelto en una
   tarjeta. Es la marca de la IA en el ERP, y una marca que cambia de
   color según el sitio deja de reconocerse de un vistazo — que es lo
   único que tiene que hacer.

   La clase la pone ya el catálogo de iconos (`UI_ICONS.IA` → `sparkles`
   en core.js), así que un icono pintado por la vía normal sale con el
   acento sin que nadie se acuerde. Los SVG escritos a mano en plantillas
   la llevan explícita.

   Los selectores de refuerzo no son redundancia: el ítem de menú y otros
   contenedores tiñen `.icon` en `:hover` con más peso, y sin ellos el
   destello perdía su color justo cuando se está mirando.
   ========================================================= */
.icon-ia,
.btn-ai:hover .icon-ia,
.actions-menu__item:hover .icon-ia,
.actions-menu__item:focus-visible .icon-ia { color: var(--violet); }

/* Excepciones, y son las mismas dos veces: sobre un fondo de COLOR PLENO el
   símbolo se pintaría de su propio fondo, o de un violeta que sobre azul no se
   lee. Ahí toma el color del contenido de ese fondo y el destello se reconoce
   por su forma, que es lo que hace de marca cuando el color no puede.
   · `.btn-ai--solido`: violeta sobre violeta.
   · `.mnu-mod-ic`: el cuadro del módulo en el menú principal y en el panel de
     inicio (misma macro, `launcher.j2`). Ahí TODOS los módulos llevan su icono
     del color de su área, y el de Contratos IA salía en violeta: el ítem parecía
     de otra clase. El cuadro es ya la marca del módulo; el destello no lo tiñe.
   · La cabecera de los modales, degradado de marca. Hoy sale blanco de rebote
     —`.form-modal-header .modal-title > svg` (components.css) gana por
     especificidad—, pero SOLO mientras el SVG sea hijo directo del título:
     envolverlo (`{{ i.icon('IA') }}` lo mete en un `span`) devolvería el
     violeta sobre el azul en las once cabeceras que lo llevan. Escrito aquí,
     deja de depender de cómo esté anidado. */
.btn-ai--solido .icon-ia,
.btn-ai--solido:hover .icon-ia,
.mnu-mod-ic .icon-ia,
.form-modal-header .modal-title .icon-ia { color: currentColor; }

/* =========================================================
   ESPERA DE IA — .is-pensando
   ---------------------------------------------------------
   El botón NO cambia de icono al procesar: el destello que se pulsó se
   queda y se pone a trabajar —respira y enciende sus puntas por turnos—.
   Es lo que lo separa del aro gris de `.is-loading`, que significa
   «esperando al disco» y dura un instante; esta espera es «esperando al
   modelo» y puede durar un minuto largo.

   El icono debe traer las clases `.s1` / `.s2` / `.s3` en sus trazos
   (las pone `UI_ICONS.IA`); sin ellas el botón sigue latiendo entero, que
   es una degradación aceptable.
   ========================================================= */

/* `.is-loading` sobre un botón de IA vale como `.is-pensando`. No es un alias
   por comodidad: es lo que hace que el estándar entre en vigor sin perseguir
   cada `classList.add('is-loading')` repartido por doce ficheros de JS — y sin
   que quede uno olvidado enseñando el aro de guardar. El nombre canónico para
   código nuevo es `.is-pensando`, que dice de qué espera se trata. */
.btn-ai.is-pensando,
.btn-ai.is-loading {
  cursor: progress;
  pointer-events: none;
  color: var(--btn-ia-ink);
  background-color: var(--btn-ia-bg);
  border-color: var(--btn-ia-border);
}

.btn-ai.is-pensando .icon,
.btn-ai.is-loading .icon { animation: ia-pensar 1.5s var(--ease) infinite; }
.btn-ai.is-pensando .icon .s1,
.btn-ai.is-loading .icon .s1 { animation: ia-destello 1.5s var(--ease) infinite; }
.btn-ai.is-pensando .icon .s2,
.btn-ai.is-loading .icon .s2 { animation: ia-destello 1.5s var(--ease) 0.28s infinite; }
.btn-ai.is-pensando .icon .s3,
.btn-ai.is-loading .icon .s3 { animation: ia-destello 1.5s var(--ease) 0.56s infinite; }

@keyframes ia-pensar {
  0%, 100% { transform: scale(1); }
  45%      { transform: scale(1.14); }
}
@keyframes ia-destello {
  0%, 100% { opacity: 0.28; }
  35%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  /* Sin movimiento sigue habiendo señal: el borde se vuelve discontinuo.
     Quitar la animación y no poner nada en su lugar dejaría el botón
     idéntico al de reposo, y quien lo pulsó no sabría si pasó algo. */
  .btn-ai.is-pensando .icon, .btn-ai.is-loading .icon,
  .btn-ai.is-pensando .icon *, .btn-ai.is-loading .icon * { animation: none; opacity: 0.75; }
  .btn-ai.is-pensando, .btn-ai.is-loading { border-style: dashed; }
}

/* =========================================================
   ESPERA EN BLOQUE — .ia-proc
   ---------------------------------------------------------
   Para procesos que pasan de unos segundos y tienen progreso real. Por
   encima de ese umbral un icono girando no basta: hay que decir en qué
   va. Sustituye a los cuatro paneles que cada módulo se hizo por su
   cuenta (Viajes, WordPress, Dossiers, Documentación).

   Markup:
     <div class="ia-proc" role="status">
       {{ i.icon('IA', cls='ia-proc__spark') }}
       <div class="ia-proc__cuerpo">
         <div class="ia-proc__texto"><span>Qué está haciendo…</span>
                                     <span class="ia-proc__pct">42 %</span></div>
         <div class="ia-proc__barra"><div class="ia-proc__fill"></div></div>
       </div>
     </div>

   El porcentaje y la barra son OPCIONALES: sin progreso real que contar,
   se deja solo el texto — una barra que avanza inventada miente sobre lo
   que falta.
   ========================================================= */

.ia-proc {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.8rem 1rem;
  background-color: var(--btn-ia-bg);
  border: 1px solid var(--btn-ia-border);
  border-radius: var(--radius);
}

.ia-proc__spark {
  width: 22px;
  height: 22px;
  flex: 0 0 auto;
  color: var(--violet);
  animation: ia-pensar 1.5s var(--ease) infinite;
}
.ia-proc__spark .s1 { animation: ia-destello 1.5s var(--ease) infinite; }
.ia-proc__spark .s2 { animation: ia-destello 1.5s var(--ease) 0.28s infinite; }
.ia-proc__spark .s3 { animation: ia-destello 1.5s var(--ease) 0.56s infinite; }

.ia-proc__cuerpo {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.ia-proc__texto {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--btn-ia-ink);
}
.ia-proc__texto > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ia-proc__pct { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

.ia-proc__barra {
  height: 5px;
  border-radius: var(--radius-full);
  background-color: color-mix(in srgb, var(--violet) 18%, transparent);
  overflow: hidden;
}
.ia-proc__fill {
  height: 100%;
  width: 0;
  border-radius: var(--radius-full);
  background-color: var(--violet);
  /* --ease-entra: la barra DESACELERA al acercarse al final («casi
     está»); la curva de salida haría lo contrario. */
  transition: width 0.35s var(--ease-entra);
}

.ia-proc[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .ia-proc__spark, .ia-proc__spark * { animation: none; }
  .ia-proc__fill { transition: none; }
}

/* ---------------------------------------------------------
   CAMPO QUE NO APLICA — .form-input:disabled
   ---------------------------------------------------------
   Distinto de `.form-input--readonly`, y la diferencia importa: aquél es un
   dato que CALCULA el sistema y hay que poder leer y copiar; éste es un control
   que ahora mismo no gobierna nada — el tope de gasto con la IA apagada. Por
   eso aquí sí se apaga el texto: no se pide leerlo, se pide entender que está
   fuera de juego. El porqué vive en su `.form-field__help`, que se queda a
   contraste pleno.
   Sin esta regla, un `disabled` conservaba el fondo transparente del estándar y
   se veía igual que un campo editable: parecía roto, no bloqueado. */
.form-input:disabled,
.form-select:disabled,
.form-textarea:disabled {
    background-color: var(--gray-100);
    border-color: var(--border-color);
    color: var(--text-muted);
    cursor: not-allowed;
}
html[data-theme="dark"] .form-input:disabled,
html[data-theme="dark"] .form-select:disabled,
html[data-theme="dark"] .form-textarea:disabled {
    background-color: var(--surface-sunken, var(--gray-100));
}



/* Botón «+» de cabecera de sección (añadir precio, añadir producto al dossier).
 * Vivía copiado en dossiers.css y productos.css; una sola copia aquí. En
 * táctil sube al mínimo de 44 px. */
.btn-add-inline {
    align-self: flex-start;
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 34px; min-height: 34px;
    background: none;
    border: 1px dashed var(--gray-300);
    color: var(--text-muted);
    border-radius: var(--radius-sm);
    padding: 0.35rem;
    line-height: 0;
    cursor: pointer;
}
.btn-add-inline:hover { border-color: var(--primary); color: var(--primary); }
.btn-add-inline[hidden] { display: none !important; }
.form-modal-section-title .btn-add-inline { margin-left: auto; }
@media (pointer: coarse) {
    .btn-add-inline { min-width: 44px; min-height: 44px; }
}
