/* VOLQTECH — primitivas de componente del sistema de diseño.
 * Fuente: "VOLQTECH Components.dc.html" / "VOLQTECH Landing.dc.html" (medidas exactas del
 * diseño; el texto de esos ficheros es placeholder de otro producto y no se copia).
 *
 * QUÉ ES ESTO
 * El aliasing de tokens ya cambió el COLOR y la TIPOGRAFÍA de las tres apps, pero los
 * componentes seguían con la FORMA del sistema viejo (radios de 14px, sombras, pesos
 * blandos). Este fichero aporta la forma: radio 2px, grilla de 8px, hairlines, mono en
 * mayúsculas para todo lo que es etiqueta, y el volt reservado para el relleno.
 *
 * CÓMO SE CARGA
 *   import '@volq/ui/css/tokens.css';      // primero: declara los --vq-*
 *   import './styles.css';                 // la hoja histórica de la app
 *   import '@volq/ui/css/primitives.css';  // último: ver "ORDEN" abajo
 * Este fichero NO hace @import de tokens.css a propósito: las tres apps ya lo importan en
 * su main.tsx y duplicarlo metería el bloque :root dos veces en el bundle. Si alguien lo
 * usa suelto (las páginas estáticas de deploy/marketing/ y deploy/demo/, que no pasan por
 * Vite y lo cargan desde /vq/), tiene que cargar tokens.css antes o todo cae a los fallbacks.
 *
 * ORDEN. Todos los selectores de acá están anclados a una clase .vq-*: no hay ni un
 * selector de elemento suelto, así que cargar este fichero al final no puede pisar nada de
 * la app. Y conviene que vaya al final: los empates de especificidad (0,1,0 contra 0,1,0
 * con las clases viejas del driver/merchant) los gana el que se declara último, o sea que
 * poner `.vq-btn` encima de un `.btn` heredado sólo funciona si primitives va después.
 *
 * REGLAS QUE ESTE FICHERO NO ROMPE
 * 1. Ni un literal de color. Todo sale de --vq-*. Lo único "de color" que aparece son las
 *    palabras clave transparent y currentColor, que son relativas al contexto y por tanto
 *    válidas en ambos temas.
 * 2. --vq-accent (volt) es SÓLO relleno. Como color de texto/línea/icono va
 *    --vq-accent-text, que en claro es oliva #465C00. El volt sobre blanco da 1.15:1: un
 *    subrayado de pestaña o un borde volt simplemente no existen en el tema claro.
 * 3. Ningún text-transform sobre cadenas que valida el e2e. Ver ".vq-verbatim" al final.
 *    La distinción exacta, por si otro agente la necesita: getByText/getByRole comparan
 *    contra el texto del DOM y NO aplican text-transform, pero innerText() SÍ. Y el helper
 *    currentStep() de operator-application.spec.ts:25 usa innerText() sobre el indicador
 *    de paso, o sea que un "Paso 3 de 9" en versalitas devuelve "PASO 3 DE 9", el regex
 *    /Paso (\d+) de 9/ no matchea y el test entero se cuelga en NaN.
 * 4. Objetivos táctiles ≥44px bajo (pointer: coarse) — el driver es una PWA que se usa con
 *    guantes, dentro de un camión, al sol.
 */

/* Seguro de caja para consumidores sin reset propio (la landing estática). Las apps ya
   tienen su `*{box-sizing:border-box}`, así que acá es redundante e inofensivo. */
[class^='vq-'],
[class*=' vq-'],
[class^='vq-']::before,
[class^='vq-']::after,
[class*=' vq-']::before,
[class*=' vq-']::after {
  box-sizing: border-box;
}

/* ── Animaciones ──────────────────────────────────────────────────────────────
 * Las tres primeras salen tal cual de la landing del diseño; se exportan desde acá para
 * que la página de marketing y las apps compartan la misma curva y no cada una la suya.
 */
@keyframes vq-rise {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes vq-scan {
  from { transform: translateY(-100%); }
  to   { transform: translateY(1400%); }
}
@keyframes vq-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@keyframes vq-spin {
  to { transform: rotate(360deg); }
}
@keyframes vq-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* ═══════════════════════════════════════════════════════════════════════════════
   TIPOGRAFÍA
   El sistema tiene dos voces y ninguna intermedia: Archivo comprimido y pesadísimo para
   lo que grita (titulares, cifras), IBM Plex Mono en versalitas espaciadas para lo que
   etiqueta (eyebrows, cabeceras de tabla, badges). El cuerpo de texto es Archivo normal.
   ═══════════════════════════════════════════════════════════════════════════════ */

/* Eyebrow: el rótulo mono en mayúsculas que encabeza cada sección ("01 — HARDWARE").
   Va en --vq-accent-text, no en --vq-accent: es texto. */
.vq-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: var(--vq-tracking-mono-wide);
  text-transform: uppercase;
  color: var(--vq-accent-text);
}
.vq-eyebrow--muted { color: var(--vq-text-label); }
.vq-eyebrow--lg    { font-size: 12px; }

/* El punto con halo del héroe. Es decorativo: no lleva información que no esté en el texto. */
.vq-eyebrow__dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--vq-accent);
  box-shadow: 0 0 0 4px var(--vq-accent-tint);
}

/* Titulares display. font-stretch sólo hace algo con la Archivo variable (eje wdth 62..125),
   que es la que cargan los tres index.html; con la estática degrada a ancho normal y el
   titular sigue funcionando, sólo que menos apretado. */
