/* ====================================================================
   GEN JET AI · Espacio SFW — tema + gating del conmutador
   --------------------------------------------------------------------
   Se carga SIEMPRE, y SIEMPRE DESPUÉS de studio.css, pero es INERTE
   mientras <html> no lleve los atributos del espacio, así que en el
   espacio actual (main, tema 'default') este fichero no pinta nada.

   DOS EJES, DOS ATRIBUTOS (no confundirlos):
     - data-theme="mono"  → el TEMA. De él cuelgan los tokens de abajo. El
       valor lo manda el backend en el catálogo (`spaces.theme`), no se
       deriva del slug: otro espacio futuro puede reutilizar 'mono' sin
       tocar este fichero.
     - data-space="sfw"   → el ESPACIO. De él cuelga solo el gating de
       secciones (qué ítems del sidebar existen).

   Reparto de responsabilidades (no duplicar en otro sitio):
     - Los tokens del tema 'mono' se definen AQUÍ y solo aquí.
     - studio.css / styles.css siguen siendo la única fuente del tema
       actual: fondo, texto y bordes NO cambian entre espacios. El
       espacio SFW es el mismo oscuro, solo sin naranja.
   ==================================================================== */

/* ---- Tokens del tema 'mono' ----
   Solo se toca el eje del acento. Todo lo que en el studio usa
   var(--accent) (barra activa del sidebar, chips, CTAs, eyebrows…)
   pasa a blanco/gris sin que haya que tocar una sola regla existente. */
:root[data-theme="mono"] {
  --accent: #ffffff;
  --accent-soft: rgba(255, 255, 255, .12);
  /* Contraste de lo que va ENCIMA del acento. Con el acento en blanco esto es
     lo que salva iconos y checks de desaparecer, así que ninguna regla puede
     volver a escribir #fff a pelo sobre un fondo de acento. */
  --on-accent: #0a0a0c;
  /* --accent-glow / --accent-ring NO se pueden dejar heredados: en styles.css
     valen .35 y .5 sobre --accent-rgb, alfas calibradas para un naranja oscuro
     (luminancia ~0.20). Con el mismo alfa en blanco puro (luminancia 1.0) los
     halos y los bordes marcados leen ~5x más fuertes y se comen el fondo. Se
     bajan a poco más de la mitad: el halo (área grande, donde el blow-out es
     peor) más que el trazo (1px, donde la finura ya compensa). Siguen por
     encima de --border-medium (.16), que es lo que los mantiene legibles como
     ÉNFASIS y no como un borde normal.
       --accent-glow  .35 → .18
       --accent-ring  .50 → .30
     Escala coherente con --accent-soft, que ya bajó de .18 a .12 aquí arriba. */
  --accent-glow: rgba(255, 255, 255, .18);
  --accent-ring: rgba(255, 255, 255, .30);
  /* OBLIGATORIO junto a --accent (ver el comentario del :root de styles.css): es el canal
     RGB suelto del que cuelgan las decenas de alfas del studio —rgba(var(--accent-rgb), .07),
     .22, .55…— y también los derivados --accent-glow / --accent-ring. Sin él los sólidos se
     volverían blancos pero los fondos y halos (fila activa del sidebar, sombras de los iconos)
     seguirían naranjas. No es un token nuevo del tema: es el mismo --accent en otro formato. */
  --accent-rgb: 255, 255, 255;
}

/* ====================================================================
   Conmutador de espacio (píldora del topbar)
   --------------------------------------------------------------------
   FAIL-CLOSED: oculto por defecto. auth.js añade .has-sfw a <html> solo
   cuando el backend confirma que la organización tiene el espacio; sin
   esa clase (o con este JS caído) la píldora no existe para el usuario.
   ==================================================================== */
.space-switch { display: none; }

html.has-sfw .space-switch {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 11px 5px 9px;
  border: 1px solid var(--border-medium, rgba(255, 255, 255, .16));
  border-radius: 9999px;
  background: rgba(255, 255, 255, .04);
  color: var(--text-soft, rgba(255, 255, 255, .85));
  font-family: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background .25s var(--ease, ease), border-color .25s var(--ease, ease),
              color .25s var(--ease, ease);
}
html.has-sfw .space-switch:hover {
  background: rgba(255, 255, 255, .09);
  border-color: rgba(255, 255, 255, .3);
  color: #fff;
}
html.has-sfw .space-switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Punto de estado: toma el acento del espacio ACTIVO, así el color de la
   píldora ya dice en cuál estás (ámbar = actual, blanco = SFW). */
.space-switch__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
.space-switch__label { line-height: 1; }
/* Sufijo del icono de intercambio: pequeño y apagado, no compite con el label. */
.space-switch__swap {
  display: block;
  opacity: .5;
  flex: none;
}
html.has-sfw .space-switch:hover .space-switch__swap { opacity: .9; }

/* ====================================================================
   Gating de secciones por espacio (fail-closed)
   --------------------------------------------------------------------
   El !important NO es decorativo: .sidebar__item es `display: grid`, que
   pisa el `[hidden] { display: none }` del user-agent y también a una
   regla nuestra de la misma especificidad. Sin él los ítems se seguirían
   viendo. Mismo patrón que el gating de Video (.has-seedance).

   Estas tres reglas son PUERTAS INDEPENDIENTES a propósito:
     1. Sin el espacio concedido (.has-sfw) no se ve NADA de SFW, ni
        durante la carga ni si el JS de sesión no llega a correr.
     2. En el espacio actual (main), lo de SFW no estorba.
     3. En el espacio SFW, las secciones exclusivas del espacio actual
        (generación, vídeo, auto script) desaparecen.
   El rebote REAL lo hace navigateTo (SPACE_PAGES); esto solo evita
   ofrecer un destino que se va a rebotar.
   ==================================================================== */
html:not(.has-sfw) .sidebar__item[data-page^="sfw-"] { display: none !important; }
html[data-space="main"] .sidebar__item[data-page^="sfw-"] { display: none !important; }
html[data-space="sfw"] .sidebar__item[data-page="generate"],
html[data-space="sfw"] .sidebar__item[data-page="autoscript"],
html[data-space="sfw"] .sidebar__item[data-page="seedance"] { display: none !important; }

/* Photo Cleaner: beta cerrada (orgs `beta_tester`, <html>.is-beta lo pone auth.js) Y solo
   admin/owner (<html>.is-admin). Dos puertas independientes, como las de arriba; el rebote real
   lo hace navigateTo (la sección no está en VA_PAGES) y el backend responde 403. */
html:not(.is-beta) .sidebar__item[data-page="sfw-cleaner"],
html:not(.is-admin) .sidebar__item[data-page="sfw-cleaner"] { display: none !important; }

/* La etiqueta del grupo sigue la suerte de sus ítems: sin ellos, un
   encabezado "SFW" suelto sobre el grupo siguiente. */
html:not(.has-sfw) .sidebar__group-label[data-group="sfw"],
html[data-space="main"] .sidebar__group-label[data-group="sfw"] { display: none !important; }

/* Chips de Prepare (pools GPU del espacio actual): en SFW no hay nada que
   preparar todavía, y el grupo solo lo gestiona la página Generate — que
   aquí ni siquiera es alcanzable. */
html[data-space="sfw"] #studio-prep-group { display: none !important; }

/* Las pantallas de las secciones SFW reutilizan .placeholder-page de studio.css
   TAL CUAL: allí ya todo el color pasa por --accent / --accent-rgb, así que el
   cambio de tokens de arriba las retinta solas. Nada que redefinir aquí. */

/* ====================================================================
   Sección Swap (.sw-) — línea de la cuenta en uso + referencias de la identidad
   --------------------------------------------------------------------
   Todo el color pasa por los tokens del studio (--accent, --accent-soft,
   --on-accent, --text-*, --border-*), así que el bloque de tokens del
   tema 'mono' de arriba lo retinta solo: aquí no hay ni un naranja ni un
   blanco de acento escritos a pelo.

   OJO con el alcance: estas clases NO están gateadas por [data-space].
   El bloque de referencias (.sw-refs y sus hijos) lo monta models.js
   TAMBIÉN dentro de la ficha de la identidad, que vive en el espacio
   actual. Es el mismo componente en dos sitios: un solo juego de reglas.

   Regla del espacio SFW: cualquier relleno de acento lleva
   `color: var(--on-accent)` — con el acento en blanco, un #fff heredado
   desaparecería. Aquí no hay ningún relleno de acento: los CTAs de esta
   feature son los del studio (.model-create__btn, que ya lo cumple).
   ==================================================================== */

/* La sección ocupa TODO el ancho del área de contenido: no hay `max-width`. Antes vivía en una
   columna de 980px centrada y el resto de la pantalla se quedaba en blanco mientras la lista de
   trabajos —que estaba abajo del todo— no se veía sin bajar. */
.sw-page {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 100%;
  padding-bottom: 24px;
}

/* ====================================================================
   Las dos columnas (.sw-work)
   --------------------------------------------------------------------
   Izquierda: preparar (los tres pasos + coste y botón). Derecha: lo que
   sale. Es un grid de UNA columna por defecto y de dos a partir de
   1100px — el punto donde la columna izquierda deja de estrangular las
   casillas de referencia (cuatro de 180px + huecos) si se le quita la
   mitad del ancho.

   `align-items: start` para que la columna derecha NO se estire hasta el
   alto de la izquierda: una tarjeta de resultados vacía tan alta como
   todo el formulario parece un error.
   ==================================================================== */
.sw-work {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 18px;
}
.sw-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

@media (min-width: 1100px) {
  .sw-work {
    grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.85fr);
    gap: 22px;
  }
  /* Los resultados se quedan a la vista mientras se prepara el siguiente vídeo: la que
     scrollea es .sw-page, así que el sticky es relativo a ella. Si la lista crece más que la
     pantalla, scrollea dentro de su propia tarjeta en vez de empujar la página. */
  .sw-col--out {
    position: sticky;
    top: 0;
    max-height: calc(100vh - 190px);
  }
  .sw-results { min-height: 0; }
  .sw-results .sw-jobs {
    overflow-y: auto;
    min-height: 0;
    padding-right: 4px;
  }
}
@media (min-width: 1500px) {
  /* Con mucho ancho el formulario no necesita crecer más: lo que gana sitio es el resultado. */
  .sw-work { grid-template-columns: minmax(0, 1fr) minmax(380px, 0.7fr); }
  /* Y aquí las cuatro casillas SÍ caben en una fila. Solo aquí: entre 1100 y 1500 el mínimo
     general (180px) las deja en 2×2, que se lee mejor que un 3+1 con una suelta debajo —el
     mismo motivo por el que ese mínimo existe. Acotado a esta pantalla: en la ficha de la
     identidad (Models) el bloque tiene todo el ancho del modal y no hace falta. */
  #page-sfw-swap .sw-refs { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
}

/* ---- La cuenta con la que se paga ----
   Con UNA sola cuenta esta fila **no se pinta**: no hay elección que ofrecer, y repetir cuál
   está puesta era ruido — además de sacar el email del usuario a una pantalla de trabajo.
   Con dos o más, un selector con el nombre que él les puso y nada más. En su variante de aviso
   (ninguna cuenta, o ninguna utilizable) lleva salida a Settings, que es donde se gestionan. */
