.form-group { display: flex; flex-direction: column; gap: var(--space-2xs); margin-bottom: var(--space-md); }

.form-group label {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text);
}

.form-group .hint {
  font-size: var(--font-size-xs);
  color: var(--color-text-muted);
}

.form-group .error {
  font-size: var(--font-size-xs);
  color: var(--color-danger);
}

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
@media (max-width: 640px) { .form-row { grid-template-columns: 1fr; } }

input:not([type]), input[type="text"], input[type="email"], input[type="tel"], input[type="number"],
input[type="password"], input[type="date"], input[type="time"], input[type="datetime-local"],
input[type="search"], input[type="url"], select, textarea {
  font-family: var(--font-body);
  /* 16px: con menos, iOS hace zoom al enfocar. 22px de línea + 8+8 de relleno + 2 de borde
     = 40px (--control-md) en cualquier modelo de caja: no se fuerza box-sizing porque hay
     campos con ancho fijo en px que se achicarían. */
  font-size: var(--fs-16);
  line-height: 1.375;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-surface);
  color: var(--color-text);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

/* Chromium le suma 1 px arriba y abajo al contenido de los campos de fecha y hora
   (::-webkit-datetime-edit): medían 42 al lado de un campo de texto de 40, en toda la app. */
input::-webkit-datetime-edit,
input::-webkit-datetime-edit-fields-wrapper { padding-block: 0; }
/* Y el ícono del selector trae relleno propio: solo se le quita el vertical, para que siga
   teniendo su ancho al tocarlo. */
input::-webkit-calendar-picker-indicator { padding-block: 0; margin-block: 0; }

input:focus, select:focus, textarea:focus {
  outline: 0;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
}

input[type="checkbox"], input[type="radio"] {
  accent-color: var(--color-primary);
  width: 18px; height: 18px;
  cursor: pointer;
  flex-shrink: 0;
}

input:disabled, select:disabled, textarea:disabled { opacity: 0.5; cursor: not-allowed; }

input[aria-invalid="true"], .form-group.invalid input, .form-group.invalid select {
  border-color: var(--color-danger);
}

textarea { resize: vertical; min-height: 80px; }

/* Campo de secreto (token, API key): se ve enmascarado pero NO es
   type="password".
   Chrome ofrece "¿Guardar la contraseña?" ante cualquier input de contraseña
   —y `autocomplete="off"` lo ignora hace años—, así que abrir Config ›
   Marketing disparaba el diálogo del gestor de claves por el Access Token de
   Meta (reporte del dueño 2026-09-04). Un token de API no es la clave de nadie
   y no tiene por qué terminar guardada en el navegador del asesor.
   .is-visible lo destapa para los botones 👁 de mostrar/ocultar. */
input.input-secreto {
  -webkit-text-security: disc;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .06em;
}
input.input-secreto.is-visible {
  -webkit-text-security: none;
  letter-spacing: normal;
}

/* ── Campo con botón pegado, en una sola caja ────────────────────────────────
   Un input y sus botones (la cámara del escáner, por ejemplo) bajo UN marco, en
   vez de dos controles sueltos uno al lado del otro. En el celular esa segunda
   pieza se caía a una línea propia y quedaba un botón huérfano bajo el buscador
   (reporte del dueño, 2026-09-07).

   Vive acá y no en cada vista porque ya lo piden tres pantallas: el buscador de
   /consumo, el campo Código / SKU del modal de ítem, y la lista de inventario
   —que usa la caja equivalente de table-view (.tv-buscador) y por eso no lleva
   esta clase—. Los valores están calcados de esa para que las tres se vean
   iguales; si una cambia, cambian las dos. */
.campo-caja {
  display: flex; align-items: center; gap: .4rem;
  padding: 0 .5rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-sm, 8px);
  background: var(--color-surface-alt); color: var(--color-text);
  min-width: 0;
}
.campo-caja:focus-within { border-color: var(--color-primary); }
/* El input pierde su marco —lo pone la caja— y conserva todo lo demás. El
   `background: none` es necesario: varias vistas le fijan uno propio y adentro
   de la caja se vería como un parche de otro color. */
.campo-caja > input {
  flex: 1 1 auto; min-width: 0; width: 100%;
  border: 0; outline: none; background: none; color: inherit;
  padding: .5rem 0; font: inherit;
}
/* Los botones de adentro son fantasma: si llevaran marco propio volveríamos a
   ver dos controles, que es justo lo que esta caja viene a evitar. */
.campo-caja-btn {
  flex: 0 0 auto; padding: 2px 4px;
  border: 0; background: none; cursor: pointer;
  color: var(--color-text-muted); font-size: var(--fs-16); line-height: 1.2;
  opacity: .8;
}
.campo-caja-btn:hover { opacity: 1; }
.campo-caja-btn[hidden] { display: none; }
