:root {
  --fondo: #0f1115;
  --panel: #171a21;
  --borde: #262b36;
  --texto: #e6e8ec;
  --suave: #8b93a3;
  --acento: #6ea8fe;
  --error: #ff6b6b;
  --bien: #6bd39a;
}

* { box-sizing: border-box; }

/* `hidden` lo pinta el navegador con `display: none`, pero cualquier regla de
   aquí que le dé un `display` a un `form` o a un `section` se lo pisa y el
   elemento sigue viéndose. Pasó de verdad: el panel salía con el formulario de
   la clave encima. */
[hidden] { display: none !important; }

body {
  margin: 0;
  padding: 2rem 1rem 4rem;
  background: var(--fondo);
  color: var(--texto);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  min-height: 100vh;
}

header, main, footer { width: 100%; max-width: 720px; }

header { text-align: center; }
h1 { margin: 0; font-size: 2rem; letter-spacing: -0.02em; }
.lema { margin: 0.25rem 0 0.75rem; color: var(--suave); }
.red { color: var(--suave); font-size: 0.875rem; }

/* Las tres frases que dicen dónde acaba el prompt de una persona no se enseñan
   hasta que el servidor ha dicho si esto es la red o la propia tarjeta de quien
   mira (ver `ponerModo` en `app.js`). El HTML trae escrita la versión de la red,
   así que sin esto, abrir el modo local empezaría por dos décimas de segundo de
   "se procesa en el ordenador de otra persona", que es justo lo contrario de lo
   que pasa. `visibility` y no `display`: así el hueco ya está hecho y la página
   no pega un salto cuando aparecen. */
body:not([data-modo]) #lema,
body:not([data-modo]) #red,
body:not([data-modo]) #pie-aviso { visibility: hidden; }

main { display: grid; gap: 1.5rem; }

form, .resultado {
  background: var(--panel);
  border: 1px solid var(--borde);
  border-radius: 12px;
  padding: 1.25rem;
  display: grid;
  gap: 0.875rem;
}

label { font-size: 0.8125rem; color: var(--suave); display: block; margin-bottom: 0.25rem; }
.pista { opacity: 0.6; }

textarea, input, select {
  width: 100%;
  background: #0d1016;
  color: var(--texto);
  border: 1px solid var(--borde);
  border-radius: 8px;
  padding: 0.6rem 0.7rem;
  font: inherit;
  font-size: 0.9375rem;
}
textarea { resize: vertical; }
/* La regla de arriba está pensada para cajas de escribir, y un deslizador no lo
   es: con ese fondo, ese borde y ese padding, Chromium pinta la barra dentro de
   una caja que parece un campo de texto vacío y no se ve dónde agarrarla. Se le
   quita todo y se deja el control tal cual lo pinta el navegador. */
input[type="range"] {
  background: none;
  border: 0;
  padding: 0.4rem 0;
  accent-color: var(--acento);
}
textarea:focus, input:focus, select:focus {
  outline: 2px solid var(--acento);
  outline-offset: -1px;
  border-color: var(--acento);
}

.fila { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.campo { min-width: 0; }
/* Los campos se esconden y se enseñan según el modelo elegido (ver `app.js`), y
   `hidden` por sí solo no gana a un `display` puesto en la hoja: sin esto, el
   día que un `.campo` lleve `display: grid` el atributo dejaría de hacer nada. */
.campo[hidden] { display: none; }

/* La imagen de partida, ya elegida. Pequeña a posta: está para confirmar que has
   cogido el fichero que querías, no para mirarla. */
#imagen-previa {
  margin-top: 0.5rem;
  max-height: 8rem;
  border-radius: 8px;
  border: 1px solid var(--borde);
}

/* El audio elegido. Ancho entero y no pequeño como la imagen: aquí lo que
   confirma que has cogido el fichero bueno no es verlo, es oírlo, y los controles
   del navegador se apelotonan si se les recorta el sitio. */
#audio-previa {
  margin-top: 0.5rem;
  width: 100%;
}

/* ------------------------------------------------------ elegir modelo */

/* Un `fieldset` trae borde, márgenes y padding propios del navegador: aquí se
   quitan todos y se usa solo por lo que aporta de verdad, que es agrupar los
   radios y que el lector de pantalla lea "Modelo" antes de cada tarjeta. */
