/* =====================================================================
   Familia QBK · piezas comunes de CSS
   public/assets/css/familia-qbk.css  ·  2026-10-22

   ESTE ARCHIVO ES IDÉNTICO EN BRÚJULA Y EN FORJA (se copia byte a byte).
   Por eso aquí no hay ni un color de una app: todo sale de las variables
   --fq-* que define el tema de cada una (el archivo de tema de cada app).
   Solo se permiten grises neutros (sombras, destello blanco).

   Tiempos y curvas salen de la maqueta aprobada
   docs/diseno/maquetas/familia-qbk-v3.html.

   Piezas:  .fq-vidrio  .fq-cta  .fq-busca  .fq-buscar-btn  .fq-e/.fq-e2..5 + .fq-play
            .fq-odo  .fq-faro  .fq-brillo-carga  .fq-aviso  .fq-ventana
            .fq-paleta (Ctrl K)

   Reglas de la familia:
     - Todo movimiento se apaga con prefers-reduced-motion (bloque final).
     - Sin backdrop-filter el vidrio usa su fondo sólido equivalente.
     - Se carga DESPUÉS del CSS de la app. Si una regla debe ganarle a una
       utilidad de Tailwind (inyectada en tiempo de ejecución), se sube la
       especificidad en el selector, nunca con !important (la única
       excepción es pausar el fondo con la pestaña oculta, abajo).
   ===================================================================== */

:root {
    /* Curvas de movimiento (iguales en toda la familia) */
    --fq-ease: cubic-bezier(.2, .75, .2, 1);
    --fq-resorte: cubic-bezier(.2, 1.35, .3, 1);
    --fq-radio: 15px;
}

/* El borde del buscador gira: --fq-ang tiene que ser un ángulo "animable". */
@property --fq-ang {
    syntax: '<angle>';
    initial-value: 0deg;
    inherits: false;
}

/* ---------------------------------------------------------------------
   Tarjeta de vidrio: fondo vidrio + brillo diagonal + borde + desenfoque.
   Al pasar el ratón, una luz del color del acento sigue el cursor; el JS
   (FamiliaQbk.focos) pone --mx / --my con la posición dentro de la tarjeta.
   --------------------------------------------------------------------- */
.fq-vidrio {
    position: relative;
    overflow: hidden;
    padding: var(--fq-pad, 14px);
    border-radius: var(--fq-radio);
    border: 1px solid var(--fq-borde);
    background: linear-gradient(160deg, var(--fq-brillo), transparent 42%), var(--fq-vidrio);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    color: var(--fq-tinta);
    transition: transform .35s var(--fq-ease), box-shadow .35s var(--fq-ease);
}
.fq-vidrio::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(200px circle at var(--mx, 50%) var(--my, 50%), var(--fq-foco), transparent 62%);
    opacity: 0;
    transition: opacity .3s;
}
.fq-vidrio:hover::after {
    opacity: 1;
}

/* ---------------------------------------------------------------------
   Botón principal: color del acento (versión para texto, que cumple AA),
   destello que lo recorre al pasar el ratón y leve hundido al oprimir.
   --------------------------------------------------------------------- */
.fq-cta {
    position: relative;
    overflow: hidden;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 0;
    border-radius: 12px;
    padding: 10px 16px;
    background: var(--fq-acento-texto);
    color: var(--fq-sobre-acento);
    font: 700 13px/1.2 var(--fq-texto);
    cursor: pointer;
    text-decoration: none;
    transition: transform .18s var(--fq-ease), box-shadow .25s var(--fq-ease);
}
.fq-cta::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(110deg, transparent 30%, rgba(255, 255, 255, .5) 50%, transparent 70%);
    transform: translateX(-120%);
}
.fq-cta:hover::after {
    transition: transform .8s var(--fq-ease);
    transform: translateX(120%);
}
.fq-cta:active {
    transform: translateY(1px) scale(.98);
}
.fq-cta:focus-visible {
    outline: 2px solid var(--fq-acento-texto);
    outline-offset: 3px;
}

/* ---------------------------------------------------------------------
   Buscador: contenedor de 1 px de relleno cuyo fondo es el borde. Al
   enfocar el campo de adentro, el borde gira con los colores del acento.
   Uso: <div class="fq-busca"><input …></div>
   --------------------------------------------------------------------- */
