/* ═══════════════════════════════════════════════════════════════════════════
   Hub de Conocimiento VITTA — sistema de diseño
   Extraído del prototipo `Hub Aula Vitta Retail.dc.html`. Los valores no son
   aproximaciones: son los mismos del original.
   ═══════════════════════════════════════════════════════════════════════════ */

/*
 * TOKENS — Y LA FRONTERA ENTRE LA MARCA Y EL PRODUCTO
 *
 * Ninguna vista debe volver a escribir un `#hex`. Si un color no está acá, no
 * existe. Los nombres son semánticos a propósito (`--v-surface`, no
 * `--v-blanco`): es lo que permite que el modo oscuro sea un remapeo de 20
 * líneas en vez de una segunda hoja de estilos.
 *
 * ┌─ DOS ARCHIVOS Y UNA FRONTERA ────────────────────────────────────────────┐
 *
 * Hasta este cambio los hex de VITTA vivían acá mismo, mezclados con la
 * semántica. La capa de tokens estaba —y su guarda también—, pero faltaba la
 * frontera: montar la escuela de otro cliente era EDITAR ESTE ARCHIVO.
 *
 *   PRIMITIVAS  `public/css/marca-{instalación}.css`, prefijo `--m-`
 *               La marca. La define el CLIENTE. Cambia por instalación.
 *
 *   SEMÁNTICA   este bloque, prefijo `--v-`
 *               Qué es fondo, qué es tinta, qué es borde, qué es un estado.
 *               La define el PRODUCTO. El cliente no la puede tocar.
 *
 * El resultado es el que se buscaba: un cliente puede cambiar su color sin
 * poder romper el contraste del sistema, porque los mínimos, los pares medidos
 * y los estados no están de su lado.
 *
 * ⚠️ REGLA DURA: `var(--m-…)` sólo puede aparecer DENTRO de este `:root` y del
 * bloque PAPEL. Un componente que lea una primitiva directo se saltó la
 * frontera y `tools/tokens-css.php` lo rechaza.
 *
 * ⚠️ Lo que NO es marca, y por eso está acá y no allá:
 *
 *   · Los ESTADOS (ok, aviso, peligro, info). Chevrolet no define un rojo de
 *     error y ninguna marca lo hace: verde/ámbar/rojo son convención de
 *     interfaz, no identidad. Si cada cliente los eligiera, cada cliente
 *     tendría que inventar cuatro colores distinguibles entre sí y legibles
 *     sobre su propia superficie. Eso es justo lo que la frontera evita.
 *   · Los colores de TERCEROS (Teams, LinkedIn, el visor de PDF de Chrome).
 *     Son la marca de otro.
 *   · Los TIPOS DE DOCUMENTO del banco. Son una taxonomía funcional —seis
 *     tonos que hay que poder distinguir entre sí—, no identidad.
 *   · La GEOMETRÍA: radios, espaciado, y la parte no cromática de las sombras.
 *   · Los velos y las sombras NEUTRAS: negro con opacidad no es de nadie.
 *
 * └──────────────────────────────────────────────────────────────────────────┘
 *
 * UN TEMA, UNA DECLARACIÓN.
 *
 * Hasta acá la paleta oscura estaba escrita DOS VECES: un bloque
 * `@media (prefers-color-scheme: dark)` y un `:root[data-theme="dark"]` con las
 * mismas 24 declaraciones copiadas. Cualquier ajuste había que hacerlo dos
 * veces, y el día que alguien hiciera una sola los dos caminos al modo oscuro
 * daban resultados distintos. Ese fallo además sólo lo ve quien tiene el
 * sistema en oscuro y nunca tocó el conmutador: nadie del equipo, y sí la
 * mitad de la gente en tienda.
 *
 * `light-dark()` resuelve la rama según el `color-scheme` EFECTIVO del elemento
 * donde el token se USA. Acá arriba se declara `light dark`, que significa
 * «acepto los dos, decide el sistema»; los dos overrides de abajo lo fijan
 * cuando la persona eligió. El conmutador no reescribe ningún color: sólo
 * cambia el atributo, y el navegador vuelve a resolver.
 *
 * ⚠️ Las dos ramas de un `light-dark()` las elige la MARCA, así que viven en el
 * archivo de primitivas. Acá sólo se dice qué rol cumple cada una.
 *
 * ⚠️ PISO DE NAVEGADOR: Chrome 123, Safari 17.5, Firefox 120 — todos de 2024.
 * Es una decisión consciente y se verificó que no chocara con nada: el proyecto
 * NO declara ningún soporte más viejo (no hay `browserslist`, ni `package.json`,
 * ni matriz de compatibilidad en la documentación), y esta misma hoja ya usa
 * `:has()` en `.v-toggle`, que exige Firefox 121 — diciembre de 2023, POSTERIOR
 * a `light-dark()`—. Para Firefox el piso no se mueve. La alternativa era
 * mantener la paleta duplicada para siempre.
 *
 * ⚠️ `light-dark()` es una función de COLOR: no acepta una sombra entera. Por
 * eso `--v-shadow-*` viene partido en geometría (fija) y color (temático). Es
 * lo único que el lenguaje obliga a separar.
 */