.vq-display {
  margin: 0;
  font-family: var(--vq-font-sans);
  font-weight: 800;
  font-stretch: var(--vq-stretch-display);
  letter-spacing: var(--vq-tracking-heading);
  line-height: 0.98;
  color: var(--vq-text);
  text-wrap: balance;
}
.vq-display--hero {
  font-size: clamp(48px, 8.4vw, 124px);
  font-weight: 900;
  font-stretch: var(--vq-stretch-hero);
  letter-spacing: var(--vq-tracking-hero);
  line-height: 0.86;
}
.vq-display--h1 {
  font-size: clamp(32px, 4.4vw, 62px);
  font-weight: 800;
  font-stretch: var(--vq-stretch-heading);
  letter-spacing: var(--vq-tracking-heading);
  line-height: 0.98;
}
.vq-display--h2 {
  font-size: clamp(26px, 3.4vw, 46px);
  font-weight: 800;
  font-stretch: var(--vq-stretch-heading);
  letter-spacing: var(--vq-tracking-tight);
  line-height: 1;
}
.vq-display--h3 {
  font-size: clamp(19px, 1.9vw, 26px);
  font-weight: 700;
  font-stretch: var(--vq-stretch-sub);
  letter-spacing: var(--vq-tracking-tight);
  line-height: 1.15;
}

/* Utilidades de texto. .vq-t-label es el mismo rótulo mono del eyebrow pero en gris: se usa
   para las etiquetas de campo y las claves de una ficha técnica.
   Tracking: escalón del MEDIO (0.14em). El .dc.html usa 0.12em en badges/chips/pestañas,
   0.14em en las etiquetas de campo y 0.16em en los eyebrows; una etiqueta no es un badge. */
.vq-t-label {
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: var(--vq-tracking-mono-mid);
  text-transform: uppercase;
  color: var(--vq-text-label);
}
.vq-t-body {
  font-family: var(--vq-font-sans);
  font-size: 15px;
  line-height: 1.6;
  color: var(--vq-text-secondary);
  text-wrap: pretty;
}
.vq-t-sm {
  font-family: var(--vq-font-sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--vq-text-muted);
}
/* La cifra grande de un tile de métrica. tabular-nums para que 6.4 y 8.8 no bailen cuando
   el número se refresca solo. */
.vq-t-stat {
  font-family: var(--vq-font-sans);
  font-size: clamp(24px, 2.6vw, 34px);
  font-weight: 800;
  font-stretch: var(--vq-stretch-heading);
  letter-spacing: var(--vq-tracking-tight);
  line-height: 1;
  color: var(--vq-text);
  font-variant-numeric: tabular-nums;
}
/* La unidad va dentro de la cifra, a 0.55em: "6.4 MW", "38/40", "B/. 1,240". */
.vq-t-stat__unit {
  font-size: 0.55em;
  font-weight: 700;
  color: var(--vq-text-secondary);
}

/* Mono suelto (IDs de viaje, placas, horas, importes fuera de tabla). Sin mayúsculas ni
   espaciado: eso lo pone .vq-t-label, esto es sólo "cambiá la fuente". */
.vq-mono {
  font-family: var(--vq-font-mono);
  font-feature-settings: 'tnum' 1;
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   BOTONES
   Medidas del diseño: 14px 26px / 15px. El primario es el único con relleno; todo lo
   demás es borde o nada. Sin sombras, sin gradientes, sin transformaciones al hover.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 14px 26px;
  min-height: 48px;
  border: 1px solid transparent;
  border-radius: var(--vq-radius);
  background: transparent;
  color: var(--vq-text);
  font-family: var(--vq-font-sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.01em;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}

/* Primario. El borde no es decorativo: en oscuro es volt-sobre-volt (invisible, cero coste)
   y en claro es el rgba oliva de --vq-accent-border, que es lo único que le da CONTORNO al
   botón — un relleno volt sobre blanco es 1.15:1 y sin ese reborde el botón flota sin
   límites. Mismo truco en .vq-meter__fill y en .vq-segmented__option seleccionada. */
.vq-btn--primary {
  background: var(--vq-accent);
  border-color: var(--vq-accent-border);
  color: var(--vq-on-accent);
  font-weight: 700;
}
.vq-btn--primary:not(:disabled):not([aria-disabled='true']):hover {
  background: var(--vq-accent-hover);
}

.vq-btn--secondary {
  background: transparent;
  border-color: var(--vq-border-button);
  color: var(--vq-text);
}
.vq-btn--secondary:not(:disabled):not([aria-disabled='true']):hover {
  border-color: var(--vq-border-strong);
  background: var(--vq-hover-wash);
}

/* Ghost: sin borde ni fondo, gris, y al hover se enciende en volt. Padding lateral más
   corto (20px) porque sin caja el aire de 26px lo despega demasiado de sus vecinos. */
.vq-btn--ghost {
  padding-inline: 20px;
  background: transparent;
  border-color: transparent;
  color: var(--vq-text-secondary);
}
.vq-btn--ghost:not(:disabled):not([aria-disabled='true']):hover {
  color: var(--vq-accent-text);
}

/* Destructivo: el diseño usa un granate plano (#2A1416) que es exactamente lo que da
   --vq-danger-tint compuesto sobre el fondo tinta. Así el mismo token sirve en claro,
   donde produce un rosa pálido con el texto rojo encima. */
.vq-btn--danger {
  background: var(--vq-danger-tint);
  border-color: var(--vq-danger-border);
  color: var(--vq-danger);
}
.vq-btn--danger:not(:disabled):not([aria-disabled='true']):hover {
  background: var(--vq-danger-tint-hover);
}

/* Tamaños. sm baja de 48px, así que bajo puntero grueso vuelve a 44: el mínimo táctil
   manda sobre la escala tipográfica. */
.vq-btn--sm {
  padding: 9px 16px;
  min-height: 36px;
  font-size: 13px;
  gap: 8px;
}
.vq-btn--lg {
  padding: 18px 34px;
  min-height: 56px;
  font-size: 17px;
}
/* Cuadrado de 46×46 del diseño. Es sólo geometría: combinalo con --secondary o --ghost
   para decidir si lleva borde. */
.vq-btn--icon {
  width: 46px;
  min-width: 46px;
  height: 46px;
  min-height: 46px;
  padding: 0;
  gap: 0;
  font-size: 18px;
}
@media (pointer: coarse) {
  .vq-btn--sm { min-height: 44px; }
  .vq-btn--icon { width: 48px; min-width: 48px; height: 48px; min-height: 48px; }
}

/* Deshabilitado: gana por especificidad (0,2,0) a los modificadores (0,1,0), así que da
   igual el orden de clases en el markup.
   El color es --vq-text-muted y NO --vq-text-disabled: un botón deshabilitado sigue
   llevando texto vivo ("Continuar", "Enviar solicitud"), que es lo que le dice al usuario
   qué va a pasar cuando se habilite. Con --vq-text-disabled sobre --vq-neutral-tint el
   contraste era 2.66:1 en oscuro y 2.27:1 en claro; con --vq-text-muted da 6.31:1 y
   5.37:1. WCAG 1.4.3 exime a los controles inactivos, pero un formulario de 9 pasos donde
   el botón de avanzar está deshabilitado la mitad del tiempo no es un caso "incidental".
   --vq-text-disabled se queda para lo que de verdad es texto muerto (placeholders de un
   campo bloqueado, filas fantasma). */
.vq-btn:disabled,
.vq-btn[aria-disabled='true'] {
  background: var(--vq-neutral-tint);
  border-color: transparent;
  color: var(--vq-text-muted);
  cursor: not-allowed;
}

/* Botón en curso: el spinner hereda currentColor, así que funciona igual dentro de un
   primario (tinta sobre volt) que de un secundario (papel sobre tinta). */
.vq-btn[aria-busy='true'] { cursor: progress; }

/* ═══════════════════════════════════════════════════════════════════════════════
   CAMPOS
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-field {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0; /* si no, un campo dentro de un grid desborda la columna */
}
/* Misma pieza que .vq-t-label (mismo escalón de tracking: 0.14em), con nombre propio
   porque vive dentro de .vq-field y cambia a rojo cuando el campo falla. */