.modelos { border: 0; margin: 0; padding: 0; min-width: 0; }
.modelos legend {
  padding: 0;
  font-size: 0.8125rem;
  color: var(--suave);
  margin-bottom: 0.25rem;
}
.modelos .rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.6rem;
}
/* El aviso de vacío ocupa la fila entera, que si no la rejilla lo mete de
   columna y las tarjetas empiezan torcidas. */
.modelos .vacio {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--suave);
  font-size: 0.875rem;
}

/* El aviso del modelo elegido. Borde a la izquierda y nada de fondo rojo, igual
   que el `.aviso` de las páginas de texto: esto no es un error del comprador, es
   lo que hay que saber antes de pagar. Y del color del texto normal y no del
   suave: si se pinta apagado se lee como letra pequeña y es justo lo contrario. */
.nota-modelo {
  margin: 0.25rem 0 0;
  padding-left: 0.75rem;
  border-left: 3px solid var(--acento);
  font-size: 0.875rem;
  line-height: 1.5;
}

.modelo {
  display: grid;
  gap: 0.15rem;
  margin: 0;
  padding: 0.4rem;
  background: #0d1016;
  border: 1px solid var(--borde);
  border-radius: 10px;
  cursor: pointer;
  position: relative;
}
/* El radio se esconde, pero no con `display: none`: así desaparece también del
   orden del tabulador y de los lectores de pantalla, y la rejilla dejaría de ser
   elegible con el teclado. Se queda puesto y sin verse. */