:root {

  /* ── Velos y sombras del color profundo ──────────────────────────────────
   *
   * Cada uno dice PARA QUÉ es, no de qué color: el color sale de
   * `--m-profundo-canales`, que declara la marca. Antes los siete tenían el
   * verde de VITTA escrito a mano dentro de la regla del componente, así que en
   * Cars salían verdes sobre una plataforma azul.
   *
   * Van acá y no en la regla porque la frontera lo exige: una primitiva sólo se
   * lee dentro de una declaración de token. Lo verifica `tools/tokens-css.php`,
   * que fue quien encontró el atajo cuando intenté hacerlo al revés. */
  --v-velo-ingreso: linear-gradient(180deg,
    rgba(var(--m-profundo-canales), 0) 0%,
    rgba(var(--m-profundo-canales), .06) 40%,
    rgba(var(--m-profundo-canales), .58) 72%,
    rgba(var(--m-profundo-canales), .90) 100%);
  --v-velo-hero: linear-gradient(90deg,
    rgba(var(--m-profundo-canales), .86) 0%,
    rgba(var(--m-profundo-canales), .35) 60%,
    rgba(var(--m-profundo-canales), 0) 100%);
  --v-velo-banda: linear-gradient(90deg, var(--v-deep) 12%,
    rgba(var(--m-profundo-canales), .86) 45%,
    rgba(var(--m-profundo-canales), .78) 100%);
  --v-velo-tarjeta: linear-gradient(180deg,
    rgba(var(--m-profundo-canales), .30) 0%,
    rgba(var(--m-profundo-canales), .78) 100%);
  --v-sombra-fase:      0 8px 24px rgba(var(--m-profundo-canales), .10);
  --v-capsula-boton:       rgba(var(--m-profundo-canales), .72);
  --v-capsula-boton-hover: rgba(var(--m-profundo-canales), .92);

  color-scheme: light dark;

  /* ── Tipografía ───────────────────────────────────────────────────────── */
  --v-font-body:    var(--m-tipo-cuerpo);
  --v-font-display: var(--m-tipo-titulo);
  --v-font-logo:    var(--m-tipo-logo);

  /* Superficie profunda: el fondo del sidebar y de las tarjetas oscuras. */
  --v-deep:      var(--m-profundo);
  --v-deep-2:    var(--m-profundo-2);   /* segundo tono del degradado y del menú activo */
  --v-line-dark: var(--m-linea-profunda);

  /* Acento y sus derivados. */
  --v-green:   var(--m-acento);          /* acciones, enlaces, foco */
  --v-green-2: var(--m-acento-fuerte);   /* disco de iniciales */
  --v-green-3: var(--m-acento-hover);    /* hover de enlaces */
  /* La chispa del logotipo: el subtítulo del sidebar, la barra del splash y el
     arco alrededor del disco. Siempre sobre superficie profunda. */
  --v-lime:    var(--m-chispa);

  /*
   * TRES ROLES QUE NO TENÍAN NOMBRE, Y AHORA SÍ.
   *
   * `--v-muted` y `--v-green` pintaban DOS superficies distintas con un solo
   * token: papel claro y fondo profundo. En VITTA no se nota porque los dos
   * valores coinciden por casualidad de la paleta —y por eso nunca se midió—,
   * pero es el mismo defecto que esta hoja ya tiene documentado en `--v-border`:
   * un token, dos trabajos.
   *
   * Con una marca que trae UN solo color de acento el defecto deja de ser
   * teórico: el acento sobre su propia superficie profunda da 1,00:1 y el
   * filete del menú activo desaparece.
   *
   * Los valores de VITTA son idénticos a los que ya tenían los tokens
   * originales, así que retail no cambia un píxel. Lo que cambia es que ahora
   * el rol se puede medir y otra marca lo puede elegir.
   */
  --v-muted-on-deep:  var(--m-apagado-profundo);
  --v-accent-on-deep: var(--m-acento-profundo);
  --v-on-accent-deep: var(--m-sobre-acento-profundo);

  /* La placa del logotipo: forma, área de aislamiento y fondo.

     Los tres primeros no son colores, y están en la capa de tokens por el
     mismo motivo que los colores: la forma del emblema la decide la marca.

     El CUARTO se agregó por un defecto visto en producción. La placa era un
     disco BLANCO fijo, y eso sólo tiene sentido cuando la obra de arte es una
     silueta pensada para apoyarse sobre papel. Cuando el emblema YA ES un
     círculo con su propio anillo y su propia transparencia —el de la escuela
     de autos lo es—, el disco blanco no sostiene nada: asoma como un halo
     alrededor del emblema sobre la superficie profunda. Una marca que no
     necesita placa tiene que poder decirlo, y es una decisión suya, no del
     producto. */
  --v-mark-size:   var(--m-marca-tamano);
  --v-mark-radius: var(--m-marca-radio);
  --v-mark-clear:  var(--m-marca-aislamiento);
  --v-mark-bg:     var(--m-marca-placa);
  /* Las otras dos placas. Vivían clavadas dentro de `.v-login-mark` y de
     `.v-splash-disc`, así que una marca podía elegir el tamaño de UNA de sus
     tres placas y las otras dos le quedaban fijas. Eso no es un detalle de
     prolijidad: es lo que impedía que una escuela ajustara la placa al arte que
     entrega, y un arte que llega al filo de su archivo lo NECESITA — si no, la
     placa le dibuja una segunda circunferencia alrededor. Los valores de VITTA
     son los que estaban escritos en las dos reglas. */
  --v-mark-size-login:  var(--m-marca-tamano-ingreso);
  --v-mark-size-splash: var(--m-marca-tamano-splash);
  /* El anillo animado del splash es del PRODUCTO, no de la marca: es la pieza
     que dibuja el arco de carga y su medida está atada al `stroke-dasharray`
     de `.v-splash-arc`. La placa se centra adentro. */
  --v-splash-mark: 188px;

  /* Texto */
  --v-ink:     var(--m-tinta);
  --v-ink-2:   var(--m-tinta-2);
  --v-muted:   var(--m-apagado);
  --v-muted-2: var(--m-apagado-2);
  /* Lo que se apoya sobre un relleno oscuro o saturado. NO se remapea porque
     esas superficies tampoco lo hacen. Vale para texto y también para las
     piezas macizas que van encima —el disco del logotipo, el del splash—. */
  --v-on-deep:      var(--m-sobre-profundo);
  --v-on-deep-soft: var(--m-sobre-profundo-suave);

  /* Superficies */
  --v-bg:      var(--m-fondo);
  --v-surface: var(--m-superficie);
  --v-border:  var(--m-borde);
  --v-fill:    var(--m-relleno);
  --v-fill-2:  var(--m-relleno-2);
  --v-fill-3:  var(--m-relleno-3);  /* casilla marcada, realce suave */
  --v-mine-bg: var(--m-realce);     /* fila propia del ranking, lección activa, tag «Nuevo» */

  /*
   * ESTADOS — del PRODUCTO, no de la marca.
   *
   * Verde es «va bien», ámbar es «ojo» y rojo es «se rompió» en cualquier
   * interfaz del mundo, con la marca que sea. Ningún brandbook define un rojo
   * de error: el de Chevrolet, por ejemplo, tiene cinco colores y ninguno lo
   * es. Dejarlos de este lado de la frontera significa que una instalación
   * nueva hereda cuatro estados ya medidos contra sus superficies en vez de
   * tener que inventarlos.
   *
   * El costo, dicho: en una instalación cuya marca no es verde, la insignia de
   * «certificado» sigue siendo verde. Es el precio de que el color signifique
   * lo mismo en todas las escuelas, y se paga a propósito.
   */
  --v-ok-bg:     light-dark(#E1EFE9, #10352C);
  /* Segundo tono de la superficie de éxito. Existe por UN degradado —el
     mosaico del icono de la evaluación práctica— y va como token igual,
     porque el par estaba escrito a mano con `#E1EFE9` de un lado y
     `#CFE6DC` del otro: en oscuro el primero se remapeaba y el segundo no,
     y el degradado terminaba yendo de verde oscuro a verde claro. */
  --v-ok-bg-2:   light-dark(#CFE6DC, #0C2A22);
  --v-ok-fg:     light-dark(#0B6B58, #6FD2AE);
  --v-warn-bg:   light-dark(#FBF1D8, #3A2E12);
  --v-warn-fg:   light-dark(#A8790F, #E5B54B);
  --v-warn-solid:#C98A14;
  --v-danger-bg: light-dark(#FBEAEA, #3A1A1A);
  --v-danger-fg: light-dark(#A32020, #F09A9A);
  --v-danger-border: light-dark(#F0C9C9, #5C2A2A);
  --v-info-bg:   light-dark(#E6EFF6, #162C3C);
  --v-info-fg:   light-dark(#1F5F8B, #8FC2E8);
  /* Marcas de terceros. No se remapean en oscuro porque el logo ajeno tampoco,
     y no son primitivas porque no son de esta casa: son de Microsoft y de
     LinkedIn, y valen lo mismo en todas las instalaciones. */
  --v-teams:     #4B53BC;
  --v-linkedin:  #0A66C2;

  /* Tipos de documento del banco. Taxonomía funcional: seis tonos que hay que
     poder distinguir entre sí de un vistazo. No son identidad. */
  --v-type-pdf:      #14A07A;
  --v-type-video:    #5C6B67;
  --v-type-onepager: #1F5F8B;
  --v-type-vr360:    #0B6B58;
  --v-type-tool:     #8A5A3C;
  --v-type-link:     #7E938E;

  /* Gris del visor nativo de PDF de Chrome y Edge. La caja que envuelve al
     iframe lo copia para que no se vea un marco claro alrededor del documento
     mientras carga. No se remapea en oscuro: es el color del visor, no nuestro. */
  --v-visor-bg: #525659;

  /* Sombras y velos — tokenizados porque en oscuro un negro al 18 % desaparece.
     `light-dark()` sólo acepta colores, así que la sombra va partida: la
     geometría no cambia con el tema y el color sí. Son tres nombres más de los
     que nos gustaría, y es el lenguaje quien los pide, no el diseño.
     La sm y la lg son negro puro con opacidad: no son de nadie. La md lleva el
     tinte de la marca y por eso viene de las primitivas. */
  --v-shadow-color-sm: light-dark(rgba(0, 0, 0, .18), rgba(0, 0, 0, .5));
  --v-shadow-color-md: var(--m-sombra-media);
  --v-shadow-color-lg: light-dark(rgba(0, 0, 0, .22), rgba(0, 0, 0, .6));
  --v-shadow-sm:  0 2px 8px var(--v-shadow-color-sm);
  --v-shadow-md:  0 8px 24px var(--v-shadow-color-md);
  --v-shadow-lg:  0 16px 50px var(--v-shadow-color-lg);
  --v-scrim:      light-dark(rgba(16, 26, 36, .5), rgba(0, 0, 0, .66));
  --v-ring:       var(--m-halo);
  /* Arranque del halo que late en la puerta al canje. Va como token y no
     suelto en la animación por la misma razón que `--v-ring`: es el color de
     marca con opacidad, y el día que la marca cambie tiene que cambiar en el
     archivo de primitivas y no en un `@keyframes` perdido a mitad de la hoja. */
  --v-halo-canje: var(--m-halo-latido);
  --v-scrollbar:  var(--m-barra);
  /* Aro del avatar. Es un color FIJO metido en una superficie TEMÁTICA —el
     encabezado—, así que tiene que ser token: en claro separa la foto del
     blanco, en oscuro un blanco al 90 % se convierte en un halo que grita.
     Blanco con opacidad: neutro, del producto. */
  --v-avatar-ring: light-dark(rgba(255, 255, 255, .9), rgba(255, 255, 255, .22));

  --v-radius:    14px;
  --v-radius-sm: 10px;

  /* Escala de espaciado. Cuatro valores, no doce: las vistas venían usando
     10/12/14/16/18/22/24/26/28/30 px sin criterio, y esa dispersión es
     precisamente lo que hace que dos tarjetas nunca se vean hermanas. */
  --v-sp-1: 6px;
  --v-sp-2: 10px;
  --v-sp-3: 14px;
  --v-sp-4: 18px;
  --v-sp-5: 24px;
  --v-sp-6: 32px;
}

/*
 * MODO OSCURO — LOS TRES ESTADOS
 *
 * En tienda con luz baja —bodega, depósito, apertura antes del amanecer— un
 * fondo claro a pantalla completa encandila. Hay tres situaciones y las tres
 * tienen que seguir funcionando:
 *
 *  1. Elección explícita CLARA  → gana sobre un sistema en oscuro.
 *  2. Elección explícita OSCURA → gana sobre un sistema en claro.
 *  3. SIN elección              → manda `prefers-color-scheme`, que es lo que
 *     `color-scheme: light dark` del `:root` ya deja pasar.
 *
 * Acá no se redeclara NI UN COLOR: se cambia `color-scheme` y el navegador
 * vuelve a resolver cada `light-dark()`. Esa es toda la diferencia con la
 * versión anterior, donde el modo oscuro eran 24 declaraciones copiadas dos
 * veces y mantenerlas sincronizadas era una promesa que nadie podía cumplir.
 *
 * ⚠️ El script del `<head>` de los layouts SIEMPRE escribe `data-theme`, y sin
 * elección guardada escribe `light` a propósito —ver la nota en
 * `layouts/app.php`: el verde sobre blanco es la identidad de VITTA y quien
 * entra por primera vez tiene que verla—. O sea: en la práctica el caso 3 sólo
 * corre si el JavaScript no llega a ejecutarse. Sigue estando porque es la
 * única red que queda en ese caso, no porque sea el camino normal.
 *
 * Los verdes de marca NO se tocan en ningún estado: el sidebar sigue siendo el
 * sidebar. Lo que cambia son las superficies y el texto que va sobre ellas.
 */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

/*
 * PAPEL — lo que NO sigue el tema
 *
 * Tres superficies de la plataforma son papel y no pantalla:
 *
 *  · `.v-diploma-sheet` — un diploma se imprime, se adjunta a un correo y se
 *    sube a LinkedIn. Si siguiera el tema, quien tenga el modo oscuro activado
 *    se descargaría una lámina negra con letras claras y la mandaría así a
 *    Talento Humano.
 *  · `.v-capsula iframe` — adentro va un paquete SCORM de un tercero con su
 *    propio HTML claro. El marco tiene que ser el mismo papel que el contenido
 *    o queda una franja oscura alrededor mientras carga.
 *  · `.v-btn-light` — botón claro sobre una superficie oscura FIJA (la tarjeta
 *    de Vity, el visor). Cuando el fondo era `#fff` y el texto `var(--v-ink)`,
 *    en oscuro el token se remapeaba y el botón quedaba blanco sobre blanco:
 *    1,05:1, invisible. Mezclar un color fijo con uno temático en la misma
 *    regla es exactamente así como se rompe.
 *
 * Se REDECLARAN los tokens que ya existen, clavados en su rama clara. No son
 * nombres nuevos: `.v-diploma-title` sigue diciendo `color: var(--v-ink)` y
 * quien lea esa regla no necesita saber que el diploma es un caso especial.
 * Esto es lo que reemplazó a los 23 `#hex` sueltos que tenía el bloque del
 * diploma, sin inventar una segunda paleta con otro prefijo.
 *
 * `color-scheme: light` acompaña para que lo que dibuja el NAVEGADOR adentro
 * —barras de desplazamiento, controles nativos del paquete SCORM— tampoco se
 * vaya a oscuro.
 */
.v-diploma-sheet,
.v-capsula iframe,
.v-btn-light {
  color-scheme: light;

  /* La marca decide de qué color es SU papel: las `--m-papel-*` son la rama
     clara, clavada, del archivo de primitivas. Este es el único lugar fuera del
     `:root` donde se puede leer una primitiva, y es porque acá se está fijando
     un ámbito de marca, no pintando un componente. */
  --v-surface: var(--m-papel-superficie);
  --v-bg:      var(--m-papel-fondo);

  --v-ink:     var(--m-papel-tinta);
  --v-ink-2:   var(--m-papel-tinta-2);
  --v-muted:   var(--m-papel-apagado);
  --v-border:  var(--m-papel-borde);
  --v-deep:    var(--m-papel-profundo);

  /* Los estados son del producto y valen lo mismo en todas las instalaciones,
     así que acá van literales: es su rama clara. */
  --v-ok-bg:     #E1EFE9;
  --v-ok-fg:     #0B6B58;
  --v-warn-bg:   #FBF1D8;
  --v-warn-fg:   #A8790F;
  --v-danger-bg: #FBEAEA;
  --v-danger-fg: #A32020;
}

/*
 * ÁMBITO PROFUNDO — lo que cambia de significado sobre un fondo saturado
 *
 * Misma técnica que el bloque PAPEL de arriba, y por el mismo motivo: hay
 * tokens que hacen DOS trabajos distintos según dónde caigan, y el lugar
 * correcto para elegir cuál es un ámbito, no sesenta reglas de componente.
 *
 * ┌─ El defecto que esto arregla ────────────────────────────────────────────┐
 *
 * `--v-muted` es el gris de apoyo sobre papel claro. Pero `.v-nav a`,
 * `.v-sidebar-role`, `.v-brand-tag`, `.v-level-name` y cualquier `.v-meta` que
 * caiga dentro de una tarjeta oscura lo usan SOBRE EL FONDO PROFUNDO. Es
 * matemáticamente imposible que un mismo color llegue a 4,5:1 contra blanco y
 * contra un fondo profundo: para lo primero su luminancia tiene que estar por
 * debajo de 0,18 y para lo segundo por encima de 0,43.
 *
 * En VITTA el par medía 3,81:1 y nadie lo sabía, porque el par no estaba en la
 * lista de `tools/contraste.php`. Ahora está —en `DESCUBIERTOS`, que es su
 * lugar: es deuda anterior a este cambio, no deuda de este cambio—.
 *
 * `--v-green` tiene el mismo problema al revés: `.v-brand-rule`, el filete del
 * menú activo, `.v-nav-tag` y el relleno de `.v-level-bar` lo apoyan SOBRE la
 * superficie profunda. Con una marca de un solo color de acento —Chevrolet trae
 * un azul y nada más— el acento sobre su propia superficie da 1,00:1 y el
 * filete del menú activo, que es la única señal de dónde estás parado,
 * literalmente desaparece.
 *
 * └──────────────────────────────────────────────────────────────────────────┘
 *
 * ⚠️ VITTA no cambia un píxel: `--v-muted-on-deep` y `--v-accent-on-deep` valen
 * en `marca-vitta.css` exactamente lo mismo que `--v-muted` y `--v-green`.
 * Redeclarar un token con su propio valor es un no-op medible.
 */
.v-sidebar,
.v-card-dark,
.v-card-gradient {
  --v-muted: var(--v-muted-on-deep);
  --v-green: var(--v-accent-on-deep);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* `hidden` debe ganarle a cualquier `display` de componente: es el interruptor
   de los diálogos y no puede depender del orden de las reglas. */
[hidden] { display: none !important; }

body {
  font-family: var(--v-font-body);
  color: var(--v-ink);
  background: var(--v-bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--v-green); text-decoration: none; }
a:hover { color: var(--v-green-3); }
input, button, select, textarea { font-family: inherit; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--v-scrollbar); border-radius: 8px; border: 2px solid var(--v-bg);
}

/* ── Foco visible ────────────────────────────────────────────────────────────
 * Antes de esto, el único `:focus` de toda la hoja estaba en `.v-input`.
 * Navegar con teclado era navegar a ciegas: el foco existía pero no se veía.
 *
 * `:focus-visible` y no `:focus`: el aro aparece para quien navega con teclado
 * y no cada vez que alguien toca un botón con el dedo.
 */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--v-green);
  outline-offset: 2px;
  border-radius: 4px;
}
/* Sobre el sidebar y las tarjetas oscuras el verde de marca no despega del
   fondo. Ahí el aro se dibuja en blanco. */
.v-sidebar :where(a, button):focus-visible,
.v-card-dark :where(a, button):focus-visible,
.v-card-gradient :where(a, button):focus-visible,
.v-drawer-head :where(a, button):focus-visible {
  outline-color: var(--v-on-deep);
}
/* Los controles con radio propio no deben recuadrarse con esquinas rectas. */
.v-btn:focus-visible, .v-chip:focus-visible { border-radius: var(--v-radius-sm); }
.v-badge:focus-visible, .v-phase-chip:focus-visible, .v-filter:focus-visible { border-radius: 99px; }

/* ── Salto al contenido ──────────────────────────────────────────────────────
 * Sin esto, llegar al contenido con teclado cuesta ~12 tabulaciones por el
 * menú lateral, en TODAS las páginas.
 */
.v-skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--v-deep); color: var(--v-on-deep); padding: 13px 20px;
  border-radius: 0 0 var(--v-radius-sm) 0; font-weight: 700; font-size: 14px;
}
.v-skip:focus {
  left: 0; color: var(--v-on-deep);
}

/* El destino del salto no debe mostrar aro: no es un control, sólo un ancla. */
.v-content:focus { outline: none; }

/* ── Texto sólo para lector de pantalla ──────────────────────────────────────
 * Hay diferencias que la pantalla comunica con color o con gris: la fila propia
 * del ranking pintada de verde, la insignia todavía no obtenida al 50 % de
 * opacidad. Para quien no ve la pantalla, esa diferencia sencillamente no
 * existe. Acá se la dice con palabras, sin ocupar un píxel.
 */
.v-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

@keyframes erFade   { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes erDrawer { from { transform: translateX(100%); } to { transform: none; } }
@keyframes erPop    { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }

/* Pantalla de carga — mismas curvas y tiempos que el prototipo. */
@keyframes vSplashOut { 0%, 78% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } }
@keyframes vLeafIn    { 0% { opacity: 0; transform: translate(6px, -8px) rotate(-12deg) scale(.85); } 100% { opacity: 1; transform: none; } }
@keyframes vBagIn     { 0% { opacity: 0; transform: translateY(14px); } 100% { opacity: 1; transform: none; } }
@keyframes vRise      { 0% { opacity: 0; transform: translateY(12px); } 100% { opacity: 1; transform: none; } }
@keyframes vBar       { 0% { transform: translateX(-100%); } 100% { transform: translateX(0); } }
@keyframes vBreath    { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }

/* Quien pidió menos movimiento no debería recibir una animación de 2,4 s. */
@media (prefers-reduced-motion: reduce) {
  #v-splash { animation: none !important; opacity: 0; visibility: hidden; }
  #v-splash * { animation: none !important; }
  /* El arco se dibuja con una animación: sin ella se quedaría invisible. */
  .v-splash-arc { stroke-dashoffset: 0; }
}

/* Avatar con foto. Circular, como el prototipo. */
.v-avatar {
  border-radius: 50%; object-fit: cover; object-position: center top;
  flex: 0 0 auto; display: block; background: var(--v-fill);
  /* Aro fino para separar la foto del fondo: sin él, una foto de fondo claro
     sobre el sidebar oscuro se ve como un recorte flotando. Va por token
     porque el avatar del encabezado se apoya en una superficie que SÍ cambia
     con el tema: el mismo blanco al 90 % que en claro apenas se nota, en
     oscuro se lee como un halo alrededor de la cara. */
  border: 2px solid var(--v-avatar-ring);
  box-shadow: var(--v-shadow-sm);
}
/* El sidebar es verde profundo en los dos temas, así que este aro NO se
   remapea: siempre se apoya sobre oscuro. */
.v-avatar-dark { border-color: rgba(255, 255, 255, .22); }

/* Vity va SIN aro. Es un robot claro sobre disco claro, así que el borde
   blanco no aporta contraste: sobre las superficies oscuras se lee como un
   halo alrededor de la carita. La imagen ya trae su propio disco. */
/*
 * Vity va SIN disco. El PNG tiene alfa real: el fondo crema que traía horneado
 * se recortó por difusión desde los bordes, respetando el casco blanco del
 * robot —que un recorte por color se habría comido—. El original quedó en
 * `vity-avatar-original.png`.
 *
 * Nada de `border-radius` acá: ya no hay disco que redondear y un círculo
 * cortaría las orejas y el cuello, que llegan al borde del lienzo.
 */
.v-vity {
  object-fit: contain; flex: 0 0 auto;
  display: block; border: none; box-shadow: none; background: none;
}
/*
 * Iniciales sobre disco verde, para quien todavía no subió foto.
 *
 * El fondo es `--v-green-2` y NO `--v-green`, y la diferencia la encontró
 * `ContrasteHeaderTest` calculando: blanco sobre `--v-green` (#14A07A) da
 * **3,31:1**, por debajo del 4,5:1 que exige un texto de 14 px. Con
 * `--v-green-2` (#0B6B58) sube a 6,44:1 sin salirse de la paleta de marca.
 *
 * Las iniciales son texto, no adorno: son la única pista de a quién pertenece
 * la cuenta abierta cuando la tablet se comparte entre turnos.
 *
 * ⚠️ El mismo par blanco/`--v-green` está en otras ocho reglas, `.v-btn-primary`
 * entre ellas. No se toca acá: cambiar el acento principal es una decisión de
 * marca, no un arreglo de encabezado. Queda reportado.
 */
.v-avatar-fallback {
  border-radius: 50%; flex: 0 0 auto; background: var(--v-green-2); color: var(--v-on-deep);
  display: flex; align-items: center; justify-content: center; font-weight: 800;
}

/* Escala de avatar. Los tamaños venían escritos a mano en el atributo `style`
   —40, 38, 26, 50— y ninguno era el mismo dos veces por casualidad: eran el
   mismo tamaño escrito cuatro veces. `.v-avatar-lg` (76 px) vive con el resto
   de la ficha de cuenta. */
.v-avatar-xs { width: 26px; height: 26px; font-size: 10px; }
.v-avatar-sm { width: 38px; height: 38px; font-size: 14px; }
.v-avatar-md { width: 40px; height: 40px; font-size: 14px; }
/* Vity a dos escalas: el ítem del menú y la cabecera del cajón. El margen
   negativo lo compensa dentro de la fila de navegación, que es más baja. */
.v-vity-xs { width: 26px; height: 26px; margin: -3px 0; }
.v-vity-md { width: 50px; height: 50px; }
/* Un enlace que sólo envuelve una imagen: sin esto el `line-height` heredado
   le agrega unos píxeles fantasma debajo de la foto. */
.v-avatar-link { display: block; line-height: 0; }
.v-block { display: block; }
/* Las burbujas del cajón traen párrafos: el margen del navegador les abre un
   hueco arriba que rompe el globo. Se apaga acá y no en cada `<p>`, porque
   varios los escribe el JS al recibir la respuesta. */
.v-msg-bot > p, .v-msg-user > p { margin: 0; }
.v-msg-bot > p + p, .v-msg-bot > p:not(:last-child) { margin-bottom: 10px; }

/* ── Tipografía de titulares ─────────────────────────────────────────────── */
.v-kicker {
  font-family: var(--v-font-display); font-size: 12px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--v-green); font-weight: 700; margin-bottom: 6px;
}
.v-h1 { font-family: var(--v-font-display); font-weight: 800; font-size: 34px; text-transform: uppercase; margin: 0; }
.v-h1-lg { font-size: 38px; line-height: 1; }
/* Cuatro pasos de titular, no ocho. Las fichas de documento, el visor y la
   verificación pública venían con 30, 28 y 24 px escritos a mano en la vista:
   tres tamaños distintos para el mismo nivel jerárquico. */
.v-h1-sm { font-size: 28px; line-height: 1.1; }
.v-h1-xs { font-size: 24px; line-height: 1.15; }
.v-h2 { font-family: var(--v-font-display); font-weight: 700; font-size: 19px; text-transform: uppercase; margin: 0; }
.v-h3 { font-family: var(--v-font-display); font-weight: 700; font-size: 17px; text-transform: uppercase; margin: 0; }
.v-lead { margin: 10px 0 0; font-size: 15px; color: var(--v-ink-2); max-width: 820px; }

/* ── Estructura ──────────────────────────────────────────────────────────── */
.v-shell { display: flex; min-height: 100vh; width: 100%; background: var(--v-bg); }

.v-sidebar {
  width: 264px; flex: 0 0 auto; background: var(--v-deep);
  display: flex; flex-direction: column; padding: 26px 18px;
  position: sticky; top: 0; height: 100vh;
}
.v-brand { padding: 4px 10px 26px; }
.v-brand-link { display: flex; align-items: center; gap: 13px; }
/*
 * La placa del logotipo, con su forma y su área de aislamiento.
 *
 * Los tres valores salen de las primitivas porque son de la MARCA, no del
 * producto. Un disco de 56 px sin margen interno es la forma correcta para el
 * logotipo de VITTA, que es redondo; el moño de Chevrolet es horizontal y su
 * brandbook exige un área de aislamiento alrededor. Meter esa decisión en una
 * regla de componente es garantizar que el próximo cliente tenga que editar la
 * hoja del producto para que su marca respire.
 *
 * `box-sizing: border-box` es global, así que el margen interno se descuenta
 * del tamaño y la placa no crece.
 *
 * ⚠️ Las dimensiones del `<img>` viven en `partials/sidebar.php` como atributos
 * de HTML, y son 46×46. La obra de arte de otra marca tiene que entregarse
 * cuadrada y con su aislamiento YA INCLUIDO en el archivo, o cambiar esos dos
 * atributos. Está dicho en `public/img/marca/cars/LEEME.md`.
 */
.v-brand-mark {
  width: var(--v-mark-size); height: var(--v-mark-size); flex: 0 0 auto;
  border-radius: var(--v-mark-radius); padding: var(--v-mark-clear);
  background: var(--v-mark-bg);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .18);
}
.v-brand-mark img { display: block; }
.v-brand-name {
  display: block; font-family: var(--v-font-logo); font-weight: 700;
  font-size: 24px; color: var(--v-on-deep); line-height: 1;
}
.v-brand-sub {
  display: block; font-family: var(--v-font-logo); font-weight: 600;
  font-size: 9px; letter-spacing: .42em; color: var(--v-lime); margin-top: 3px;
}
.v-brand-rule { width: 20px; height: 2px; background: var(--v-green); flex: 0 0 auto; }
.v-brand-tag {
  font-family: var(--v-font-display); font-weight: 700; font-size: 10px;
  letter-spacing: .24em; text-transform: uppercase; color: var(--v-muted);
}

.v-nav { display: flex; flex-direction: column; gap: 4px; }
/* El botón de Vity comparte todo con los enlaces menos el elemento: se estilan
   juntos para que nadie note la diferencia salvo el teclado, que sí debe. */
.v-nav a, .v-nav-action {
  display: flex; align-items: center; gap: 12px; padding: 11px 14px;
  border-radius: var(--v-radius-sm); font-size: 14px; font-weight: 600;
  color: var(--v-muted); transition: background .15s, color .15s;
}
.v-nav-action {
  background: none; border: none; width: 100%; text-align: left;
  font-family: inherit; cursor: pointer;
}
.v-nav a:hover, .v-nav-action:hover { background: rgba(255, 255, 255, .06); color: var(--v-on-deep); }
/* Verde profundo + barra interna, como el prototipo. Un bloque verde sólido
   pesa demasiado y compite con el acento de las acciones. */
.v-nav a.is-active {
  background: var(--v-deep-2); color: var(--v-on-deep); font-weight: 700;
  box-shadow: inset 3px 0 0 var(--v-green);
}
.v-nav a.is-active:hover { background: var(--v-deep-2); color: var(--v-on-deep); }
/* El fondo lo resuelve el ámbito profundo (`--v-green` ya está remapeado acá
   dentro), pero la TINTA de encima no puede: `--v-on-deep` es lo que se lee
   sobre la superficie, no sobre el acento. Con un acento dorado, blanco encima
   da 1,74:1. Por eso este es el único rótulo de la hoja con token propio. */
.v-nav-tag {
  margin-left: auto; font-family: var(--v-font-display); font-size: 10px;
  font-weight: 700; letter-spacing: .08em; background: var(--v-green);
  color: var(--v-on-accent-deep); padding: 2px 7px; border-radius: 6px;
}
.v-nav-sep { height: 1px; border: none; background: var(--v-line-dark); margin: 14px 6px 10px; }

.v-sidebar-foot {
  margin-top: auto; border-top: 1px solid var(--v-line-dark); padding-top: 16px;
  display: flex; align-items: center; gap: 12px;
}
.v-sidebar-name { display: block; font-size: 13px; font-weight: 700; color: var(--v-on-deep); }
.v-sidebar-role { display: block; font-size: 11px; color: var(--v-muted); }
.v-sidebar-out { color: var(--v-muted); flex: 0 0 auto; }
.v-sidebar-out:hover { color: var(--v-on-deep); }

.v-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/*
 * El encabezado es una SUPERFICIE, no un rectángulo blanco.
 *
 * Acá decía `background: #fff` y era el único color fijo que quedaba en la
 * barra superior. El efecto medido en producción: en tema claro el header era
 * `rgb(255,255,255)` y el cuerpo `rgb(244,247,245)` —coherente—; al pasar a
 * oscuro el cuerpo se iba a `rgb(10,25,23)` y el header **se quedaba en blanco
 * puro**, flotando sobre una aplicación negra. No es un detalle: es la primera
 * franja que se ve al entrar y la que sostiene el título, el buscador y el
 * avatar.
 *
 * `--v-surface` es el token que ya remapea (#FFFFFF → #122622): es la misma
 * superficie que usan las tarjetas, y el encabezado es exactamente eso.
 */
.v-header {
  height: 74px; flex: 0 0 auto; background: var(--v-surface); border-bottom: 1px solid var(--v-border);
  display: flex; align-items: center; gap: 20px; padding: 0 34px; position: sticky; top: 0; z-index: 20;
}
.v-page-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 21px;
  text-transform: uppercase; letter-spacing: .02em;
}
.v-content { flex: 1; overflow-y: auto; padding: 36px 40px 56px; }
.v-container { max-width: 1240px; margin: 0 auto; animation: erFade .3s ease; }
.v-container-narrow { max-width: 1040px; margin: 0 auto; animation: erFade .3s ease; }

/* ── Tarjetas ────────────────────────────────────────────────────────────── */
.v-card {
  background: var(--v-surface); border: 1px solid var(--v-border);
  border-radius: var(--v-radius); padding: 24px 26px;
}
.v-card-dark { background: var(--v-deep); color: var(--v-on-deep); border: none; border-radius: var(--v-radius); padding: 24px 26px; }
.v-card-gradient {
  background: linear-gradient(135deg, var(--v-deep) 0%, var(--v-deep-2) 100%);
  color: var(--v-on-deep); border: none; border-radius: var(--v-radius); padding: 28px 30px;
}

.v-kpi-label {
  font-family: var(--v-font-display); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--v-muted); font-weight: 700;
}
.v-kpi-value {
  font-family: var(--v-font-display); font-weight: 800; font-size: 56px;
  line-height: 1; margin: 10px 0 2px; letter-spacing: -2px;
}
.v-kpi-note { font-size: 13px; color: var(--v-ink-2); }

/* ── Barras de progreso ──────────────────────────────────────────────────── */
.v-bar { height: 8px; background: var(--v-fill); border-radius: 99px; overflow: hidden; }
.v-bar-sm { height: 6px; }
.v-bar > span { display: block; height: 100%; background: var(--v-green); border-radius: 99px; }
/* El color comunica el estado sin leer la etiqueta. */
.v-bar.is-certified > span { background: var(--v-green-2); }
.v-bar.is-progress  > span { background: var(--v-warn-solid); }
.v-bar.is-locked    > span { background: var(--v-muted-2); }

/* ── Insignias de estado ─────────────────────────────────────────────────── */
.v-badge {
  font-size: 11px; font-weight: 700; padding: 4px 10px; border-radius: 99px;
  display: inline-block; white-space: nowrap;
}
.v-badge-ok      { color: var(--v-ok-fg);     background: var(--v-ok-bg); }
.v-badge-warn    { color: var(--v-warn-fg);   background: var(--v-warn-bg); }
.v-badge-danger  { color: var(--v-danger-fg); background: var(--v-danger-bg); }
.v-badge-info    { color: var(--v-info-fg);   background: var(--v-info-bg); }
.v-badge-muted   { color: var(--v-muted);     background: var(--v-fill); }
.v-badge-progress{ color: var(--v-on-deep);  background: var(--v-warn-solid); }

/* ── Botones ─────────────────────────────────────────────────────────────── */
.v-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  border: none; border-radius: var(--v-radius-sm); padding: 13px 22px;
  font-size: 14px; font-weight: 800; cursor: pointer; transition: filter .15s;
}
.v-btn:hover { filter: brightness(1.06); }
.v-btn-primary { background: var(--v-green); color: var(--v-on-deep); }
.v-btn-primary:hover { color: var(--v-on-deep); }
.v-btn-dark { background: var(--v-deep); color: var(--v-on-deep); }
.v-btn-dark:hover { color: var(--v-on-deep); }
/*
 * Botón claro sobre una superficie oscura FIJA (la tarjeta de Vity, el visor).
 *
 * Los dos valores son fijos a propósito —ver el bloque PAPEL arriba, que es
 * donde se clavan—. Cuando el fondo era `#fff` y el texto `var(--v-ink)`, en
 * modo oscuro el token se remapeaba a un color claro y el botón quedaba blanco
 * sobre blanco: contraste 1,05:1, invisible. Mezclar un color fijo con uno
 * temático en la misma regla es exactamente así como se rompe.
 *
 * Acá se escriben con los MISMOS nombres semánticos que el resto de la hoja:
 * lo que los hace fijos no es esta regla, es el `--v-surface: #FFFFFF` que el
 * bloque PAPEL declara sobre este mismo selector.
 */
.v-btn-light { background: var(--v-surface); color: var(--v-ink); }
.v-btn-light:hover { color: var(--v-ink); }
.v-btn-ghost { background: var(--v-fill); color: var(--v-ink-2); }
.v-btn-teams { background: var(--v-teams); color: var(--v-on-deep); font-weight: 700; }
.v-btn-teams:hover { color: var(--v-on-deep); }
.v-btn:disabled { opacity: .5; cursor: not-allowed; filter: none; }
/* Acción de retirada: «Ya no voy», «Cancelar». Sin fondo y con el peso de un
   enlace, para que no compita con la acción que la pantalla sí quiere ofrecer
   —anotarse, unirse—. No es `v-btn-danger`: darse de baja de una reunión no es
   destructivo ni irreversible, y pintarlo de rojo asustaría de una decisión
   que se puede deshacer con un clic. */
.v-btn-quiet {
  background: none; color: var(--v-muted); font-weight: 700;
  text-decoration: underline; text-underline-offset: 3px;
}
.v-btn-quiet:hover { color: var(--v-ink-2); }
.v-btn-sm { padding: 9px 16px; font-size: 13px; }

/* ── Grillas ─────────────────────────────────────────────────────────────── */
.v-grid { display: grid; gap: 18px; }
.v-grid-2 { grid-template-columns: repeat(2, 1fr); }
.v-grid-3 { grid-template-columns: repeat(3, 1fr); }
.v-grid-4 { grid-template-columns: repeat(4, 1fr); }
.v-grid-5 { grid-template-columns: repeat(5, 1fr); }

/* ── Encabezado de sección numerada ──────────────────────────────────────── */
.v-section-head { display: flex; align-items: center; gap: 12px; margin: 38px 0 16px; }
.v-section-num { font-family: var(--v-font-display); font-weight: 800; font-size: 22px; color: var(--v-green); }
.v-section-rule { height: 1px; background: var(--v-border); flex: 1; }
.v-section-note { font-size: 13px; color: var(--v-muted); font-weight: 700; }

/* ── Tarjeta de fase del método ──────────────────────────────────────────── */
.v-phase {
  display: flex; flex-direction: column; background: var(--v-surface);
  border: 1px solid var(--v-border); border-radius: 13px; padding: 20px 22px;
  cursor: pointer; transition: transform .15s, box-shadow .15s;
  /* Altura pareja entre las 5 fases: sin esto, la de descripción más corta
     queda notoriamente más baja y la fila se ve rota. */
  min-height: 230px;
}
.v-phase:hover { transform: translateY(-2px); box-shadow: var(--v-sombra-fase); }
.v-phase.is-locked { opacity: .72; cursor: not-allowed; }
.v-phase.is-locked:hover { transform: none; box-shadow: none; }
/* La letra de la fase es el ancla visual de la tarjeta: en verde cuando la fase
   está a mano, apagada cuando todavía no se puede entrar. El color lo decide la
   tarjeta, no la vista: es estado, no dato. */
.v-phase-letter {
  font-family: var(--v-font-display); font-weight: 800; font-size: 52px;
  line-height: .85; color: var(--v-green);
}
.v-phase.is-locked .v-phase-letter { color: var(--v-muted-2); }
/* El nombre de la fase, debajo de la letra.
   `min-width: 0` en la columna porque el contenedor es un flex: sin eso, un
   nombre largo como «Transforma» empuja la insignia de estado fuera de la
   tarjeta en vez de partirse. */
.v-phase-mark { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.v-phase-name {
  font-family: var(--v-font-display); font-weight: 700; font-size: 12px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--v-ink-2);
  line-height: 1.1; overflow-wrap: anywhere;
}
.v-phase.is-locked .v-phase-name { color: var(--v-muted-2); }
/* Márgenes negativos para que la foto toque los bordes de la tarjeta. */
.v-phase-thumb {
  height: 96px; background-size: cover; background-position: center;
  margin: -20px -22px 14px; border-radius: 12px 12px 0 0;
}

/* ── Formularios ─────────────────────────────────────────────────────────── */
/* Única definición de `.v-field` y `.v-input` de toda la hoja. */
.v-form-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 22px; }
.v-form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
.v-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; margin-bottom: 16px; }
/* Dentro de una grilla el espacio ya lo pone el `gap`: sumarle margen deja el
   doble de aire entre filas y la mitad del previsto en la última. */