.sw-acct-line {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0;
  padding: 11px 14px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-soft);
  overflow-wrap: anywhere;
}
.sw-acct-line--warn {
  border-color: var(--border-medium);
  color: var(--text-muted);
}
.sw-acct__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}
/* Botón de texto: no compite con los CTAs de la sección, solo abre Settings. */
.sw-acct-line__go {
  border: 0;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.sw-acct-line__go:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- Selector de identidad ---- */
.sw-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 320px;
}
.sw-field__label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sw-select {
  padding: 9px 12px;
  border: 1px solid var(--border-medium);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13.5px;
}
.sw-select:focus { outline: none; border-color: var(--accent); }
.sw-select:disabled { opacity: 0.55; }
.sw-select option { background: var(--bg-secondary); color: var(--text-primary); }

.sw-note {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 0;
}

/* ====================================================================
   Los tres pasos (.sw-steps)
   --------------------------------------------------------------------
   La columna de preparar, con los tres pasos a la vista: identidad,
   vídeo de origen y modelo del proveedor. No es un asistente de varias
   pantallas a propósito — cualquiera de los tres datos se cambia sin
   volver atrás, y el coste de abajo se recalcula solo. (El paso del
   fondo ya no existe: el fondo es una foto más de la identidad y vive
   en el bloque de referencias del paso 1.)

   El número va en su propia columna (círculo) y el cuerpo al lado, así
   que la lista se lee como una secuencia aunque cada paso crezca
   (previsualización de vídeo, casillas de referencia, avisos).
   ==================================================================== */
/* MISMA TRAMPA que el gating del sidebar, y hay que cerrarla igual: casi todo lo de esta
   pantalla tiene `display` propio (flex/grid/block), que PISA el `[hidden] { display: none }`
   del user-agent. Sin esta regla, esconder algo con `el.hidden = true` desde swap.js no
   esconde nada — y lo que se quedaría a la vista es la zona de soltar junto al vídeo ya
   subido, o el paso del fondo en un estilo que no lo usa. Se cierra de una vez para toda la
   sección; acotada a #page-sfw-swap para no tocar el mismo bloque de referencias cuando
   models.js lo monta dentro de la ficha de la identidad. */
#page-sfw-swap [hidden] { display: none !important; }

.sw-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  counter-reset: none;
}
.sw-step {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 14px;
  padding: 18px 20px;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.02);
}
/* El número es un relleno de acento: con el acento en blanco, el texto de dentro TIENE que ir
   en --on-accent o desaparece (regla del espacio SFW). */
.sw-step__num {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  margin-top: 2px;
}
.sw-step__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.sw-step__title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}
.sw-step__opt {
  margin-left: 7px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sw-step__hint {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 540px;
  margin: 0;
}
/* Aviso que BLOQUEA el botón (hoy: la identidad sin foto de cara). No es un error del
   servidor: es una condición que el usuario puede arreglar en esta misma pantalla. */
.sw-flag {
  margin: 0;
  padding: 9px 12px;
  border: 1px solid rgba(232, 90, 90, 0.35);
  border-radius: 10px;
  background: rgba(232, 90, 90, 0.08);
  font-size: 12.5px;
  line-height: 1.5;
  color: #e85a5a;
}
/* Mensajes sueltos de un paso (fallo de subida). SIEMPRE textos fijos: swap.js no pinta
   nunca el detalle del servidor. */
.sw-msg {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: #e85a5a;
}
/* La misma línea, cuando NO es un fallo sino "esto es lo que falta para poder pulsar". */
.sw-msg--soft { color: var(--text-faint); }
/* Procedencias que aún no existen. Se dice lo que hay en vez de ofrecer un botón muerto. */
.sw-soon {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-faint);
  font-style: italic;
}

/* ---- Zona de arrastrar/soltar ----
   El <input type=file> es el que manda (teclado y ratón acaban en él); el arrastre solo le
   pasa el fichero. Por eso la zona es focusable y tiene su propio :focus-visible. */
.sw-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 26px 18px;
  border: 1px dashed var(--border-medium);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
.sw-drop:hover { border-color: var(--accent-ring); }
.sw-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.sw-drop.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.sw-drop.is-busy { opacity: 0.55; cursor: default; }
.sw-drop__title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
}
.sw-drop__hint {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-faint);
}

/* Barra de progreso de la subida (XHR: es progreso REAL, no una animación). */
.sw-bar {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.sw-bar__fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.15s linear;
}

/* ---- Lo ya subido ---- */
.sw-picked {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px;
  border: 1px solid var(--border-medium);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-picked__video,
.sw-picked__img {
  width: 108px;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.35);
  flex: none;
  display: block;
}
.sw-picked__img { aspect-ratio: 4 / 3; width: 132px; }
.sw-picked__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.sw-picked__name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
/* El corte a 30 s se avisa AQUÍ, antes de gastar: es un aviso, no un error. */
.sw-picked__warn {
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-soft);
}
.sw-picked__swap {
  align-self: flex-start;
  border: 0;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.sw-picked__swap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ====================================================================
   Coste + lanzamiento
   --------------------------------------------------------------------
   El coste va SIEMPRE encima del botón, con su estado de carga: son
   créditos del cliente y el botón no se activa hasta que se sabe
   cuántos. El propio botón repite la cifra, que es lo que se lee justo
   antes de pulsar.
   ==================================================================== */
.sw-launch {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  padding: 16px 20px;
  border: 1px solid var(--border-medium);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.03);
}
.sw-cost {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.sw-cost__text {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-soft);
}
.sw-cost__retry {
  border: 0;
  background: none;
  padding: 0;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.sw-cost__retry:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* .model-create__btn ya lleva `color: var(--on-accent)` en studio.css: el relleno de acento
   en blanco no puede llevar texto blanco heredado. */
.sw-go:disabled { opacity: 0.5; cursor: default; }

/* ====================================================================
   Lista de trabajos
   --------------------------------------------------------------------
   Un trabajo terminado se pinta con su reproductor: el resultado es lo
   que el usuario ha venido a ver. Lo que sigue abierto se queda
   compacto, con el tiempo que lleva. Un fallo NO enseña el texto del
   servidor (puede arrastrar el nombre del proveedor): una sola frase
   fija y punto.
   ==================================================================== */
.sw-jobs {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.sw-job {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 12px;
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-job--succeeded { border-color: var(--border-medium); }
.sw-job__player {
  width: 132px;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border-radius: 10px;
  background: #000;
  flex: none;
  display: block;
}
.sw-job__thumb {
  width: 56px;
  aspect-ratio: 9 / 16;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.05);
  flex: none;
}
.sw-job__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 220px;
  min-width: 0;
}
.sw-job__meta {
  font-size: 12.5px;
  color: var(--text-soft);
  overflow-wrap: anywhere;
}
.sw-job__note { font-size: 11.5px; color: var(--text-faint); }
.sw-job__error { font-size: 12px; color: #e85a5a; }
/* Barra indeterminada: el proveedor no da porcentaje, así que no se finge uno. */
.sw-job__bar {
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
  max-width: 220px;
}
.sw-job__bar-fill {
  display: block;
  height: 100%;
  width: 35%;
  background: var(--accent);
  animation: sw-job-scan 1.6s var(--ease, ease) infinite;
}
@keyframes sw-job-scan {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(320%); }
}
@media (prefers-reduced-motion: reduce) {
  .sw-job__bar-fill { animation: none; width: 100%; opacity: 0.35; }
}
.sw-job__actions { flex: none; }

/* ---- Esqueleto ----
   Lo que se ve mientras llega la primera lista de trabajos: la MISMA fila, sin datos. Antes
   había un "Loading…" suelto y la columna cambiaba de forma al llegar los datos. */
.sw-job--skeleton { border-style: dashed; }
.sw-skel {
  display: block;
  height: 9px;
  width: 45%;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
}
.sw-skel--wide { width: 75%; }
/* Un latido lento, lo justo para que se lea como "esto está llegando" y no como un fallo. */
.sw-job--skeleton .sw-job__thumb,
.sw-skel,
.sw-refs.is-loading .sw-ref__thumb {
  animation: sw-skel-pulse 1.4s ease-in-out infinite;
}
@keyframes sw-skel-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .sw-job--skeleton .sw-job__thumb,
  .sw-skel,
  .sw-refs.is-loading .sw-ref__thumb { animation: none; }
}

/* ====================================================================
   Las tres casillas de referencia (front / back / face)
   --------------------------------------------------------------------
   Compartidas con la ficha de la identidad de la página Models: el
   markup lo genera createRefsPanel() en models.js y estas reglas son las
   únicas que lo visten. No añadir un segundo juego en studio.css.
   ==================================================================== */
.sw-refs {
  display: grid;
  /* 180px y no 220: con cuatro fotos, el mínimo anterior partía la fila en 3 + 1 en pantallas
     normales, y la que se quedaba sola parecía otra cosa. Las cuatro son lo mismo. */
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
}
.sw-refs__status {
  grid-column: 1 / -1;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-muted);
  margin: 0;
}
/* Sin identidad seleccionada: las casillas se ven apagadas y no aceptan clics, pero el
   aviso de al lado sigue legible y clicable (por eso no se apaga el contenedor entero). */
.sw-refs.is-locked .sw-ref { opacity: 0.5; }
.sw-refs.is-locked .sw-ref__actions { pointer-events: none; }