.vq-field-label {
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: var(--vq-tracking-mono-mid);
  text-transform: uppercase;
  color: var(--vq-text-label);
}
.vq-field-hint {
  font-size: 13px;
  line-height: 1.5;
  color: var(--vq-text-muted);
}
.vq-field-error {
  font-size: 13px;
  line-height: 1.5;
  color: var(--vq-danger);
}

.vq-input,
.vq-select,
.vq-textarea {
  width: 100%;
  min-height: 48px;
  padding: 14px 16px;
  background-color: var(--vq-surface-2);
  border: 1px solid var(--vq-border-input);
  border-radius: var(--vq-radius);
  color: var(--vq-text);
  font-family: var(--vq-font-sans);
  font-size: 15px; /* la medida del diseño */
  line-height: 1.3;
  outline: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
/* Táctil. Dos correcciones, no una:
   · 16px — Safari iOS hace zoom al viewport en cuanto enfocás un campo con cuerpo menor a
     16px. El driver se salva porque su index.html trae maximum-scale=1, pero merchant y
     admin no, y ahí el salto de zoom te saca del formulario a mitad de un paso. La
     diferencia de 1px no se percibe; el salto sí.
   · 48px de alto ya viene del min-height base; acá sólo se sube el aire vertical. */
@media (pointer: coarse) {
  .vq-input,
  .vq-select,
  .vq-textarea {
    font-size: 16px;
    padding-block: 15px;
  }
}
.vq-input::placeholder,
.vq-textarea::placeholder {
  color: var(--vq-text-muted);
  opacity: 1; /* Firefox se lo baja a 0.54 y deja el placeholder por debajo de AA */
}
.vq-textarea {
  min-height: 96px;
  line-height: 1.55;
  resize: vertical;
}

/* El foco pinta el borde de volt (oscuro) u oliva (claro). Es --vq-accent-text y no
   --vq-accent porque un borde es un límite de control: WCAG 1.4.11 pide 3:1 y el volt
   sobre blanco no llega ni de lejos. */
.vq-input:focus,
.vq-select:focus,
.vq-textarea:focus {
  border-color: var(--vq-accent-text);
}

/* Estado inválido. Se dispara con aria-invalid (lo que ya escribe React) o con la
   pseudoclase nativa :user-invalid, que sólo salta después de que el usuario tocó el
   campo — nunca al montar el formulario vacío. */
.vq-input[aria-invalid='true'],
.vq-select[aria-invalid='true'],
.vq-textarea[aria-invalid='true'],
.vq-input:user-invalid,
.vq-textarea:user-invalid {
  border-color: var(--vq-danger);
}
/* El diseño pone también la etiqueta en rojo cuando el campo falla. :has evita tener que
   duplicar el estado en el markup del cliente. */
.vq-field:has([aria-invalid='true']) > .vq-field-label {
  color: var(--vq-danger);
}

.vq-input:disabled,
.vq-select:disabled,
.vq-textarea:disabled {
  background-color: var(--vq-neutral-tint);
  border-color: var(--vq-border-hairline);
  color: var(--vq-text-muted);
  cursor: not-allowed;
}

/* El <select> pierde la flecha nativa con appearance:none, así que se redibuja con dos
   gradientes de 6px (el truco clásico de dos triángulos). Va con currentColor a propósito:
   un SVG en data-URI obligaría a incrustar un hex y este fichero no tiene ni uno. */
.vq-select {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: 40px;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 22px) calc(50% - 2px),
    calc(100% - 16px) calc(50% - 2px);
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
}
/* OJO: el desplegable del <select> y los calendarios de <input type="date"> los pinta el
   sistema operativo, no el CSS. color-scheme es lo único que los vuelve oscuros; sin esto
   el conductor abre el selector de fecha y le explota un panel blanco en la cara. */