.v-form-grid > .v-field, .v-toggle-grid > .v-field { margin-bottom: 0; }
/* Campo que ocupa la fila entera de la grilla —«contraseña actual» antes de las
   dos nuevas—. Era un `style="grid-column:1/-1"` inline, que además obligaba a
   escribir un selector `[style*=…]` para poder anularlo en celular. */
.v-field-wide { grid-column: 1 / -1; }
.v-field-label {
  font-family: var(--v-font-display); font-size: 11px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--v-ink-2);
}
.v-field-hint { font-size: 12px; color: var(--v-ink-2); line-height: 1.4; }
.v-input {
  width: 100%; padding: 12px 15px; border: 1px solid var(--v-border);
  border-radius: var(--v-radius-sm);
  font-family: inherit; font-size: 14.5px; color: var(--v-ink); background: var(--v-surface);
  transition: border-color .15s, box-shadow .15s;
}
.v-input:focus { outline: none; border-color: var(--v-green); box-shadow: 0 0 0 3px var(--v-ring); }
.v-input::placeholder { color: var(--v-muted-2); }
.v-readonly {
  padding: 12px 15px; border: 1px dashed var(--v-border); border-radius: var(--v-radius-sm);
  font-size: 14.5px; color: var(--v-ink-2); background: var(--v-surface);
}

/* Casillas con área de toque completa: en piso de venta se marca con el pulgar,
   no con el puntero sobre un cuadradito de 13 px. */
.v-toggle-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.v-toggle-grid-1 { grid-template-columns: 1fr; }
.v-toggle {
  display: flex; align-items: center; gap: 13px; padding: 14px 16px; cursor: pointer;
  border: 1px solid var(--v-border); border-radius: 12px; background: var(--v-surface);
  transition: border-color .15s, background .15s;
}
.v-toggle:has(input:checked) { border-color: var(--v-green); background: var(--v-fill-3); }
.v-toggle:has(input:disabled) { opacity: .55; cursor: not-allowed; }
.v-toggle input { width: 19px; height: 19px; flex: 0 0 auto; accent-color: var(--v-green); cursor: inherit; }
.v-toggle-emoji { font-size: 20px; flex: 0 0 auto; }
.v-toggle-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.v-toggle-text strong { font-size: 14px; color: var(--v-ink); }
.v-toggle-text small { font-size: 12px; color: var(--v-ink-2); line-height: 1.35; }

@media (max-width: 680px) {
  .v-form-grid, .v-toggle-grid { grid-template-columns: 1fr; }
  .v-field-wide { grid-column: auto; }
  .v-input { padding: 13px 14px; font-size: 16px; }  /* 16px: iOS no hace zoom. */
}

/* ── Evaluación práctica ─────────────────────────────────────────────────── */
.v-practica { padding: 38px 40px; max-width: 860px; margin: 0 auto; }

.v-practica-head { display: flex; align-items: center; gap: 20px; margin-bottom: 30px; }
.v-practica-icon {
  width: 72px; height: 72px; flex: 0 0 auto; border-radius: 20px;
  background: linear-gradient(140deg, var(--v-ok-bg), var(--v-ok-bg-2));
  display: flex; align-items: center; justify-content: center; font-size: 36px;
}

/* Los tres pasos: numerados, porque el orden importa y sin número se leen
   como tres opciones sueltas en vez de como un recorrido. */
.v-pasos { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 30px; }
.v-paso {
  position: relative; padding: 20px 16px 18px; border-radius: 14px;
  background: var(--v-bg); border: 1px solid var(--v-border); text-align: center;
}
.v-paso-num {
  position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
  width: 24px; height: 24px; border-radius: 50%; background: var(--v-deep); color: var(--v-on-deep);
  font-family: var(--v-font-display); font-size: 12px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.v-paso-emoji   { display: block; font-size: 30px; margin: 6px 0 10px; }
.v-paso-titulo  { display: block; font-size: 15px; color: var(--v-ink); margin-bottom: 5px; }
.v-paso-detalle { display: block; font-size: 12.5px; color: var(--v-ink-2); line-height: 1.45; }

.v-rubrica-head { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
.v-rubrica { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-bottom: 30px; }
.v-rubrica-item {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: 11px; border: 1px solid var(--v-border); background: var(--v-surface);
}
.v-rubrica-emoji { font-size: 21px; flex: 0 0 auto; }
.v-rubrica-fase  { font-size: 13.5px; font-weight: 700; color: var(--v-ink); }
.v-rubrica-que   { font-size: 12px; color: var(--v-ink-2); line-height: 1.35; }

.v-practica-cta {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding-top: 24px; border-top: 1px solid var(--v-border);
}

@media (max-width: 680px) {
  .v-practica { padding: 24px 18px; }
  .v-practica-head { gap: 14px; margin-bottom: 26px; }
  .v-practica-icon { width: 56px; height: 56px; font-size: 28px; border-radius: 16px; }
  /* Los pasos apilados mantienen el número: es lo que los ordena. */
  .v-pasos { grid-template-columns: 1fr; gap: 16px; }
  .v-paso { display: flex; align-items: center; gap: 14px; text-align: left; padding: 14px 16px; }
  .v-paso-num { position: static; transform: none; flex: 0 0 auto; }
  .v-paso-emoji { margin: 0; font-size: 26px; }
  .v-paso-texto { flex: 1; min-width: 0; }
  .v-rubrica { grid-template-columns: 1fr; }
  .v-practica-cta { flex-direction: column; align-items: stretch; text-align: center; }
}

/* ── Reproductor de cápsulas ─────────────────────────────────────────────── */
/*
 * La cápsula está construida a 16/9 por dentro: es la proporción que usa su
 * propio maquetado. Si el iframe tiene otra, la cápsula centra su contenido y
 * deja franjas blancas a los lados —que es exactamente lo que se veía.
 *
 * La caja mantiene 16/9 SIEMPRE. El `max-width` se deriva del alto disponible,
 * así que cuando la altura es el límite el ancho se reduce en proporción en
 * lugar de estirarse y volver a generar franjas.
 */
.v-capsula {
  position: relative; width: 100%;
  aspect-ratio: 16 / 9;
  max-width: calc((100vh - var(--capsula-chrome)) * 16 / 9);
  margin: 0 auto;
  border-radius: 14px; overflow: hidden;
  border: 1px solid var(--v-border); background: var(--v-deep);
  --capsula-chrome: 200px;   /* cabecera + barra de lecciones + navegación */
}
.v-capsula iframe { width: 100%; height: 100%; border: none; display: block; background: var(--v-surface); }

/* En pantalla completa manda la pantalla, no el cálculo. */
.v-capsula:fullscreen {
  max-width: none; width: 100vw; height: 100vh;
  aspect-ratio: auto; border: none; border-radius: 0;
  display: flex; align-items: center; justify-content: center;
}
.v-capsula:fullscreen iframe { aspect-ratio: 16 / 9; width: auto; height: 100%; max-width: 100vw; }

.v-capsula-full {
  position: absolute; top: 12px; right: 12px; z-index: 2;
  background: var(--v-capsula-boton); color: var(--v-on-deep); border: none;
  border-radius: 9px; padding: 9px 11px; cursor: pointer; line-height: 0;
}
.v-capsula-full:hover { background: var(--v-capsula-boton-hover); }

@media (max-width: 960px) { .v-capsula { --capsula-chrome: 168px; } }
@media (max-width: 680px) {
  /* En un teléfono vertical, 16/9 deja la cápsula muy chica. Se le da el ancho
     completo del contenido y se empuja la pantalla completa, que es donde
     realmente se puede trabajar. */
  .v-capsula { --capsula-chrome: 150px; max-width: 100%; border-radius: 12px; }
}

/* ── Atajos del resumen de la malla ──────────────────────────────────────── */
/* Sin señal de que se puede tocar, un elemento clicable es un elemento que
   nadie toca. De ahí el desplazamiento y la flecha al pasar por encima. */
/* Las cinco fases reparten el ancho en partes iguales: si una se ensancha
   porque su etiqueta es más larga («En curso» contra «✓»), la fila deja de
   leerse como un recorrido de cinco pasos iguales. */
.v-phase-chip { display: block; flex: 1; text-align: center; transition: transform .15s; }
.v-phase-chip:hover { transform: translateY(-3px); }
.v-phase-chip.is-locked { opacity: .75; }
.v-phase-chip.is-locked:hover { transform: none; }

.v-line-row {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 10px; margin: 0 -10px; border-radius: 10px;
  transition: background .15s;
}
.v-line-row:hover { background: var(--v-bg); }
.v-line-row.is-locked { opacity: .68; }
/* La flecha hereda su color de acá y no del atributo `stroke` del SVG: es lo
   que permite que el mismo icono viva en modo claro y oscuro sin tocarlo. */
.v-line-arrow { flex: 0 0 auto; opacity: 0; color: var(--v-muted-2); transition: opacity .15s, transform .15s; }
.v-line-row:hover .v-line-arrow { opacity: 1; transform: translateX(2px); }

/* ── Tabla ───────────────────────────────────────────────────────────────── */
.v-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.v-table th {
  text-align: left; font-family: var(--v-font-display); font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--v-muted);
  font-weight: 700; padding: 12px 14px; border-bottom: 1px solid var(--v-border);
}
.v-table td { padding: 14px; border-bottom: 1px solid var(--v-fill-2); }
.v-table tr:last-child td { border-bottom: none; }

/* ── Formularios ─────────────────────────────────────────────────────────── */
/* `.v-field` y `.v-input` estaban declarados DOS veces —acá y en el bloque de
   cuenta—, así que esta copia le pisaba el `display:flex` a la otra y el
   formulario de perfil perdía su `gap`. Una sola definición, arriba. */
.v-label { display: block; font-size: 13px; font-weight: 700; color: var(--v-ink); margin-bottom: 7px; }
.v-error { color: var(--v-danger-fg); font-size: 13px; margin-top: 6px; }

.v-alert { border-radius: var(--v-radius-sm); padding: 13px 16px; font-size: 14px; margin-bottom: 18px; }
.v-alert-error { background: var(--v-danger-bg); color: var(--v-danger-fg); border: 1px solid var(--v-danger-border); }
.v-alert-info  { background: var(--v-info-bg);   color: var(--v-info-fg); }
.v-alert-ok    { background: var(--v-ok-bg);     color: var(--v-ok-fg); }

/* ── Estado vacío ────────────────────────────────────────────────────────── */
.v-empty {
  text-align: center; padding: 56px 24px; color: var(--v-muted);
  background: var(--v-surface); border: 1px dashed var(--v-border); border-radius: var(--v-radius);
}
/* Un estado vacío tiene siempre las mismas tres piezas: un icono grande que
   avisa desde lejos que no hay nada, una frase que explica qué pasó y una que
   dice qué hacer. Estaban escritas a mano —y con tamaños distintos— en cada
   vista que podía quedarse sin datos. */
.v-empty-icon  { display: block; font-size: 38px; margin-bottom: var(--v-sp-3); }
.v-empty-title { display: block; color: var(--v-ink); margin-bottom: var(--v-sp-1); }
/* La salida —«ir a mi malla»— necesita despegarse de la frase, o se lee como
   parte del párrafo y deja de parecer un botón. */
.v-empty-cta   { margin-top: var(--v-sp-4); }
/* En una columna angosta —el panel de retos— 56 px de relleno dejan el cartel
   de vacío más alto que las tarjetas que lo rodean, y el vacío pesa más que el
   contenido. */
.v-empty-sm    { padding: var(--v-sp-5) var(--v-sp-4); }

/* ── Cajón de VITY ───────────────────────────────────────────────────────── */
.v-drawer-backdrop { position: fixed; inset: 0; z-index: 60; }
.v-drawer-scrim { position: absolute; inset: 0; background: rgba(16, 26, 36, .45); }
.v-drawer {
  position: absolute; top: 0; right: 0; height: 100%; width: 440px; max-width: 100%;
  background: var(--v-surface); display: flex; flex-direction: column;
  box-shadow: -16px 0 50px rgba(0, 0, 0, .22); animation: erDrawer .28s cubic-bezier(.2, .8, .2, 1);
}
.v-msg-user {
  align-self: flex-end; max-width: 80%; background: var(--v-green); color: var(--v-on-deep);
  padding: 13px 16px; border-radius: 14px 14px 4px 14px; font-size: 14px; line-height: 1.5;
}
.v-msg-bot {
  /* `var(--v-surface)` y no `#fff`: la burbuja vive dentro del cajón de Vity,
     cuyo fondo sigue el tema. Con el blanco fijo, en modo oscuro quedaba una
     tarjeta blanca con texto claro encima. */
  align-self: flex-start; max-width: 88%; background: var(--v-surface); border: 1px solid var(--v-border);
  padding: 15px 17px; border-radius: 14px 14px 14px 4px; font-size: 14px; line-height: 1.6; color: var(--v-ink);
}
.v-source {
  display: flex; align-items: center; gap: 8px; background: var(--v-bg);
  border-radius: 9px; padding: 9px 12px; font-size: 12px; color: var(--v-ink-2); margin-top: 10px;
}
.v-chip {
  font-size: 12px; font-weight: 600; color: var(--v-ink-2); background: var(--v-bg);
  border: 1px solid var(--v-border); border-radius: 99px; padding: 6px 12px; cursor: pointer;
}
.v-chip:hover { border-color: var(--v-green); color: var(--v-green); }
/* ═══════════════════════════════════════════════════════════════════════════
   COMPONENTES

   Todo lo que antes vivía en 544 atributos `style` repartidos por las vistas.
   La regla es simple y no tiene excepciones: **una vista no escribe colores,
   tamaños ni espaciados**. Sólo elige clases.

   Lo único que sigue viajando inline son los valores que dependen del dato
   —la imagen de portada y el porcentaje de una barra—, y viajan como
   *custom properties* (`--img`, `--pct`), no como declaraciones sueltas.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Utilidades de composición ───────────────────────────────────────────── */
/* Cuatro primitivas, no cuarenta. Alcanzan para el 90 % de los casos. */
.v-row        { display: flex; align-items: center; gap: var(--v-sp-3); }
.v-row-start  { align-items: flex-start; }
.v-row-end    { align-items: flex-end; }
.v-row-wrap   { flex-wrap: wrap; }
.v-between    { justify-content: space-between; }
.v-stack      { display: flex; flex-direction: column; gap: var(--v-sp-3); }
.v-stack-sm   { gap: var(--v-sp-2); }
.v-stack-lg   { gap: var(--v-sp-5); }
.v-push       { margin-left: auto; }
.v-fill-rest  { flex: 1; min-width: 0; }
.v-fixed      { flex: 0 0 auto; }
.v-nowrap     { white-space: nowrap; }
.v-truncate   { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.v-mt-auto    { margin-top: auto; }

/*
 * La escala va COMPLETA del 1 al 6, aunque hoy no se usen todos.
 *
 * Una utilidad que falta no da error: simplemente no hace nada, y el hueco
 * aparece semanas después en una pantalla que nadie relaciona con esto. Así
 * quedó el logotipo pegado al rótulo «Hub Comercial» —el markup pedía
 * `v-mt-3`, que no existía— y así quedó una fila del detalle de persona sin
 * estilo por pedir una clase con un sufijo de más.
 */
/* Grilla que NO estira: cada tarjeta toma su altura natural.
   Por defecto las columnas se igualan, y una tarjeta con poco contenido al lado
   de otra larga queda con un hueco vacío abajo que parece un error de carga. */
.v-grid-top { align-items: start; }

.v-gap-1 { gap: var(--v-sp-1); } .v-gap-2 { gap: var(--v-sp-2); }
.v-gap-3 { gap: var(--v-sp-3); } .v-gap-4 { gap: var(--v-sp-4); }
.v-gap-5 { gap: var(--v-sp-5); } .v-gap-6 { gap: var(--v-sp-6); }
.v-mb-1  { margin-bottom: var(--v-sp-1); }
.v-mb-4  { margin-bottom: var(--v-sp-4); }
.v-mb-5  { margin-bottom: var(--v-sp-5); }
.v-mb-6  { margin-bottom: var(--v-sp-6); }

/* ── Texto ───────────────────────────────────────────────────────────────── */
.v-text     { font-size: 14px; color: var(--v-ink-2); line-height: 1.55; }
.v-text-sm  { font-size: 13px; color: var(--v-ink-2); line-height: 1.5; }
.v-meta     { font-size: 12px; color: var(--v-muted); }
.v-meta-sm  { font-size: 11px; color: var(--v-muted); }
/* 12 px pero con el color del texto, no el del apagado: hay metadatos que son
   contenido —quién es el responsable de un documento— y no ruido de interfaz. */
.v-text-xs  { font-size: 12px; color: var(--v-ink-2); line-height: 1.45; }
.v-ink      { color: var(--v-ink); }
.v-accent   { color: var(--v-green); }
.v-title    { font-size: 16px; font-weight: 700; line-height: 1.35; color: var(--v-ink); margin: 0; }
.v-title-lg { font-size: 18px; font-weight: 800; line-height: 1.3; color: var(--v-ink); margin: 0; }
.v-eyebrow  {
  font-family: var(--v-font-display); font-size: 10px; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--v-muted);
}

/* ── Cabecera de página ──────────────────────────────────────────────────── */
.v-page-head { margin-bottom: var(--v-sp-5); }
/* Título con una acción chica al lado —«Conectar con mi calendario»—. Se separa
   de `.v-page-head-split` porque ahí la acción se va al extremo opuesto de la
   fila; acá tiene que quedar PEGADA al título, para que se lea como algo que
   se le hace a esa página y no como una acción global de la pantalla.
   El `baseline` alinea el texto del botón con el del titular en vez de centrar
   dos cajas de alturas muy distintas; al envolver, el botón baja completo. */
.v-title-row { align-items: baseline; margin-bottom: var(--v-sp-2); }
.v-page-head-split {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--v-sp-5); flex-wrap: wrap; margin-bottom: 28px;
}