.sw-ref {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-ref.has-img { border-color: var(--border-medium); }
.sw-ref.is-busy .sw-ref__actions { opacity: 0.5; pointer-events: none; }

.sw-ref__thumb {
  position: relative;
  aspect-ratio: 3 / 4;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border-medium);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  overflow: hidden;
}
.sw-ref.has-img .sw-ref__thumb { border-style: solid; }
.sw-ref__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.sw-ref__img[hidden] { display: none; }
.sw-ref__empty {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.sw-ref__body { display: flex; flex-direction: column; gap: 5px; }
.sw-ref__label { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.sw-ref__hint { font-size: 11.5px; line-height: 1.5; color: var(--text-muted); }

.sw-ref__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 3px;
}
.sw-ref__btn {
  padding: 6px 12px;
  border: 1px solid var(--border-medium);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-soft);
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.sw-ref__btn:hover { border-color: var(--accent); color: var(--text-primary); }
.sw-ref__btn:disabled { opacity: 0.5; cursor: default; }
.sw-ref__btn--main { display: inline-flex; align-items: center; }

.sw-ref__bar {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.sw-ref__bar-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.15s linear;
}
.sw-ref__msg {
  font-size: 11.5px;
  line-height: 1.45;
  color: #e85a5a;
  margin: 0;
}

/* Fail-closed de rol: subir o quitar una referencia es admin-only en el backend
   (`organization.manage`) y la sección Swap SÍ la ve el VA. swap.js ya no le pide las
   referencias; esto cierra la puerta también desde el CSS, igual que el resto del studio
   (`html:not(.is-admin)`), para que ni un JS a medias ofrezca un botón que da 403. Se limita
   a #page-sfw-swap: en Models el bloque vive dentro de una página que ya es admin-only. */
html:not(.is-admin) #page-sfw-swap .sw-ref__actions { display: none; }

@media (max-width: 720px) {
  .sw-card { padding: 16px; }
  .sw-card__head { flex-direction: column; align-items: flex-start; gap: 10px; }
  /* El número deja de tener columna propia: en pantalla estrecha esos 44px salen del ancho
     útil del paso, que es donde viven el vídeo y las tres casillas. */
  .sw-step { grid-template-columns: 1fr; gap: 10px; padding: 15px 16px; }
  .sw-launch { padding: 14px 16px; align-self: stretch; }
  .sw-go { width: 100%; }
  .sw-picked__video, .sw-picked__img { width: 92px; }
  .sw-job__player { width: 104px; }
}

/* ====================================================================
   Tarjeta "Video accounts" de Settings (.va-)
   --------------------------------------------------------------------
   Vive dentro de una .settings-card normal (studio.html): el marco, la
   cabecera y el cuerpo ya los viste studio.css, y los controles son los
   del studio (.model-create__btn para el CTA, .model-create__input para
   el nombre, .ghost-btn para lo secundario). Estas reglas solo montan lo
   que no existía: la LISTA de cuentas, el panel del EMPAREJADO (un comando
   copiable, dos pestañas de sistema y una cuenta atrás) y el formulario de
   pegado manual, que queda como vía secundaria.

   Todo el color pasa por los tokens; el único relleno de acento es el
   .model-create__btn, que ya lleva `color: var(--on-accent)` en
   studio.css — con el acento del espacio SFW en blanco, un #fff heredado
   desaparecería.

   FAIL-CLOSED: Settings es una página COMPARTIDA por los dos espacios,
   así que la tarjeta se oculta mientras <html> no lleve `.has-sfw` (la
   pone auth.js cuando el backend confirma el espacio). Sin la clase no
   se ve ni durante la carga ni si el JS no llega a correr; el gate real
   sigue siendo el 403 del backend.
   ==================================================================== */
html:not(.has-sfw) #settings-connections-card { display: none !important; }
/* Las tarjetas de Connections CRECEN al desplegar un formulario (pegar credenciales, pegar la
   sesión de Pinterest, el comando del emparejado). La rejilla de Settings es un flex item con
   `overflow: auto` dentro de `.page`, y con filas `auto` Chrome las dimensiona por la
   contribución MÍNIMA de las tarjetas (que llevan `overflow: hidden`): la fila se queda en la
   altura calculada antes del despliegue y el cuerpo sale cortado — medido: fila de 239px con
   550px de contenido, y la tarjeta siguiente encima. Con `max-content` las filas miden lo que
   hay dentro, que es lo que una rejilla de tarjetas tiene que hacer. Afecta a TODA la página
   de Settings a propósito: el fallo era de la rejilla, no de una tarjeta. */
.settings-page { grid-auto-rows: max-content; }
/* ---- La tarjeta Connections: un subapartado por cuenta externa ----
   Higgsfield · Google Drive · Pinterest viven en UNA tarjeta, separados por una línea, cada uno
   con su cabecera (`.settings-subhead`, la misma que usa Organization) y su cuerpo pintado por
   swap.js. Un subapartado oculto (`hidden`, p.ej. Drive sin app) no deja hueco ni línea. */
.settings-connections { gap: 0; padding-top: 6px; }
.settings-connection {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 0 18px;
  border-top: 1px solid var(--border-soft);
}
.settings-connection:first-child { border-top: 0; padding-top: 8px; }
.settings-connection:last-child { padding-bottom: 2px; }
.settings-connection[hidden] { display: none !important; }
.settings-connection__body { display: flex; flex-direction: column; gap: 12px; }

.va-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.va-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 13px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
/* La cuenta en uso se distingue de un vistazo: borde de acento (alfa, no relleno) y la
   etiqueta "In use" de al lado. */
.va-row.is-active {
  border-color: var(--accent-ring);
  background: var(--accent-soft);
}
.va-row__pick {
  display: flex;
  align-items: center;
  gap: 11px;
  flex: 1 1 220px;
  min-width: 0;
  cursor: pointer;
}
.va-row__radio {
  flex: none;
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
  cursor: pointer;
}
.va-row__radio:disabled { cursor: default; opacity: 0.5; }
.va-row__info {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.va-row__name {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
.va-row__meta {
  font-size: 12px;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.va-row__tags {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
}
.va-tag {
  padding: 4px 9px;
  border-radius: 4px;
  background: rgba(var(--accent-rgb), 0.15);
  color: var(--accent);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Píldora de estado. Solo hay dos desenlaces (conectada o no): el alta es síncrona y no
   deja estados intermedios que pintar. */
.va-pill {
  padding: 4px 10px;
  border: 1px solid var(--border-medium);
  border-radius: 9999px;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  white-space: nowrap;
}
.va-pill[data-state="connected"] {
  border-color: var(--accent-ring);
  color: var(--text-primary);
}
.va-pill[data-state="error"] {
  border-color: rgba(232, 90, 90, 0.4);
  color: #e85a5a;
}
.va-row .ghost-btn { flex: none; }

/* ---- Textos ---- */
.va-note {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-soft);
  max-width: 560px;
  margin: 0;
}
.va-note--muted { color: var(--text-faint); }
/* Mensajes de fallo: siempre FIJOS (swap.js nunca pinta el `error` del servidor). */
.va-note--bad { color: #e85a5a; }
/* Confirmación corta tras conectar. Va en acento (no en verde de semáforo): el acento del
   espacio es blanco y un verde suelto sería el único color ajeno a la paleta. */
.va-note--good { color: var(--text-primary); font-weight: 600; }

/* ---- Vía secundaria ----
   "Paste credentials instead" es un botón de verdad (accesible con teclado) pintado como un
   enlace: comparte fila con el CTA y no puede competir con él. */
.va-link {
  appearance: none;
  border: 0;
  background: none;
  padding: 4px 2px;
  font: inherit;
  font-size: 12px;
  color: var(--text-muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color 0.25s var(--ease);
}
.va-link:hover { color: var(--text-primary); }
.va-link:disabled { opacity: 0.5; cursor: not-allowed; }
.va-link:focus-visible {
  outline: 1px solid var(--accent-ring);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---- Emparejado: UN comando y una cuenta atrás ----
   El código de un solo uso NO se pinta suelto en ningún sitio: viaja DENTRO del comando (que
   es un .va-code normal) y desaparece del DOM en cuanto la cuenta conecta o el panel se
   cierra. Aquí no hay ninguna regla que lo enseñe aparte, y no debe añadirse. */
.va-pair {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 15px 16px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
/* Selector de sistema. `navigator.platform` preselecciona uno, pero los dos siguen ahí: la
   detección falla más de lo que parece y un comando que la terminal no entiende es un callejón
   sin salida. */
.va-tabs {
  display: inline-flex;
  gap: 4px;
  padding: 3px;
  border: 1px solid var(--border-soft);
  border-radius: 9px;
  align-self: flex-start;
  max-width: 100%;
  flex-wrap: wrap;
}
.va-tab {
  appearance: none;
  border: 0;
  background: none;
  padding: 6px 12px;
  border-radius: 6px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.25s var(--ease), color 0.25s var(--ease);
}
.va-tab:hover { color: var(--text-primary); }
/* Relleno de acento → el texto va en --on-accent o desaparece (el acento del espacio SFW es
   blanco). Misma regla que el resto de sólidos de este fichero. */
.va-tab.is-active {
  background: var(--accent);
  color: var(--on-accent);
}
.va-tab:disabled { opacity: 0.5; cursor: not-allowed; }
/* Cuenta atrás del código. La refresca swap.js cada segundo sobre este mismo nodo (repintar el
   panel entero se llevaría por delante el "Copied" del botón de copiar). */
.va-pair__count {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* ---- Alta de una cuenta: dos pasos ----
   Uno ocurre FUERA del studio (dos órdenes en el ordenador del usuario) y el otro aquí (pegar
   lo que la segunda imprimió). Por eso es una lista numerada de verdad y no dos campos
   sueltos: el orden es la mitad de la instrucción.

   No hay panel de espera ni spinner: el alta es síncrona y el único estado intermedio es el
   botón deshabilitado mientras la petición está en vuelo. */
.va-form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 15px 16px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.va-form__title {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
}
.va-steps {
  list-style: decimal;
  margin: 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* El número lo pinta el marcador de la lista: se deja apagado para que el que manda sea el
   texto del paso, no la numeración. */
.va-step { color: var(--text-faint); }
.va-step__text {
  margin: 0 0 9px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-soft);
  max-width: 560px;
}
.va-step__hint {
  margin: 9px 0 0;
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

/* ---- Las dos órdenes, copiables ----
   Monoespaciada y seleccionable: si el portapapeles del navegador falla o está denegado, el
   texto sigue estando ahí para copiarlo a mano (el JS lo selecciona por si acaso). */
.va-code {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border-medium);
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.28);
  max-width: 560px;
}
.va-code__pre,
.va-step__hint code,
.va-ta {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}
.va-code__pre {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  white-space: pre;
  overflow-x: auto;
  font-size: 12px;
  line-height: 1.75;
  color: var(--text-primary);
  user-select: text;
}
/* El comando del emparejado es UNA línea larga (la URL lleva el código dentro). Se deja
   PARTIR en varias líneas en vez de esconderla tras una barra horizontal: un comando del que
   solo se ve el principio no se lee, y no se puede comprobar antes de pegarlo en la terminal.
   Partir aquí es seguro: el botón copia el texto exacto desde JS, y una selección a mano sobre
   un salto BLANDO tampoco arrastra saltos de línea. */
.va-code__pre--wrap {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: visible;
}
.va-code__copy { flex: none; }
.va-step__hint code {
  font-size: 11px;
  color: var(--text-soft);
}

/* ---- Campos ---- */
.va-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 10px;
  max-width: 560px;
}
.va-field__label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* El cuadro donde se pega el fichero. Mismo lenguaje que .model-create__input (studio.css)
   pero en monoespaciada y en varias líneas: lo que va dentro es un fichero, no una frase. */
.va-ta {
  width: 100%;
  min-height: 112px;
  resize: vertical;
  padding: 10px 12px;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.24);
  color: var(--text-primary);
  font-size: 12px;
  line-height: 1.6;
  transition: border-color 0.25s var(--ease);
}
.va-ta::placeholder { color: var(--text-faint); }
.va-ta:focus {
  outline: none;
  border-color: var(--accent-ring);
}
.va-ta:disabled { opacity: 0.55; cursor: not-allowed; }

.va-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

@media (max-width: 720px) {
  .va-row { align-items: flex-start; }
  /* En pantalla estrecha el botón de copiar no cabe al lado de las órdenes: baja debajo,
     que es preferible a apretar el bloque de código hasta que no se lea. */
  .va-code { flex-direction: column; align-items: stretch; }
  .va-code__copy { align-self: flex-start; }
}


/* ---------------------------------------------------------------- horas: no se enseñan aquí
   Decisión de producto: en este espacio el usuario NO ve su consumo de horas de GPU ni el
   contador de la demo. Se OCULTA, no se apaga: la medición sigue corriendo por debajo igual
   que en el otro espacio (`repo_metering`), así que el gasto se conoce y las cuotas siguen
   aplicándose — lo que cambia es qué se le cuenta al usuario, no qué se cobra.

   Va por CSS y no por JS a propósito: el mismo elemento lo escriben `applySession` y cada
   refresco de sesión, así que esconderlo desde el JS obligaría a acordarse en los tres sitios
   y volvería a aparecer al primer refresco que alguien añada. Aquí no puede reaparecer.
   `!important` por lo mismo que el resto del gating de este fichero: la regla base pinta con
   display propio y el atributo `hidden` del UA no gana. */
html[data-space="sfw"] #user-card-plan,
html[data-space="sfw"] #user-card-demo { display: none !important; }


/* ---------------------------------------------- el bloque dentro del popup de la identidad
   El popup de la modelo tiene alto FIJO y `overflow:hidden`, y su regla
   `#model-info-modal .trainer-section { flex: 1 }` reparte el alto entre secciones. Sin estas
   dos reglas, el bloque de fotos peleaba con la lista de LoRAs y lo que sobraba se cortaba por
   debajo del pie: las tarjetas aparecían pisadas por los botones de borrar. */
#model-info-modal .modelinfo-refs {
  flex: 0 0 auto;          /* ocupa lo suyo y no compite por el alto restante */
  min-height: 0;
  overflow: visible;
}

/* Versión compacta: cuatro fotos en una fila dentro de 640px de popup. Es el MISMO componente
   que la sección Swap; solo cambia el tamaño, para no mantener dos bloques distintos. */
.sw-refs--compact {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}
.sw-refs--compact .sw-ref { padding: 8px; gap: 6px; border-radius: 10px; }
.sw-refs--compact .sw-ref__thumb { aspect-ratio: 1 / 1; }
.sw-refs--compact .sw-ref__label { font-size: 12px; }
/* La descripción larga sobra aquí: el nombre del hueco ya lo dice, y el detalle vive en la
   sección Swap, donde hay sitio para leerlo. */
.sw-refs--compact .sw-ref__hint { display: none; }
.sw-refs--compact .sw-ref__btn { padding: 4px 8px; font-size: 10.5px; }

@media (max-width: 560px) {
  .sw-refs--compact { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   Paso 3 · Prendas (outfits)
   --------------------------------------------------------------------------
   Cada prenda es un pedido: de cada una salen DOS vídeos. Por eso la rejilla enseña las que
   hay a la vez y no una "seleccionada" — el usuario tiene que ver de un vistazo el tamaño de
   lo que va a pagar.

   El área de soltar NO desaparece con la primera (al revés que la del vídeo, donde solo cabe
   una): aquí se van añadiendo. Solo se esconde al llegar al tope.
   ========================================================================== */
.sw-drop--slim { padding: 18px 16px; }

.sw-outfits {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(116px, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.sw-outfits:empty { display: none; }

.sw-outfit {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-outfit__img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
  display: block;
}
.sw-outfit__name {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-secondary);
}
.sw-outfit__del {
  align-self: flex-start;
  padding: 3px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  background: transparent;
  color: var(--text-faint);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}
.sw-outfit__del:hover { color: var(--text-primary); border-color: var(--accent-ring); }

/* El hueco que ocupa una subida en vuelo: mantiene la altura de la rejilla para que las
   tarjetas ya subidas no salten al aterrizar la siguiente. */
.sw-outfit--busy {
  align-items: center;
  justify-content: center;
  min-height: 140px;
  border-style: dashed;
  color: var(--text-faint);
  font-size: 11.5px;
}

/* --------------------------------------------------------------------------
   Tomas por prenda + recuento
   --------------------------------------------------------------------------
   El segmentado va pegado a la rejilla de prendas porque es la misma decisión: cuántos vídeos
   salen. Y el recuento se pinta como una fórmula (3 outfits × 2 videos each = 6 videos) en vez
   de como una frase: lo que hay que leer de un vistazo es el número final, que es lo que se
   paga.
   -------------------------------------------------------------------------- */
.sw-runs {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 14px;
}
.sw-runs__label {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.sw-seg {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-seg__btn {
  min-width: 34px;
  padding: 5px 10px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-faint);
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: color 0.2s var(--ease), background 0.2s var(--ease);
}
.sw-seg__btn:hover { color: var(--text-secondary); }
.sw-seg__btn.is-on {
  background: var(--text-primary);
  color: var(--bg-base);
}
.sw-seg__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sw-tally {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 7px;
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-faint);
}
.sw-tally__part b {
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.sw-tally__x,
.sw-tally__eq { color: var(--border-medium); }
.sw-tally__total {
  padding: 2px 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.06);
  color: var(--text-primary);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}



/* Casilla de referencia mientras se arrastra una foto encima. El realce va en la TARJETA
   entera porque ese es el destino: apuntar a la miniatura mientras se arrastra es justo lo que
   falla. */
.sw-ref.is-over {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.sw-ref.is-over .sw-ref__thumb {
  border-color: var(--accent);
}

/* --------------------------------------------------------------------------
   El visor de vídeo
   --------------------------------------------------------------------------
   El reproductor de la lista sirve para reconocer cuál es; para verlo de verdad hace falta
   pantalla. Se abre encima y no en otra pestaña: la URL está firmada y no tiene por qué acabar
   en la barra de direcciones.

   El alto lo manda `min()` sobre el viewport: un reel es 9:16, así que lo que se acaba antes es
   la altura, y fijar el ancho dejaría el vídeo cortado por arriba y por abajo.
   -------------------------------------------------------------------------- */
.sw-viewer {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.82);
  backdrop-filter: blur(6px);
}
.sw-viewer__box {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 100%;
}
.sw-viewer__video {
  display: block;
  /* Alto y ancho FIJOS, no `max-*`: un `<video>` sin metadatos cargados mide 300x150 por
     defecto, así que con `max-height` el visor se abría como un sello y crecía de golpe al
     llegar la cabecera del fichero. Con la caja fija, el hueco es el definitivo desde el primer
     momento y `object-fit: contain` centra el vídeo dentro sin recortarlo. */
  height: min(86vh, 1200px);
  width: min(92vw, 620px);
  object-fit: contain;
  border-radius: 14px;
  background: #000;
  outline: none;
}
.sw-viewer__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.sw-viewer__title {
  font-size: 12px;
  color: var(--text-faint);
}
.sw-viewer__close {
  padding: 5px 12px;
  border: 1px solid var(--border-medium);
  border-radius: 999px;
  background: transparent;
  color: var(--text-secondary);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
}
.sw-viewer__close:hover { color: var(--text-primary); border-color: var(--accent-ring); }

/* Mientras el visor está abierto, la página de detrás no se desplaza: con el scroll suelto,
   cerrar te devolvía a otro sitio del que estabas. */
body.sw-viewer-open { overflow: hidden; }

/* --------------------------------------------------------------------------
   Interruptor (sonido del vídeo generado)
   --------------------------------------------------------------------------
   Con su explicación al lado y no solo un rótulo: "Sound" no dice si es el sonido de esta
   pantalla o el del vídeo que se va a hacer. La fila se esconde entera cuando el modelo elegido
   no sabe generar audio — un control que no hace nada es peor que no tenerlo.
   -------------------------------------------------------------------------- */
.sw-switch {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 14px;
  cursor: pointer;
}
.sw-switch input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.sw-switch__box {
  flex: 0 0 auto;
  position: relative;
  width: 34px;
  height: 20px;
  margin-top: 1px;
  border: 1px solid var(--border-medium);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.03);
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.sw-switch__box::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--text-faint);
  transition: transform 0.2s var(--ease), background 0.2s var(--ease);
}
.sw-switch.is-on .sw-switch__box {
  border-color: var(--text-primary);
  background: var(--text-primary);
}
.sw-switch.is-on .sw-switch__box::after {
  transform: translateX(14px);
  background: var(--bg-base);
}
.sw-switch input:focus-visible + .sw-switch__box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.sw-switch__text { display: flex; flex-direction: column; gap: 3px; }
.sw-switch__title { font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
.sw-switch__hint { font-size: 11.5px; line-height: 1.5; color: var(--text-faint); }

/* Botón de sonido sobre la miniatura de un resultado. Las miniaturas nacen mudas (varias a la
   vez sonando es inusable), y este botón es la forma de oír una sin abrir el visor. */
.sw-job__sound {
  position: absolute;
  right: 6px;
  /* ARRIBA, no abajo: abajo se solapa con la barra de progreso de los controles nativos del
     `<video>`, y acababas pulsando uno queriendo el otro. */
  top: 6px;
  z-index: 2;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--border-medium);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  color: var(--text-secondary);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
}
.sw-job__sound:hover { color: var(--text-primary); border-color: var(--accent-ring); }
.sw-job__media { position: relative; display: block; }

/* Variante selector: la etiqueta y el desplegable en una línea, sin ocupar todo el ancho. */
.sw-acct-line--pick { gap: 12px; }
.sw-acct-line__label {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sw-acct-line__select { min-width: 180px; max-width: 280px; }

/* ====================================================================
   Bulk (espacio SFW)
   --------------------------------------------------------------------
   Estilos de `#page-sfw-bulk`, que pinta `bulk.js`. Prefijo propio `.bk-`
   y NADA de `.sw-`: el bloque de referencias `.sw-ref*` lo monta también
   `models.js` en la ficha de la identidad, o sea FUERA de este espacio, y
   retocarlo desde aquí cambiaría una pantalla que no es esta.

   La pantalla es una lista de runs y, dentro de un run, cuatro pasos:
   para quién es · los encuadres · las fotos · hacerlas. La rejilla de
   abajo es la demo de la feature: cada tarjeta enseña la REFERENCIA y
   cambia a la foto terminada EN SU SITIO, una detrás de otra según
   avanza la cola. Por eso el hueco de la foto tiene proporción fija y no
   salta de tamaño cuando llega el resultado.

   Regla del espacio SFW: cualquier relleno de acento lleva
   `color: var(--on-accent)` — con el acento en blanco, un `#fff`
   heredado desaparecería. Aquí el único relleno de acento propio es
   `.bk-step__num`; los CTAs son los del studio y ya lo cumplen.

   Los CTAs NO estrenan clases: `.model-create__btn` (primario),
   `.ghost-btn` / `.ghost-btn--danger` (secundario). Aquí solo se monta
   lo que no existe ya.
   ==================================================================== */

/* LA PRIMERA DE TODAS, y no es opcional: casi todo lo de esta pantalla trae su propio
   `display` (flex/grid/block), que PISA el `[hidden] { display: none }` del user-agent. Sin
   esto, `el.hidden = true` desde bulk.js no esconde NADA — y lo que se quedaría a la vista es
   media pantalla de un run que no existe. Misma trampa que ya cierran `.sidebar__item`,
   `.topbar__jets` y `#page-sfw-swap`. Acotada a `#page-sfw-bulk` para no tocar nada de fuera. */
#page-sfw-bulk [hidden] { display: none !important; }

/* Y, como cinturón sobre el tirante, la companion por clase que pide la regla de la casa:
   sirve aunque un `.bk-` acabe montado fuera de la sección. */
.bk-page[hidden], .bk-head[hidden], .bk-card[hidden], .bk-card__head[hidden],
.bk-step[hidden], .bk-step__num[hidden], .bk-step__body[hidden], .bk-step__tally[hidden],
.bk-fields[hidden], .bk-field[hidden], .bk-shots[hidden], .bk-shot[hidden],
.bk-styles[hidden], .bk-pin[hidden], .bk-pin__row[hidden], .bk-pin__form[hidden],
.bk-drop[hidden], .bk-bar[hidden], .bk-bar__fill[hidden], .bk-actions[hidden],
.bk-runs[hidden], .bk-run-row[hidden], .bk-grid[hidden], .bk-tile[hidden],
.bk-tile__actions[hidden], .bk-eyebrow[hidden], .bk-skel[hidden],
.bk-run__state[hidden], .bk-msg[hidden], .bk-msg--soft[hidden], .bk-flag[hidden] { display: none; }

/* ---- La página ----
   Ocupa TODO el ancho del área de contenido (sin `max-width`), igual que `.sw-page`: la
   rejilla de resultados es lo que más sitio pide, y dejarla en una columna estrecha obliga a
   bajar para ver cómo avanza la tanda, que es justo lo que hay que ver. */
.bk-page {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 100%;
  padding-bottom: 24px;
}

/* ---- Cabecera ---- */
.bk-head { display: block; }
.bk-eyebrow {
  display: block;
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
  margin-bottom: 12px;
}
.bk-title {
  font-family: var(--font-serif);
  font-size: 44px;
  font-weight: 400;
  line-height: 1.05;
  color: var(--text-primary);
  margin: 0 0 12px;
}
.bk-title em { color: var(--accent); font-style: italic; }
.bk-lede {
  color: var(--text-soft);
  font-size: 14px;
  line-height: 1.65;
  max-width: 560px;
  margin: 0;
}

/* Lo que BLOQUEA la pantalla entera (feature apagada, ninguna identidad lista). Lo que solo
   bloquea un botón vive abajo pegado al botón, en `.bk-msg--soft`. */
.bk-flag {
  margin: 0;
  padding: 9px 12px;
  border: 1px solid rgba(232, 90, 90, 0.35);
  border-radius: 10px;
  background: rgba(232, 90, 90, 0.08);
  font-size: 12.5px;
  line-height: 1.5;
  color: #e85a5a;
}

/* ---- Tarjetas ---- */
.bk-card {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 20px 22px;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.02);
}
.bk-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}
.bk-card__title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 6px;
}
.bk-card__hint {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 540px;
  margin: 0;
}

/* Cabecera del run abierto: no es una tarjeta, es la línea de título + "All runs". */
.bk-run__head { padding: 0 2px; }
.bk-run__state {
  display: inline-block;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* ---- Los cuatro pasos ----
   El número en su columna y el cuerpo al lado, para que se lea como una secuencia aunque un
   paso crezca (la rejilla de encuadres, la zona de soltar). Mismo lenguaje que `.sw-step`. */
.bk-step {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 14px;
  padding: 18px 20px;
}
/* Relleno de acento: el texto de dentro TIENE que ir en --on-accent o desaparece con el
   acento en blanco (regla del espacio SFW). */
.bk-step__num {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  margin-top: 2px;
}
.bk-step__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.bk-step__title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}
.bk-step__tally {
  margin-left: 8px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--text-faint);
}
.bk-step__hint {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-muted);
  max-width: 540px;
  margin: 0;
}

/* ---- Campos (identidad · look · tarea · forma) ---- */
.bk-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.bk-field {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 170px;
  flex: 1 1 170px;
}
.bk-field__label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
}
/* Mismo lenguaje que `.model-create__input` del studio (no hay clase de `<select>` que
   reutilizar). La flecha nativa se deja: dibujarla a mano se desalinea en cada navegador. */
.bk-select {
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  color: var(--text-primary);
  padding: 8px 10px;
  font-family: inherit;
  font-size: 13px;
  width: 100%;
  min-width: 0;
  cursor: pointer;
}
.bk-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bk-select:disabled { opacity: 0.45; cursor: not-allowed; }
/* El menú desplegado lo pinta el sistema: sin esto hereda blanco sobre blanco en algunos
   navegadores y no se lee ni una opción. */
.bk-select option { background: var(--bg-secondary); color: var(--text-primary); }

/* ---- Encuadres (el multiplicador) ---- */
.bk-shots {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.bk-shot {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 13px;
  border: 1px solid var(--border-medium);
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.02);
  cursor: pointer;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.bk-shot:hover { border-color: var(--accent-ring); }
.bk-shot__box { accent-color: var(--accent); cursor: inherit; margin: 0; }
.bk-shot__label {
  font-size: 12.5px;
  color: var(--text-soft);
  line-height: 1;
  white-space: nowrap;
}
/* El chip marcado se lee de un vistazo: es la cifra de "8 fotos × 3 encuadres" lo que se
   está eligiendo aquí. `.is-on` va al lado por si el JS acaba marcándolo a mano. */
.bk-shot:has(.bk-shot__box:checked),
.bk-shot.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.bk-shot:has(.bk-shot__box:checked) .bk-shot__label,
.bk-shot.is-on .bk-shot__label { color: var(--text-primary); }
.bk-shot:has(.bk-shot__box:disabled) { opacity: 0.45; cursor: default; }

/* ---- Estilos (opcionales) ----
   Reutilizan los chips `.bk-shot` a propósito: son la misma clase de elección y darles otra
   forma haría creer que funcionan distinto. Lo único propio es la separación y el encabezado,
   que es lo que marca que esta fila NO es obligatoria — a diferencia de los encuadres, un run
   sin estilos se genera perfectamente. */
.bk-styles { margin-top: 18px; }
.bk-styles__head { margin-bottom: 10px; }
.bk-styles__opt { color: var(--text-faint); }

/* ---- Pinterest (la otra puerta de entrada de fotos) ----
   Va DEBAJO de la zona de soltar y no al lado: son alternativas, no un formulario de dos
   columnas, y en móvil ya se apilarían igual. El bloque entero desaparece cuando el servidor no
   tiene app de Pinterest (`hidden` desde el JS): un bloque que no puede hacer nada es peor que no
   tenerlo. */
.bk-pin { margin-top: 16px; }
.bk-pin__head { margin-bottom: 10px; }
.bk-pin__head strong { color: var(--text-primary); font-weight: 600; }
.bk-pin__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
.bk-pin__board { min-width: 200px; flex: 1 1 200px; }
.bk-pin__who {
  font-size: 12px;
  color: var(--text-faint);
  white-space: nowrap;
}
/* "Disconnect" es una acción secundaria y RARA: se pinta como enlace y no como botón para que
   no compita con "Add photos", que es lo que se pulsa de verdad. */
.bk-pin__unlink {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 12px;
  color: var(--text-faint);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  transition: color 0.2s var(--ease);
}
.bk-pin__unlink:hover { color: var(--text-soft); }
.bk-pin__unlink:disabled { opacity: 0.45; cursor: default; }
/* El cuadro de "pega tu sesión". Reutiliza la caja de texto de la tarjeta de Settings (`.va-ta`)
   en forma: monoespaciada y baja, porque lo que se pega es un token largo, no un texto. */
.bk-pin__form { display: flex; flex-direction: column; gap: 10px; }
.bk-pin__form code { font-size: 12px; color: var(--text-primary); }
.bk-pin__ta {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  padding: 10px 12px;
  border: 1px solid var(--border-medium);
  border-radius: 10px;
  background: var(--bg-base);
  color: var(--text-primary);
  font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  transition: border-color 0.2s var(--ease);
}
.bk-pin__ta:focus { outline: none; border-color: var(--accent); }

/* ---- Zona de soltar fotos ---- */
.bk-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 26px 18px;
  border: 1px dashed var(--border-medium);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  text-align: center;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
.bk-drop:hover { border-color: var(--accent-ring); }
.bk-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bk-drop.is-over { border-color: var(--accent); background: var(--accent-soft); }
.bk-drop.is-busy { opacity: 0.55; cursor: default; }
.bk-drop__title { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.bk-drop__hint { font-size: 11.5px; line-height: 1.5; color: var(--text-faint); }

/* ---- Barra + línea de recuento ----
   Sirven a DOS momentos (subir fotos y generarlas) porque nunca coinciden: el servidor no
   deja tocar las fotos de un run que está corriendo. El ancho lo pone bulk.js en línea
   (`style.width`), así que aquí NO se declara `width` en el relleno. */
.bk-bar {
  height: 4px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}
.bk-bar__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width 0.3s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .bk-bar__fill { transition: none; }
}
.bk-progress { margin: 0; font-size: 11.5px; color: var(--text-muted); }

/* ---- Botonera ---- */
.bk-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

/* ---- Líneas de texto ----
   `.bk-msg` es un fallo (siempre una frase FIJA de bulk.js, nunca el detalle del servidor);
   `.bk-msg--soft` es lo que falta o lo que conviene saber antes de pulsar; `.bk-note` es el
   estado vacío de una lista. */
.bk-msg { margin: 0; font-size: 12.5px; line-height: 1.5; color: #e85a5a; }
.bk-msg--soft { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-faint); }
.bk-note { margin: 0; font-size: 12px; line-height: 1.5; color: var(--text-faint); }

/* ---- Lista de runs ---- */
.bk-runs { display: flex; flex-direction: column; gap: 8px; }
.bk-run-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 14px;
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--border-soft);
  border-left: 2px solid var(--border-medium);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s var(--ease), background 0.2s var(--ease);
}
.bk-run-row:hover { background: rgba(255, 255, 255, 0.05); border-color: var(--accent-ring); }
.bk-run-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bk-run-row__title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bk-run-row__state {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  justify-self: end;
  white-space: nowrap;
}
.bk-run-row__counts { font-size: 11.5px; color: var(--text-faint); }
.bk-run-row__when {
  font-size: 11px;
  color: var(--text-faint);
  justify-self: end;
  white-space: nowrap;
}
/* El borde de la izquierda ES el punto de estado de la fila: dice en qué va el run sin
   añadir una pieza más. Ni `error` ni `blocked` son finales (de los dos se rearranca), así
   que solo `error` se pinta en rojo. */