.modelo input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
}
.modelo img, .modelo .sin-foto {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 6px;
  background: #161a22;
  display: block;
}
.modelo .nombre { font-size: 0.8125rem; font-weight: 600; }
.modelo .detalle { font-size: 0.75rem; color: var(--suave); }
.modelo:hover:not(:has(input:disabled)) { border-color: #3a4254; }
/* Elegido: el borde de acento y un anillo, para que se note también cuando la
   miniatura ya es de por sí de un color parecido al borde. */
.modelo:has(input:checked) {
  border-color: var(--acento);
  box-shadow: 0 0 0 1px var(--acento);
}
/* El foco del teclado se ve en la tarjeta, no en el radio escondido. */
.modelo:has(input:focus-visible) { outline: 2px solid var(--acento); outline-offset: 2px; }
.modelo:has(input:disabled) { opacity: 0.4; cursor: not-allowed; }

details { border-top: 1px solid var(--borde); padding-top: 0.75rem; }
details summary { cursor: pointer; color: var(--suave); font-size: 0.875rem; }
details > * + * { margin-top: 0.75rem; }

button {
  background: var(--acento);
  color: #07101f;
  border: 0;
  border-radius: 8px;
  padding: 0.75rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
button:hover:not(:disabled) { filter: brightness(1.1); }
button:disabled { opacity: 0.45; cursor: progress; }

.estado { color: var(--suave); font-size: 0.9375rem; }

.barra { height: 6px; background: #0d1016; border-radius: 999px; overflow: hidden; }
.barra > div {
  height: 100%;
  width: 0;
  background: var(--acento);
  transition: width 0.25s ease;
}

figure { margin: 0; display: grid; gap: 0.5rem; }
figure img {
  width: 100%;
  border-radius: 8px;
  background: #0d1016;
  display: block;
}
/* El reproductor ocupa el ancho entero, como la imagen a la que sustituye. Los
   controles los pinta el navegador y no se tocan: son los que la persona ya
   sabe usar, y los suyos llevan volumen y velocidad sin que aquí haya nada. */
figure audio { width: 100%; display: block; }
/* Como la imagen a la que sustituye, con una diferencia: el alto se deja libre.
   Un vídeo de 480x480 dentro de una columna más ancha se estiraría, y estirar un
   vídeo generado es enseñar algo que la red no ha hecho. */
figure video {
  width: 100%;
  height: auto;
  border-radius: 8px;
  background: #0d1016;
  display: block;
}
/* La pieza en 3D. Cuadrada porque la vista es una esfera alrededor del objeto y
   un lienzo apaisado deja media pantalla vacía por arriba y por abajo se mire lo
   que se mire. `touch-action: none` es lo que hace que en un móvil arrastrar
   encima gire la pieza en vez de desplazar la página; sin eso el visor no se
   puede usar con el dedo. El cursor dice que eso se coge. */
figure canvas {
  width: 100%;
  aspect-ratio: 1;
  border-radius: 8px;
  background: #0d1016;
  display: block;
  touch-action: none;
  cursor: grab;
}
figure canvas:active { cursor: grabbing; }
/* El texto que va cayendo. Va en un <pre> y no en un <p> porque un LLM contesta
   con saltos de línea y con listas, y un <p> los aplasta todos en un párrafo
   corrido; `pre-wrap` respeta los saltos que él pone y sigue partiendo las líneas
   largas donde toque. La fuente es la del resto de la página y no la monoespaciada
   que trae `pre` de casa: esto es prosa para leer, no código.
   El alto máximo existe porque la respuesta crece mientras se escribe y sin él la
   página entera pega tirones hacia abajo cada décima de segundo. */
figure pre {
  margin: 0;
  font-family: inherit;
  font-size: 0.9375rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 26rem;
  overflow-y: auto;
  background: #0d1016;
  border: 1px solid var(--borde);
  border-radius: 8px;
  padding: 0.9rem;
}
figcaption {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 0.8125rem;
  color: var(--suave);
}
figcaption a { color: var(--acento); }

.error {
  margin: 0;
  color: var(--error);
  background: #2a1618;
  border: 1px solid #57282c;
  border-radius: 8px;
  padding: 0.7rem;
  font-size: 0.8125rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* La contraria del `.error`, con la misma caja. Hace falta porque hay sitios
   —reclamar una máquina, cobrar— donde lo que ha salido bien no se ve en ningún
   otro lado: el dinero tarda dos días en aparecer en el banco, y sin esto la
   página se quedaría igual que antes de darle al botón. */
.bien {
  margin: 0;
  color: var(--bien);
  background: #16241d;
  border: 1px solid #2a4a3a;
  border-radius: 8px;
  padding: 0.7rem;
  font-size: 0.8125rem;
  white-space: pre-wrap;
  word-break: break-word;
}

footer { color: var(--suave); font-size: 0.8125rem; text-align: center; }
footer p { margin: 0; }
footer a { color: var(--suave); }

/* ----------------------------------------------------------------- galería */

.volver { margin: 0.5rem 0 0; font-size: 0.875rem; }
.volver a { color: var(--acento); }

/* La galería es la única página que gana con más ancho: son miniaturas, y con los
   720 rem del resto caben tres y sobra pantalla a los lados. Solo el `main`: el
   texto de la cabecera y del pie se lee peor estirado a metro y medio. */
body:has(.galeria) main { max-width: 1080px; }

.galeria {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: 0.9rem;
}
/* Lo que ocupa la fila entera —"cargando", "borrada"— o la rejilla lo mete de
   columna y las tarjetas de al lado quedan a media altura. */
.galeria .vacio { grid-column: 1 / -1; }

.vacio { margin: 0; color: var(--suave); font-size: 0.875rem; }
.vacio a { color: var(--acento); }

.gen {
  background: var(--panel);
  border: 1px solid var(--borde);
  border-radius: 12px;
  padding: 0.6rem;
  display: grid;
  gap: 0.4rem;
  align-content: start;
  position: relative;
}

.gen .lienzo { display: block; }
.gen img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 8px;
  background: #0d1016;
  display: block;
}

/* Un audio ocupa el mismo cuadrado que una imagen, con el reproductor centrado
   dentro: sin esto, una locución en medio de la rejilla deja su tarjeta a un
   tercio de alto y descoloca a las de al lado. */
.gen .lienzo-audio {
  aspect-ratio: 1;
  border-radius: 8px;
  background: #0d1016;
  border: 1px solid var(--borde);
  display: flex;
  align-items: center;
  padding: 0.7rem;
}
.gen .lienzo-audio audio { width: 100%; }

/* La pieza en 3D: el mismo cuadrado, con el botón centrado hasta que alguien lo
   pulsa y el lienzo ocupa su sitio. */
.gen .lienzo-pieza {
  aspect-ratio: 1;
  border-radius: 8px;
  background: #0d1016;
  border: 1px solid var(--borde);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.gen .lienzo-pieza canvas {
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
  cursor: grab;
}
.gen .lienzo-pieza canvas:active { cursor: grabbing; }
.gen .ver-pieza {
  background: transparent;
  border: 1px solid var(--borde);
  border-radius: 6px;
  color: var(--texto);
  padding: 0.45rem 0.8rem;
  font: inherit;
  font-size: 0.875rem;
  cursor: pointer;
}
.gen .ver-pieza:hover { border-color: var(--acento); }
.gen .ver-pieza:disabled { cursor: default; opacity: 0.6; }

/* Un vídeo ocupa el mismo cuadrado que una imagen y se recorta igual que ella:
   la rejilla la hacen los cuadrados, y uno apaisado en medio deja un escalón que
   se arrastra hasta el final de la fila. `object-fit: cover` recorta la muestra,
   que es lo que ya hace la imagen de al lado; el original entero está a un clic
   en el propio reproductor. */
.gen .lienzo-video { border-radius: 8px; overflow: hidden; }
.gen .lienzo-video video {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  background: #0d1016;
  display: block;
}

/* Una respuesta de texto ocupa el mismo cuadrado que una imagen, con el principio
   arriba y el resto recortado: en la rejilla se viene a reconocer cuál era, no a
   leerla entera. El desbordamiento se corta y no se hace scrollable a propósito,
   porque 24 cajas con scroll dentro de una página con scroll es imposible de usar
   con la rueda del ratón; para leerla entera está el enlace de descargar. */
.gen .lienzo-texto {
  margin: 0;
  aspect-ratio: 1;
  border-radius: 8px;
  background: #0d1016;
  border: 1px solid var(--borde);
  padding: 0.7rem;
  overflow: hidden;
  font-family: inherit;
  font-size: 0.8125rem;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--suave);
}

/* Las que no dejaron imagen ocupan lo mismo que las que sí: con la altura del
   texto a secas, la rejilla se descuadra en cuanto hay una fallida en medio. */
.gen .nota {
  margin: 0;
  aspect-ratio: 1;
  border-radius: 8px;
  background: #0d1016;
  border: 1px dashed var(--borde);
  padding: 0.7rem;
  display: flex;
  align-items: center;
  font-size: 0.8125rem;
  color: var(--suave);
}
.gen .nota.enMarcha { color: var(--acento); }

/* El prompt se corta a dos líneas: uno de 300 palabras haría una tarjeta de un
   metro y las demás quedarían perdidas debajo. Entero está en el `title`. */
.gen .prompt {
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gen .meta { margin: 0; font-size: 0.75rem; color: var(--suave); }

.acciones { display: flex; gap: 0.4rem; align-items: center; }
.gen .bajar {
  color: var(--acento);
  font-size: 0.8125rem;
  text-decoration: none;
  padding: 0.3rem 0;
}
.gen .bajar:hover { text-decoration: underline; }
.gen .borrar { margin-left: auto; justify-self: auto; }

/* La confirmación tapa la tarjeta entera a posta: mientras está, se ve de cuál se
   está hablando y no se puede pulsar nada de debajo por error. */
.confirmar {
  position: absolute;
  inset: 0;
  background: rgba(15, 17, 21, 0.94);
  border-radius: 12px;
  padding: 0.8rem;
  display: grid;
  gap: 0.6rem;
  align-content: center;
}
.confirmar p { margin: 0; font-size: 0.8125rem; }
.confirmar .acciones { justify-content: center; }
button.peligro {
  background: var(--error);
  color: #2a0d0f;
  padding: 0.45rem 0.8rem;
  font-size: 0.8125rem;
}

#mas { justify-self: center; }

/* --------------------------------------------------------- panel del worker */

.panel {
  background: var(--panel);
  border: 1px solid var(--borde);
  border-radius: 12px;
  padding: 1.25rem;
  display: grid;
  gap: 0.875rem;
}
.panel h2 { margin: 0; font-size: 1rem; font-weight: 600; }
.pie { margin: 0; color: var(--suave); font-size: 0.8125rem; }
.pie a, .panel a { color: var(--acento); }
code { background: #0d1016; border-radius: 4px; padding: 0.1rem 0.3rem; font-size: 0.85em; }

button.secundario {
  background: transparent;
  color: var(--suave);
  border: 1px solid var(--borde);
  justify-self: start;
  padding: 0.4rem 0.8rem;
  font-size: 0.8125rem;
  font-weight: 400;
}

/* Los cuatro números que el proveedor mira de un vistazo: lo ganado, lo hecho,
   su nota y cuánto se ha usado su tarjeta. */
.tarjetas { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.75rem; }
.tarjeta {
  background: var(--panel);
  border: 1px solid var(--borde);
  border-radius: 12px;
  padding: 1rem;
  display: grid;
  gap: 0.15rem;
  align-content: start;
}
.cifra { font-size: 1.75rem; font-weight: 600; line-height: 1.1; }

/* El código de reclamo. Grande, monoespaciado y con las cifras separadas porque
   se lee en una pantalla y se teclea en otra —a veces en el móvil—, y seis
   dígitos pegados en la tipografía del texto se copian mal. */
.codigo {
  margin: 0.75rem 0 0.25rem;
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 2rem;
  font-weight: 600;
  letter-spacing: 0.35em;
  user-select: all;
}

/* Un ejemplo de código para copiar y pegar. `pre` de verdad y no un `code` con
   saltos: en una llamada de `curl` la barra al final de la línea es parte de la
   llamada, y un bloque que reajusta los saltos al ancho de la ventana rompe lo
   que se está copiando. Por eso `overflow-x` y no `wrap`: antes una barra de
   desplazamiento que un comando que no funciona al pegarlo. */
.ejemplo {
  margin: 0;
  background: #0d1016;
  border: 1px solid var(--borde);
  border-radius: 8px;
  padding: 0.8rem 0.9rem;
  overflow-x: auto;
  font-size: 0.8125rem;
  line-height: 1.5;
}
.ejemplo code { background: none; padding: 0; font-size: inherit; }

.etiqueta { color: var(--suave); font-size: 0.8125rem; }
.tarjeta .pie { font-size: 0.75rem; min-height: 1.1em; }

.datos { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 1rem; }
.datos dt { color: var(--suave); font-size: 0.8125rem; }
.datos dd { margin: 0; font-size: 0.875rem; }

.horas { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.horas th, .horas td { padding: 0.4rem 0.5rem; text-align: right; }
.horas th:first-child, .horas td:first-child { text-align: left; }
.horas th {
  color: var(--suave);
  font-weight: 400;
  font-size: 0.75rem;
  border-bottom: 1px solid var(--borde);
}
.horas tbody tr + tr td { border-top: 1px solid #1e222b; }
.horas td:last-child { font-weight: 600; }

/* ------------------------------------------------------- entrar y tu cuenta */

/* Un botón que hace de enlace. Es un <button> y no un <a href="#"> porque lo que
   hace es una acción y no ir a un sitio: así el teclado lo activa con espacio y
   un lector de pantalla no promete una página que no existe. */
button.enlace {
  background: none;
  border: 0;
  padding: 0;
  color: var(--acento);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 400;
  cursor: pointer;
  text-decoration: underline;
}
button.enlace:hover { filter: brightness(1.2); }

/* El formulario de entrar es estrecho a posta: dos campos ocupando 720 px de
   ancho parecen un formulario a medio hacer. La clase va en el <body> y no un
   `:has(#form)`, que en la página de generar también acertaría. */
body.entrar main { max-width: 400px; }
body.entrar #cambiar-linea, body.entrar #regalo { text-align: center; }

/* Entrar con Google y compañía. Los botones son `<a>` y no `<button>` porque lo
   que hacen es irse a otro sitio, así que aquí hay que devolverles a mano el
   aspecto de botón que el navegador solo le da a los de verdad. */
.plataformas { display: grid; gap: 1rem; margin-bottom: 1.25rem; }
.botones-plataformas { display: grid; gap: 0.6rem; }

/* Grises y no del color del acento, al revés que el botón de entrar. Son dos
   caminos para lo mismo y el llamativo tiene que ser uno solo: si los cuatro
   «continuar con» gritan igual que el botón grande, la pantalla no dice qué
   hacer, dice cinco cosas a la vez. */
.boton-plataforma {
  display: block;
  text-align: center;
  text-decoration: none;
  background: var(--panel);
  color: var(--texto);
  border: 1px solid var(--borde);
  border-radius: 8px;
  padding: 0.75rem;
  font-weight: 600;
}
.boton-plataforma:hover { border-color: var(--acento); }

/* El «o con tu correo». La raya sale de dos bordes que crecen a los lados del
   texto, y no de un `<hr>` con el texto encima, para que se parta bien cuando la
   letra sea grande. */
.separador {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  color: var(--suave);
  font-size: 0.8125rem;
}
.separador::before, .separador::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--borde);
}

/* El recado de la vuelta de Google lleva la caja del error pero con más aire
   debajo: lo que hay justo detrás son los botones de plataformas, y sin esto el
   motivo parece la etiqueta del primero. */
.recado { margin-bottom: 1.25rem; }

.tarjetas.dos { grid-template-columns: repeat(2, 1fr); }
.tarjetas.tres { grid-template-columns: repeat(3, 1fr); }

/* Los paquetes de créditos son tarjetas con botón. El botón se pega abajo con un
   margen de más para que no quede colgando del precio: lo que se compara al
   mirar los tres es la cifra de créditos y el importe, y el botón es lo último
   que se toca. */
.tarjeta button { margin-top: 0.6rem; width: 100%; }

.clave-api { margin: 0; display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; }
.clave-api code { word-break: break-all; }

/* La clave recién hecha. Va con el borde del acento y no con el del error: no ha
   pasado nada malo, pero es lo único de la página que hay que hacer ahora o se
   pierde, y tiene que ganarle la mirada a la tabla que hay debajo. */
.aviso-clave {
  border: 1px solid var(--acento);
  border-radius: 8px;
  padding: 0.9rem;
  margin: 1rem 0;
  display: grid;
  gap: 0.6rem;
}
.aviso-clave p { margin: 0; }
.aviso-clave code { font-size: 0.9375rem; }
.aviso-clave button.secundario { justify-self: start; }

/* Una revocada no se va de la lista —su gasto pasado sigue siendo suyo— pero
   tampoco puede seguir pareciendo una clave que vale. */
.claves .revocada { opacity: 0.45; }
.claves .revocada code { text-decoration: line-through; }

/* Los dos topes, uno debajo del otro dentro de la misma celda: son dos cifras
   distintas de la misma cosa —cuánto puede gastar esta clave— y separarlas en dos
   columnas obligaría a leer la cabecera para saber cuál es cuál. */
.claves .topes { min-width: 13rem; }
.claves .topes-dichos { display: flex; flex-direction: column; gap: 0.1rem; }
.claves .tope-dia { font-size: 0.875rem; }
.claves .tope-min { font-size: 0.8125rem; color: var(--suave); }

/* El formulario de cambiar topes ocupa la celda mientras está abierto. Las
   etiquetas se quedan porque «60» y «20» sin nada al lado no dicen si son
   créditos o peticiones, que es justo lo que hay que acertar aquí. */
.claves .topes-forma { display: grid; gap: 0.4rem; }
.claves .topes-forma input { padding: 0.35rem 0.45rem; font-size: 0.875rem; }
.claves .topes-forma label { margin-bottom: 0.15rem; }
.claves .topes-forma button.secundario { justify-self: start; padding: 0.35rem 0.7rem; }

/* En el historial, el concepto y el prompt van uno debajo del otro dentro de la
   misma celda: el prompt es lo que de verdad identifica un cargo —"generación"
   lo son todas—, pero es largo y no puede robarle la columna a la fecha. */
.apuntes td:nth-child(2) { display: flex; flex-direction: column; gap: 0.1rem; }
.apuntes .concepto { font-size: 0.875rem; }
.apuntes .prompt {
  color: var(--suave);
  font-size: 0.75rem;
  /* Una sola línea con puntos suspensivos: el prompt entero está en el `title`
     y en la galería, y aquí lo que se está mirando es la columna de la derecha. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 42ch;
}
.apuntes .suma { color: var(--acento); }
.apuntes .resta { color: var(--suave); }
.apuntes td:last-child { font-variant-numeric: tabular-nums; }

/* -------------------------------------- términos y «dónde va tu prompt»
   Las dos páginas que se leen en vez de usarse. Todo lo de aquí va colgado de
   `body.texto` para que no se cuele en las demás: un `p { line-height: 1.65 }`
   suelto le cambiaría el aire a la página de generar, donde no hay párrafos que
   leer sino etiquetas y pistas de una línea. */

body.texto main { gap: 1rem; }
/* Más aire entre líneas que en el resto del sitio: aquí se leen párrafos
   seguidos, y el 1.5 del `body` es para textos de dos renglones. */
body.texto p { line-height: 1.65; margin: 0; }
body.texto .panel { gap: 0.75rem; }
body.texto a { color: var(--acento); }
/* El <strong> no cambia de color a posta. Lo que va en negrita en estas páginas
   es lo que no se puede pasar por alto, y ya destaca; pintarlo además de otro
   color lo haría parecer un enlace, que es lo único que aquí es de color. */
body.texto strong { font-weight: 600; }

/* El primer bloque de cada una de las dos páginas: lo que hay que haber leído
   aunque no se lea nada más. Un borde a la izquierda y nada de fondo rojo: esto
   no es un error, es el aviso que da sentido al resto. */
.aviso { border-left: 3px solid var(--acento); }

/* El recorrido del prompt, numerado. Va en <ol> y no en <ul> porque el orden es
   el contenido: son pasos, y el cuarto —dónde sale de nuestras manos— solo
   significa algo sabiendo que el tercero ya ha pasado. */
.recorrido, .lista { margin: 0; padding-left: 1.2rem; display: grid; gap: 0.5rem; }
.recorrido { padding-left: 1.4rem; }
.recorrido li, .lista li { line-height: 1.55; }
.recorrido li::marker, .lista li::marker { color: var(--suave); }

/* Qué se guarda y dónde. Es un <dl> porque cada línea es un sitio y su
   explicación; en dos columnas cuando cabe, para poder recorrer los sitios con
   la vista sin leerse las respuestas. */
.guardado { margin: 0; display: grid; grid-template-columns: 12rem 1fr; gap: 0.75rem 1.25rem; }
.guardado dt { font-weight: 600; font-size: 0.9375rem; }
.guardado dd { margin: 0; color: var(--suave); line-height: 1.55; }

/* ------------------------------------------------------ sprites con estilo */

/* Un párrafo que dice de qué va la herramienta antes del formulario. No es un
   adorno: esta pantalla hace algo que no se adivina mirándola —mete N cuadros en
   una sola pasada del modelo— y sin la frase parece un editor de imágenes más. */
.explica {
  margin: 0;
  color: var(--suave);
  line-height: 1.55;
  font-size: 0.9375rem;
}

/* Una etiqueta con su mando en la misma línea, para la casilla de los avanzados
   y para el deslizador de fotogramas por segundo. */
.linea {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 400;
  cursor: pointer;
}
.linea input[type="range"] { flex: 1; }

/* Los tres lienzos de esta página. Lo que traen de serie los <canvas> de
   `figure` es lo que necesita la pieza en 3D: cuadrado, y con el cursor de
   agarrar porque se gira con el dedo. Aquí las tres cosas estorban —una tira de
   ocho cuadros es 8:1 y aquí no se arrastra nada—, así que se deshacen. */
body.sprites figure canvas {
  aspect-ratio: auto;
  cursor: default;
  touch-action: auto;
  /* Y esto es lo único que no es cosmético: un sprite se mira de cerca y el
     suavizado del navegador le come los píxeles justo donde se mira si es pixel
     art. `pixelated` enseña lo que hay, que es de lo que va esta pantalla. */
  image-rendering: pixelated;
}
body.sprites #lienzo-tira, body.sprites #lienzo-salida {
  width: 100%;
  height: auto;
}

/* Los mandos de la pieza en 3D son tres y `.fila` reparte en dos, que dejaría el
   tercero solo en una segunda línea. En pantalla estrecha vuelven a apilarse por
   la regla de `.fila` del final del fichero. */
body.sprites #campo-3d .fila { grid-template-columns: repeat(3, 1fr); }

/* El reproductor va a su tamaño y centrado, no a lo ancho. Estirar un sprite de
   128 px hasta los 640 de la columna es enseñar una animación que no es la que
   se va a usar, y el parpadeo —lo único que esto sirve para ver— se disimula al
   agrandar. El tope existe para que un sprite de 512 no se salga de la columna. */
body.sprites .anima {
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  padding: 0.75rem;
  background: #0d1016;
  border: 1px solid var(--borde);
  border-radius: 8px;
}
body.sprites #lienzo-anima {
  width: auto;
  max-width: 100%;
  background: transparent;
}
body.sprites .anima .linea { width: 100%; max-width: 20rem; }
body.sprites .anima #fps-valor {
  font-size: 0.8125rem;
  color: var(--suave);
  min-width: 4rem;
}

/* ------------------------------------------------- películas de un guion */

/* La lista de planos. Numerada de verdad con <ol>, y no con un contador puesto a
   mano, porque el orden de los planos ES la película: quien use un lector de
   pantalla tiene que oír "elemento 3 de 8" igual que el que lo ve. */
body.peliculas #lista {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.75rem;
  counter-reset: plano;
}
body.peliculas .plano {
  position: relative;
  padding: 0.875rem 0.875rem 0.875rem 3rem;
  background: var(--panel);
  border: 1px solid var(--borde);
  border-radius: 8px;
}
/* El número del plano, grande y a la izquierda. Es lo que se busca con la vista
   cuando algo va mal a mitad de rodaje. */
body.peliculas .plano::before {
  counter-increment: plano;
  content: counter(plano);
  position: absolute;
  left: 0.875rem;
  top: 0.875rem;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--suave);
}
/* Y el estado como una barra de color en el borde: se ve de un vistazo cuál se
   está rodando ahora sin leer nada. */
body.peliculas .plano[data-estado="dibujando"] { border-left: 3px solid #b58900; }
body.peliculas .plano[data-estado="rodando"] { border-left: 3px solid var(--acento); }
body.peliculas .plano[data-estado="hecho"] { border-left: 3px solid var(--bien); }

body.peliculas .plano h3 {
  margin: 0 0 0.5rem;
  font-size: 0.9375rem;
  font-weight: 600;
}
body.peliculas .plano textarea {
  width: 100%;
  font-size: 0.875rem;
}
body.peliculas .plano .pista { display: block; margin: 0.375rem 0 0; }

/* Las puntas del plano y el plano ya rodado, uno al lado del otro cuando caben:
   puestos así se ve de un golpe si el vídeo arrancó y acabó donde tenía, que es
   lo único que hay que comprobar de un plano dirigido por los dos extremos. */
body.peliculas .plano .puntas, body.peliculas .plano .clip {
  display: inline-flex;
  gap: 0.25rem;
  width: calc(50% - 0.25rem);
  min-width: 12rem;
  margin-top: 0.5rem;
  border-radius: 6px;
  background: #0d1016;
  vertical-align: top;
}
/* Y dentro, las dos a medias. `min-width: 0` porque si no el mínimo de 12rem de
   la caja de fuera se lo lleva cada una y el par se sale del plano. */
body.peliculas .plano .fotograma {
  flex: 1;
  min-width: 0;
  border-radius: 6px;
}

body.peliculas #mandos { align-items: center; }
body.peliculas #cine {
  width: 100%;
  border-radius: 8px;
  background: #0d1016;
}

@media (max-width: 720px) {
  .tarjetas { grid-template-columns: repeat(2, 1fr); }
  .apuntes .prompt { max-width: 24ch; }
  /* Dos vídeos de 12rem no caben en un móvil: uno debajo del otro y a lo ancho. */
  body.peliculas .plano .puntas, body.peliculas .plano .clip { width: 100%; }
  /* Con menos de 12rem para el término, la columna de la izquierda parte los
     títulos por la mitad y el <dl> se lee peor que una lista. */
  .guardado { grid-template-columns: 1fr; gap: 0.25rem; }
  .guardado dd { margin-bottom: 0.75rem; }
}

@media (max-width: 520px) {
  .fila { grid-template-columns: 1fr; }
  /* Y los tres de la pieza también, que su regla es más específica que ésta y
     sin repetirla aquí se quedarían en tres columnas de cinco centímetros. */
  body.sprites #campo-3d .fila { grid-template-columns: 1fr; }
  .datos { grid-template-columns: 1fr; gap: 0 0; }
  .datos dd { margin-bottom: 0.5rem; }
}