/* ── Imagen y progreso gobernados por el dato ────────────────────────────── */
/* Uso: style="--img:url('…')" — una sola propiedad, no cinco declaraciones. */
.v-img {
  background-image: var(--img); background-size: cover; background-position: center;
  background-color: var(--v-fill); background-repeat: no-repeat;
}
/* Uso: <span style="--pct:62%"> dentro de .v-bar */
.v-bar > span { width: var(--pct, 0%); }

/* ── Píldora de estado ───────────────────────────────────────────────────── */
.v-pill {
  display: inline-flex; align-items: center; gap: var(--v-sp-2);
  border-radius: 999px; padding: 10px 18px; font-size: 13px; font-weight: 700;
  background: var(--v-ok-bg); color: var(--v-ok-fg);
}
.v-pill-dot { width: 9px; height: 9px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }

/* ── Iniciales ───────────────────────────────────────────────────────────── */
.v-initials {
  width: 34px; height: 34px; flex: 0 0 auto; border-radius: 50%;
  background: var(--v-green); color: var(--v-on-deep);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800;
}
.v-initials-sm { width: 26px; height: 26px; font-size: 10px; background: var(--v-fill); color: var(--v-ink-2); }

/* ── Iconografía ─────────────────────────────────────────────────────────── */
/* Los SVG del sistema comparten trazo y tamaño; los emoji quedan como acento
   emocional (títulos, estados vacíos), nunca como control. */
.v-icon { width: 20px; height: 20px; flex: 0 0 auto; display: block; }
/* Los SVG del sistema pintan con `currentColor`, así que el color se elige
   acá y no en un atributo `stroke` con un hex adentro. */
.v-muted-icon { color: var(--v-muted); }
.v-accent-icon { color: var(--v-green); }
.v-on-dark-icon { color: var(--v-on-deep); }
.v-icon-sm { width: 16px; height: 16px; }
.v-icon-lg { width: 26px; height: 26px; }
.v-emoji { font-style: normal; line-height: 1; }
.v-emoji-lg { font-size: 30px; }
.v-emoji-xl { font-size: 38px; }

/* ── Inicio: continuar donde quedaste ────────────────────────────────────── */
.v-continue { padding: 28px 30px; }
.v-continue-body { display: flex; gap: 22px; align-items: center; flex-wrap: wrap; }
.v-continue-letter {
  width: 120px; height: 120px; flex: 0 0 auto; border-radius: 12px;
  background: var(--v-deep); color: var(--v-on-deep);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--v-font-display); font-weight: 800; font-size: 64px;
}
.v-continue-main { flex: 1; min-width: 220px; }
.v-continue-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 24px;
  text-transform: uppercase; margin: 4px 0 10px;
}

/* ── Inicio: tarjeta de Vity ─────────────────────────────────────────────── */
.v-vity-card { display: flex; flex-direction: column; }
.v-vity-name { font-weight: 800; font-size: 16px; }
.v-vity-role { font-size: 11px; color: var(--v-muted); }
.v-vity-copy { font-size: 14px; line-height: 1.55; color: var(--v-on-deep-soft); margin: 0 0 var(--v-sp-4); }

/* ── Inicio: próxima reunión ─────────────────────────────────────────────── */
.v-next-live {
  display: flex; align-items: center; gap: var(--v-sp-3);
  background: var(--v-surface); border: 1px solid var(--v-border);
  border-radius: var(--v-radius); padding: 18px 24px; margin-bottom: 26px;
  transition: border-color .15s;
}
.v-next-live:hover { border-color: var(--v-green); }
.v-next-live-cta {
  background: var(--v-green); color: var(--v-on-deep); border-radius: var(--v-radius-sm);
  padding: 9px 16px; font-size: 13px; font-weight: 700; white-space: nowrap;
}

/* ── Inicio: atajo de fase ───────────────────────────────────────────────── */
.v-phase-tile {
  height: 52px; border-radius: 9px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--v-font-display); font-weight: 800; font-size: 26px;
  background: var(--v-fill); color: var(--v-ink-2);
}
.v-phase-tile.is-certified { background: var(--v-ok-bg);     color: var(--v-ok-fg); }
.v-phase-tile.is-progress  { background: var(--v-warn-solid); color: var(--v-on-deep); }
.v-phase-tile.is-locked    { background: var(--v-fill);      color: var(--v-muted-2); }
.v-phase-note { font-size: 10px; margin-top: 5px; font-weight: 700; color: var(--v-muted-2); }
.v-phase-note.is-certified { color: var(--v-ok-fg); }
.v-phase-note.is-progress  { color: var(--v-warn-fg); }

/* ── Bloque de portada con velo ──────────────────────────────────────────── */
/* Lo comparten la inducción de la malla y la cabecera de reuniones. */
.v-hero {
  position: relative; overflow: hidden; border-radius: var(--v-radius);
  background: var(--v-deep); color: var(--v-on-deep);
}
.v-hero-media { position: absolute; inset: 0; opacity: .30; }
/* El encuadre de la fachada: el cartel de la tienda vive por encima del centro
   de la foto y un centrado exacto lo deja fuera del recorte. */
.v-hero-media-store { background-position: center 62%; }
.v-hero-veil {
  position: absolute; inset: 0;
  background: var(--v-velo-banda);
}
.v-hero-body {
  position: relative; padding: 24px 28px;
  display: flex; align-items: center; gap: 30px; flex-wrap: wrap;
}
.v-hero-intro { flex: 0 0 auto; max-width: 250px; }
.v-hero-eyebrow {
  font-family: var(--v-font-display); font-size: 11px; letter-spacing: .14em;
  text-transform: uppercase; color: var(--v-muted-2); font-weight: 700; margin-bottom: 4px;
}
.v-hero-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 23px;
  text-transform: uppercase; margin: 0;
}
.v-hero-state {
  display: inline-flex; align-items: center; gap: var(--v-sp-1); margin-top: 12px;
  background: var(--v-green); color: var(--v-on-deep); border-radius: 99px;
  padding: 5px 13px; font-size: 12px; font-weight: 700;
}
.v-hero-state.is-done { background: var(--v-green-2); }
/* Hermano de `.v-hero-cards` para los héroes que llevan un párrafo en vez de
   tres fichas: la pantalla de inducción muestra el resumen del curso tal como
   lo escribió el curador. Mismo `flex: 1` y mismo `min-width`, o el texto se
   apretaría contra el intro en vez de bajar. El color es `--v-on-deep-soft`
   —7,80:1 sobre el velo en claro, 10,80:1 en oscuro—, no un gris cualquiera. */
.v-hero-summary {
  position: relative; flex: 1; min-width: 280px; margin: 0;
  color: var(--v-on-deep-soft); font-size: 15px; line-height: 1.55;
}
.v-hero-cards {
  position: relative; flex: 1; min-width: 280px; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--v-sp-3);
}
.v-hero-card { background: rgba(20, 82, 74, .92); border-radius: 11px; padding: 16px 18px; }
.v-hero-card-title { font-size: 14px; font-weight: 700; margin-bottom: 4px; }
.v-hero-card-note  { font-size: 12px; color: var(--v-muted); line-height: 1.45; }

/* Variante de banda: sin tarjetas, sólo un titular sobre la foto. */
.v-hero-band { height: clamp(130px, 26vw, 190px); margin-bottom: var(--v-sp-4); }
.v-hero-band .v-hero-media { opacity: 1; }
.v-hero-band .v-hero-veil {
  background: var(--v-velo-hero);
}
/* La foto del equipo se encuadra un poco por encima del centro. Centrada, la
   banda corta las caras justo a la altura de los ojos y la portada deja de
   mostrar personas para mostrar frentes. */
.v-hero-band .v-hero-media { background-position: center 40%; }
.v-hero-band-body { position: absolute; left: 28px; bottom: 24px; max-width: 420px; }
.v-hero-band-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 22px;
  text-transform: uppercase; margin-top: 6px;
}

/* ── Malla: tarjeta de línea de producto ─────────────────────────────────── */
.v-line-card { padding: 0; overflow: hidden; }
.v-line-cover { position: relative; height: 118px; }
.v-line-cover .v-badge { position: absolute; top: 10px; left: 10px; }
.v-line-body { padding: 16px 18px; }
.v-line-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 17px;
  text-transform: uppercase; margin: 0 0 3px; line-height: 1.15;
}
.v-line-cta { font-size: 12px; font-weight: 700; margin-top: var(--v-sp-2); color: var(--v-green); }
.v-line-cta.is-locked { color: var(--v-muted); }
.v-line-thumb { width: 34px; height: 34px; border-radius: 8px; flex: 0 0 auto; }

/* ── Banco: filtros y tarjetas de documento ──────────────────────────────── */
.v-filters { display: flex; gap: var(--v-sp-2); flex-wrap: wrap; margin-bottom: 22px; }
.v-filter { border-radius: 99px; border: 1px solid var(--v-border); }

/* ── Banco: buscador de la pantalla ──────────────────────────────────────── */
/* El del encabezado global en celular vive plegado detrás de un botón. Este
   está desplegado y arriba de todo, que es lo que se pidió. */
.v-banco-buscador {
  display: flex; align-items: center; gap: var(--v-sp-2);
  background: var(--v-surface); border: 1px solid var(--v-border);
  border-radius: 14px; padding: 8px 10px 8px 14px; margin-bottom: 18px;
}
.v-banco-buscador:focus-within { border-color: var(--v-green); }
.v-banco-buscador-campo {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font: inherit; color: var(--v-ink); padding: 6px 0;
}
/* El aspa que Safari y Chrome ponen dentro de `type="search"` hereda un color
   propio que en tema oscuro queda invisible. */
.v-banco-buscador-campo::-webkit-search-cancel-button { filter: grayscale(1) opacity(.6); }

/* ── Banco: cajas o lista ────────────────────────────────────────────────── */
.v-vista-toggle {
  display: flex; gap: 2px; padding: 3px;
  background: var(--v-fill); border: 1px solid var(--v-border); border-radius: 12px;
}
.v-vista-btn {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; background: none; cursor: pointer;
  font-family: var(--v-font-display); font-size: 12px; font-weight: 700;
  color: var(--v-ink-2); padding: 7px 12px; border-radius: 9px;
  transition: background .15s, color .15s;
}
.v-vista-btn:hover { color: var(--v-ink); }
.v-vista-btn.is-on { background: var(--v-surface); color: var(--v-ink); box-shadow: var(--v-shadow-sm); }

/* Un solo contenedor para las dos vistas: cambia la clase, no el marcado. */
.v-doc-lista { display: grid; gap: 18px; }
.v-doc-lista.is-cajas { grid-template-columns: repeat(3, 1fr); }
.v-doc-lista.is-lista { grid-template-columns: 1fr; gap: 10px; }

.v-doc-card { display: flex; flex-direction: column; overflow: hidden; }
.v-doc-card.is-expired { opacity: .82; }
/* Portada a sangre: los márgenes negativos compensan el padding de la tarjeta. */
.v-doc-cover { height: 104px; margin: -24px -26px 18px; }
/* La foto real: el encuadre lo manda la vista con `--encuadre`, distinto por
   documento. El respaldo `center` es el de `.v-img`, que sigue valiendo si
   algún día alguien pone la clase sin la variable. */
.v-doc-cover-foto { background-position: var(--encuadre, center); }

/* La tarjeta en fila: la portada a la izquierda y el texto en una columna.
   Todo lo que no es la portada se fuerza a la columna 2 en vez de dejarlo al
   acomodo automático: con la portada ocupando tres filas, el cuarto hijo —el
   sello de vencido, que sólo aparece a veces— caería en la columna 1 y la
   tarjeta se desarmaría justo en los documentos vencidos. */
.v-doc-lista.is-lista .v-doc-card {
  display: grid; grid-template-columns: 96px minmax(0, 1fr);
  align-items: center; column-gap: 16px; row-gap: 2px; padding: 12px 16px;
}
.v-doc-lista.is-lista .v-doc-card > *:not(.v-doc-cover) { grid-column: 2; }
.v-doc-lista.is-lista .v-doc-cover {
  grid-column: 1; grid-row: 1 / span 3;
  height: 62px; margin: 0; border-radius: 10px;
}
.v-doc-lista.is-lista .v-doc-cover-emoji { font-size: 26px; }
/* El resumen se va: es lo que ocupa el alto que la lista viene a recuperar.
   El título y el responsable alcanzan para elegir, y el resumen entero está a
   un clic en la ficha. */
.v-doc-lista.is-lista .v-doc-summary { display: none; }
.v-doc-lista.is-lista .v-doc-card .v-row { margin-bottom: 0; }
.v-doc-lista.is-lista .v-title { margin-bottom: 2px; }
.v-doc-lista.is-lista .v-doc-foot { margin-top: 4px; border-top: 0; padding-top: 0; }

/*
 * ── Portada generada ────────────────────────────────────────────────────────
 *
 * `kb_documents` no tiene columna de portada: la única imagen del banco es la
 * de la CATEGORÍA, y repartirla por categoría era lo que hacía que los doce
 * documentos de «Organización» se vieran iguales. Hasta que exista una portada
 * por documento —decisión de producto, con su campo de carga—, la tarjeta se
 * pinta sola con lo que sí se sabe: el tipo manda el color y el ícono, y el
 * slug manda el ángulo del degradado y dónde cae el brillo.
 *
 * Los dos valores llegan como variables desde la vista y salen de `crc32` del
 * slug, así que el mismo documento se ve siempre igual. Ver `portada_helper`:
 * si esto fuera aleatorio, la tarjeta cambiaría de color en cada recarga y se
 * perdería la referencia visual con la que la gente encuentra un documento sin
 * leer el título.
 *
 * El respaldo de `--v-doc-tipo` es el verde de marca: un tipo que todavía no
 * tenga clase se ve entero, no a medio pintar.
 */
.v-doc-cover-gen {
  --v-doc-tipo: var(--v-green);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  background:
    radial-gradient(circle at var(--mancha, 50%) 28%, rgba(255, 255, 255, .18), transparent 62%),
    linear-gradient(var(--angulo, 135deg), var(--v-doc-tipo), var(--v-deep));
}
.v-doc-cover-gen.is-pdf      { --v-doc-tipo: var(--v-type-pdf); }
.v-doc-cover-gen.is-video    { --v-doc-tipo: var(--v-type-video); }
.v-doc-cover-gen.is-onepager { --v-doc-tipo: var(--v-type-onepager); }
.v-doc-cover-gen.is-vr360    { --v-doc-tipo: var(--v-type-vr360); }
.v-doc-cover-gen.is-tool     { --v-doc-tipo: var(--v-type-tool); }
.v-doc-cover-gen.is-link     { --v-doc-tipo: var(--v-type-link); }
/* El ícono es decorativo y va con `aria-hidden`: el tipo ya está escrito en
   `.v-doc-type`, y leerlo dos veces sólo alarga el recorrido del lector. */
.v-doc-cover-emoji {
  font-size: 38px; line-height: 1;
  /* La sombra lo despega del degradado en los tipos claros —`onepager` sobre
     azul— sin cambiarle el color al emoji, que trae el suyo propio. */
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .35));
}

.v-doc-type {
  font-family: var(--v-font-display); font-size: 10px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--v-on-deep);
  padding: 4px 9px; border-radius: 7px; background: var(--v-type-link);
}
.v-doc-type.is-pdf      { background: var(--v-type-pdf); }
.v-doc-type.is-video    { background: var(--v-type-video); }
.v-doc-type.is-onepager { background: var(--v-type-onepager); }
.v-doc-type.is-vr360    { background: var(--v-type-vr360); }
.v-doc-type.is-tool     { background: var(--v-type-tool); }
.v-doc-summary { font-size: 13px; color: var(--v-ink-2); line-height: 1.5; margin: 0 0 auto; }
.v-doc-foot {
  display: flex; align-items: center; gap: 8px;
  margin-top: var(--v-sp-4); padding-top: var(--v-sp-3); border-top: 1px solid var(--v-fill-2);
}
.v-doc-valid { margin-left: auto; font-size: 11px; color: var(--v-muted); }
.v-doc-valid.is-expired { color: var(--v-danger-fg); }
/* El aviso de vencido va dentro de la tarjeta, no arriba: se lee junto al
   título, que es donde alguien decide si el documento le sirve o no. */
.v-doc-flag { margin-top: var(--v-sp-3); align-self: flex-start; }

/* ── Ficha de documento ──────────────────────────────────────────────────── */
/* Acá se lee texto largo, no se escanea una tarjeta: más aire a los costados
   que en una `.v-card` común, y una medida de línea que no canse. */
.v-doc-page { padding: 36px 40px; }
/* La cinta de metadatos —responsable, versión, vigencia, consultas— la
   comparten la ficha y el visor. La variante `is-ruled` es la de la ficha, que
   la usa como separador entre la cabecera y el cuerpo del documento. */
.v-doc-meta {
  display: flex; align-items: center; gap: var(--v-sp-4);
  flex-wrap: wrap; margin-top: var(--v-sp-2);
}
.v-doc-meta.is-ruled {
  margin: var(--v-sp-5) 0; padding-bottom: var(--v-sp-5);
  border-bottom: 1px solid var(--v-fill-2);
}
/* Una fecha vencida se dice con color Y con palabra («Venció el…»): el color
   solo no llega a quien no lo distingue. */
.v-doc-meta .is-expired { color: var(--v-danger-fg); }
.v-doc-lead { font-size: 17px; line-height: 1.65; color: var(--v-ink); margin: 0 0 var(--v-sp-5); }
/* `pre-line` porque el texto viene del buscador con sus saltos de línea
   propios: sin eso, un documento entero se aplana en un solo párrafo. */
.v-doc-body { font-size: 15px; line-height: 1.75; color: var(--v-ink-2); white-space: pre-line; }

/* ── Visor de PDF ────────────────────────────────────────────────────────── */
.v-visor {
  position: relative; overflow: hidden;
  border: 1px solid var(--v-border); border-radius: var(--v-radius);
  background: var(--v-visor-bg);
}
/*
 * OJO: esta altura es FUNCIONAL, no decorativa. Un iframe sin alto explícito
 * colapsa a 150 px y el PDF queda ilegible. `min(78vh, 900px)` le da casi toda
 * la pantalla sin estirarse de más en un monitor vertical, y el `min-height`
 * sostiene el caso del teléfono apaisado, donde 78vh son cuatro renglones.
 */
.v-visor iframe {
  display: block; width: 100%; border: none;
  height: min(78vh, 900px); min-height: 420px;
}
.v-visor-note {
  display: flex; align-items: center; gap: var(--v-sp-2);
  margin-top: var(--v-sp-3); font-size: 12px; color: var(--v-muted);
}

/* ── Llamado a Vity al pie de un material ────────────────────────────────── */
/* La misma pieza en la ficha y en el visor: quien terminó de leer y quedó con
   una duda tiene que encontrar dónde preguntarla sin volver al menú. */
.v-ask-vity {
  display: flex; align-items: center; gap: var(--v-sp-4);
  flex-wrap: wrap; margin-top: var(--v-sp-5);
}
.v-ask-vity .v-vity { width: 48px; height: 48px; }
.v-ask-vity-main  { flex: 1; min-width: 200px; }
.v-ask-vity-title { font-weight: 800; font-size: 15px; }
.v-ask-vity-note  { font-size: 13px; color: var(--v-on-deep-soft); }

/* ── Verificación pública de certificado ─────────────────────────────────── */
/*
 * Página suelta: no usa el armazón del Hub porque quien la abre viene de
 * escanear un QR en papel y no tiene sesión. Una sola tarjeta centrada sobre
 * el verde de marca, sin menú ni cabecera que la distraigan.
 */
.v-verify-shell {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; padding: var(--v-sp-5); background: var(--v-deep);
}
.v-verify-card { width: 100%; max-width: 520px; padding: var(--v-sp-6); }
.v-verify-logo { text-align: center; margin-bottom: var(--v-sp-6); }
.v-verify-logo img { display: block; margin: 0 auto; }
.v-verify-result { text-align: center; }
.v-verify-result p { margin: 0; }
/* El estado es el `<h1>` de la página, pero se ve como insignia: hay que
   quitarle el margen que el navegador le da a cualquier encabezado. */
.v-verify-state { margin: 0; }
/* El emoji acompaña; el que informa es el texto de la insignia. Por eso va
   `aria-hidden` en la vista: leerlo en voz alta no agrega nada. */
.v-verify-icon { display: block; font-size: 44px; margin-bottom: var(--v-sp-3); }
/* `fixed` + ancho de la primera columna: sin eso un nombre largo del titular
   empuja las etiquetas y cada fila arranca en un lugar distinto. */
.v-verify-table { table-layout: fixed; margin-bottom: var(--v-sp-1); }
.v-verify-table th { width: 40%; }
.v-verify-holder { font-weight: 700; }
/* El código se compara carácter por carácter contra el del papel: monoespaciada
   y partible, porque no tiene espacios donde cortar. */
.v-verify-code { font-family: monospace; font-size: 12px; word-break: break-all; }
.v-verify-foot {
  text-align: center; margin: var(--v-sp-5) 0 0;
  padding-top: var(--v-sp-4); border-top: 1px solid var(--v-fill-2);
  font-size: 12px; color: var(--v-muted);
}