.bk-run-row--writing,
.bk-run-row--reviewing { border-left-color: var(--text-muted); }
.bk-run-row--running { border-left-color: var(--accent); }
.bk-run-row--done { border-left-color: var(--text-primary); }
.bk-run-row--blocked { border-left-color: var(--text-faint); }
.bk-run-row--error { border-left-color: #e85a5a; }

/* Esqueleto: la MISMA fila sin datos, para que la tarjeta tenga ya su forma cuando llegue la
   lista, en vez de un "Loading…" que luego cambia de tamaño. */
.bk-run-row--skeleton { border-style: dashed; cursor: default; }
.bk-skel {
  display: block;
  height: 9px;
  width: 45%;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.07);
  animation: bk-skel-pulse 1.4s ease-in-out infinite;
}
.bk-skel--wide { width: 75%; }
@keyframes bk-skel-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .bk-skel { animation: none; }
}

/* ====================================================================
   La rejilla de fotos
   --------------------------------------------------------------------
   La tarjeta es un grid de una columna donde la PRIMERA fila la comparten
   la foto y todo lo que va encima (anillo, encuadre, estado, la ×), y lo
   demás —acciones, fallo, texto del prompt— fluye debajo. Así la foto
   conserva su proporción y los rótulos no se despegan de ella aunque a un
   admin se le abra el cuadro de texto.
   ==================================================================== */