.fq-busca {
    position: relative;
    border-radius: 12px;
    padding: 1px;
    background: var(--fq-borde);
}
.fq-busca:focus-within {
    background: conic-gradient(from var(--fq-ang, 0deg), var(--fq-acento), transparent 30%, var(--fq-acento2), transparent 70%, var(--fq-acento));
    animation: fq-gira-borde 3s linear infinite;
}
.fq-busca input {
    display: block;
    width: 100%;
    box-sizing: border-box;
    border: 0;
    border-radius: 11px;
    padding: 10px 13px;
    background: color-mix(in srgb, var(--fq-fondo) 94%, var(--fq-tinta));
    color: var(--fq-tinta);
    font: 500 13px var(--fq-texto);
    outline: none;
}
.fq-busca input::placeholder {
    color: var(--fq-suave);
}
@keyframes fq-gira-borde {
    to { --fq-ang: 360deg; }
}

/* ---------------------------------------------------------------------
   Entrada de la pantalla: UN solo momento. El ancestro recibe .fq-play
   (FamiliaQbk.entrada, una vez) y los grupos .fq-e … .fq-e5 pasan de
   borrosos a nítidos subiendo unos píxeles, escalonados. Sin .fq-play el
   contenido se ve normal (si el JS no corre, nada queda oculto).
   --------------------------------------------------------------------- */
.fq-play .fq-e,
.fq-play .fq-e2,
.fq-play .fq-e3,
.fq-play .fq-e4,
.fq-play .fq-e5 {
    animation: fq-entra .6s var(--fq-ease) both;
}
.fq-play .fq-e2 { animation-delay: .08s; }
.fq-play .fq-e3 { animation-delay: .16s; }
.fq-play .fq-e4 { animation-delay: .24s; }
.fq-play .fq-e5 { animation-delay: .32s; }
@keyframes fq-entra {
    from { opacity: 0; transform: translateY(10px); filter: blur(6px); }
    to   { opacity: 1; transform: none; filter: none; }
}
/* Sin parpadeo: .fq-play llega con el script diferido, y en una página larga
   el navegador ya pudo pintar el contenido; se vería aparecer, desaparecer y
   volver a entrar. Si la página marca <html class="fq-js"> en el <head>
   (antes de pintar), los grupos del <main> esperan ocultos desde el primer
   cuadro hasta que llega .fq-play. Si nunca llega (el JS falló), fq-aguarda
   los muestra solos a los 2 s: nada se queda oculto. Solo en pantalla y sin
   «reducir movimiento» (ahí nunca se ocultan), y sin .fq-js todo es como
   antes. */
@media screen and (prefers-reduced-motion: no-preference) {
    html.fq-js main:not(.fq-play) .fq-e,
    html.fq-js main:not(.fq-play) .fq-e2,
    html.fq-js main:not(.fq-play) .fq-e3,
    html.fq-js main:not(.fq-play) .fq-e4,
    html.fq-js main:not(.fq-play) .fq-e5 {
        animation: fq-aguarda 0s 2s both;
    }
}
@keyframes fq-aguarda {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ---------------------------------------------------------------------
   Odómetro: cada dígito es una columna 0-9 que rueda hasta su lugar.
   FamiliaQbk.odometros arma el contenido de <span class="fq-odo" data-n="5920">:
     .fq-dg  columna de dígitos (su transform lo pone el JS, el retraso también)
     .fq-sep separador (el punto de miles)
   Sin JS se ve el número tal cual por ::before.
   --------------------------------------------------------------------- */
.fq-odo {
    display: inline-flex;
    height: 1em;
    overflow: hidden;
    line-height: 1;
    font-family: var(--fq-titulo);
    font-variant-numeric: tabular-nums;
    color: var(--fq-acento);       /* cifra grande: basta 3:1 */
}
.fq-odo:empty::before {
    content: attr(data-n);
}
.fq-odo .fq-dg {
    display: flex;
    flex-direction: column;
    transition: transform 1.4s var(--fq-ease);
}
.fq-odo .fq-dg > span {
    display: block;
    height: 1em;
}
.fq-odo .fq-sep {
    display: inline-block;
}

/* ---------------------------------------------------------------------
   Faro: punto que late con dos ondas. El color de la etapa se pasa con
   --fq-faro (si no, el acento): <span class="fq-faro" style="--fq-faro:…">.
   --------------------------------------------------------------------- */
.fq-faro {
    position: relative;
    flex: none;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--fq-faro, var(--fq-acento));
    box-shadow: 0 0 12px var(--fq-faro, var(--fq-acento));
}
.fq-faro::before,
.fq-faro::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1.5px solid var(--fq-faro, var(--fq-acento));
    animation: fq-onda 2.8s ease-out infinite;
}
.fq-faro::after {
    animation-delay: 1.4s;
}
@keyframes fq-onda {
    0%   { transform: scale(1); opacity: .8; }
    100% { transform: scale(3.4); opacity: 0; }
}