/* ── Certificados y reuniones: fila con icono ────────────────────────────── */
.v-item-row { display: flex; align-items: center; gap: var(--v-sp-4); }
.v-item-row.is-dim { opacity: .68; }
.v-item-icon {
  width: 60px; height: 60px; flex: 0 0 auto; border-radius: 13px;
  background: var(--v-mine-bg);
  display: flex; align-items: center; justify-content: center; font-size: 32px;
}
.v-item-icon-alt { background: var(--v-bg); width: 58px; height: 58px; font-size: 30px; }
/* El título de la fila pesa más que un `.v-title` común: en una lista de
   certificados es lo único que se lee de reojo. */
.v-item-title { font-size: 16px; font-weight: 800; line-height: 1.3; margin: 3px 0 4px; }
/* El estado de vigencia cuelga bajo la fecha. El `.v-badge` es inline y sin
   este respiro se pega al renglón anterior como si fuera parte de él. */
.v-item-state { margin-top: var(--v-sp-1); }

.v-live-row { padding: 22px 26px; flex-wrap: wrap; gap: 22px; }
.v-live-main { flex: 1; min-width: 220px; }
/* La reunión abre con el estado y el tipo de sesión: una línea de contexto
   antes del título, no un título suelto que hay que abrir para entender. */
.v-live-meta {
  display: flex; align-items: center; gap: var(--v-sp-2);
  flex-wrap: wrap; margin-bottom: var(--v-sp-1); font-weight: 600;
}
/* Título y fecha son un solo bloque: 4 px los separan sin desarmarlo. */
.v-live-main .v-title-lg { margin-bottom: 4px; }

/* ── Juego ───────────────────────────────────────────────────────────────── */
/* La grilla principal —insignias a la izquierda, retos y ranking al costado—
   es la misma `.v-grid-split` que usa el inicio: una sola proporción para
   «bloque que manda + bloque que acompaña», definida más abajo. */
.v-level { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; margin-bottom: 26px; }
/* El trofeo es el acento emocional de la tarjeta, no un icono de interfaz: por
   eso tiene su propio tamaño y no entra en la escala de `.v-icon`. */
.v-level-emoji { font-size: 56px; flex: 0 0 auto; }
/* Lo que falta para el próximo nivel va pegado a la barra: es su leyenda. */
.v-level-note { margin-top: 8px; }
.v-level-main { flex: 1; min-width: 260px; }
.v-level-name {
  font-family: var(--v-font-display); font-size: 12px; color: var(--v-muted);
  font-weight: 700; text-transform: uppercase; letter-spacing: .14em;
}
.v-level-pts {
  font-family: var(--v-font-display); font-weight: 800; font-size: 42px;
  line-height: 1; margin: 6px 0 12px; letter-spacing: -1px;
}
.v-level-pts small { font-size: 20px; color: var(--v-muted); letter-spacing: 0; }
.v-level-bar { height: 8px; background: var(--v-line-dark); border-radius: 99px; overflow: hidden; max-width: 520px; }
.v-level-bar > span { display: block; height: 100%; width: var(--pct, 0%); background: var(--v-green); }
.v-streak { text-align: center; padding: 0 var(--v-sp-3); }
.v-streak-days { font-family: var(--v-font-display); font-weight: 800; font-size: 26px; }

/* La grilla de insignias va más apretada que las de contenido: son iconos de
   54 px, no tarjetas que haya que leer. El responsive ya usa esta clase para
   forzar dos columnas en celular. */
.v-badge-grid { gap: var(--v-sp-3); }

/* `position: relative` para que el «?» y su globo se cuelguen de la casilla.
   La opacidad de `.is-locked` NO puede alcanzar al botón de ayuda: la insignia
   pendiente es justo aquella sobre la que alguien se pregunta cómo se gana, y
   dejarla al 50 % haría ilegible la única respuesta. Ver `.v-ayuda` abajo. */
.v-badge-tile { text-align: center; padding: 18px 14px; position: relative; }
/* La insignia sin ilustración cae en el emoji de reserva. Necesita ocupar su
   propia línea, como la imagen a la que reemplaza. */
.v-badge-tile .v-emoji { display: block; margin-bottom: 8px; }
/*
 * La casilla pendiente se apaga por HIJOS y no como bloque.
 *
 * Antes era `.v-badge-tile.is-locked { opacity: .5 }`, y `opacity` en el padre
 * no se puede deshacer desde un hijo: crea un contexto de composición y arrastra
 * a TODO lo que tiene adentro, sin excepción posible. Con el «?» dentro de la
 * casilla, eso dejaba la explicación al 50 % justamente en las insignias que
 * todavía no se ganaron —que son las únicas sobre las que alguien se pregunta
 * cómo se ganan—.
 *
 * Apagando cada hijo menos el botón de ayuda, el efecto visual es el mismo y la
 * respuesta se lee entera.
 */
.v-badge-tile.is-locked > :not(.v-ayuda) { opacity: .5; }
.v-badge-tile.is-locked img, .v-badge-tile.is-locked .v-emoji { filter: grayscale(1); }
.v-badge-tile img { width: 54px; height: 54px; object-fit: contain; margin-bottom: var(--v-sp-2); }
.v-badge-tile-name { font-size: 12px; font-weight: 700; line-height: 1.3; }
.v-badge-tile-date { font-size: 10px; color: var(--v-ok-fg); font-weight: 700; margin-top: 5px; }

/* ── Globo de ayuda ──────────────────────────────────────────────────────────
 *
 * El «?» que explica cómo se gana cada insignia. Es un componente propio y no
 * una regla de `.v-badge-tile` porque la pregunta «¿esto cómo funciona?» se va
 * a repetir en otras cajas de la plataforma.
 *
 * Reemplaza al `title=""` que traía la casilla: ese atributo no se abre con
 * teclado, no existe en pantalla táctil —la mitad del piso de venta— y los
 * lectores de pantalla lo anuncian de forma inconsistente.
 */
.v-ayuda { position: absolute; top: 8px; right: 8px; }
.v-ayuda-btn {
  width: 22px; height: 22px; padding: 0; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--v-border); background: var(--v-surface);
  color: var(--v-ink-2); font-size: 12px; font-weight: 800; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.v-ayuda-btn:hover { border-color: var(--v-green); color: var(--v-green); }
/* Aro de foco explícito: es un botón chico y redondo, y el contorno que pone
   el navegador por defecto se pierde contra el borde de la casilla. Sin esto,
   quien navega con teclado no sabe dónde está parado. */
.v-ayuda-btn:focus-visible {
  outline: 2px solid var(--v-green); outline-offset: 2px;
}
.v-ayuda-globo {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 30;
  width: 232px; text-align: left;
  background: var(--v-surface); border: 1px solid var(--v-border);
  border-radius: var(--v-radius-sm); box-shadow: var(--v-shadow-md);
  padding: 12px 14px; font-size: 12px; line-height: 1.5; color: var(--v-ink-2);
  font-weight: 500;
}
.v-ayuda-globo-titulo {
  font-weight: 800; color: var(--v-ink); margin-bottom: 4px; font-size: 12px;
}
/* Cerca del borde derecho de la pantalla el globo de 232 px se sale. En
   angosto se ancla al lado opuesto y se deja crecer hasta donde haya lugar. */
@media (max-width: 680px) {
  .v-ayuda-globo { right: auto; left: 0; width: min(232px, 74vw); }
}

.v-challenge { padding: 16px 18px; }
/* Emoji, título y contador en una línea, y la barra debajo: el reto se lee de
   un vistazo sin que el contador se despegue de lo que cuenta. */
.v-challenge-head { display: flex; align-items: center; gap: var(--v-sp-2); margin-bottom: var(--v-sp-2); }
.v-challenge-emoji { font-size: 22px; flex: 0 0 auto; }
.v-challenge-title { flex: 1; min-width: 0; font-size: 14px; font-weight: 700; }
.v-challenge-count { font-size: 12px; color: var(--v-green); font-weight: 700; }
.v-challenge-done { font-size: 11px; color: var(--v-ok-fg); font-weight: 700; margin-top: 8px; }

.v-rank-row {
  border-radius: 12px; padding: 14px 18px; display: flex; align-items: center; gap: var(--v-sp-3);
  background: var(--v-surface); border: 1px solid var(--v-border);
}
.v-rank-row.is-me { background: var(--v-ok-bg); border-color: var(--v-green); }
.v-rank-pos {
  font-size: 15px; font-weight: 800; color: var(--v-muted);
  min-width: 28px; text-align: center; flex: 0 0 auto;
}
.v-rank-pos.is-medal { font-size: 24px; }
/* El nombre no es un título de tarjeta: en una lista de diez filas, 16 px
   convierten el ranking en una pared. */
.v-rank-name { font-size: 14px; font-weight: 700; }
.v-rank-pts { font-family: var(--v-font-display); font-weight: 800; font-size: 20px; color: var(--v-green); }

/* ── Lección: barra de navegación e índice ───────────────────────────────── */
.v-lesson-bar {
  display: flex; align-items: center; gap: var(--v-sp-3);
  margin-bottom: var(--v-sp-4); flex-wrap: wrap;
}
.v-lesson-back {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 700; color: var(--v-muted); white-space: nowrap;
}
.v-lesson-back:hover { color: var(--v-green); }
.v-lesson-tabs {
  display: flex; align-items: center; gap: 8px;
  flex: 1; min-width: 0; overflow-x: auto; padding-bottom: 2px;
}
.v-lesson-tab {
  display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto;
  padding: 8px 14px; border-radius: 99px; font-size: 13px; font-weight: 600;
  white-space: nowrap; background: var(--v-surface); color: var(--v-ink-2);
  border: 1px solid var(--v-border);
}
.v-lesson-tab.is-current {
  background: var(--v-deep); color: var(--v-on-deep);
  border-color: var(--v-deep); font-weight: 700;
}
.v-lesson-num {
  width: 20px; height: 20px; flex: 0 0 auto; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; background: var(--v-fill); color: var(--v-muted-2);
}
.v-lesson-tab.is-current .v-lesson-num { background: var(--v-green); color: var(--v-on-deep); }
.v-lesson-num.is-done { background: var(--v-green-2); color: var(--v-on-deep); }
/* «Completada» le gana a «acá estás parado»: si no, la lección actual ya vista
   pierde su verde de certificada y parece pendiente. Hace falta la
   especificidad extra para vencer a la regla de `.is-current` de arriba. */
.v-lesson-tab.is-current .v-lesson-num.is-done { background: var(--v-green-2); }
.v-lesson-shell { max-width: 1500px; margin: 0 auto; animation: erFade .3s ease; }

/* ── Cajón de VITY: estructura ───────────────────────────────────────────── */
.v-drawer-head {
  background: var(--v-deep); color: var(--v-on-deep);
  padding: 22px 24px; display: flex; align-items: center; gap: var(--v-sp-3);
}
.v-drawer-body {
  flex: 1; overflow-y: auto; padding: 24px; background: var(--v-bg);
  display: flex; flex-direction: column; gap: var(--v-sp-4);
}
.v-drawer-foot { padding: 16px 20px; border-top: 1px solid var(--v-border); background: var(--v-surface); }
.v-drawer-form {
  display: flex; align-items: center; gap: var(--v-sp-2);
  background: var(--v-bg); border: 1px solid var(--v-border);
  border-radius: 12px; padding: 12px 15px;
}
.v-drawer-input { flex: 1; border: none; background: none; outline: none; font-size: 14px; color: var(--v-ink); }
.v-drawer-send {
  width: 38px; height: 38px; flex: 0 0 auto; border-radius: 9px; background: var(--v-green);
  border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
}
.v-drawer-chips { display: flex; gap: 8px; margin-bottom: var(--v-sp-3); flex-wrap: wrap; }
.v-drawer-close {
  background: none; border: none; cursor: pointer; padding: 4px;
  color: var(--v-muted); flex: 0 0 auto; line-height: 0;
}
.v-drawer-close:hover { color: var(--v-on-deep); }
/* Los atajos son botones: hacen algo, no llevan a ningún lado. */
.v-chip { font-family: inherit; }
.v-msg-pending { color: var(--v-muted); }
.v-msg-error   { margin: 0; color: var(--v-danger-fg); }

/* ── Búsqueda del encabezado ─────────────────────────────────────────────── */
.v-search {
  flex: 1; max-width: 440px; margin-left: var(--v-sp-3);
  display: flex; align-items: center; gap: var(--v-sp-2);
  background: var(--v-bg); border: 1px solid var(--v-border);
  border-radius: var(--v-radius-sm); padding: 10px 14px;
}
.v-search input { flex: 1; border: none; background: none; outline: none; font-size: 14px; color: var(--v-ink); }
/*
 * El marcador de posición NO tenía regla: quedaba en el gris que inventa cada
 * navegador (≈54 % del color del texto), que sobre `--v-bg` en claro da 2,9:1
 * —por debajo del mínimo— y en oscuro depende de si el navegador respetó
 * `color-scheme`. Con `--v-ink-2` da 5,19:1 en claro y 9,23:1 en oscuro, y es
 * el mismo gris de texto secundario del resto del sistema.
 *
 * `--v-muted` era el candidato natural por nombre, pero mide 3,02:1 sobre
 * `--v-bg`: alcanza para un icono (no-texto, 3:1) y no para una frase.
 */
.v-search input::placeholder { color: var(--v-ink-2); opacity: 1; }
.v-search:focus-within { border-color: var(--v-green); box-shadow: 0 0 0 3px var(--v-ring); }

/* Botón que abre la búsqueda en pantallas donde la barra no entra. */
.v-search-toggle { display: none; }

/* ── Cambio de tema ──────────────────────────────────────────────────────── */
.v-theme-toggle {
  background: none; border: 1px solid var(--v-border); border-radius: var(--v-radius-sm);
  padding: 9px 11px; cursor: pointer; color: var(--v-ink-2); line-height: 0;
}
.v-theme-toggle:hover { color: var(--v-green); border-color: var(--v-green); }
/* Cada icono aparece sólo en su tema: mostrar el sol en modo claro no le dice
   a nadie qué va a pasar si lo toca. */
.v-theme-toggle .v-theme-dark  { display: block; }
.v-theme-toggle .v-theme-light { display: none; }
:root[data-theme="dark"] .v-theme-toggle .v-theme-dark,
:root[data-theme="dark"] .v-theme-toggle .v-theme-light { display: none; }
:root[data-theme="dark"] .v-theme-toggle .v-theme-light { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .v-theme-toggle .v-theme-dark  { display: none; }
  :root:not([data-theme="light"]) .v-theme-toggle .v-theme-light { display: block; }
  :root[data-theme="light"] .v-theme-toggle .v-theme-dark  { display: block; }
  :root[data-theme="light"] .v-theme-toggle .v-theme-light { display: none; }
}

/* ── Estado de envío ─────────────────────────────────────────────────────── */
/* Un formulario que no avisa que está trabajando se aprieta dos veces. */
.v-btn.is-busy { position: relative; color: transparent !important; pointer-events: none; }
.v-btn.is-busy::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border: 2px solid rgba(255, 255, 255, .45); border-top-color: var(--v-on-deep);
  border-radius: 50%; animation: vSpin .6s linear infinite;
}
.v-btn-ghost.is-busy::after, .v-btn-light.is-busy::after {
  border-color: rgba(0, 0, 0, .2); border-top-color: var(--v-ink);
}
@keyframes vSpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .v-btn.is-busy::after { animation-duration: 2s; }
}

/* ── Aviso de conexión ───────────────────────────────────────────────────── */
/* En piso de venta la señal se corta. Que la pantalla lo diga es la diferencia
   entre «está lento» y «no tengo señal». */
.v-offline {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  z-index: 80; display: none; align-items: center; gap: var(--v-sp-2);
  background: var(--v-deep); color: var(--v-on-deep);
  border-radius: 999px; padding: 11px 20px; font-size: 13px; font-weight: 700;
  box-shadow: var(--v-shadow-lg);
}
body.is-offline .v-offline { display: flex; }

/* ── Texto sólo para lectores de pantalla ────────────────────────────────────
 * Un `✓` es un símbolo, no una palabra. Quien navega con lector escucha «marca
 * de verificación» —o nada— donde debería escuchar «completada». Este es el
 * patrón estándar: sale del flujo visual sin salir del árbol de accesibilidad.
 * `display:none` NO sirve acá: eso lo saca también del lector.
 */
/* El texto para lectores de pantalla vive una sola vez, arriba, como
   `.v-sr-only`. Acá había una copia con otro nombre: dos clases idénticas
   compitiendo por el mismo trabajo es cómo empiezan los sistemas que nadie
   entiende. */

/* ── Utilidades que faltaban ─────────────────────────────────────────────── */
/* La escala de márgenes estaba a medias: existía `mb-4/5/6` y ningún `mt`, así
   que cualquier separación vertical hacia arriba volvía a caer en el `style`. */
.v-mb-2 { margin-bottom: var(--v-sp-2); }
.v-mb-3 { margin-bottom: var(--v-sp-3); }
.v-mt-1 { margin-top: var(--v-sp-1); }
.v-mt-2 { margin-top: var(--v-sp-2); }
.v-mt-3 { margin-top: var(--v-sp-3); }
.v-mt-4 { margin-top: var(--v-sp-4); }
.v-mt-5 { margin-top: var(--v-sp-5); }
.v-mt-6 { margin-top: var(--v-sp-6); }
.v-m0   { margin: 0; }
/* Hermano de `.v-fill-rest` para cuando el bloque NO tiene que crecer: sin
   `min-width:0` un nombre largo dentro de un flex se niega a encogerse y
   empuja al resto de la fila fuera de la tarjeta. */
.v-min-0 { min-width: 0; }
.v-center { justify-content: center; }
/* Negrita semántica del sistema: 700 es el único «fuerte» de la hoja. Evita
   que cada vista invente su propio `font-weight`. */
.v-strong { font-weight: 700; }

/* ── Texto: dos tamaños que faltaban ─────────────────────────────────────── */
/* `.v-text-lg` es el párrafo de lectura de una lección o una intro; `.v-note`
   es la aclaración al pie de una acción —13 px, apagada, nunca protagonista. */
.v-text-lg { font-size: 15px; color: var(--v-ink-2); line-height: 1.6; }
.v-note    { font-size: 13px; color: var(--v-muted); }
.v-emoji-sm { font-size: 18px; }

/* ── Enlace de regreso ───────────────────────────────────────────────────── */
/* Hermano de `.v-lesson-back`, pero para las páginas donde el enlace abre la
   vista en vez de vivir dentro de una barra: por eso trae su propio margen. */
.v-back {
  display: inline-flex; align-items: center; gap: var(--v-sp-1);
  font-size: 13px; font-weight: 700; color: var(--v-muted);
  margin-bottom: var(--v-sp-3);
}
.v-back:hover { color: var(--v-green); }

/* ── Botones e insignias: los tamaños que faltaban ───────────────────────── */
/* Existía `.v-btn-sm` y nada del otro lado. La acción principal de una página
   —«Solicitar mi evaluación»— necesita pesar más que un botón de fila. */
.v-btn-lg    { padding: 14px 26px; font-size: 15px; }
.v-btn-block { width: 100%; }
/* Una insignia que ES el resultado del curso no puede tener el mismo cuerpo
   que la etiqueta de una fila de tabla. */
.v-badge-lg  { font-size: 14px; padding: 11px 20px; }

/* ── Pie de tarjeta ──────────────────────────────────────────────────────── */
/* La acción secundaria de una tarjeta va separada por una línea, no por aire:
   sin la regla se lee como un ítem más de la lista de arriba. */
.v-card-foot {
  margin-top: var(--v-sp-5); padding-top: var(--v-sp-4);
  border-top: 1px solid var(--v-fill-2);
}

/* ── Curso: cabecera ─────────────────────────────────────────────────────── */
/* El título del curso es un `h1` pero no el de la plataforma: va un escalón
   por debajo de `.v-h1` para no competir con el nombre de la sección. */
.v-course-title {
  font-family: var(--v-font-display); font-weight: 800; font-size: 31px;
  text-transform: uppercase; margin: 4px 0 0;
}
/* El bloque de progreso se alinea a la derecha y reserva ancho: si se encoge
   con el texto, el porcentaje baila de posición entre curso y curso. */
.v-course-progress { min-width: 220px; text-align: right; }
.v-course-progress .v-badge { margin-bottom: var(--v-sp-2); }
.v-course-progress-count { display: block; margin: var(--v-sp-2) 0 var(--v-sp-1); }

/* Contenido y índice. La proporción 1.6/1 le da a la portada el peso que le
   corresponde sin dejar el índice en una columna de dos palabras de ancho. */
.v-course-layout { grid-template-columns: 1.6fr 1fr; gap: var(--v-sp-5); align-items: start; }

/* ── Curso: portada con velo ─────────────────────────────────────────────── */
/* Es pariente de `.v-hero` pero no es lo mismo: acá el velo baja en vertical
   —de transparente arriba a casi opaco abajo— porque el texto se apoya en el
   piso de la caja, no a la izquierda. */
.v-course-hero {
  position: relative; border-radius: var(--v-radius); overflow: hidden;
  background: var(--v-deep); aspect-ratio: 16 / 9;
  display: flex; align-items: center; justify-content: center;
}
.v-course-hero-media { position: absolute; inset: 0; }
.v-course-hero-veil {
  position: absolute; inset: 0;
  background: var(--v-velo-tarjeta);
}
.v-course-hero-body {
  position: relative; text-align: center; color: var(--v-on-deep); padding: var(--v-sp-6);
}
.v-course-letter {
  font-family: var(--v-font-display); font-weight: 800; font-size: 88px; line-height: 1;
}
/* El nombre de la fase, debajo de la letra. Va sobre el velo oscuro del
   recuadro, así que el color sale de `--v-on-deep-soft` y no del gris de
   cuerpo: ese gris sobre fondo oscuro no llega al contraste mínimo. */