.bk-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 10px;
}
.bk-tile {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  border: 1px solid var(--border-soft);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.02);
  overflow: hidden;
  min-width: 0;
}
.bk-tile__img,
.bk-tile__link,
.bk-tile__ring,
.bk-tile__shot,
.bk-tile__state,
.bk-tile__x { grid-area: 1 / 1; }
.bk-tile__img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  background: rgba(0, 0, 0, 0.35);
}
.bk-tile__link { display: block; aspect-ratio: 3 / 4; }
.bk-tile__link .bk-tile__img { aspect-ratio: auto; }
.bk-tile.is-clickable { cursor: pointer; }
.bk-tile.is-clickable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* El cambio de la referencia a la foto terminada es la demo de la feature: se funde en su
   sitio en vez de aparecer de golpe. */
.bk-tile--done .bk-tile__img { animation: bk-tile-in 0.35s var(--ease) both; }
@keyframes bk-tile-in {
  from { opacity: 0.35; }
  to { opacity: 1; }
}

/* Contorno que late mientras hay trabajo sobre ESTA foto. No captura el ratón: la tarjeta de
   debajo sigue siendo clicable. */
.bk-tile__ring {
  pointer-events: none;
  border: 1px solid var(--accent);
  border-radius: 13px;
  animation: bk-ring-pulse 1.6s ease-in-out infinite;
}
@keyframes bk-ring-pulse {
  0%, 100% { opacity: 0.25; }
  50% { opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .bk-tile--done .bk-tile__img { animation: none; }
  .bk-tile__ring { animation: none; opacity: 0.6; }
}

/* Rótulos sobre la foto. El encuadre abajo (es del contenido), el estado arriba a la derecha
   (es del progreso) y la × arriba a la izquierda, que es donde no tapa a ninguno de los dos. */
.bk-tile__shot,
.bk-tile__state {
  align-self: end;
  justify-self: start;
  margin: 6px;
  padding: 3px 7px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.62);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  line-height: 1.3;
  color: var(--text-soft);
  max-width: calc(100% - 12px);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bk-tile__state { align-self: start; justify-self: end; color: var(--text-primary); }
.bk-tile--error .bk-tile__state { color: #e85a5a; }
.bk-tile__x {
  align-self: start;
  justify-self: start;
  margin: 6px;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 1px solid var(--border-medium);
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.62);
  color: var(--text-soft);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.bk-tile__x:hover { color: #e85a5a; border-color: #e85a5a; }
.bk-tile__x:disabled { opacity: 0.4; cursor: not-allowed; }

/* Lo que fluye DEBAJO de la foto. Los botones son `.ghost-btn` del studio: aquí solo se
   ajusta el tamaño para que quepan dos en una tarjeta de 130px. */
.bk-tile__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px;
}
.bk-tile__act { padding: 5px 11px; font-size: 10px; flex: 1 1 auto; }
.bk-tile__act--keep { border-color: var(--accent-ring); }
.bk-tile__err {
  margin: 0;
  padding: 0 8px 8px;
  font-size: 11px;
  line-height: 1.45;
  color: #e85a5a;
}
/* La MISMA frase cuando la foto NO falló (el servidor no pudo leerla y la deja accionable):
   es un aviso, no el fallo, y en rojo se leería como una tarjeta perdida. Va justo detrás
   para que gane por orden, que a igual especificidad manda la última. */
.bk-tile__err--warn { color: var(--text-faint); }
/* Solo lo ve un admin. Plegado, porque lo que se revisa aquí es la FOTO y el ENCUADRE. */
.bk-tile__text {
  padding: 0 8px 8px;
  font-size: 11px;
  color: var(--text-faint);
  cursor: pointer;
}
.bk-tile__prompt {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 7px 9px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  color: var(--text-primary);
  font-family: inherit;
  font-size: 11.5px;
  line-height: 1.45;
  resize: vertical;
  cursor: auto;
}
.bk-tile__prompt:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.bk-tile__prompt:disabled { opacity: 0.5; }

/* La preview local mientras la foto sube: es un hueco, no un item del servidor. */
.bk-tile--ghost { border-style: dashed; opacity: 0.7; }

/* Los estados intermedios (`pending`/`writing`/`ready`/`generating`) NO retintan la tarjeta: ya
   los dicen el rótulo de estado y el anillo, y una rejilla de 60 fotos con seis colores a la vez
   deja de leerse. Solo se marcan los tres que cambian lo que el usuario puede ESPERAR de esa
   foto: la descartada, la que ya está en cola y la que falló. */
.bk-tile--skipped { opacity: 0.45; }
.bk-tile--skipped .bk-tile__img { filter: grayscale(1); }
.bk-tile--queued { border-color: var(--accent-ring); }
.bk-tile--error { border-color: rgba(232, 90, 90, 0.4); }

/* Con sitio de sobra la rejilla respira y las tarjetas dejan de ir apretadas: esta es la
   pantalla donde se mira cómo avanza la tanda, así que la foto manda. */
@media (min-width: 900px) {
  .bk-grid { grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px; }
  .bk-card { padding: 22px 26px; }
  .bk-step { padding: 22px 26px; gap: 16px; }
  .bk-run-row {
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    gap: 18px;
    padding: 13px 16px;
  }
  .bk-run-row__counts { justify-self: end; }
}

/* ==========================================================================
   Spoof (espacio SFW): un clip de origen, N copias únicas
   --------------------------------------------------------------------------
   Dos columnas como Swap: a la izquierda se prepara (origen · fuerza · datos del fichero) y a
   la derecha se mira lo que sale. La rejilla de resultados es lo que más sitio pide —son vídeos
   verticales y hay hasta diez—, así que la página ocupa todo el ancho y no lleva `max-width`.
   ========================================================================== */
/* `[hidden]` dentro de esta pantalla gana SIEMPRE. Sin la regla ancha, un `.btn` (que tiene su
   propio `display`) se sigue viendo con el atributo puesto: el botón del ZIP aparecía antes de
   que existiera un solo lote. */
.sp-page [hidden], .sp-page[hidden] { display: none !important; }

.sp-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  width: 100%;
}
.sp-col { display: flex; flex-direction: column; gap: 18px; min-width: 0; }

.sp-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px 22px;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.02);
}
.sp-card--results { min-height: 260px; }
.sp-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.sp-card__title { margin: 4px 0 0; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.sp-eyebrow {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}

/* ---- Zona de arrastre ---- */
.sp-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 132px;
  padding: 18px;
  border: 1px dashed var(--border-soft);
  border-radius: 14px;
  cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, background .15s ease;
}
.sp-drop:hover, .sp-drop.is-over {
  border-color: var(--accent-ring);
  background: rgba(255, 255, 255, 0.03);
}
.sp-drop__text { font-size: 12px; color: var(--text-dim); }
/* La condición del material, dicha DONDE se suelta el fichero. En la tarjeta de opciones ya se
   explica el porqué, pero quien arrastra un vídeo no lee la columna entera antes. */