/* ---------------------------------------------------------------------
   Mientras la IA trabaja (ficha, estudio, revisor, Copilot): un brillo del
   acento recorre el espacio donde saldrá el resultado.
   --------------------------------------------------------------------- */
.fq-brillo-carga {
    position: relative;
    overflow: hidden;
}
.fq-brillo-carga::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--fq-acento) 24%, transparent) 50%, transparent 70%);
    transform: translateX(-100%);
    animation: fq-barre 1.8s var(--fq-ease) infinite;
}
@keyframes fq-barre {
    to { transform: translateX(100%); }
}

/* ---------------------------------------------------------------------
   Aviso («Guardado», «Aprobado»): sube con un rebote corto y la marca de
   visto se dibuja. Marca: <svg class="fq-visto"><path d="M2 6.5l2.6 2.4L10 3.3"/></svg>
   Para retirarlo, agregar .fq-sale.
   --------------------------------------------------------------------- */
.fq-aviso {
    position: fixed;
    left: 50%;
    bottom: 24px;
    z-index: 1000;
    display: inline-flex;
    align-items: center;
    gap: 10px;
    max-width: calc(100vw - 32px);
    padding: 11px 16px;
    border-radius: 14px;
    border: 1px solid var(--fq-borde);
    background: linear-gradient(160deg, var(--fq-brillo), transparent 60%), var(--fq-vidrio-solido);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    backdrop-filter: blur(18px) saturate(140%);
    color: var(--fq-tinta);
    font: 600 13px/1.3 var(--fq-texto);
    box-shadow: 0 18px 40px rgba(0, 0, 0, .28);
    transform: translateX(-50%);
    animation: fq-sube .55s var(--fq-resorte) both;
}
.fq-aviso.fq-sale {
    animation: fq-baja .3s var(--fq-ease) forwards;
}
.fq-aviso .fq-visto {
    flex: none;
    width: 14px;
    height: 14px;
    overflow: visible;
}
.fq-aviso .fq-visto path {
    fill: none;
    stroke: var(--fq-acento);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 30;
    stroke-dashoffset: 30;
    animation: fq-dibuja .45s .3s var(--fq-ease) forwards;
}
@keyframes fq-sube {
    from { opacity: 0; transform: translate(-50%, 24px) scale(.96); }
    to   { opacity: 1; transform: translate(-50%, 0) scale(1); }
}
@keyframes fq-baja {
    to { opacity: 0; transform: translate(-50%, 12px) scale(.98); }
}
@keyframes fq-dibuja {
    to { stroke-dashoffset: 0; }
}

/* ---------------------------------------------------------------------
   Ventana (confirmaciones, formularios emergentes): crece con un pequeño
   resorte. Sirve para un <dialog class="fq-ventana"> o un div.
   --------------------------------------------------------------------- */