.v-course-name {
  font-family: var(--v-font-display); font-weight: 700; font-size: 15px;
  letter-spacing: .16em; text-transform: uppercase; color: var(--v-on-deep-soft);
  margin-top: 6px;
}
.v-course-summary {
  font-size: 15px; color: var(--v-on-deep-soft); line-height: 1.55;
  max-width: 520px; margin: 12px auto 22px;
}

/* ── Curso: índice de lecciones ──────────────────────────────────────────── */
/* Lista compacta: las filas se tocan casi entre sí a propósito, para que se
   lean como un solo recorrido y no como seis tarjetas sueltas. */
.v-toc { display: flex; flex-direction: column; gap: 4px; }
.v-toc-item {
  display: flex; align-items: center; gap: var(--v-sp-2);
  padding: 12px; border-radius: var(--v-radius-sm);
  transition: background .15s;
}
.v-toc-item:hover { background: var(--v-bg); }
.v-toc-title { flex: 1; font-size: 13px; font-weight: 600; color: var(--v-ink); line-height: 1.35; }
/* En el índice del curso hay más aire que en la barra de la lección, así que
   el disco puede crecer: mismo componente, otra escala. */
.v-lesson-num-lg { width: 26px; height: 26px; font-size: 12px; }

/* ── Lección: hoja de lectura ────────────────────────────────────────────── */
/* Ancho de lectura acotado: una línea de 1500 px obliga a barrer la cabeza de
   izquierda a derecha en cada renglón y se pierde el hilo. */
.v-lesson-doc { padding: 44px; max-width: 860px; margin: 0 auto; }

/* Aviso de la cápsula en pantallas chicas. Arranca oculto y lo enciende el JS
   sólo bajo 900 px: mostrarlo en escritorio es ruido, ahí ya se ve grande. */
.v-capsula-hint {
  display: none; font-size: 12px; color: var(--v-muted);
  margin: var(--v-sp-2) 0 0; text-align: center;
}

/* ── Evaluación práctica: piezas que faltaban ────────────────────────────── */
.v-practica-titulo { font-size: 26px; margin: 0 0 var(--v-sp-1); }
.v-rubrica-label   { font-size: 14px; color: var(--v-ink); }
/* Sin `min-width:0` un nombre largo estira la fila y rompe la grilla de dos
   columnas de la rúbrica. */
.v-rubrica-text    { min-width: 0; }

/* ── Inicio y malla: lo que faltaba para vaciar sus `style` ──────────────────
 * Todo lo de acá abajo salió de los 101 atributos `style` que tenían
 * `aula/dashboard.php` y `aula/malla.php`. No son utilidades sueltas: cada una
 * nombra una pieza que ya existía en el diseño y que las vistas venían
 * redibujando a mano, con un hex distinto cada vez.
 */

/* Texto — se suman a la familia `.v-title` / `.v-ink` que ya estaba. */
/* El titular de 15 px es el que usan las filas y las tarjetas chicas: `.v-title`
   pesa un punto más y ahí compite con el título de la sección. */
.v-title-sm { font-size: 15px; font-weight: 700; line-height: 1.35; color: var(--v-ink); }
/* Hermano de `.v-h1-lg`: mismo titular, un escalón más arriba, para el bloque
   que abre una sección dentro de la página. */
.v-h2-lg { font-size: 21px; }
/* El apagado que faltaba: `.v-ink` y `.v-accent` cubrían los extremos y para el
   gris del medio las vistas volvían a escribir `#7E938E`. */
.v-dim { color: var(--v-muted); }
/* El contador que acompaña a un título de tarjeta («3/5 listas»): es metadato,
   pero en negrita, porque es la cifra que se busca de un vistazo. */
.v-meta-strong { font-size: 12px; color: var(--v-muted); font-weight: 700; }
/* La bajada del saludo del inicio. Va un punto por encima de `.v-lead` y sin
   ancho máximo: comparte fila con la píldora de estado y ya está acotada. */
.v-page-lead { margin: 10px 0 0; font-size: 16px; color: var(--v-ink-2); }
/* El «ver todo» de una sección. No es un botón: es un enlace que pesa lo justo
   para encontrarse sin robarle protagonismo al contenido. */
.v-link-more { font-size: 14px; font-weight: 700; }
/* Faltaba el escalón intermedio de la escala de emoji. */
.v-emoji-md { font-size: 20px; }

/* Aviso con dos renglones: el segundo enumera y no debe pesar como el primero.
   Sólo cambia el tamaño porque el color lo pone la variante del aviso. */
.v-alert-detail { font-size: 13px; margin-top: 2px; }

/* KPI */
/* Sobre la tarjeta oscura `--v-ink-2` está calculado para fondo claro y el pie
   del KPI se pierde: ahí manda el gris de marca. */
.v-kpi-note.is-on-dark { color: var(--v-muted); }
/* El denominador de un KPI («/12»): parte del mismo número, pero es el techo,
   no el logro, así que no compite en tamaño ni en color. */
.v-kpi-total { font-size: 30px; color: var(--v-muted-2); }

/* Inicio: continuar donde quedaste */
/* El rótulo del bloque. No es `.v-kicker`: aquel abre la página, este abre una
   tarjeta y por eso respira menos. */
.v-continue-head {
  font-family: var(--v-font-display); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--v-green); font-weight: 700;
  margin-bottom: var(--v-sp-3);
}
.v-continue-track { font-size: 12px; color: var(--v-muted); font-weight: 600; }

/* Inicio: resumen de la malla */
/* El nombre de la línea empuja al resto de la fila hacia la derecha y se recorta
   antes que romperla. */
.v-line-row-name {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 14px; font-weight: 600; color: var(--v-ink);
}

/* Malla */
/* La primera sección de la página no separa de nada: los 38 px de `.v-section-head`
   están pensados para el aire ENTRE bloques. */
.v-section-head.is-first { margin-top: var(--v-sp-2); }
/* El emoji que encabeza cada tarjeta de la portada. */
.v-hero-card-emoji { font-size: 22px; margin-bottom: 8px; }
/* Título y resumen de la tarjeta de fase: son el par que se lee al escanear la
   fila de cinco, y por eso no comparten estilo con los títulos de sección. */
.v-phase-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 19px;
  text-transform: uppercase; margin: var(--v-sp-3) 0 var(--v-sp-1);
}
.v-phase-summary { font-size: 13px; line-height: 1.45; color: var(--v-ink-2); margin: 0 0 var(--v-sp-4); }
/* El pie de la tarjeta: cuántas lecciones van, o qué falta para desbloquearla. */
.v-phase-foot { font-size: 11px; color: var(--v-muted); margin-top: var(--v-sp-2); font-weight: 600; }

/* Composición */
/* La proporción del inicio: el bloque de «continuar» manda y Vity acompaña.
   Estaba escrita en el `style` de la vista, que es exactamente lo que obligaba
   al `!important` de la versión angosta. */
.v-grid-split { grid-template-columns: 1.5fr 1fr; }
/* Botón que ocupa el ancho de su tarjeta porque es su única acción. */
.v-btn-block { width: 100%; }
/* Una tarjeta que además es enlace: `<a>` es inline por defecto y sin esto la
   caja se encoge hasta el ancho del texto. */
a.v-card, a.v-card-dark { display: block; }

/* (v-mb-2, v-mb-3 y v-mt-4 ya están declaradas más arriba) */


/* ── Formularios: cabecera y envío ───────────────────────────────────────── */
/* El emoji que titula un bloque de formulario. No es contenido —va con
   `aria-hidden`— así que no puede heredar el tamaño del texto de al lado. */
.v-form-head-icon { font-size: 20px; flex: 0 0 auto; line-height: 1; }
/* La bajada que explica para qué sirve el bloque. Vivía inline en los cuatro
   formularios de la cuenta, cada uno con su propio margen. */
.v-form-head-note { margin: 2px 0 0; font-size: 13px; color: var(--v-muted); }
/* El botón que cierra un formulario necesita despegarse de los campos: pegado
   al último `input` se lee como parte del campo, no como la acción. */
.v-form-submit { margin-top: var(--v-sp-5); }

/* ── Grupos de casillas ──────────────────────────────────────────────────── */
/*
 * `fieldset` + `legend` es lo que le dice a un lector de pantalla que las tres
 * casillas de «De qué» son UNA pregunta y no tres preguntas sueltas. Antes el
 * encabezado era un `<div>`: se veía igual y no significaba nada.
 *
 * El navegador le pone al `fieldset` borde, padding y un `min-width` propios;
 * se los sacamos porque acá quien agrupa a la vista es la grilla.
 */
.v-fieldset { border: none; margin: 0; padding: 0; min-width: 0; }
.v-fieldset + .v-fieldset { margin-top: var(--v-sp-5); }
.v-legend { padding: 0; margin-bottom: var(--v-sp-2); }

/* ── Tarjeta apagada ─────────────────────────────────────────────────────── */
/* Para el bloque que se muestra pero no se toca —la ficha laboral—. Se funde
   con el fondo de la página a propósito: la señal de «esto no es tuyo» es que
   no tiene el papel blanco que sí tienen los formularios de arriba.
   Reemplaza un `style="background:var(--v-mist)"` que apuntaba a un token
   inexistente, así que la tarjeta venía quedando transparente por accidente. */
.v-card-muted { background: var(--v-bg); }

/* ── Mi cuenta ───────────────────────────────────────────────────────────── */
/* Los cuatro bloques de la cuenta son hermanos y se separan igual. Cada uno
   traía su `margin-bottom` inline: bastaba con que alguien escribiera 24 en
   uno para que la columna se viera desprolija. */
.v-account-section { margin-bottom: var(--v-sp-5); }

/* Cabecera de identidad: foto, nombre y las dos insignias del juego. Envuelve
   en pantalla angosta para que las insignias bajen enteras en vez de
   comprimir el nombre hasta partirlo. */
.v-identity { display: flex; align-items: center; gap: var(--v-sp-5); flex-wrap: wrap; }
/* La foto de la cuenta es la más grande de la plataforma: es de quien mira. */
.v-avatar-lg { width: 76px; height: 76px; font-size: 25px; }
.v-identity-name {
  font-family: var(--v-font-display); font-weight: 800; font-size: 22px; color: var(--v-ink);
}
.v-identity-role { font-size: 13px; color: var(--v-muted); margin-top: 3px; }

/* ── Ingreso ─────────────────────────────────────────────────────────────── */
/*
 * Dos columnas: la foto de tienda a la izquierda y el formulario a la derecha.
 * Todo esto vivía en un `<style>` dentro de la vista, con nueve `#hex` escritos
 * a mano —o sea: la única pantalla de la plataforma que jamás iba a acompañar
 * un cambio de paleta era justo la primera que ve alguien.
 *
 * La foto no es decoración: ubica la plataforma en el mundo real de quien entra
 * desde el celular, parado en el piso de venta.
 */
.v-login { display: flex; min-height: 100vh; }
.v-login-art {
  flex: 1; position: relative; overflow: hidden;
  display: flex; align-items: flex-end; padding: 56px;
  background: var(--v-deep);
}
.v-login-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: .88;
}
/*
 * Velo vertical: sin él el texto blanco cae sobre la parte clara de la foto.
 *
 * ⚠️ El velo protege AL TEXTO, no a la foto entera.
 *
 * Antes iba de .55 a .95 sobre una foto al 42 % de opacidad. Entre las dos
 * capas la fachada casi no se veía: quedaba una mancha verde con una forma
 * adentro. Y la foto no es decoración —ubica la plataforma en el lugar real de
 * quien entra desde el celular, parado en el piso de venta—: si no se ve, no
 * hace su trabajo y sólo pesa.
 *
 * Ahora la foto va al 88 % y el velo arranca TRANSPARENTE: el degradado no
 * empieza a oscurecer hasta el 40 %, que es donde termina la imagen y empieza
 * el texto. Arriba se ve la fachada; abajo, donde está el nombre de la escuela
 * en blanco, hay .90 de fondo — más oscuro que antes, no menos.
 *
 * O sea que la foto se ve MÁS y el texto se lee MEJOR. No es un compromiso
 * entre las dos cosas: el velo estaba puesto donde no hacía falta.
 */
.v-login-veil {
  position: absolute; inset: 0;
  background: var(--v-velo-ingreso);
}
.v-login-copy { position: relative; color: var(--v-on-deep); max-width: 520px; }
.v-login-tag { display: flex; align-items: center; gap: 8px; margin-bottom: 20px; }
.v-login-rule { width: 28px; height: 2px; background: var(--v-green); flex: 0 0 auto; }
.v-login-lockup { display: flex; align-items: center; gap: 18px; }
.v-login-mark {
  width: var(--v-mark-size-login); height: var(--v-mark-size-login);
  flex: 0 0 auto; border-radius: var(--v-mark-radius); background: var(--v-mark-bg);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .26);
}
.v-login-mark img { display: block; }
.v-login-name {
  font-family: var(--v-font-logo); font-weight: 700; font-size: 40px;
  color: var(--v-on-deep); line-height: 1;
}
.v-login-sub {
  font-family: var(--v-font-logo); font-weight: 600; font-size: 13px;
  letter-spacing: .5em; color: var(--v-lime); margin-top: 5px;
}
.v-login-claim {
  font-family: var(--v-font-display); font-weight: 800; font-size: 46px;
  line-height: 1.04; margin: 28px 0 14px;
}
.v-login-lead {
  font-size: 16px; line-height: 1.6; color: var(--v-on-deep-soft);
  margin: 0; max-width: 470px;
}
.v-login-legal { margin-top: 38px; font-size: 12px; color: var(--v-muted); }

.v-login-form {
  width: min(520px, 100%); flex: 0 0 auto; background: var(--v-surface);
  display: flex; align-items: center; padding: 56px;
}
.v-login-box { width: 100%; max-width: 380px; margin: 0 auto; }
.v-login-title {
  font-family: var(--v-font-display); font-weight: 800; font-size: 32px;
  text-transform: uppercase; margin: 0 0 8px;
}
.v-login-note { font-size: 14px; color: var(--v-ink-2); margin: 0 0 28px; }
.v-login-help { margin-top: 20px; text-align: center; }

/* «Mantener mi sesión abierta». El aviso del equipo compartido va SIEMPRE
   visible debajo de la casilla, nunca detrás de un ícono de ayuda: se tiene que
   leer antes de marcar, no después de averiguar. */
.v-login-recordar { margin: 4px 0 18px; }
.v-recordar-fila {
  display: flex; align-items: center; gap: 10px;
  font-size: 14px; cursor: pointer;
}
.v-recordar-fila input { width: 17px; height: 17px; accent-color: var(--v-green); flex: 0 0 auto; }
.v-login-recordar .v-field-hint { margin-top: 6px; display: block; }

/* Bajo 900 px la foto se va: en un teléfono, media pantalla de fotografía es
   media pantalla menos para el único formulario que importa acá. */
@media (max-width: 900px) {
  .v-login-art { display: none; }
  .v-login-form { width: 100%; padding: 32px 24px; }
}

/* ── Pantalla suelta de autenticación ────────────────────────────────────── */
/* Una tarjeta centrada sobre el verde de marca, sin sidebar ni cabecera: son
   las pantallas donde todavía no hay sesión completa —definir la contraseña
   provisoria— y cualquier navegación sería una salida en falso. */
.v-auth-screen {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; background: var(--v-deep);
}
.v-auth-card { width: 100%; max-width: 440px; padding: 36px 38px; }
.v-auth-title {
  font-family: var(--v-font-display); font-weight: 800; font-size: 26px;
  text-transform: uppercase; margin: 0 0 8px;
}
.v-auth-note { font-size: 14px; color: var(--v-ink-2); margin: 0 0 24px; }
.v-auth-foot { text-align: center; margin-top: var(--v-sp-4); }
.v-auth-out { font-size: 13px; color: var(--v-muted); }
.v-auth-out:hover { color: var(--v-green); }
/* Los formularios de autenticación cierran con un botón a todo el ancho y muy
   pegado: no hay nada más en la tarjeta de lo que separarlo. */
.v-auth-submit { width: 100%; margin-top: var(--v-sp-1); }

/* ── Pantalla de carga ───────────────────────────────────────────────────── */
/*
 * Vivía completa en un `<style>` dentro de `partials/splash.php`. Los tiempos,
 * los retardos y las curvas son EXACTAMENTE los del prototipo: la secuencia
 * dura 2,4 s y cada pieza entra donde tiene que entrar. No se tocó ni un valor
 * al traerla; lo único que cambió son los `#hex`, que ahora son tokens.
 */
#v-splash {
  position: fixed; inset: 0; z-index: 200; background: var(--v-deep);
  display: flex; align-items: center; justify-content: center;
  animation: vSplashOut 2.4s ease forwards;
}
/* Apenas empieza el desvanecido la capa deja de interceptar clics: sin esto la
   primera interacción del asesor no responde durante medio segundo. */
.v-splash-done { pointer-events: none; }

.v-splash-grain {
  position: absolute; inset: 0; opacity: .1;
  background-image: repeating-linear-gradient(115deg, var(--v-green) 0 2px, transparent 2px 30px);
}
.v-splash-halo {
  position: absolute; width: 560px; height: 560px; border-radius: 50%;
  background: radial-gradient(circle, rgba(20, 160, 122, .30) 0%, rgba(20, 160, 122, 0) 68%);
  animation: vBreath 3.2s ease-in-out infinite;
}
.v-splash-stack { position: relative; display: flex; flex-direction: column; align-items: center; }

.v-splash-mark { position: relative; width: var(--v-splash-mark); height: var(--v-splash-mark); }
.v-splash-ring { position: absolute; inset: 0; }
.v-splash-track { stroke: rgba(20, 160, 122, .35); stroke-width: 2; }

/* 572 ≈ 2πr con r=91. El trazo arranca oculto y se dibuja entero. */
.v-splash-arc {
  stroke: var(--v-lime); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 572; stroke-dashoffset: 572;
  animation: vDraw 1.5s cubic-bezier(.45, .05, .2, 1) .25s forwards;
}

/* `inset` calculado y no un número: la placa se centra dentro del anillo, y lo
   que la marca declara es su TAMAÑO —lo mismo que declara para las otras dos—.
   Con `inset: 20px` fijo, el tamaño de esta placa era 188 menos cuarenta y
   nadie podía cambiarlo sin mover el anillo. En VITTA da los mismos 148 px. */
.v-splash-disc {
  position: absolute;
  inset: calc((var(--v-splash-mark) - var(--v-mark-size-splash)) / 2);
  border-radius: var(--v-mark-radius); background: var(--v-mark-bg);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .28);
  animation: vMarkIn .8s cubic-bezier(.2, .9, .3, 1) both, vFloat 3.4s ease-in-out 1.2s infinite;
}
.v-splash-disc img { display: block; animation: vLogoIn .9s cubic-bezier(.2, .9, .3, 1) .18s both; }

.v-splash-bar {
  width: 260px; height: 4px; border-radius: 99px; background: var(--v-deep-2);
  overflow: hidden; margin-top: 40px; animation: vRise .6s ease 1s both;
}
.v-splash-bar > span {
  display: block; width: 100%; height: 100%; background: var(--v-lime);
  animation: vBar 1.5s cubic-bezier(.5, 0, .2, 1) 1s both;
}
.v-splash-text {
  margin-top: 20px; font-family: var(--v-font-display); font-weight: 700;
  font-size: 12px; letter-spacing: .24em; text-transform: uppercase;
  color: var(--v-on-deep-soft);
  animation: vRise .6s ease 1.15s both;
}

@keyframes vDraw   { to { stroke-dashoffset: 0; } }
@keyframes vMarkIn { 0% { opacity: 0; transform: scale(.72); } 100% { opacity: 1; transform: scale(1); } }
@keyframes vLogoIn { 0% { opacity: 0; transform: scale(.6) rotate(-14deg); } 100% { opacity: 1; transform: none; } }
@keyframes vFloat  { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }

/* ═══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   Buena parte del equipo entra desde el celular, en piso de venta. El móvil no
   es una degradación del escritorio: es el caso principal.
   ═══════════════════════════════════════════════════════════════════════════ */

.v-burger {
  display: none; background: none; border: 1px solid var(--v-border);
  border-radius: var(--v-radius-sm); padding: 9px 11px; cursor: pointer;
  color: var(--v-ink); line-height: 0;
}
/* El velo del cajón de navegación usa `--v-scrim`, que es exactamente este
   valor y ADEMÁS se oscurece en tema oscuro. Quedaba escrito a mano y el
   token estaba declarado sin que lo usara nadie: la capa existía y esta
   regla no la miraba.
   ⚠️ `.v-drawer-scrim` (.45) y `.v-diploma-scrim` (.62) siguen sueltos a
   propósito: son OTRAS opacidades y unificarlas cambia lo que se ve. Si
   alguna vez se deciden iguales, este es el token. */
.v-scrim { display: none; position: fixed; inset: 0; z-index: 45; background: var(--v-scrim); }

/* Red de seguridad: nada desborda el ancho de la pantalla, incluido el
   contenido con ancho fijo que se agregue más adelante. */
img, svg, video, iframe { max-width: 100%; }