.sp-drop__hint { font-size: 10.5px; letter-spacing: .02em; color: var(--text-dim); opacity: .7; }

.sp-bar { display: block; width: 100%; height: 3px; border-radius: 2px; background: var(--accent-soft); }
.sp-bar__fill { display: block; height: 100%; width: 0; border-radius: 2px; background: var(--accent); transition: width .2s ease; }

.sp-source { display: flex; gap: 14px; align-items: center; }
.sp-source__video {
  width: 96px;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border-radius: 10px;
  background: #000;
}
.sp-source__meta { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.sp-source__len { font-size: 12px; color: var(--text-dim); }

/* ---- Controles ---- */
.sp-field { display: flex; flex-direction: column; gap: 8px; }
.sp-label {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.sp-value { color: var(--text); font-variant-numeric: tabular-nums; }
.sp-range { width: 100%; accent-color: var(--accent); }
/* `color-scheme: dark` + el color de las `option` NO son adorno: el desplegable nativo lo pinta el
   sistema en blanco, y con el texto claro que hereda del studio las opciones salían BLANCO SOBRE
   BLANCO — la lista parecía vacía salvo la fila seleccionada. Mismo remedio que `.sd-select`,
   `.tk-va-select` y `.trainer-select`. */
.sp-select {
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font-size: 12px;
  color-scheme: dark;
}
.sp-select option { background: var(--bg-secondary); color: var(--text-primary); }
.sp-seg { display: flex; gap: 6px; }
.sp-seg__btn {
  flex: 1;
  padding: 9px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  font-size: 11px;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.sp-seg__btn.is-active {
  border-color: var(--accent-ring);
  background: rgba(255, 255, 255, 0.05);
  color: var(--text);
}
.sp-check { display: flex; align-items: center; gap: 9px; font-size: 12px; cursor: pointer; }
.sp-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* El cuadro de color nativo: sin `padding: 0` Chrome deja un borde blanco enorme dentro. */
.sp-color {
  width: 42px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border-soft);
  border-radius: 8px;
  background: transparent;
  cursor: pointer;
}
.sp-color:disabled { opacity: .35; cursor: not-allowed; }
/* La subida de fuente es un <label> con el input dentro (clicar el input abre el diálogo nativo
   igual, y así se estiliza como los demás botones). */
.sp-upload { cursor: pointer; }
.sp-hint { margin: 0; font-size: 11px; line-height: 1.5; color: var(--text-dim); }

.sp-actions { display: flex; flex-direction: column; gap: 10px; }
.sp-msg { margin: 0; font-size: 12px; color: #e85a5a; }
.sp-state { margin: 0; font-size: 12px; color: var(--text-dim); }

/* ---- Resultados ----
   Las tarjetas llevan el vídeo con controles: la comprobación que hace el usuario es MIRAR que
   la copia sigue siendo el mismo contenido, y para eso hay que poder reproducirla aquí. */
.sp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px;
}
.sp-tile {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.sp-tile--error { border-color: rgba(232, 90, 90, 0.4); }
.sp-tile__video, .sp-tile__ph {
  width: 100%;
  aspect-ratio: 9 / 16;
  border-radius: 8px;
  background: #000;
  object-fit: cover;
}
/* En PANTALLA COMPLETA manda el vídeo, no la tarjeta. Sin esto, el `object-fit: cover` —que en la
   rejilla está bien, porque recorta a la caja vertical— recorta el vídeo al 16:9 de la pantalla y
   se ve ampliado y cortado por arriba y por abajo: justo lo que NO se quiere al revisar una copia.
   El `aspect-ratio: auto` es igual de necesario: forzar 9/16 a pantalla completa deja el vídeo
   metido en una franja. */
.sp-tile__video:fullscreen,
.sp-tile__video:-webkit-full-screen {
  object-fit: contain;
  aspect-ratio: auto;
  width: 100%;
  height: 100%;
  border-radius: 0;
}
.sp-tile__ph {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-dim);
  background: rgba(255, 255, 255, 0.03);
}
.sp-tile__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.sp-tile__label { font-size: 11px; color: var(--text-dim); }

/* Con sitio de sobra, preparar y mirar dejan de turnarse: la columna de resultados se pone al
   lado y las copias caben de tres en tres. */
@media (min-width: 1100px) {
  .sp-page { grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); align-items: start; }
  .sp-card { padding: 22px 26px; }
  .sp-grid { grid-template-columns: repeat(auto-fill, minmax(178px, 1fr)); }
}

/* ---- Spoof: orígenes múltiples, slots, bibliotecas y ajustes finos ----
   Todo con el mismo prefijo `.sp-`; la regla ancha de `[hidden]` de arriba cubre también estos. */
.sp-seg--mini .sp-seg__btn { padding: 6px 10px; font-size: 10.5px; }

.sp-sources { display: flex; flex-direction: column; gap: 8px; }
.sp-src {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 10px;
}
.sp-src__thumb {
  width: 38px;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  border-radius: 6px;
  background: #000;
  flex: none;
}
.sp-src__meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.sp-src__name {
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sp-src__len { font-size: 10.5px; color: var(--text-dim); }

/* Los 3 huecos de origen colgado. Se ven siempre, también vacíos: un hueco vacío enseña que la
   opción existe; una fila que solo aparece cuando ya hay algo, no. */
.sp-slots { display: flex; gap: 8px; }
.sp-slot {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  border: 1px dashed var(--border-soft);
  border-radius: 10px;
  font-size: 10.5px;
  color: var(--text-dim);
}
.sp-slot.is-filled { border-style: solid; color: var(--text); }
.sp-slot__label { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sp-slot__btn {
  border: 0;
  background: transparent;
  color: inherit;
  font-size: 10.5px;
  cursor: pointer;
  padding: 2px 4px;
  opacity: .75;
}
.sp-slot__btn:hover { opacity: 1; }

/* Bibliotecas (rótulos y pistas): lista con casilla, texto y borrar. */
.sp-lib { display: flex; flex-direction: column; gap: 4px; max-height: 190px; overflow-y: auto; }
.sp-lib__row { display: flex; align-items: center; gap: 8px; font-size: 11.5px; cursor: pointer; }
.sp-lib__text { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sp-lib__uses { font-size: 10px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.sp-lib__del {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 0 2px;
}
.sp-lib__del:hover { color: #e85a5a; }

.sp-input {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font-size: 12px;
}

/* Ajustes finos: una fila por efecto — interruptor, nombre, mínimo, máximo y unidad. */
.sp-fx__title {
  margin: 14px 0 6px;
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.sp-fx {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) 62px 62px 34px;
  align-items: center;
  gap: 8px;
  padding: 3px 0;
}
.sp-fx__name { font-size: 11.5px; }
.sp-fx__num {
  width: 100%;
  padding: 5px 6px;
  border: 1px solid var(--border-soft);
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.03);
  color: var(--text);
  font-size: 11px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.sp-fx__unit { font-size: 10px; color: var(--text-dim); }

/* Un bloque por lote: con varios orígenes hay varias tandas a la vez y hay que saber cuál es cuál. */
.sp-job { margin-top: 14px; }
.sp-job__head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.sp-job__state { font-size: 12px; color: var(--text-dim); }

/* (retirado) El plegado de "More options". La feature tiene veinte mandos, pero el camino de cada día es
   soltar un fichero y pulsar: si para llegar al botón hay que pasar por cinco tarjetas, la
   pantalla trabaja en tu contra. */
.sp-more {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: transparent;
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.sp-more:hover { border-color: var(--accent-ring); background: rgba(255, 255, 255, 0.03); }
.sp-more__hint { font-size: 10.5px; color: var(--text-dim); }
#sp-extras { display: flex; flex-direction: column; gap: 18px; }

/* ---- Panel de ajustes ----
   Los veinte mandos viven aquí y no en la columna. La pantalla de cada día es soltar un fichero y
   pulsar; tener la configuración delante del botón convierte una acción de dos pasos en una de
   veinte. Mismo criterio que el panel de ajustes del spoofer de origen, que también es su propia
   pantalla. */
.sp-settings-btn {
  align-self: flex-start;
  padding: 7px 12px;
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  background: transparent;
  color: var(--text-dim);
  font-size: 11px;
  letter-spacing: .04em;
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.sp-settings-btn:hover { color: var(--text); border-color: var(--accent-ring); }

.sp-modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  background: rgba(0, 0, 0, .62);
  backdrop-filter: blur(2px);
  overflow-y: auto;
}
.sp-modal__panel {
  width: min(560px, 100%);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: #0b0b0d;
  max-height: calc(100vh - 80px);
}
.sp-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border-soft);
}
.sp-modal__close {
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.sp-modal__close:hover { color: var(--text); }
.sp-modal__body {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 18px 20px 24px;
  overflow-y: auto;
}
/* Las tarjetas de dentro no llevan su propio marco: ya están dentro de uno. */
.sp-modal__body .sp-card { border: 0; padding: 0; background: transparent; }
.sp-modal__body .sp-card + .sp-card { border-top: 1px solid var(--border-soft); padding-top: 16px; }
body.sp-modal-open { overflow: hidden; }

/* ---- Indicador de trabajo ----
   Una copia tarda minutos. Sin nada que se mueva, la pantalla se lee como colgada y la gente
   vuelve a pulsar (o recarga a mitad de tanda). */
@keyframes sp-spin { to { transform: rotate(360deg); } }
.sp-spinner {
  width: 15px;
  height: 15px;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: sp-spin .8s linear infinite;
}
.sp-tile__ph { flex-direction: column; gap: 8px; }

/* El botón hace de indicador principal: es lo único que se ve sin bajar a la rejilla. */
.btn.is-busy { position: relative; opacity: 1; cursor: progress; }
.btn.is-busy::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: sp-sweep 1.4s ease-in-out infinite;
}
@keyframes sp-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

.sp-job__bar { margin-bottom: 10px; }

/* `prefers-reduced-motion`: quien pide menos movimiento sigue necesitando saber que algo pasa,
   así que el giro se para pero el indicador no desaparece. */
@media (prefers-reduced-motion: reduce) {
  .sp-spinner { animation: none; border-top-color: var(--accent); }
  .btn.is-busy::after { animation: none; }
}

/* --------------------------------------------------------------------------
   Datos de la descarga
   --------------------------------------------------------------------------
   Plegado por defecto y con el resumen a la vista ("iPhone 17 Pro Max · location"): es una
   preferencia que se toca una vez y luego solo hay que poder comprobarla de un vistazo.
   -------------------------------------------------------------------------- */
.sw-meta {
  margin: 0 0 12px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.sw-meta__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  cursor: pointer;
  list-style: none;
}
.sw-meta__head::-webkit-details-marker { display: none; }
.sw-meta__title {
  font-size: 10.5px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.sw-meta__state {
  font-size: 12px;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}
.sw-meta__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 14px 14px;
  border-top: 1px solid var(--border-soft);
  padding-top: 12px;
}
.sw-meta__hint {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text-faint);
}
.sw-meta__clear {
  align-self: flex-start;
  padding: 4px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  background: transparent;
  color: var(--text-faint);
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  cursor: pointer;
}
.sw-meta__clear:hover { color: var(--text-primary); border-color: var(--accent-ring); }

.sw-input {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--border-soft);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-primary);
  font-size: 13px;
  font-family: inherit;
}
.sw-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.sw-input::placeholder { color: var(--text-faint); }

.ghost-btn.is-busy { opacity: 0.6; pointer-events: none; }

/* Estado + ciudad, en una fila. El campo libre queda debajo para un sitio que no esté en la
   lista o para pegar coordenadas tal cual. */
.sw-place {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-bottom: 8px;
}
.sw-place .sw-select { min-width: 0; }

/* "Clean" vive en la cabecera de la lista, no entre los vídeos: es una acción sobre el
   conjunto, y suelta entre las filas se pulsa sin querer. */
.sw-clean { align-self: flex-start; }

/* ==========================================================================
   Compactado de la pantalla de Swap
   --------------------------------------------------------------------------
   La pantalla creció paso a paso (identidad, vídeo, prendas, modelo, calidad, sonido, datos de
   descarga) y acabó midiendo varias veces el alto de la ventana. Esto la aprieta sin quitar
   nada: menos aire, tipografías un punto más pequeñas y las fotos de la identidad a un tamaño
   de RECONOCER, que es para lo que están — no se editan aquí, se comprueba que son las que
   tocan.

   Va al final del fichero y acotado a `#page-sfw-swap` a propósito: el mismo bloque de
   referencias vive también en la ficha de la identidad (Models), donde hay sitio de sobra y no
   se toca.
   ========================================================================== */

/* ---- Un solo scroll ----
   Antes la columna derecha era `sticky` con su propia caja de scroll y un alto calculado a
   ojo (`100vh - 190px`). Eso dejaba DOS barras compitiendo: la rueda sobre los resultados
   movía la lista interior y, al llegar al final, saltaba a la página. Con la pantalla ya
   compacta no hace falta: scrollea la página entera y se acabó. */
@media (min-width: 1100px) {
  #page-sfw-swap .sw-col--out {
    position: static;
    max-height: none;
  }
  #page-sfw-swap .sw-results .sw-jobs {
    overflow-y: visible;
    padding-right: 0;
  }
}