.vq-input,
.vq-select,
.vq-textarea {
  color-scheme: dark;
}
[data-theme='light'] .vq-input,
[data-theme='light'] .vq-select,
[data-theme='light'] .vq-textarea {
  color-scheme: light;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   SEGMENTED — grupo de opciones excluyentes (turno, tipo de material, rango…)
   Es el MOTIVO DE GRILLA HAIRLINE en miniatura: el contenedor se pinta del color del
   borde y las celdas opacas dejan ver 1px entre ellas. Así la línea divisoria mide
   exactamente 1px real y no 2px por bordes que se suman.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-segmented {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1px;
  /* SIN overflow:hidden a propósito: recortaba el box-shadow del anillo de foco de las
     opciones, dejando sin indicador visible a quien navega con teclado. Las esquinas
     se redondean en las celdas extremas, no recortando el contenedor. */
  padding: 0;
  background: var(--vq-border);
  border: 1px solid var(--vq-border);
  border-radius: var(--vq-radius);
}
.vq-segmented__option {
  flex: 1 1 auto;
  min-height: 44px;
  padding: 12px 20px;
  border: none;
  border-radius: 0;
  background: var(--vq-surface);
  color: var(--vq-text-secondary);
  font-family: var(--vq-font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--vq-tracking-mono);
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.18s ease, color 0.18s ease;
}
.vq-segmented__option:not(:disabled):hover {
  background: var(--vq-surface-3);
  color: var(--vq-text);
}
/* Seleccionado por cualquiera de las dos vías del contrato. box-shadow inset en vez de
   border para el reborde del tema claro: un border cambiaría la altura de la celda y
   rompería la alineación de 1px del grupo. */
.vq-segmented__option[aria-pressed='true'],
.vq-segmented__option[data-selected],
.vq-segmented__option[aria-checked='true'] {
  background: var(--vq-accent);
  color: var(--vq-on-accent);
  box-shadow: inset 0 0 0 1px var(--vq-accent-border);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   CHECKBOX / TOGGLE
   Los dos se montan sobre <input type="checkbox"> con appearance:none, que es la única
   forma de tener control total conservando el foco, el teclado y la semántica nativa.
   El toggle acepta además un <button aria-pressed>, que es como lo dibuja el diseño.

   ⚠️ El input TIENE QUE SEGUIR SIENDO VISIBLE. La receta habitual de "input con opacity:0
   + cuadrito falso al lado" rompe el e2e: operator-application.spec.ts:168 hace
   `for (const box of await page.locator('input[type="checkbox"]').all()) await box.check()`
   y check() falla sobre un elemento no visible. appearance:none conserva la caja real, que
   es justo lo que se está pintando. Y ojo con lo mismo al revés: ese bucle marca TODOS los
   checkbox del paso 9, así que no metas ahí un .vq-toggle que deba quedar apagado.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-checkbox {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  place-content: center;
  width: 22px;
  height: 22px;
  flex: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--vq-border-input);
  border-radius: var(--vq-radius);
  background: var(--vq-surface-2);
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
/* El check se dibuja con clip-path, no con un borde rotado: no depende de
   transform-origin ni del redondeo subpíxel, y escala limpio a cualquier tamaño. */
.vq-checkbox::before {
  content: '';
  width: 12px;
  height: 12px;
  background: var(--vq-on-accent);
  clip-path: polygon(14% 44%, 0 65%, 43% 100%, 100% 16%, 80% 0, 40% 62%);
  transform: scale(0);
  transition: transform 0.12s ease-out;
}
.vq-checkbox:checked {
  background: var(--vq-accent);
  border-color: var(--vq-accent-border);
}
.vq-checkbox:checked::before { transform: scale(1); }
/* Indeterminado: la misma pieza convertida en guion. */
.vq-checkbox:indeterminate {
  background: var(--vq-accent);
  border-color: var(--vq-accent-border);
}
.vq-checkbox:indeterminate::before {
  clip-path: none;
  height: 2px;
  transform: scale(1);
}
.vq-checkbox:disabled {
  background: var(--vq-neutral-tint);
  border-color: var(--vq-border-hairline);
  cursor: not-allowed;
}
/* La caja mide 22px, muy por debajo del mínimo táctil. La fila entera es el objetivo: el
   <label> que envuelve al control se estira a 44px sin que haya que inventar una clase.
   Especificidad 0,1,1: le gana a una clase suelta de la app sobre ese mismo <label>, pero
   sólo dentro de labels que de verdad contienen un .vq-checkbox. */
label:has(> .vq-checkbox) {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  cursor: pointer;
}

.vq-toggle {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  width: 52px;
  height: 28px;
  flex: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--vq-border-button);
  border-radius: var(--vq-radius-pill);
  background: var(--vq-neutral-tint);
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease;
}
/* Perilla. Apagada usa el gris de texto (claro sobre riel oscuro, oscuro sobre riel claro:
   se ve en los dos temas); encendida pasa a tinta, que es lo legible sobre el volt. */
.vq-toggle::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--vq-text-secondary);
  transition: transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1), background-color 0.2s ease;
}
.vq-toggle:checked,
.vq-toggle[aria-pressed='true'] {
  background: var(--vq-accent);
  border-color: var(--vq-accent-border);
}
.vq-toggle:checked::after,
.vq-toggle[aria-pressed='true']::after {
  background: var(--vq-on-accent);
  transform: translateX(24px);
}
.vq-toggle:disabled,
.vq-toggle[aria-disabled='true'] {
  cursor: not-allowed;
  opacity: 0.5;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   BADGES Y CHIPS
   Badge = ESTADO (píldora, punto, color semántico). Chip = ESPECIFICACIÓN (rectángulo de
   2px, sin relleno). No son intercambiables: si lleva punto es porque cambia solo.

   CONTRATO DE MEDIDAS — hay TRES cajas y ninguna más. Las tres salen del .dc.html:
     · píldora de estado ......... padding 7px 14px · 11px · radio 999px   (.vq-badge)
     · chip rectangular .......... padding 6px 12px · 11px · radio 2px     (.vq-chip)
     · píldora dentro de tabla ... padding 6px 12px · 10px · radio 999px   (.vq-badge--sm)
   Las clases propias de cada app (.badge, .chip, .pill, .tag, .sync-badge, .status-chip)
   TIENEN que caer en una de las tres: hoy circulan además 5px 12px, 5px 11px y 6px 10px, y
   la consecuencia es que dos estados que se leen juntos en la misma fila tienen alturas
   distintas. Si hace falta una caja nueva, se agrega ACÁ como modificador, no en la app.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border-radius: var(--vq-radius-pill);
  background: var(--vq-neutral-tint);
  color: var(--vq-text-secondary);
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--vq-tracking-mono);
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
/* El punto hereda currentColor: cada modificador sólo tiene que declarar dos propiedades. */
.vq-badge__dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: 50%;
  background: currentColor;
}
.vq-badge--accent  { background: var(--vq-accent-tint);  color: var(--vq-accent-text); }
.vq-badge--neutral { background: var(--vq-neutral-tint); color: var(--vq-text-secondary); }
.vq-badge--info    { background: var(--vq-info-tint);    color: var(--vq-info); }
.vq-badge--warn    { background: var(--vq-warn-tint);    color: var(--vq-warn); }
.vq-badge--danger  { background: var(--vq-danger-tint);  color: var(--vq-danger); }