/* ── Escritorio angosto ──────────────────────────────────────────────────── */
@media (max-width: 1180px) {
  .v-grid-5 { grid-template-columns: repeat(3, 1fr); }
  .v-grid-4 { grid-template-columns: repeat(2, 1fr); }
}

/* ── Tablet ──────────────────────────────────────────────────────────────── */
@media (max-width: 960px) {
  /* El sidebar NO se oculta: pasa a ser un cajón. Ocultarlo sin alternativa
     dejaba la plataforma sin ninguna navegación. */
  .v-sidebar {
    position: fixed; top: 0; left: 0; z-index: 50;
    transform: translateX(-100%); transition: transform .25s cubic-bezier(.2,.8,.2,1);
    box-shadow: 8px 0 40px rgba(0, 0, 0, .3);
  }
  body.v-nav-open .v-sidebar { transform: none; }
  body.v-nav-open .v-scrim  { display: block; }
  body.v-nav-open { overflow: hidden; }

  .v-burger { display: inline-flex; align-items: center; }
  .v-header { padding: 0 16px; gap: 12px; }
  .v-search { max-width: none; margin-left: 0; }
  .v-page-title { display: none; }
  .v-content { padding: 24px 20px 44px; }

  .v-hero-body { padding: 20px 22px; gap: 22px; }
  .v-continue-letter { width: 92px; height: 92px; font-size: 48px; }

  .v-grid-2, .v-grid-3, .v-grid-4, .v-grid-5 { grid-template-columns: repeat(2, 1fr); }

  /* Las grillas de proporción ya no viven en el atributo `style` sino en clases
     como `.v-grid-split`, así que el `!important` dejó de ser obligatorio. Se
     mantiene a propósito: cubre de una sola vez cualquier proporción que se
     agregue después y evita tener que enumerarlas acá una por una. */
  .v-container > .v-grid,
  .v-container-narrow > .v-grid { grid-template-columns: 1fr !important; }
}

/* ── Celular ─────────────────────────────────────────────────────────────── */
@media (max-width: 680px) {
  .v-header { height: 62px; padding: 0 14px; gap: 10px; }
  .v-content { padding: 18px 14px 40px; }

  /* ── Búsqueda en celular ──────────────────────────────────────────────────
   * Antes acá decía `display:none` y la justificación era «la búsqueda vive en
   * el banco». No es cierta: para buscar había que abrir el menú, entrar al
   * banco y recién ahí escribir. Tres toques para lo que es la acción más
   * frecuente de alguien parado frente a un cliente.
   *
   * Ahora la barra se colapsa en un botón de lupa y se despliega como una fila
   * completa bajo el encabezado. Sigue siendo el mismo formulario: un solo
   * campo, un solo destino.
   */
  .v-search-toggle {
    display: inline-flex; align-items: center; background: none;
    border: 1px solid var(--v-border); border-radius: var(--v-radius-sm);
    padding: 9px 11px; cursor: pointer; color: var(--v-ink); line-height: 0;
  }
  .v-search {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    margin: 0; max-width: none; border-radius: 0;
    border-left: none; border-right: none; padding: 12px 14px;
  }
  body.v-search-open .v-search { display: flex; }
  /* 16px o iOS hace zoom al enfocar y descuadra todo el encabezado. */
  .v-search input { font-size: 16px; }
  .v-header { position: sticky; }

  /* Las tarjetas con contenido (cursos, documentos) van a una columna: apiladas
     se leen; en dos quedan ilegibles. Los KPI son lo contrario —un número y una
     etiqueta— así que se quedan en 2×2 y el tablero se ve de un vistazo en vez
     de obligar a scrollear cuatro pantallas para leer cuatro cifras. */
  .v-grid, .v-grid-2, .v-grid-3, .v-grid-5 { grid-template-columns: 1fr !important; gap: 14px; }
  .v-grid-4 { grid-template-columns: repeat(2, 1fr) !important; gap: 12px; }
  .v-grid-4 > .v-card, .v-grid-4 > .v-card-dark, .v-grid-4 > a.v-card { padding: 15px 14px; }
  .v-grid-4 .v-kpi-label { font-size: 10px; letter-spacing: .1em; }
  .v-grid-4 .v-kpi-value { font-size: 30px; }
  .v-grid-4 .v-kpi-value span { font-size: 20px; }
  .v-grid-4 .v-kpi-note, .v-grid-4 .v-card > div:last-child { font-size: 11px; line-height: 1.35; }

  .v-card, .v-card-dark { padding: 18px; }
  .v-card-gradient { padding: 20px; }

  .v-h1     { font-size: 24px; }
  .v-h1-lg  { font-size: 26px; line-height: 1.12; }
  .v-h1-sm  { font-size: 22px; }
  .v-h1-xs  { font-size: 20px; }
  .v-h2     { font-size: 17px; }
  .v-h3     { font-size: 15px; }
  .v-lead   { font-size: 14px; }
  .v-kicker { font-size: 11px; }

  .v-kpi-value    { font-size: 38px; letter-spacing: -1px; }
  .v-phase-letter { font-size: 40px; }
  .v-phase        { min-height: 0; }

  /* Altura de dedo: 46 px es el mínimo cómodo en piso de venta, muchas veces
     con el celular en una mano. */
  .v-btn    { width: 100%; justify-content: center; padding: 14px 18px; min-height: 46px; }
  .v-btn-sm { width: auto; min-height: 40px; padding: 10px 14px; }

  .v-drawer { width: 100%; }
  .v-section-head { flex-wrap: wrap; gap: 8px; margin: 26px 0 14px; }
  .v-section-rule { display: none; }
  .v-empty { padding: 36px 16px; }

  /* Las tablas se leen de a una fila. En 375 px, seis columnas no entran ni
     con scroll horizontal cómodo: hay que girar el teléfono para leer un
     nombre, y nadie lo hace. */
  .v-table-stack thead { display: none; }
  .v-table-stack, .v-table-stack tbody { display: block; }
  .v-table-stack tr {
    display: block; background: var(--v-surface); border: 1px solid var(--v-border);
    border-radius: 12px; padding: 12px 14px; margin-bottom: 10px;
  }
  .v-table-stack td {
    display: flex; align-items: center; justify-content: space-between;
    gap: 14px; padding: 7px 0; border: none; text-align: right;
  }
  .v-table-stack td::before {
    content: attr(data-label); flex: 0 0 auto; text-align: left;
    font-family: var(--v-font-display); font-size: 10px; letter-spacing: .1em;
    text-transform: uppercase; color: var(--v-muted); font-weight: 700;
  }
  .v-table-stack td:first-child {
    border-bottom: 1px solid var(--v-fill-2); padding-bottom: 12px; margin-bottom: 4px;
  }
  .v-table-stack td:first-child::before { display: none; }
}

/* ── Celular: componentes ────────────────────────────────────────────────── */
@media (max-width: 680px) {
  .v-page-head-split { gap: var(--v-sp-3); margin-bottom: 20px; }

  /* La cabecera del inicio: el saludo arriba, la píldora debajo y a la
     izquierda. Estirada a lo ancho parece un botón, y no lo es. */
  .v-page-head-split .v-pill { align-self: flex-start; }

  .v-continue { padding: 18px; }
  .v-continue-body { gap: var(--v-sp-3); }
  .v-continue-letter { width: 100%; height: 84px; font-size: 44px; }
  .v-continue-main { min-width: 0; }
  .v-continue-title { font-size: 19px; }

  .v-hero-body { padding: 18px; gap: var(--v-sp-4); }
  .v-hero-intro { max-width: none; }
  .v-hero-cards { grid-template-columns: 1fr; gap: var(--v-sp-2); }
  .v-hero-band { height: 148px; }
  .v-hero-band-body { left: 18px; bottom: 18px; right: 18px; }
  .v-hero-band-title { font-size: 18px; }

  .v-level { gap: var(--v-sp-4); }
  .v-level-pts { font-size: 34px; }
  .v-streak { padding: 0; display: flex; align-items: center; gap: var(--v-sp-2); }
  .v-streak-days { font-size: 20px; }

  /* Las insignias en dos columnas: en una sola quedan cuatro pantallas de
     scroll para ver ocho iconos. */
  .v-badge-grid { grid-template-columns: repeat(2, 1fr) !important; }

  .v-item-row { gap: var(--v-sp-3); flex-wrap: wrap; }
  .v-item-icon { width: 48px; height: 48px; font-size: 26px; border-radius: 11px; }
  .v-item-icon-alt { width: 48px; height: 48px; font-size: 26px; }
  .v-live-row { padding: 18px; gap: var(--v-sp-3); }
  /* El botón de la fila ocupa el ancho: es la acción de la tarjeta. */
  .v-item-row > .v-btn { width: 100%; }

  .v-next-live { padding: 16px 18px; flex-wrap: wrap; }
  .v-next-live-cta { width: 100%; text-align: center; }

  .v-doc-cover { margin: -18px -18px 16px; height: 92px; }

  /* Estos tres paddings antes viajaban en el atributo `style`, así que ninguna
     media query podía tocarlos: 40 px de aire lateral en una pantalla de 375
     dejaban la columna de texto en la mitad del ancho. */
  .v-doc-page { padding: 18px; }
  .v-verify-shell { padding: var(--v-sp-3); }
  .v-verify-card  { padding: var(--v-sp-4); }

  .v-doc-meta { gap: var(--v-sp-2) var(--v-sp-3); }
  .v-ask-vity { gap: var(--v-sp-3); }

  .v-lesson-bar { gap: var(--v-sp-2); margin-bottom: var(--v-sp-3); }
  /* El índice de lecciones scrollea; que se note dónde termina. */
  .v-lesson-tabs { margin: 0 -14px; padding: 0 14px 4px; }
  .v-lesson-bar > .v-btn-sm { width: auto; }
  .v-lesson-doc { padding: 20px; }

  /* Apilado, el bloque de progreso deja de ser una columna: alinear a la
     derecha un texto que ahora ocupa todo el ancho lo deja huérfano. */
  .v-course-progress { min-width: 0; text-align: left; }
  .v-course-title { font-size: 22px; }
  .v-course-hero-body { padding: var(--v-sp-4); }
  .v-course-letter { font-size: 54px; }
  .v-course-summary { font-size: 14px; margin: var(--v-sp-2) auto var(--v-sp-4); }

  .v-rank-row { padding: 12px 14px; gap: var(--v-sp-2); }
  .v-rank-pts { font-size: 17px; }

  .v-drawer-head { padding: 16px 18px; }
  .v-drawer-body { padding: 18px 16px; }
  .v-offline { bottom: 12px; font-size: 12px; padding: 10px 16px; }
}

@media (max-width: 400px) {
  .v-content { padding: 16px 12px 36px; }
  .v-h1, .v-h1-lg { font-size: 22px; }
  .v-kpi-value { font-size: 32px; }
  .v-card, .v-card-dark { padding: 15px; }
  .v-badge-grid { grid-template-columns: repeat(2, 1fr) !important; }
}

/* ── Diploma ─────────────────────────────────────────────────────────────────
 * ATENCIÓN: este bloque NO usa los tokens del tema, y es a propósito.
 *
 * Un diploma es un documento: se imprime, se adjunta a un correo, se sube a
 * LinkedIn. El papel es blanco siempre. Si la lámina siguiera el tema, quien
 * tuviera el modo oscuro activado se descargaría una lámina negra con letras
 * claras y la mandaría así a Talento Humano.
 *
 * Esto ANTES eran 23 `#hex` escritos a mano, con un comentario acá arriba
 * explicando por qué. El comentario decía la verdad y aun así el bloque era el
 * mayor foco de color suelto de toda la hoja: repetía cinco veces el verde de
 * marca y seis veces la tinta, y el día que la marca cambie de verde hay que
 * acordarse de este rincón.
 *
 * Ahora la lámina usa los MISMOS nombres semánticos que el resto de la
 * plataforma —`var(--v-ink)`, `var(--v-border)`, `var(--v-ok-fg)`— y lo que la
 * mantiene quieta es el bloque PAPEL de arriba, que redeclara esos tokens
 * clavados en su rama clara sobre `.v-diploma-sheet`. Un solo lugar donde
 * mirar, y las reglas de acá abajo se leen igual que cualquier otra.
 *
 * ⚠️ Lo único que NO pasa por PAPEL es `--v-green`: no se remapea en ningún
 * tema, así que en la lámina vale exactamente lo mismo que en el resto.
 * ─────────────────────────────────────────────────────────────────────────── */
.v-diploma-modal {
  position: fixed; inset: 0; z-index: 70;
  display: flex; align-items: center; justify-content: center; padding: 22px;
}
.v-diploma-scrim { position: absolute; inset: 0; background: rgba(16, 26, 36, .62); }
.v-diploma-box {
  position: relative; width: 1080px; max-width: 100%; height: 100%;
  display: flex; flex-direction: column; animation: erPop .25s ease;
}

.v-diploma-actions {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px; color: var(--v-on-deep); flex-wrap: wrap;
}
.v-diploma-actions-title {
  font-family: var(--v-font-display); font-weight: 700; font-size: 16px; margin-right: auto;
}
.v-diploma-close {
  background: rgba(255, 255, 255, .14); border: none; color: var(--v-on-deep);
  cursor: pointer; padding: 8px; line-height: 0; border-radius: 9px; flex: 0 0 auto;
}
.v-diploma-close:hover { background: rgba(255, 255, 255, .24); }
.v-diploma-actions :where(a, button):focus-visible { outline-color: var(--v-on-deep); }
.v-btn-linkedin { background: var(--v-linkedin); color: var(--v-on-deep); }
.v-btn-linkedin:hover { color: var(--v-on-deep); }
/* Un enlace que todavía no tiene destino no debe parecer disponible. */
.v-diploma-actions [aria-disabled="true"] { opacity: .5; cursor: default; }

.v-diploma-stage { flex: 1; min-height: 0; position: relative; }

.v-diploma-sheet {
  position: absolute; top: 0; left: 50%; width: 1080px;
  transform-origin: top center; background: var(--v-surface); border-radius: 6px;
  overflow: hidden; box-shadow: 0 30px 80px rgba(0, 0, 0, .4); display: flex;
}
.v-diploma-accent { width: 9px; flex: 0 0 auto; background: var(--v-green); }
.v-diploma-body { flex: 1; min-width: 0; padding: 8px; }
.v-diploma-frame { border: 1px solid var(--v-border); padding: 44px; display: flex; gap: 44px; flex-wrap: wrap; }

.v-diploma-main { flex: 2 1 420px; min-width: 0; }
.v-diploma-kicker {
  font-family: var(--v-font-display); font-size: 10px; letter-spacing: .3em;
  text-transform: uppercase; color: var(--v-muted); font-weight: 600; margin-bottom: 10px;
}
.v-diploma-brand { display: flex; align-items: center; gap: 14px; }
.v-diploma-brand img { display: block; }
.v-diploma-brand-name {
  font-family: var(--v-font-logo); font-weight: 700; font-size: 28px;
  color: var(--v-deep); line-height: 1;
}
.v-diploma-brand-sub {
  font-family: var(--v-font-logo); font-weight: 600; font-size: 10px;
  letter-spacing: .46em; color: var(--v-green); margin-top: 3px;
}
.v-diploma-rule { width: 58px; height: 3px; background: var(--v-green); margin: 26px 0 30px; }

.v-diploma-lead { font-size: 15px; color: var(--v-ink-2); }
.v-diploma-lead-2 { margin-top: 26px; }
.v-diploma-person {
  font-family: var(--v-font-display); font-weight: 700; font-size: 44px;
  line-height: 1.1; color: var(--v-ink); margin: 10px 0 6px;
}
.v-diploma-id { font-size: 13px; color: var(--v-muted); }
.v-diploma-title { font-size: 23px; font-weight: 800; color: var(--v-ink); margin: 8px 0 12px; line-height: 1.3; }
.v-diploma-summary { font-size: 14px; color: var(--v-ink-2); line-height: 1.6; max-width: 560px; }

.v-diploma-signs { display: flex; gap: 44px; margin-top: 48px; flex-wrap: wrap; }
.v-diploma-sign { flex: 1; min-width: 200px; }
.v-diploma-sign-name {
  font-family: Georgia, 'Times New Roman', serif; font-style: italic;
  font-size: 21px; color: var(--v-ink); margin-bottom: 4px;
}
.v-diploma-sign-rule { height: 1px; background: var(--v-ink); margin-bottom: 8px; }
.v-diploma-sign-role { font-size: 12px; color: var(--v-ink-2); }

.v-diploma-aside {
  flex: 1 1 260px; min-width: 0; border-left: 1px solid var(--v-border);
  padding-left: 40px; display: flex; flex-direction: column; align-items: center;
}
.v-diploma-aside-head {
  align-self: stretch; text-align: right; font-family: var(--v-font-display);
  font-size: 13px; letter-spacing: .3em; text-transform: uppercase;
  color: var(--v-ink); font-weight: 600; line-height: 2;
}
.v-diploma-seal {
  width: 158px; height: 158px; border: 2px solid var(--v-green); border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  margin: 34px 0 30px; color: var(--v-green);
}
.v-diploma-seal svg { margin-bottom: 10px; }
.v-diploma-seal-label {
  font-family: var(--v-font-display); font-size: 10px; letter-spacing: .18em; font-weight: 700;
}
.v-diploma-seal-year {
  font-family: var(--v-font-display); font-size: 14px; letter-spacing: .14em;
  font-weight: 700; margin-top: 3px;
}
.v-diploma-qr { width: 168px; height: auto; display: block; }
.v-diploma-qr-note { font-size: 12px; color: var(--v-ink-2); text-align: center; margin-top: 14px; line-height: 1.6; }
.v-diploma-code {
  font-family: var(--v-font-display); font-size: 14px; font-weight: 700;
  color: var(--v-ink); letter-spacing: .04em; margin-top: 12px;
}
.v-diploma-state { margin-top: 14px; }
/* El estado va con palabra Y color. Quien no distingue el verde del ámbar
   tiene que poder leer si el certificado sigue vigente. */
.v-diploma-state-tag { font-size: 11px; font-weight: 700; padding: 4px 12px; border-radius: 99px; }
.v-diploma-state-tag.is-vigente  { color: var(--v-ok-fg);     background: var(--v-ok-bg); }
.v-diploma-state-tag.is-vencido  { color: var(--v-warn-fg);   background: var(--v-warn-bg); }
.v-diploma-state-tag.is-revocado { color: var(--v-danger-fg); background: var(--v-danger-bg); }

.v-diploma-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 16px 48px 22px; border-top: 1px solid var(--v-border); margin: 0 8px;
  font-size: 12px; color: var(--v-muted); flex-wrap: wrap;
}

@media (max-width: 680px) {
  .v-diploma-modal { padding: 12px; }
  .v-diploma-actions { gap: 8px; margin-bottom: 10px; }
  .v-diploma-actions-title { flex: 1 0 100%; margin-right: 0; font-size: 14px; }
  /* Los botones de acción NO se estiran acá: son tres en una fila y el ancho
     completo los apilaría, empujando la lámina fuera de la pantalla. */
  .v-diploma-actions .v-btn { width: auto; }
}

/* ── Recorrido guiado ─────────────────────────────────────────────────────────
 * Se muestra una sola vez, en el inicio, la primera vez que alguien entra.
 *
 * El foco se resalta con un `box-shadow` enorme sobre un recuadro transparente:
 * oscurece TODA la pantalla menos el elemento del que se está hablando, sin
 * tener que clonar ese elemento ni recortar nada. Un clon se desincroniza; el
 * hueco no.
 * ─────────────────────────────────────────────────────────────────────────── */
.v-tour { position: fixed; inset: 0; z-index: 90; }

.v-tour-spot {
  position: absolute; border-radius: 12px; pointer-events: none;
  box-shadow: 0 0 0 9999px rgba(6, 26, 23, .78), 0 0 0 3px var(--v-green);
  transition: top .28s cubic-bezier(.2,.8,.2,1), left .28s cubic-bezier(.2,.8,.2,1),
              width .28s cubic-bezier(.2,.8,.2,1), height .28s cubic-bezier(.2,.8,.2,1);
}
/* Pasos sin elemento que señalar —la bienvenida y el cierre—: el hueco se
   colapsa en el centro y queda sólo el velo. */
.v-tour-spot.is-blank { box-shadow: 0 0 0 9999px rgba(6, 26, 23, .82); border-radius: 0; }

.v-tour-card {
  position: absolute; width: min(370px, calc(100vw - 32px));
  background: var(--v-surface); border-radius: var(--v-radius);
  box-shadow: var(--v-shadow-lg); padding: 24px 26px;
  transition: top .28s cubic-bezier(.2,.8,.2,1), left .28s cubic-bezier(.2,.8,.2,1);
}
/* La flecha que ata la tarjeta con lo que señala. Sin ella, en una pantalla
   grande la tarjeta parece un cartel suelto. */
.v-tour-card::before {
  content: ''; position: absolute; width: 14px; height: 14px;
  background: var(--v-surface); transform: rotate(45deg);
}
.v-tour-card[data-arrow="up"]::before    { top: -7px;    left: var(--arrow-x, 32px); }
.v-tour-card[data-arrow="down"]::before  { bottom: -7px; left: var(--arrow-x, 32px); }
.v-tour-card[data-arrow="left"]::before  { left: -7px;   top: var(--arrow-y, 32px); }
.v-tour-card[data-arrow="right"]::before { right: -7px;  top: var(--arrow-y, 32px); }
.v-tour-card[data-arrow="none"]::before  { display: none; }