/* ---- Los pasos ---- */
#page-sfw-swap .sw-step { padding: 13px 15px; gap: 11px; grid-template-columns: 24px 1fr; }
#page-sfw-swap .sw-step__num { width: 21px; height: 21px; font-size: 11px; }
#page-sfw-swap .sw-step__body { gap: 7px; }
#page-sfw-swap .sw-step__title { font-size: 13.5px; }
#page-sfw-swap .sw-step__hint { font-size: 11.5px; line-height: 1.45; }
#page-sfw-swap .sw-steps { gap: 10px; }
#page-sfw-swap .sw-work { gap: 16px; }
#page-sfw-swap .sw-col { gap: 10px; }

/* ---- Las fotos de la identidad ----
   De 180px de ancho mínimo a 108, y la miniatura deja de ser 3/4 (que es lo alto que hace
   crecer el bloque) para ser cuadrada. Cuatro fotos caben en una fila desde mucho antes. */
#page-sfw-swap .sw-refs {
  /* Tope de 132px y `justify-content: start`, no `1fr`: con `1fr` las cuatro casillas se
     reparten TODO el ancho de la columna, y como la miniatura es cuadrada, ancho = alto. En una
     pantalla ancha eso daba tarjetas de 430px de lado — cuatro fotos ocupando más que el resto
     de la página junta. Aquí no se editan las fotos, se comprueba que son las que tocan. */
  grid-template-columns: repeat(auto-fill, minmax(104px, 132px));
  justify-content: start;
  gap: 8px;
}
#page-sfw-swap .sw-ref { padding: 7px; gap: 6px; border-radius: 10px; }
#page-sfw-swap .sw-ref__thumb { aspect-ratio: 1 / 1; border-radius: 7px; }
#page-sfw-swap .sw-ref__body { gap: 2px; }
#page-sfw-swap .sw-ref__label { font-size: 11.5px; }
/* La frase de cada casilla pasa a `title`: el rótulo ya dice cuál es, y cuatro párrafos de
   ayuda ocupaban más que las propias fotos. */
#page-sfw-swap .sw-ref__hint { display: none; }
#page-sfw-swap .sw-ref__actions { gap: 5px; margin-top: 1px; }
#page-sfw-swap .sw-ref__btn { padding: 3px 8px; font-size: 10px; }

/* ---- Prendas ---- */
#page-sfw-swap .sw-outfits { grid-template-columns: repeat(auto-fill, minmax(92px, 1fr)); gap: 8px; }
#page-sfw-swap .sw-outfit { padding: 6px; gap: 4px; }
#page-sfw-swap .sw-outfit__name { font-size: 10.5px; }
#page-sfw-swap .sw-outfit__del { padding: 2px 6px; font-size: 9.5px; }
#page-sfw-swap .sw-drop { padding: 14px 14px; }
#page-sfw-swap .sw-drop--slim { padding: 11px 14px; }

/* ---- Resultados ---- */
#page-sfw-swap .sw-job__player { width: 78px; }
#page-sfw-swap .sw-jobs { gap: 8px; }

/* Traer el clip de un enlace. Va encima del área de soltar: cuando la cuenta no está capada es
   el camino corto —pegar y listo— y el de subir a mano queda como respaldo. */
.sw-link {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 10px;
}
.sw-link .sw-input { flex: 1; min-width: 0; }
.sw-link .ghost-btn { flex: none; }

/* ==========================================================================
   Swap · reparto de la pantalla (2026-09-12)
   --------------------------------------------------------------------------
   Antes: cuatro pasos apilados a la izquierda (la tarjeta de la identidad con medio ancho
   vacío) y una columna de resultados ancha en la que el vídeo era un sello de 78px al lado de
   una línea de texto y tres botones. Ahora:

   - Los tres pasos de ELEGIR (identidad · vídeo de origen · modelo) van uno al lado de otro
     —los tres son un desplegable y poco más— y el de las prendas ocupa el ancho entero
     debajo, porque su rejilla crece a lo ancho.
   - La columna de resultados mide lo que mide UN reel (unos 300px) y cada tarjeta es el
     vídeo, grande, con la línea de estado y los botones debajo. Un clic sobre el vídeo abre
     el visor.
   ========================================================================== */