.fq-ventana {
    border: 1px solid var(--fq-borde);
    border-radius: 18px;
    padding: 20px;
    background: linear-gradient(160deg, var(--fq-brillo), transparent 42%), var(--fq-vidrio-solido);
    -webkit-backdrop-filter: blur(22px) saturate(140%);
    backdrop-filter: blur(22px) saturate(140%);
    color: var(--fq-tinta);
    box-shadow: 0 30px 80px rgba(0, 0, 0, .42);
    animation: fq-abre .45s var(--fq-resorte) both;
}
.fq-ventana::backdrop {
    background: rgba(0, 0, 0, .45);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
}
@keyframes fq-abre {
    from { opacity: 0; transform: translateY(8px) scale(.94); }
    to   { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------------------
   Paleta de búsqueda rápida (Ctrl K): panel de vidrio centrado arriba.
   Piezas: .fq-paleta-entrada (campo), .fq-paleta-lista, .fq-paleta-item
   (aria-selected="true" marca la opción activa), .fq-paleta-vacio.
   --------------------------------------------------------------------- */
.fq-paleta {
    position: fixed;
    top: 14vh;
    left: 50%;
    z-index: 1100;
    width: min(560px, calc(100vw - 32px));
    max-height: 70vh;
    margin: 0;
    padding: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--fq-borde);
    border-radius: 18px;
    background: linear-gradient(160deg, var(--fq-brillo), transparent 42%), var(--fq-vidrio-solido);
    -webkit-backdrop-filter: blur(22px) saturate(140%);
    backdrop-filter: blur(22px) saturate(140%);
    color: var(--fq-tinta);
    box-shadow: 0 30px 80px rgba(0, 0, 0, .42);
    transform: translateX(-50%);
    animation: fq-abre-paleta .4s var(--fq-resorte) both;
}
/* `display:flex` de arriba le gana al `dialog:not([open]){display:none}` del
   navegador: sin esta regla un <dialog class="fq-paleta"> cerrado se vería siempre. */
dialog.fq-paleta:not([open]) {
    display: none;
}
.fq-paleta::backdrop {
    background: rgba(0, 0, 0, .45);
}
.fq-paleta .fq-paleta-entrada {
    width: 100%;
    box-sizing: border-box;
    border: 0;
    border-bottom: 1px solid var(--fq-linea);
    padding: 16px 18px;
    background: transparent;
    color: var(--fq-tinta);
    font: 500 15px var(--fq-texto);
    outline: none;
}
.fq-paleta .fq-paleta-entrada::placeholder {
    color: var(--fq-suave);
}
.fq-paleta .fq-paleta-lista {
    list-style: none;
    margin: 0;
    padding: 6px;
    overflow-y: auto;
}
.fq-paleta .fq-paleta-item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 9px 12px;
    border-radius: 10px;
    color: var(--fq-tinta);
    font: 600 13px/1.3 var(--fq-texto);
    cursor: pointer;
    text-decoration: none;
}
.fq-paleta .fq-paleta-item small {
    color: var(--fq-suave);
    font-weight: 500;
    font-size: 11.5px;
}
.fq-paleta .fq-paleta-item[aria-selected="true"],
.fq-paleta .fq-paleta-item:hover {
    background: color-mix(in srgb, var(--fq-acento) 14%, transparent);
}
.fq-paleta .fq-paleta-vacio {
    padding: 18px;
    color: var(--fq-suave);
    font: 500 13px var(--fq-texto);
}
@keyframes fq-abre-paleta {
    from { opacity: 0; transform: translate(-50%, 8px) scale(.95); }
    to   { opacity: 1; transform: translate(-50%, 0) scale(1); }
}

/* ---------------------------------------------------------------------
   Botón «Buscar  Ctrl K»: abre la paleta. Píldora de vidrio con la
   combinación de teclas a la derecha. Es un <button> (se alcanza con
   Tab y se activa con Enter o Espacio). En pantallas angostas queda solo
   la lupa: ahí no hay teclado que anunciar.
   Uso: <button type="button" class="fq-buscar-btn" data-fq-paleta aria-label="Buscar Ctrl K">
          <svg …/><span class="fq-buscar-txt">Buscar</span><kbd class="fq-buscar-kbd">Ctrl K</kbd></button>
   --------------------------------------------------------------------- */
.fq-buscar-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: 34px;
    padding: 0 8px 0 11px;
    box-sizing: border-box;
    border: 1px solid var(--fq-borde);
    border-radius: 11px;
    background: var(--fq-vidrio);
    color: var(--fq-suave);
    font: 500 12.5px var(--fq-texto);
    cursor: pointer;
    transition: border-color .25s, background .25s, color .25s;
}
.fq-buscar-btn:hover {
    border-color: var(--fq-acento);
    color: var(--fq-tinta);
}
.fq-buscar-btn:focus-visible {
    outline: 2px solid var(--fq-acento-texto);
    outline-offset: 2px;
}
.fq-buscar-btn svg {
    flex: none;
    display: block;
}
.fq-buscar-btn .fq-buscar-kbd {
    padding: 2px 6px;
    border: 1px solid var(--fq-linea);
    border-radius: 6px;
    background: var(--fq-vidrio);
    color: var(--fq-suave);
    font: 600 11px var(--fq-texto);
    letter-spacing: .02em;
}
@media (max-width: 640px) {
    .fq-buscar-btn {
        padding: 0 10px;
    }
    .fq-buscar-btn .fq-buscar-txt,
    .fq-buscar-btn .fq-buscar-kbd {
        display: none;
    }
}

/* ---------------------------------------------------------------------
   Sin desenfoque de fondo (navegadores sin backdrop-filter): las piezas de
   vidrio usan su fondo sólido equivalente y se siguen leyendo igual.
   --------------------------------------------------------------------- */
@supports not (backdrop-filter: blur(1px)) {
    .fq-vidrio {
        background: var(--fq-vidrio-solido);
    }
    .fq-aviso,
    .fq-ventana,
    .fq-paleta {
        background: var(--fq-vidrio-solido);
    }
}