/* Píldora compacta. Es la del diseño DENTRO de una celda: la fila de tabla mide 16px de
   padding y la píldora de 7px/11px la engorda de más, así que baja a 6px/10px. Se aplica
   sola dentro de un <tbody> (que es donde el diseño la usa) y a mano con --sm en cualquier
   otro sitio apretado; --sm va después para que gane si alguien quiere la compacta fuera
   de la tabla o la píldora grande no le sirve. Sólo toca caja y cuerpo: el color sigue
   viniendo del modificador semántico. */
.vq-table tbody .vq-badge,
.vq-badge--sm {
  padding: 6px 12px;
  font-size: 10px;
}

.vq-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border: 1px solid var(--vq-border-input);
  border-radius: var(--vq-radius);
  background: transparent;
  color: var(--vq-text-secondary);
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: var(--vq-tracking-mono);
  white-space: nowrap;
  vertical-align: middle;
}
.vq-chip--accent {
  border-color: var(--vq-accent-border);
  color: var(--vq-accent-text);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ALERTS
   Barra de 3px al inicio + lavado al 8% del mismo color. Los dos valores viajan en dos
   custom properties locales para que los modificadores sean de dos líneas y para que el
   icono pueda leer el color de la barra sin repetirlo.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-alert {
  display: flex;
  gap: 14px;
  padding: 16px 18px;
  border-radius: var(--vq-radius);
  /* border-inline-start y no border-left: el día que haya una variante en otro idioma no
     hay que tocar esto. */
  border-inline-start: var(--vq-accent-bar) solid var(--vq-alert-line, var(--vq-border-strong));
  background: var(--vq-alert-bg, var(--vq-hover-wash));
  color: var(--vq-text);
}
.vq-alert__icon {
  flex: none;
  font-family: var(--vq-font-mono);
  font-size: 13px;
  line-height: 1.5;
  color: var(--vq-alert-line, var(--vq-text));
}
.vq-alert__title {
  margin: 0 0 4px;
  font-family: var(--vq-font-sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--vq-text);
}
.vq-alert__body {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--vq-text-muted);
  text-wrap: pretty;
}
.vq-alert--accent { --vq-alert-line: var(--vq-accent-text); --vq-alert-bg: var(--vq-accent-wash); }
.vq-alert--info   { --vq-alert-line: var(--vq-info);        --vq-alert-bg: var(--vq-info-wash); }
.vq-alert--warn   { --vq-alert-line: var(--vq-warn);        --vq-alert-bg: var(--vq-warn-wash); }
.vq-alert--danger { --vq-alert-line: var(--vq-danger);      --vq-alert-bg: var(--vq-danger-wash); }

/* ═══════════════════════════════════════════════════════════════════════════════
   CARDS
   Superficie plana + 1px de hairline. En oscuro --vq-shadow-card es `none` (el diseño no
   tiene ni una sombra); en claro es 1px, que es lo que separa una tarjeta blanca de un
   fondo casi blanco. O sea: la misma regla, dos resultados correctos.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-card {
  padding: var(--vq-card-pad, 22px);
  background: var(--vq-surface);
  border: 1px solid var(--vq-border);
  border-radius: var(--vq-radius);
  box-shadow: var(--vq-shadow-card);
  color: var(--vq-text);
}
/* Elevada: el gradiente 160° del diseño en oscuro; en claro --vq-elevated es blanco plano
   y quien hace el trabajo es la sombra. */
.vq-card--raised {
  background: var(--vq-elevated);
  border-color: var(--vq-border);
  box-shadow: var(--vq-shadow-pop);
}
/* Acentuada: bloque volt macizo. Acá está el detalle que más se rompe si se copia mal —
   dentro de esta tarjeta TODO cambia de contexto, así que en vez de escribir reglas hijas
   una por una se REDEFINEN los tokens en el propio nodo. Cualquier primitiva anidada
   (.vq-t-body, .vq-eyebrow, .vq-chip, .vq-rule) se re-tematiza sola por herencia.
   Lo que NO hay que meter adentro: un .vq-btn--primary (volt sobre volt). Usá
   --secondary, que hereda el borde de tinta. */