@media (min-width: 1100px) {
  /* Anchura FIJA para los resultados, no una fracción: lo que se enseña ahí es un reel 9:16 y
     su tamaño no tiene por qué crecer con la pantalla. Todo lo que sobra va al formulario. */
  #page-sfw-swap .sw-work { grid-template-columns: minmax(0, 1fr) 340px; }
  /* Dos columnas de pasos entre 1100 y 1500: identidad y vídeo en la primera fila, y el modelo
     solo en la segunda a todo el ancho (un tercio vacío al lado se lee como un fallo). */
  #page-sfw-swap .sw-steps {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
  }
  #page-sfw-swap .sw-step:nth-child(3),
  #page-sfw-swap .sw-step--wide { grid-column: 1 / -1; }
}
@media (min-width: 1500px) {
  #page-sfw-swap .sw-work { grid-template-columns: minmax(0, 1fr) 380px; }
  #page-sfw-swap .sw-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  #page-sfw-swap .sw-step:nth-child(3) { grid-column: auto; }
  /* Las fotos de la identidad se quedan con el `auto-fill` de 104-132px de arriba: en un
     tercio estrecho caen solas a 2×2 y en una pantalla ancha van las cuatro en fila. Un
     `1fr` aquí daba cuadrados de 400px de lado en un monitor grande. */
}

/* ---- La tarjeta de un vídeo terminado: el vídeo manda ----
   Solo cuando HAY vídeo (`:has`): un trabajo terminado cuyo fichero ya se fue (Drive o el
   barrido) sigue siendo una fila de texto. */
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 8px;
}
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) .sw-job__media { width: 100%; }
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) .sw-job__player {
  width: 100%;
  border-radius: 9px;
  cursor: zoom-in;
}
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) .sw-job__body { flex: none; padding: 0 2px; }
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) .sw-job__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 2px 2px;
}
#page-sfw-swap .sw-job--succeeded:has(.sw-job__media) .sw-job__actions .ghost-btn { padding: 5px 10px; font-size: 10.5px; }

/* El visor vive colgado de `<body>`, fuera de `#page-sfw-swap`, así que la regla ancha de
   `[hidden]` de esta pantalla no lo alcanza — y su `display: flex` pisaba el `[hidden]` del
   navegador: al cerrarlo se quedaba la cortina puesta. */
.sw-viewer[hidden] { display: none; }

/* ==========================================================================
   Photo Cleaner (espacio SFW): fotos regeneradas, sin metadatos ni marca invisible
   --------------------------------------------------------------------------
   Copia de las primitivas de Spoof (.sp-) con prefijo propio: dos columnas (preparar a la
   izquierda, mirar lo que sale a la derecha), zona de arrastre, barra, tarjetas y rejilla.
   Prefijo aparte a propósito: retocar .sp- desde aquí cambiaría la pantalla de Spoof.
   Todo el color pasa por los tokens del studio, así que el tema 'mono' lo retinta solo.
   ========================================================================== */
/* `[hidden]` dentro de esta pantalla gana SIEMPRE: un `.btn` tiene su propio `display` y se
   seguiría viendo con el atributo puesto (los botones de la cabecera de resultados nacen ocultos). */
.pc-page [hidden], .pc-page[hidden] { display: none !important; }

.pc-page {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 18px;
  width: 100%;
}
.pc-col { display: flex; flex-direction: column; gap: 18px; min-width: 0; }
.pc-col--wide { min-width: 0; }

.pc-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 20px 22px;
  border: 1px solid var(--border-soft);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.02);
}
.pc-card--results { min-height: 260px; }
.pc-card__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.pc-card__title { margin: 4px 0 0; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
.pc-eyebrow {
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
}
/* Contador N/50: tabular para que no baile al cambiar de 9 a 10. Lleno = acento. */
.pc-count { font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pc-count.is-full { color: var(--accent); }

/* ---- Zona de arrastre ---- */
.pc-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 132px;
  padding: 18px;
  border: 1px dashed var(--border-soft);
  border-radius: 14px;
  cursor: pointer;
  text-align: center;
  transition: border-color .15s ease, background .15s ease;
}
.pc-drop:hover, .pc-drop.is-over {
  border-color: var(--accent-ring);
  background: rgba(255, 255, 255, 0.03);
}
.pc-drop__text { font-size: 12px; color: var(--text-dim); }
.pc-drop__hint { font-size: 10.5px; letter-spacing: .02em; color: var(--text-dim); opacity: .7; }

.pc-bar { display: block; width: 100%; height: 3px; border-radius: 2px; background: var(--accent-soft); }
.pc-bar__fill { display: block; height: 100%; width: 0; border-radius: 2px; background: var(--accent); transition: width .6s ease; }
#pc-job { margin-top: 14px; }
.pc-job__bar { margin-bottom: 10px; position: relative; overflow: hidden; }
/* Mientras una foto está en el pod no hay progreso intermedio que pintar (el relleno solo avanza
   por foto terminada): un brillo que recorre la barra dice "sigue vivo" sin inventar porcentajes. */
.pc-job__bar.is-live::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 40%;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.55), transparent);
  animation: pc-sweep 1.4s ease-in-out infinite;
}
@keyframes pc-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* ---- Las fotos elegidas ---- */
.pc-files { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; }
.pc-file {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.02);
}
.pc-file__thumb, .pc-file__zip {
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: 7px;
  object-fit: cover;
  background: #000;
}
.pc-file__zip {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .08em;
  color: var(--text-dim);
  background: rgba(255, 255, 255, 0.04);
  border: 1px dashed var(--border-soft);
}
.pc-file__meta { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.pc-file__name {
  font-size: 11.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pc-file__size { font-size: 10.5px; color: var(--text-dim); }

/* ---- Controles ---- */
.pc-check { display: flex; align-items: center; gap: 9px; font-size: 12px; cursor: pointer; }
.pc-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border: 1px solid var(--border-soft);
  border-radius: 9999px;
  font-size: 9.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.pc-hint { margin: 0; font-size: 11px; line-height: 1.5; color: var(--text-dim); }
.pc-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pc-actions { display: flex; flex-direction: column; gap: 10px; }
.pc-msg { margin: 0; font-size: 12px; color: #e85a5a; }
.pc-state { margin: 0; font-size: 12px; color: var(--text-dim); }

/* ---- Engine (pod de la organización): pastilla de estado + Prepare/Stop ----
   Los mismos colores que el chip de preparar de Generate (`.studio-prep`): verde listo, ámbar
   preparando, apagado en el gris del texto secundario, rojo si el plan cerró la puerta. */
.pc-card--engine { gap: 10px; }
.pc-engine__actions { justify-content: flex-end; }
.pc-state-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px;
  border: 1px solid var(--border-soft);
  border-radius: 999px;
  font-size: 10.5px;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-dim);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.pc-state-pill::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: .6;
}
.pc-state-pill[data-state="warming"] { color: #e9a24b; border-color: rgba(233, 162, 75, .4); }
.pc-state-pill[data-state="warming"]::before { opacity: 1; animation: pc-pill-pulse 1.2s ease-in-out infinite; }
.pc-state-pill[data-state="ready"] { color: #5fcf8a; border-color: rgba(95, 207, 138, .4); }
.pc-state-pill[data-state="ready"]::before { opacity: 1; box-shadow: 0 0 8px rgba(95, 207, 138, .6); }
.pc-state-pill[data-state="expired"] { color: #e85a5a; border-color: rgba(232, 90, 90, .4); }
@keyframes pc-pill-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* El bloque "Download details" es el de Swap (`.sw-meta*`), para que se vea idéntico; aquí solo
   se ajusta el margen al ritmo de la tarjeta de resultados. */
.pc-meta { margin: 4px 0 14px; }

/* ---- Resultados ----
   Las tarjetas son cuadradas: las fotos llegan con cualquier proporción, y una rejilla de
   miniaturas de alturas distintas baila en cada sondeo. `object-fit: cover` recorta para la
   miniatura; la foto entera se ve al descargarla. */
.pc-carousel {
  display: flex;
  gap: 14px;
  overflow-x: auto;
  min-width: 0;
  padding: 2px 0 12px;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--border-soft) transparent;
}
.pc-carousel:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.pc-carousel .pc-grid, #pc-history, .pc-history-group { display: contents; }
.pc-carousel-nav { display: flex; justify-content: flex-end; gap: 8px; margin-bottom: 10px; }
.pc-carousel-nav[hidden] { display: none; }
.pc-carousel-nav .btn { min-width: 38px; padding: 6px 12px; font-size: 18px; }
.pc-carousel .pc-tile { flex: 0 0 clamp(170px, 22vw, 230px); min-width: 0; scroll-snap-align: start; }
.pc-tile {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.02);
}
.pc-tile--error { border-color: rgba(232, 90, 90, 0.4); }
.pc-tile--skipped { opacity: .6; }
.pc-preview-trigger { display: block; width: 100%; padding: 0; border: 0; border-radius: 8px; background: transparent; cursor: zoom-in; }
.pc-preview-trigger .pc-tile__img { display: block; }
.pc-preview-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.pc-preview { position: fixed; inset: 0; margin: auto; width: min(1100px, 94vw); max-width: 94vw; max-height: 94dvh; padding: 44px 16px 12px; border: 1px solid rgba(255,255,255,.18); border-radius: 16px; background: #101010; color: #eee; overflow: auto; }
.pc-preview::backdrop { background: rgba(0,0,0,.85); }
.pc-preview__close { position: absolute; top: 6px; right: 10px; width: 36px; height: 36px; border: 0; border-radius: 50%; background: #252525; color: #fff; font-size: 28px; cursor: pointer; }
.pc-preview__stage { display: flex; align-items: center; justify-content: center; min-height: 180px; }
#pc-preview-status { display: flex; align-items: center; gap: 10px; }
.pc-preview__stage img { display: block; max-width: 100%; max-height: calc(94dvh - 110px); width: auto; height: auto; object-fit: contain; }
.pc-preview [hidden] { display: none !important; }
.pc-preview__caption { margin: 12px 0 0; text-align: center; font-size: 12px; overflow-wrap: anywhere; color: #aaa; }
.pc-history-actions { margin-top: 16px; font-size: 12px; color: var(--text-dim); }
.pc-history-actions summary { cursor: pointer; }
#pc-history-buttons > .pc-row { flex-wrap: wrap; margin-top: 12px; }
#pc-history-more { margin-top: 18px; }
.pc-tile__img, .pc-tile__ph {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  background: #000;
  object-fit: cover;
}
.pc-tile__ph {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px;
  font-size: 11px;
  text-align: center;
  color: var(--text-dim);
  background: rgba(255, 255, 255, 0.03);
}
.pc-tile__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.pc-tile__label {
  font-size: 11px;
  color: var(--text-dim);
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Un texto quieto durante un minuto se lee como "colgado"; algo que gira, no. */
@keyframes pc-spin { to { transform: rotate(360deg); } }
.pc-spinner {
  width: 15px;
  height: 15px;
  border: 2px solid var(--accent-soft);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: pc-spin .8s linear infinite;
}

/* Con sitio de sobra, preparar y mirar dejan de turnarse: la columna de resultados se pone al
   lado y las fotos caben de cuatro en cuatro. Mismo punto de corte que Swap y Spoof. */
@media (min-width: 1100px) {
  .pc-page { grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); align-items: start; }
  .pc-card { padding: 22px 26px; }
}

/* El spinner mantiene su giro normal también con movimiento reducido. */
@media (prefers-reduced-motion: reduce) {
  .pc-job__bar.is-live::after { animation: none; display: none; }
  .pc-state-pill[data-state="warming"]::before { animation: none; }
}