/* ---------------------------------------------------------------------
   Indicador de la barra lateral (.fq-ind): píldora de vidrio con una barra
   de 3 px del color del acento a la izquierda. Se coloca con
   FamiliaQbk.indicador(nav), que le pone top, height y translateY hasta la
   opción .on; el resorte de la transición hace que "salte" con rebote.
   Va dentro de un contenedor position:relative, detrás de las opciones (que
   deben llevar position:relative y z-index:1). Mientras no se ha colocado
   (sin opción .on) queda invisible: el JS le pone height en línea al colocarlo.
   --------------------------------------------------------------------- */
.fq-ind {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    z-index: 0;
    height: 0;
    opacity: 0;
    pointer-events: none;
    border-radius: 11px;
    border: 1px solid var(--fq-borde);
    background: linear-gradient(160deg, var(--fq-brillo), transparent 60%), color-mix(in srgb, var(--fq-acento) 14%, transparent);
    transition: transform .5s var(--fq-resorte);
}
.fq-ind[style*="height"] {
    opacity: 1;
}
.fq-ind::before {
    content: "";
    position: absolute;
    left: -1px;
    top: 9px;
    bottom: 9px;
    width: 3px;
    border-radius: 3px;
    background: var(--fq-acento);
    box-shadow: 0 0 12px var(--fq-acento);
}

/* ---------------------------------------------------------------------
   Cambio de modo con círculo (FamiliaQbk.transicionCirculo): el navegador
   hace una captura de la página vieja y otra de la nueva; el JS revela la
   nueva con un clip-path circular. Su fundido por defecto se apaga, o se
   doblaría con el círculo.
   --------------------------------------------------------------------- */
::view-transition-old(root),
::view-transition-new(root) {
    animation: none;
    mix-blend-mode: normal;
}

/* ---------------------------------------------------------------------
   Fondo quieto con la pestaña oculta (FamiliaQbk.pausarFondo pone .fq-pausa
   en <html>). Es la única regla con !important: tiene que ganarle al
   `animation` abreviado de cada pieza, que reinicia el estado a "running".
   --------------------------------------------------------------------- */
html.fq-pausa *,
html.fq-pausa *::before,
html.fq-pausa *::after {
    animation-play-state: paused !important;
}

/* ---------------------------------------------------------------------
   Papel: lo que solo sirve en pantalla no se imprime (paleta, avisos,
   botón de búsqueda, indicador de la barra). El vidrio queda transparente,
   sin desenfoque (Chrome lo imprime como imagen y el texto sale borroso) y
   sin sombra; la luz del cursor, las ondas del faro y el brillo de espera
   se apagan, y la entrada no deja nada a medio aparecer. Los colores del
   papel (fondo blanco, tinta oscura) los pone el tema de cada app, con sus
   variables --fq-* dentro de @media print.
   --------------------------------------------------------------------- */
@media print {
    .fq-paleta,
    .fq-aviso,
    .fq-buscar-btn,
    .fq-ind {
        display: none;
    }
    .fq-vidrio,
    .fq-ventana {
        background: transparent;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        box-shadow: none;
    }
    .fq-faro {
        box-shadow: none;
    }
    .fq-vidrio::after,
    .fq-faro::before,
    .fq-faro::after,
    .fq-brillo-carga::after {
        display: none;
    }
    .fq-play .fq-e,
    .fq-play .fq-e2,
    .fq-play .fq-e3,
    .fq-play .fq-e4,
    .fq-play .fq-e5 {
        animation: none;
    }
}

/* ---------------------------------------------------------------------
   «Reducir movimiento»: el contenido aparece directo y nada se mueve. El
   estado final de cada pieza queda visible (el faro conserva su punto, el
   odómetro salta a su cifra, el visto aparece dibujado).
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    .fq-vidrio,
    .fq-vidrio::after,
    .fq-cta,
    .fq-cta:hover::after,
    .fq-buscar-btn,
    .fq-ind,
    .fq-odo .fq-dg {
        transition: none;
    }
    .fq-play .fq-e,
    .fq-play .fq-e2,
    .fq-play .fq-e3,
    .fq-play .fq-e4,
    .fq-play .fq-e5,
    .fq-aviso,
    .fq-aviso.fq-sale,
    .fq-ventana,
    .fq-paleta {
        animation: none;
    }
    .fq-aviso,
    .fq-paleta {
        opacity: 1;
    }
    .fq-aviso .fq-visto path {
        animation: none;
        stroke-dashoffset: 0;
    }
    .fq-faro::before,
    .fq-faro::after {
        animation: none;
        display: none;
    }
    .fq-busca:focus-within {
        animation: none;
        background: var(--fq-acento);
    }
    .fq-brillo-carga::after {
        animation: none;
        transform: none;
        background: color-mix(in srgb, var(--fq-acento) 10%, transparent);
    }
}