.vq-card--accent {
  background: var(--vq-accent);
  border-color: var(--vq-accent-border);
  color: var(--vq-on-accent);

  --vq-text: var(--vq-on-accent);
  --vq-text-secondary: var(--vq-on-accent-muted);
  --vq-text-muted: var(--vq-on-accent-muted);
  --vq-text-label: var(--vq-on-accent-muted);
  --vq-text-disabled: var(--vq-on-accent-muted);
  --vq-accent-text: var(--vq-on-accent);
  --vq-border: var(--vq-on-accent-line);
  --vq-border-hairline: var(--vq-on-accent-line);
  --vq-border-row: var(--vq-on-accent-line);
  /* El campo de acá abajo se hunde con un velo de tinta, así que su contorno no puede ser
     el mismo velo (sería invisible): sube al 72%. 7.91:1 contra el volt de la tarjeta y
     4.79:1 contra el propio relleno del campo. */
  --vq-border-input: var(--vq-on-accent-muted);
  --vq-border-button: var(--vq-on-accent-muted);
  --vq-border-strong: var(--vq-on-accent);
  /* Contorno de cualquier pieza volt anidada (checkbox marcado, opción segmentada
     seleccionada, relleno del meter): volt sobre volt es 1.00:1.
     Se lo come también el `border-color` de arriba, que es lo que se busca: en claro el
     borde de la tarjeta pasa de 2.14:1 a 9.11:1 contra la página, y ese borde es TODO lo
     que delimita el bloque (el relleno volt sobre blanco es 1.15:1). En oscuro el bloque
     ya se delimita solo contra el fondo tinta y el borde queda como un pelo más oscuro. */
  --vq-accent-border: var(--vq-on-accent-muted);

  /* Y ACÁ ESTÁ LA PARTE QUE SE OLVIDA. Re-atar los colores de texto sin re-atar las
     SUPERFICIES deja piezas anidadas pintando su propio fondo oscuro con el texto ya
     convertido en tinta: un .vq-input dentro de esta tarjeta era tinta sobre #101317,
     1.08:1 — invisible. Las superficies se resuelven contra el volt, no contra la app.
     · surface / elevated → el mismo volt: una .vq-card anidada no abre un pozo negro.
     · surface-2 / surface-3 → velo de tinta al 22% (#A8C933): campo hundido, tinta 10.54:1.
     · neutral-tint → el mismo velo: pista del meter, fondo del botón deshabilitado. */
  --vq-surface: var(--vq-accent);
  --vq-surface-2: var(--vq-on-accent-line);
  --vq-surface-3: var(--vq-on-accent-line);
  --vq-elevated: var(--vq-accent);
  --vq-neutral-tint: var(--vq-on-accent-line);
  --vq-hover-wash: var(--vq-on-accent-line);
  --vq-row-hover: var(--vq-on-accent-line);

  /* El anillo de foco del token es volt (oscuro) u oliva (claro): sobre este relleno da
     1.00:1 y 2.23:1. Pasa a tinta, 17.40:1 contra el volt. Sin esto, un tabulador dentro
     de una tarjeta acentuada deja de tener foco visible — que es exactamente el fallo
     que .vq-btn--primary evita con su outline aparte. */
  --vq-focus-ring: 0 0 0 2px var(--vq-on-accent);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   TABLAS
   CONTRATO DE MEDIDAS (del .dc.html; no son sugerencias):
     · th ............ padding 12px 16px · mono 11px versalitas · --vq-text-label
     · td ............ padding 16px EN LOS CUATRO LADOS · 15px
     · cuerpo ........ 15px (lo pone .vq-table, las celdas heredan)
     · celda mono .... 14px (.vq-num; el dato duro baja un punto para que la columna de
                       importes no pese más que el texto que la rodea)
   El 16px de la celda es simétrico a propósito: una tabla de acarreo se lee en diagonal y
   un padding vertical más corto que el horizontal la aplasta. Una vista que necesite filas
   más bajas quita columnas o usa .vq-badge--sm; no recorta el padding por su cuenta.
   ═══════════════════════════════════════════════════════════════════════════════ */
/* Contenedor de scroll. En el celular del conductor una tabla de 5 columnas no entra: que
   scrollee la tabla y NO el documento. */
.vq-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.vq-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--vq-font-sans);
  font-size: 15px;
}
.vq-table thead th {
  padding: 12px 16px;
  border-bottom: 1px solid var(--vq-border);
  font-family: var(--vq-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: var(--vq-tracking-mono);
  text-transform: uppercase;
  color: var(--vq-text-label);
  text-align: left;
  white-space: nowrap;
}
.vq-table tbody td {
  padding: 16px;
  border-bottom: 1px solid var(--vq-border-row);
  color: var(--vq-text-secondary);
  vertical-align: middle;
}
/* La primera celda es el identificador de la fila: se lee como título, no como dato. */
.vq-table tbody td:first-child {
  font-weight: 600;
  color: var(--vq-text);
}
.vq-table tbody tr {
  transition: background-color 0.2s ease;
}
.vq-table tbody tr:hover {
  background: var(--vq-row-hover);
}
/* Celda numérica. Mono + tabular para que los importes en B/. alineen por la coma; a la
   derecha porque una columna de dinero se compara por la unidad, no por el primer dígito.
   Fuera de una tabla .vq-num sólo cambia la fuente (sin alinear). */
.vq-num {
  font-family: var(--vq-font-mono);
  font-feature-settings: 'tnum' 1;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
.vq-table th.vq-num,
.vq-table td.vq-num {
  text-align: right;
  font-size: 14px;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   TABS
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--vq-border);
  overflow-x: auto;
  scrollbar-width: none;
}
.vq-tabs::-webkit-scrollbar { display: none; }

.vq-tab {
  flex: none;
  padding: 14px 20px;
  min-height: 48px;
  border: none;
  /* El -1px monta el subrayado sobre la línea del contenedor en vez de dejar dos reglas
     paralelas separadas por un pelo. */
  border-bottom: var(--vq-rule-strong) solid transparent;
  margin-bottom: -1px;
  background: none;
  color: var(--vq-text-label);
  font-family: var(--vq-font-mono);
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--vq-tracking-mono);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.18s ease, border-color 0.18s ease;
}
.vq-tab:not(:disabled):hover { color: var(--vq-text); }
/* Activo por cualquiera de las tres vías que usan las apps hoy. El subrayado es
   --vq-accent-text: una línea de 2px es contenido no textual y necesita 3:1, cosa que el
   volt no da sobre el blanco del admin. */
.vq-tab[aria-selected='true'],
.vq-tab[aria-current='page'],
.vq-tab[data-active='true'],
.vq-tab--active {
  color: var(--vq-text);
  border-bottom-color: var(--vq-accent-text);
}

/* ═══════════════════════════════════════════════════════════════════════════════
   METER — barra de progreso/ocupación (cupo del job, batería, avance del formulario)
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-meter {
  height: 6px;
  width: 100%;
  background: var(--vq-neutral-tint);
  border-radius: var(--vq-radius-pill);
  overflow: hidden;
}
/* El ancho se pasa por --vq-meter-value (style="--vq-meter-value:64%") o con un width
   inline, que gana por ser una declaración de estilo. El inset de 1px es el mismo reborde
   oliva del botón primario: sin él, en tema claro un relleno volt sobre una pista gris
   clara es 1.05:1 y la barra no se lee. */