.v-tour-emoji { font-size: 34px; line-height: 1; display: block; margin-bottom: var(--v-sp-2); }
.v-tour-title {
  font-family: var(--v-font-display); font-weight: 800; font-size: 19px;
  text-transform: uppercase; margin: 0 0 8px; color: var(--v-ink); line-height: 1.2;
}
.v-tour-text { font-size: 14px; line-height: 1.6; color: var(--v-ink-2); margin: 0; }

.v-tour-foot {
  display: flex; align-items: center; gap: var(--v-sp-2);
  margin-top: var(--v-sp-5); padding-top: var(--v-sp-3);
  border-top: 1px solid var(--v-fill-2);
}
.v-tour-dots { display: flex; gap: 6px; margin-right: auto; }
.v-tour-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--v-border);
  transition: background .2s, transform .2s;
}
.v-tour-dot.is-current { background: var(--v-green); transform: scale(1.35); }
.v-tour-dot.is-done    { background: var(--v-muted-2); }

.v-tour-skip {
  background: none; border: none; cursor: pointer; font-family: inherit;
  font-size: 13px; font-weight: 700; color: var(--v-muted); padding: 8px 4px;
}
.v-tour-skip:hover { color: var(--v-ink-2); }

@media (max-width: 680px) {
  /* En celular la tarjeta deja de perseguir al elemento y se ancla abajo: con
     370 px de tarjeta sobre 375 px de pantalla, «al lado de» no existe. El
     hueco iluminado sigue marcando de qué se está hablando. */
  .v-tour-card {
    position: fixed; left: 12px !important; right: 12px;
    bottom: 12px; top: auto !important; width: auto;
    padding: 20px; animation: erFade .25s ease;
  }
  .v-tour-card::before { display: none; }
  .v-tour-title { font-size: 17px; }
  .v-tour-foot { margin-top: var(--v-sp-4); }
  .v-tour-foot .v-btn { min-height: 42px; padding: 11px 18px; }
}

/* Quien pidió menos movimiento no quiere que el foco se deslice por la
   pantalla en cada paso. */
@media (prefers-reduced-motion: reduce) {
  .v-tour-spot, .v-tour-card { transition: none; }
}

/* ── Pie de la plataforma ──────────────────────────────────────────────────── */
.v-footer {
  margin-top: var(--v-sp-6);
  padding-top: var(--v-sp-5);
  border-top: 1px solid var(--v-border);
}
.v-footer-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--v-sp-4); flex-wrap: wrap;
  font-size: 12.5px; color: var(--v-muted);
}
.v-footer-inner p { margin: 0; }
.v-footer-brand strong { color: var(--v-ink-2); font-weight: 700; }
.v-footer-links { display: flex; align-items: center; gap: var(--v-sp-2); flex-wrap: wrap; }
.v-footer a, .v-footer-link { color: var(--v-muted); text-decoration: none; }
.v-footer a:hover, .v-footer-link:hover { color: var(--v-green); text-decoration: underline; }
/* El botón de Vity se ve igual que los enlaces vecinos: es la misma clase de
   acción y distinguirlo sólo obligaría a mirar dos veces. */
.v-footer-link { background: none; border: none; padding: 0; font: inherit; cursor: pointer; }
.v-footer-sep { color: var(--v-muted-2); }
.v-footer-meta { font-variant-numeric: tabular-nums; }

@media (max-width: 680px) {
  /* Apilado y centrado: en un teléfono las tres piezas en fila se parten en
     lugares arbitrarios y el pie termina ocupando media pantalla. */
  .v-footer-inner { flex-direction: column; text-align: center; gap: var(--v-sp-2); }
}

/* ── Catálogo de canje ───────────────────────────────────────────────────────
 *
 * La vidriera donde los puntos dejan de ser un marcador. Vive acá y no en
 * `admin.css` porque es una pantalla del colaborador, hermana de Logros y
 * retos, y reusa su misma tarjeta oscura de encabezado.
 */

/* Tarjeta de saldo: misma familia visual que `.v-level`, porque contesta la
   pregunta gemela —cuánto tengo para gastar, contra cuánto llevo ganado—. */
.v-saldo { display: flex; align-items: center; gap: 28px; flex-wrap: wrap; margin-bottom: 26px; }
.v-saldo-emoji { font-size: 56px; flex: 0 0 auto; }
.v-saldo-label {
  font-family: var(--v-font-display); font-size: 12px; color: var(--v-muted);
  font-weight: 700; text-transform: uppercase; letter-spacing: .14em;
}
.v-saldo-pts {
  font-family: var(--v-font-display); font-weight: 800; font-size: 42px;
  line-height: 1; margin: 6px 0 8px; letter-spacing: -1px;
}
.v-saldo-pts small { font-size: 20px; color: var(--v-muted); letter-spacing: 0; }
.v-saldo-nota { max-width: 46ch; }
.v-saldo-link { margin-left: auto; }

/* Cada premio es una tarjeta en columna: la acción siempre al pie, a la misma
   altura en todas, para que la fila se pueda recorrer sin saltar la vista. */
.v-premio-tile { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px; }
.v-premio-tile > form { margin-top: auto; width: 100%; }
.v-premio-img { border-radius: var(--v-radius-sm); object-fit: cover; }
.v-premio-cat {
  font-size: 11px; font-weight: 700; letter-spacing: .12em;
  text-transform: uppercase; color: var(--v-muted);
}
.v-premio-tile-name { font-weight: 700; color: var(--v-ink); }
.v-premio-tile-desc { font-size: 13px; color: var(--v-ink-2); margin: 0; }
.v-premio-tile-costo {
  font-family: var(--v-font-display); font-weight: 800; font-size: 24px;
  color: var(--v-green); line-height: 1;
}
.v-premio-tile-costo small { font-size: 13px; color: var(--v-muted); }
.v-premio-btn { width: 100%; }
.v-premio-stock { font-size: 12px; color: var(--v-warn-fg); font-weight: 700; }

/*
 * El premio que no alcanza se apaga por HIJOS y no como bloque.
 *
 * Mismo motivo que `.v-badge-tile.is-locked`: `opacity` en el padre crea un
 * contexto de composición y arrastra a todo lo que tiene adentro sin excepción
 * posible. Acá eso dejaría al 50 % justamente el texto que dice cuántos puntos
 * faltan, que es lo ÚNICO que esa tarjeta vino a comunicar. El costo y el
 * nombre se atenúan; la meta, no.
 */
.v-premio-tile.is-locked .v-premio-img,
.v-premio-tile.is-locked .v-emoji,
.v-premio-tile.is-locked .v-premio-cat,
.v-premio-tile.is-locked .v-premio-tile-name,
.v-premio-tile.is-locked .v-premio-tile-desc,
.v-premio-tile.is-locked .v-premio-tile-costo { opacity: .55; }

/* Lo que falta, y por qué no se puede: en verde de marca y no en rojo. Es una
   meta, no un error — la persona no hizo nada mal. */
.v-premio-falta { font-size: 13px; font-weight: 700; color: var(--v-green-2); margin-top: 6px; }
.v-premio-bloqueo {
  margin-top: auto; width: 100%; padding: 10px;
  border-radius: var(--v-radius-sm); background: var(--v-fill);
  font-size: 13px; font-weight: 700; color: var(--v-muted);
}

/* «Cancelado» a secas deja a la persona preguntándose si perdió los puntos. */
.v-premio-devuelto { font-size: 12px; color: var(--v-ok-fg); margin-top: 4px; }

/* La cifra de puntos se lee igual acá que en el ranking y en administración. */
.v-premio-costo { font-family: var(--v-font-display); font-weight: 800; color: var(--v-green); white-space: nowrap; }

@media (max-width: 680px) {
  /* El enlace a Logros deja de flotar a la derecha: apilado ocupa su línea. */
  .v-saldo-link { margin-left: 0; }
}

/* ── Logros como tablero ─────────────────────────────────────────────────── */
/* Primera fila: el estado a la izquierda y la acción a la derecha. El nivel
   pide más ancho porque lleva la barra de progreso; la tarjeta de canje es
   una columna estrecha y alta, que es la forma que la hace destacar al lado de
   una tarjeta ancha. */
.v-tablero-cabecera { grid-template-columns: 1.55fr 1fr; align-items: stretch; }
/* Dentro de la grilla el espacio lo pone el `gap`: el margen propio de
   `.v-level` dejaría las dos tarjetas de la fila con distinta altura. */
.v-tablero-cabecera > .v-level { margin-bottom: 0; }

/* El ranking en columnas. Se lee por filas —primero, segundo, tercero de
   izquierda a derecha—, así que el orden del documento sigue siendo el orden
   del ranking y no hace falta repartirlo del lado del servidor. */
.v-rank-grid { grid-template-columns: repeat(3, 1fr); gap: 10px; }

/* La puerta al catálogo desde Logros y retos. Es una tarjeta entera clickeable
   —no un enlace de texto— porque compite con el nivel y el ranking por la
   atención, y un renglón se pierde entre los dos. */
.v-canje-destacado {
  display: flex; flex-direction: column; align-items: flex-start;
  justify-content: center; gap: 6px;
  text-decoration: none; color: inherit;
  border-color: var(--v-green);
  transition: transform .18s ease, box-shadow .18s ease;
}
.v-canje-destacado-emoji { font-size: 42px; line-height: 1; }
.v-canje-destacado-title {
  font-family: var(--v-font-display); font-weight: 800; font-size: 21px;
  color: var(--v-ink); line-height: 1.15;
}
.v-canje-destacado-note { max-width: 34ch; }
.v-canje-destacado-cta {
  display: inline-flex; align-items: center; gap: 8px; margin-top: auto;
  padding-top: var(--v-sp-3);
  font-family: var(--v-font-display); font-weight: 800; font-size: 14px;
  color: var(--v-green);
}
.v-canje-destacado-arrow { font-size: 20px; transition: transform .18s ease; }
.v-canje-destacado:hover, .v-canje-destacado:focus-visible {
  transform: translateY(-3px); box-shadow: var(--v-shadow-lg);
}
.v-canje-destacado:hover .v-canje-destacado-arrow { transform: translateX(4px); }

/*
 * El halo que late.
 *
 * El cliente pidió que la acción principal de la pantalla estuviera «más
 * visible y animada». La animación es un anillo que se expande y se desvanece:
 * la tarjeta NO se mueve de lugar ni cambia de tamaño, así que no arrastra la
 * vista de quien está leyendo el nivel de al lado, y `box-shadow` no dispara
 * recálculo de posiciones como lo haría animar `transform: scale` sobre un
 * elemento de una grilla.
 *
 * El ciclo es de 3,2 s y no de 1 s a propósito: algo que parpadea rápido en un
 * costado deja de leerse como una invitación y pasa a leerse como una alarma.
 */
@keyframes vCanjeHalo {
  0%   { box-shadow: 0 0 0 0 var(--v-halo-canje); }
  70%  { box-shadow: 0 0 0 14px rgba(20, 160, 122, 0); }
  100% { box-shadow: 0 0 0 0 rgba(20, 160, 122, 0); }
}
.v-canje-destacado { animation: vCanjeHalo 3.2s ease-out infinite; }
/* Al pasar el cursor manda la sombra del hover: dos sombras compitiendo en el
   mismo elemento se pisan y el halo se ve cortado. */
.v-canje-destacado:hover, .v-canje-destacado:focus-visible { animation: none; }

/* `prefers-reduced-motion` no es una preferencia estética: hay gente a la que
   el movimiento en pantalla le produce mareo o migraña. Se apaga el latido y
   también el desplazamiento del hover; la tarjeta sigue destacándose por el
   borde verde, el tamaño y la sombra, que no se mueven. */
@media (prefers-reduced-motion: reduce) {
  .v-canje-destacado { animation: none; transition: none; }
  .v-canje-destacado:hover, .v-canje-destacado:focus-visible { transform: none; }
  .v-canje-destacado-arrow { transition: none; }
  .v-canje-destacado:hover .v-canje-destacado-arrow { transform: none; }
}

/* El tablero se angosta antes que el resto: la tarjeta de canje necesita ancho
   para que el texto no se parta en cuatro renglones, y el ranking en tres
   columnas deja de ser legible bastante antes de llegar al celular. */
@media (max-width: 1180px) {
  .v-rank-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 960px) {
  .v-tablero-cabecera { grid-template-columns: 1fr; }
  .v-canje-destacado-note { max-width: none; }
}

/* ══ Ayuda y soporte ═══════════════════════════════════════════════════════
   Tres reglas y ninguna paleta nueva: la pantalla se apoya entera en
   `v-card v-account-section`, `v-field` y `v-badge`, que es la misma gramática
   de Mi cuenta. Lo único que hace falta agregar es la altura del relato y la
   lista de pedidos propios. */

/* Un renglón alcanza para el asunto y no para contar qué pasó: si el campo del
   relato se ve chico, la gente escribe «no anda» y el ticket arranca con una
   pregunta. */
.v-soporte-relato { min-height: 132px; resize: vertical; line-height: 1.5; font-family: inherit; }

.v-soporte-lista { list-style: none; margin: 0; padding: 0; }
.v-soporte-item { padding: var(--v-sp-3) 0; border-top: 1px solid var(--v-border); }
.v-soporte-item:first-child { border-top: none; padding-top: 0; }

/* El estado va al extremo opuesto del asunto: es lo único que esta pantalla
   puede prometer —no hay notificación por correo— y tiene que leerse de un
   vistazo desde el pasillo de una tienda. Con `wrap`, en celular baja solo. */
.v-soporte-item-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--v-sp-2); flex-wrap: wrap; margin-bottom: 4px;
}

/* ══ Política de tratamiento de datos ══════════════════════════════════════
   Segunda pantalla pública de la plataforma, después del verificador de
   diplomas, y con una necesidad que ninguna otra tiene: es un documento LARGO
   que alguien va a leer entero al menos una vez. Por eso una columna de texto
   con ancho de lectura y no el ancho de la ventana — una línea de 180
   caracteres se abandona en el tercer renglón. */
.v-legal-shell { background: var(--v-bg); }
.v-legal { max-width: 74ch; margin: 0 auto; padding: var(--v-sp-6) var(--v-sp-4) 64px; }
.v-legal-head { text-align: center; margin-bottom: var(--v-sp-6); }
.v-legal-head img { margin-bottom: var(--v-sp-3); }
.v-legal-head .v-h1 { margin-bottom: var(--v-sp-2); }

/* ⚠️ El aviso de borrador. Es lo más importante de la pantalla y tiene que
   verse ANTES que el índice, sin desplazar y sin confundirse con el contenido.
   Va en el amarillo de advertencia del sistema y no en el rojo de error: no es
   que algo salió mal, es que esto todavía no rige. */
.v-legal-borrador {
  border: 2px solid var(--v-warn-solid); border-radius: var(--v-radius);
  background: var(--v-warn-bg); color: var(--v-warn-fg);
  padding: var(--v-sp-4); margin-bottom: var(--v-sp-6);
}
.v-legal-borrador p { margin: 0 0 var(--v-sp-2); line-height: 1.55; }
.v-legal-borrador p:last-child { margin-bottom: 0; }
.v-legal-borrador-titulo {
  font-family: var(--v-font-display); font-weight: 800; font-size: 16px;
  letter-spacing: .04em;
}

.v-legal-indice {
  background: var(--v-surface); border: 1px solid var(--v-border);
  border-radius: var(--v-radius); padding: var(--v-sp-4); margin-bottom: var(--v-sp-6);
}
.v-legal-indice ol { margin: var(--v-sp-3) 0 0; padding-left: 20px; }
.v-legal-indice li { margin-bottom: 6px; }
.v-legal-indice a { color: var(--v-green-3); }

.v-legal-seccion { margin-bottom: var(--v-sp-6); }
.v-legal-seccion .v-h2 { margin-bottom: var(--v-sp-3); }
.v-legal-seccion .v-h3 { margin: var(--v-sp-4) 0 var(--v-sp-2); }
.v-legal-seccion p, .v-legal-seccion li { line-height: 1.6; color: var(--v-ink); }
.v-legal-seccion ul { padding-left: 20px; }
.v-legal-seccion li { margin-bottom: 6px; }

/* Las tablas del documento: comparan responsable contra encargado, dato contra
   finalidad y familia contra plazo. Con `scroll` para que en celular la tabla
   se desplace sola en vez de romper el ancho de lectura de todo el texto. */
.v-legal-tabla {
  width: 100%; border-collapse: collapse; margin: var(--v-sp-3) 0;
  font-size: 14px; display: block; overflow-x: auto;
}
.v-legal-tabla th, .v-legal-tabla td {
  border: 1px solid var(--v-border); padding: 8px 10px; text-align: left; vertical-align: top;
}
.v-legal-tabla th { background: var(--v-fill); font-weight: 700; }
.v-legal-meses { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; }

/* Lo que falta completar, marcado y no disimulado: un aviso de privacidad con
   los datos de contacto inventados le da al titular un canal que no existe. */
.v-legal-pendiente {
  color: var(--v-danger-fg); background: var(--v-danger-bg);
  font-weight: 700; letter-spacing: .03em;
}

.v-legal-nota {
  background: var(--v-fill); border-left: 3px solid var(--v-muted-2);
  padding: var(--v-sp-3); border-radius: var(--v-radius-sm); margin: var(--v-sp-3) 0;
  font-size: 14px; color: var(--v-ink-2);
}

/* El canal para ejercer los derechos, destacado en verde: es lo ÚNICO
   accionable de todo el documento, y tiene que encontrarse sin leerlo entero. */
.v-legal-canal {
  background: var(--v-fill-3); border: 1px solid var(--v-green);
  border-radius: var(--v-radius); padding: var(--v-sp-4); margin-top: var(--v-sp-3);
}
.v-legal-canal a { color: var(--v-green-3); word-break: break-all; }

.v-legal-estado-vity {
  background: var(--v-info-bg); color: var(--v-info-fg);
  border-radius: var(--v-radius-sm); padding: var(--v-sp-3);
}

.v-legal-pie {
  border-top: 1px solid var(--v-border); padding-top: var(--v-sp-4);
  font-size: 13px; color: var(--v-muted); text-align: center;
}
.v-legal-pie a { color: var(--v-green-3); }
.v-legal-borrador-recordatorio { color: var(--v-warn-fg); font-weight: 700; }

/* ══════════════════════════════════════════════════════════════════════════
   Rollovers: qué es cada cosa, sin tener que entrar a averiguarlo
   ══════════════════════════════════════════════════════════════════════════

   Cualquier elemento con `data-ayuda` muestra su explicación al pasar el mouse
   o al recibir foco de teclado.

   ⚠️ `:focus-visible` NO es un agregado de cortesía. Un rollover que sólo
   aparece al pasar el mouse es invisible para quien navega con teclado — y esa
   persona es justamente la que más necesita saber qué hace cada icono, porque
   no puede explorar a tientas. Sin esa línea la función existe para la mitad de
   la gente.

   Se usa `::after` con `content: attr(data-ayuda)` en vez de JavaScript: no hay
   nada que inicializar, funciona en contenido que aparece después, y no puede
   quedar un globo huérfano si el elemento se va de la pantalla.

   ⚠️ Y NO se usa `title=""`, que sería gratis. Tres motivos: tarda medio
   segundo largo en aparecer —tiempo suficiente para que alguien se rinda—, no
   se puede dar formato, y en varios navegadores se corta a una línea. Estas
   ayudas son frases, no palabras. */

[data-ayuda] { position: relative; }

[data-ayuda]::after {
  content: attr(data-ayuda);
  position: absolute;
  z-index: 60;
  /* Ancho fijo y texto que envuelve: son frases de una línea y media. Sin el
     `white-space: normal` heredaría el `nowrap` de la barra y saldría una tira
     de 400 px que se sale de la pantalla. */
  width: max-content;
  max-width: 260px;
  white-space: normal;
  padding: var(--v-sp-2) var(--v-sp-3);
  border-radius: var(--v-radius-sm);
  background: var(--v-deep);
  color: var(--v-on-deep);
  box-shadow: var(--v-shadow-md);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.45;
  text-align: left;
  text-transform: none;
  letter-spacing: normal;
  /* Invisible hasta que hace falta, y sin capturar el mouse: un globo que
     recibe eventos se interpone con lo que está debajo y hace parpadear el
     rollover al mover el cursor. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .12s ease, visibility .12s;
}

[data-ayuda]:hover::after,
[data-ayuda]:focus-visible::after { opacity: 1; visibility: visible; }

/* En la barra lateral sale a la DERECHA, centrado con el renglón: hacia abajo
   taparía el ítem siguiente, que es justo el que la persona está por leer. */
.v-nav [data-ayuda]::after,
.v-sidebar [data-ayuda]::after {
  top: 50%;
  left: calc(100% + 10px);
  transform: translateY(-50%);
}

/* En el encabezado sale ABAJO y alineado a la derecha. Alineado a la izquierda,
   el globo del avatar —que es el último icono— se saldría de la pantalla. */
.v-header [data-ayuda]::after {
  top: calc(100% + 8px);
  right: 0;
}

/* ⚠️ En pantallas donde el mouse no existe, NO se dibuja.
   `hover: none` es cierto en celular y tablet. Ahí el rollover no se puede
   invocar —no hay con qué— pero el `::after` igual ocuparía lugar en el árbol
   de composición, y en algunos navegadores llega a aparecer pegado a un toque y
   quedarse fijo hasta que se toca otra cosa. Los rótulos de la barra ya están
   escritos en pantalla y los iconos tienen `aria-label`: no se pierde nada. */
@media (hover: none) {
  [data-ayuda]::after { display: none; }
}