.vq-meter__fill {
  display: block;
  height: 100%;
  width: var(--vq-meter-value, 0%);
  background: var(--vq-accent);
  box-shadow: inset 0 0 0 1px var(--vq-accent-border);
  transition: width 0.35s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.vq-meter__fill--warn   { background: var(--vq-warn);   box-shadow: none; }
.vq-meter__fill--danger { background: var(--vq-danger); box-shadow: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   MODAL
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  overscroll-behavior: contain;
}
.vq-modal__scrim {
  position: absolute;
  inset: 0;
  background: var(--vq-scrim);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.vq-modal__panel {
  position: relative; /* por encima del scrim sin necesidad de z-index */
  width: min(520px, 100%);
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  padding: clamp(24px, 3vw, 38px);
  background: var(--vq-elevated);
  border: 1px solid var(--vq-border-input);
  border-radius: var(--vq-radius);
  box-shadow: var(--vq-shadow-pop);
  animation: vq-rise 0.22s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
/* Uso sobre <dialog>: el elemento nativo trae borde, padding y un ancho máximo propios que
   hay que desarmar; el velo lo pinta ::backdrop y entonces no hace falta .vq-modal__scrim. */
dialog.vq-modal {
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  border: none;
  background: transparent;
  color: var(--vq-text);
}
dialog.vq-modal::backdrop {
  background: var(--vq-scrim);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
dialog.vq-modal:not([open]) { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════════
   ESTRUCTURA
   ═══════════════════════════════════════════════════════════════════════════════ */
/* EL MOTIVO CARACTERÍSTICO DEL SISTEMA. En vez de darle borde a cada celda (que produce
   2px donde dos celdas se tocan, y encima desalineados en los bordes exteriores), se pinta
   el CONTENEDOR del color de la línea y se dejan 1px de gap entre celdas opacas. Resultado:
   todas las divisiones miden exactamente 1px y el rectángulo exterior cierra perfecto.
   Requisito: las celdas tienen que ser opacas, por eso el fondo va forzado en los hijos
   directos. Corolario: NO metas .vq-card dentro de una .vq-hairline-grid — la tarjeta trae
   su propio borde y verías la línea doble que todo esto evita.
   Palancas: --vq-grid-min (ancho mínimo de celda), --vq-grid-cols (para forzar el número
   de columnas) y --vq-grid-pad. */
.vq-hairline-grid {
  display: grid;
  grid-template-columns: var(--vq-grid-cols, repeat(auto-fit, minmax(var(--vq-grid-min, 280px), 1fr)));
  gap: 1px;
  background: var(--vq-border-hairline);
  border: 1px solid var(--vq-border-hairline);
}
.vq-hairline-grid > * {
  background: var(--vq-surface);
  padding: var(--vq-grid-pad, clamp(20px, 2.4vw, 32px));
  min-width: 0; /* si no, un <pre> o una tabla ancha estira la columna y rompe la grilla */
}

.vq-rule {
  height: 1px;
  border: none;
  margin: 0;
  background: var(--vq-border);
}
/* Regla de 2px que encabeza los pasos numerados de la landing. Va en --vq-accent-text por
   lo mismo que el subrayado de las tabs. */
.vq-rule--accent {
  height: var(--vq-rule-strong);
  background: var(--vq-accent-text);
}

/* Franja de peligro volt/tinta. Es señalética de obra: se usa para marcar zonas de acción
   irreversible (cancelar un viaje, revertir un asiento) o el borde de un bloque de aviso.
   Ink y volt son literales de marca en los DOS temas, así que se lee igual en claro. */
.vq-hazard {
  height: var(--vq-hazard-size, 8px);
  width: 100%;
  background-image: repeating-linear-gradient(
    45deg,
    var(--vq-accent) 0 10px,
    var(--vq-ink) 10px 20px
  );
}

/* ═══════════════════════════════════════════════════════════════════════════════
   MARCA
   La cadena visible es VOLQTECH: estas clases sólo ponen la forma, nunca el texto.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-logo {
  display: inline-flex;
  align-items: center;
  gap: 12px;
}
/* El isotipo de la marca: una caja enmascarada con su silueta, que escala por --vq-logo-size
   y se colorea por --vq-logo-fill (volt en oscuro, tinta en claro). */
.vq-logo-mark {
  width: var(--vq-logo-size, 24px);
  height: var(--vq-logo-size, 24px);
  flex: none;
  background: var(--vq-logo-fill);
  /* La forma sale de --vq-logo-mask (el isotipo real) y el color de --vq-logo-fill. Con
     máscara y no con `background-image`: así el mismo dibujo se pinta volt en oscuro y tinta
     en claro sin dos archivos. El prefijo -webkit- es por iOS viejo, que el conductor usa. */
  -webkit-mask: var(--vq-logo-mask) center / contain no-repeat;
  mask: var(--vq-logo-mask) center / contain no-repeat;
}
/* OJO: sin text-transform. El markup ya trae "VOLQTECH" en mayúsculas y meter un transform
   acá cambiaría lo que devuelve innerText() en cualquier aserción sobre la marca. */
.vq-wordmark {
  font-family: var(--vq-font-sans);
  font-size: var(--vq-wordmark-size, 17px);
  font-weight: 800;
  font-stretch: var(--vq-stretch-heading);
  letter-spacing: var(--vq-tracking-brand);
  line-height: 1;
  color: var(--vq-text);
  text-transform: none;
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   ESTADOS DE CARGA Y VACÍO
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 48px 24px;
  border: 1px dashed var(--vq-border);
  border-radius: var(--vq-radius);
  text-align: center;
  color: var(--vq-text-muted);
}
.vq-empty__title {
  font-family: var(--vq-font-sans);
  font-size: 17px;
  font-weight: 700;
  font-stretch: var(--vq-stretch-sub);
  letter-spacing: var(--vq-tracking-tight);
  color: var(--vq-text);
}
.vq-empty__body {
  max-width: 46ch;
  font-size: 14px;
  line-height: 1.55;
  color: var(--vq-text-muted);
  text-wrap: pretty;
}

.vq-spinner {
  display: inline-block;
  width: var(--vq-spinner-size, 18px);
  height: var(--vq-spinner-size, 18px);
  flex: none;
  border: 2px solid var(--vq-border-button);
  border-top-color: var(--vq-accent-text);
  border-radius: 50%;
  animation: vq-spin 0.7s linear infinite;
  /* Las dos declaraciones de abajo pisan a las dos de arriba SÓLO si el motor entiende
     color-mix; si no, se descartan y quedan las de token. Con currentColor el spinner se
     adapta al contexto: dentro de un .vq-btn--primary sale tinta sobre volt, dentro de un
     secundario sale papel — sin necesidad de un modificador por cada caso. */
  border-color: color-mix(in srgb, currentColor 22%, transparent);
  border-top-color: currentColor;
}

.vq-skeleton {
  display: block;
  min-height: var(--vq-skeleton-h, 1em);
  border-radius: var(--vq-radius);
  background-image: linear-gradient(
    90deg,
    var(--vq-neutral-tint),
    var(--vq-hover-wash),
    var(--vq-neutral-tint)
  );
  background-size: 200% 100%;
  animation: vq-shimmer 1.4s linear infinite;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   FOCO
   El .dc.html no tiene NINGÚN estilo de foco: sólo hover. Es un agujero de accesibilidad,
   no una decisión de diseño, así que acá se agrega. :focus-visible y no :focus para no
   ponerle un anillo al botón que acaba de recibir un toque en el celular.
   ═══════════════════════════════════════════════════════════════════════════════ */
.vq-btn:focus-visible,
.vq-input:focus-visible,
.vq-select:focus-visible,
.vq-textarea:focus-visible,
.vq-segmented__option:focus-visible,
.vq-checkbox:focus-visible,
.vq-toggle:focus-visible,
.vq-tab:focus-visible,
.vq-chip:focus-visible,
.vq-modal__panel:focus-visible {
  /* El outline transparente es para el modo de alto contraste de Windows, que descarta los
     box-shadow y dejaría el control sin ninguna marca de foco. */
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: var(--vq-focus-ring);
}
/* Controles con relleno volt: el anillo del token TAMBIÉN es volt, o sea que sobre el
   relleno no se ve nada. Se le suma un outline sólido separado 2px, cuyo hueco deja ver el
   lienzo real (sea el que sea) y recorta la silueta del control contra el fondo. */
.vq-btn--primary:focus-visible,
.vq-segmented__option[aria-pressed='true']:focus-visible,
.vq-segmented__option[data-selected]:focus-visible,
.vq-checkbox:checked:focus-visible,
.vq-toggle:checked:focus-visible,
.vq-toggle[aria-pressed='true']:focus-visible {
  /* Re-atado obligatorio: sin esto el box-shadow del token (volt@55%) rellena el hueco de
     2px que outline-offset abre, y el indicador se derrite en el propio relleno volt. */
  --vq-focus-ring: 0 0 0 2px var(--vq-on-accent);
  outline: 2px solid var(--vq-accent-text);
  outline-offset: 2px;
}
/* Dentro de una tarjeta acentuada el --vq-accent-text está re-atado a tinta, así que el
   outline de arriba sale tinta sobre volt: correcto sin ninguna regla extra. */

/* ═══════════════════════════════════════════════════════════════════════════════
   UTILIDADES
   ═══════════════════════════════════════════════════════════════════════════════ */
/* Entrada de la landing: sube y aparece. Se le puede meter retardo por instancia con
   style="animation-delay:.12s", como hace el panel del héroe del diseño. */
.vq-rise {
  animation: vq-rise 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

/* ⚠️ CONTRATO E2E. Playwright compara con innerText(), que devuelve el texto RENDERIZADO:
   un text-transform:uppercase le convierte "Paso 3 de 9" en "PASO 3 DE 9" y el test se cae
   sin que cambie una sola cadena del código. Cualquier nodo que contenga una de las
   cadenas bajo contrato —"Paso N de 9", "✓ Cargado", "Continuar", "Enviar solicitud",
   "Solicitud en revisión", "Solicitud de incorporación", "+ Agregar préstamo"— lleva
   .vq-verbatim si además usa una primitiva que pone versalitas (.vq-eyebrow, .vq-badge,
   .vq-tab, .vq-t-label, .vq-segmented__option).
   El !important es deliberado: esto no es estilo, es una salvaguarda.
   (Estrictamente, el único que HOY rompe un test es el indicador de paso, porque es el que
   se lee con innerText(); las demás se listan igual, porque la próxima aserción que alguien
   escriba con innerText() no va a venir a leer este comentario primero.) */
.vq-verbatim,
.vq-eyebrow--verbatim,
.vq-badge--verbatim,
.vq-tab--verbatim {
  text-transform: none !important;
}
/* El tracking de versalitas mono abre demasiado un texto en caja mixta; se afloja, pero sin
   !important para que se pueda ajustar por caso. */
.vq-verbatim,
.vq-eyebrow--verbatim,
.vq-badge--verbatim,
.vq-tab--verbatim {
  letter-spacing: 0.04em;
}

/* Sólo para lectores de pantalla (etiqueta de un botón de icono, leyenda de un grupo). */
.vq-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════════════════════
   MOVIMIENTO REDUCIDO
   Se limita a los nodos con clase vq-* para no pisar lo que cada app ya haya decidido
   sobre su propio movimiento. Las transiciones no se ponen en 0s sino en 0.01ms: así los
   eventos transitionend siguen disparando y no se cuelga ninguna máquina de estados que
   los esté esperando.
   ═══════════════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  [class^='vq-'],
  [class*=' vq-'],
  [class^='vq-']::before,
  [class^='vq-']::after,
  [class*=' vq-']::before,
  [class*=' vq-']::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  /* Excepción razonada: el spinner NO comunica nada si se congela, y una rotación uniforme
     sobre su propio eje no dispara el malestar vestibular que la preferencia busca evitar
     (eso lo causan los desplazamientos y los zooms grandes). Se deja girando, más lento. */
  .vq-spinner {
    animation: vq-spin 1.4s linear infinite !important;
  }
  /* El skeleton sí se congela: su brillo es puro adorno. Se deja un poco apagado para que
     siga leyéndose como "esto todavía no es contenido". */
  .vq-skeleton {
    animation: none !important;
    opacity: 0.7;
  }
}
