/*
 * Room Teachers — una sola hoja, sin framework.
 *
 * Sustituye a la hoja terracota manteniendo todos los nombres de tokens y de
 * componentes: las plantillas Jinja no cambian una línea.
 *
 * Dirección: noche tranquila. Fondo azul-gris profundo, un solo acento
 * blurple usado como línea y como halo, nunca como relleno grande. Los
 * botones primarios son un contorno del acento, no un bloque de color: nada
 * en la pantalla grita antes de que el alumno hable.
 *
 * Tema claro incluido: `prefers-color-scheme` y, si algún día hay un
 * conmutador, `[data-theme="light"]` / `[data-theme="dark"]` en <html>.
 */

/* --- tokens: noche -------------------------------------------------------- */
:root {
  --ink: #e9e9ed;
  --ink-soft: #b2b6ca;
  --ink-faint: #75798c;
  --paper: #161826;
  --card: #232532;
  --line: #3f424d;
  --line-soft: #2b2d38;

  /* Blurple. Suficiente cromatismo para ser un acento sobre rampas
     desaturadas, sin la urgencia del rojo o del naranja. */
  --accent: #9184d9;
  --accent-dark: #b5abfc;   /* el paso más claro: hover y texto sobre el wash */
  --accent-wash: #2b2741;
  --good: #7ec8a2;
  --good-wash: #1d2e2a;
  --warn: #d8b46a;
  --warn-wash: #302b1f;
  --bad: #e08a86;
  --bad-wash: #332229;

  --radius: 8px;
  --radius-sm: 4px;
  --shadow: 0 0 0 1px var(--line), 0 6px 18px rgba(0, 0, 0, .45);
  --font: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Inter", ui-sans-serif, system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --shell: 1080px;
  color-scheme: dark;
}

/* --- tokens: día ---------------------------------------------------------- */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ink: #23252e;
    --ink-soft: #595d6c;
    --ink-faint: #75798c;
    --paper: #f3f5fe;
    --card: #fdfdff;
    --line: #cfd3e5;
    --line-soft: #e4e7f5;

    --accent: #5d5294;
    --accent-dark: #423a6a;
    --accent-wash: #f5f4ff;
    --good: #2f6b50;
    --good-wash: #e9f3ee;
    --warn: #7d5e12;
    --warn-wash: #f7f0dd;
    --bad: #97403c;
    --bad-wash: #f9ebea;

    --shadow: 0 1px 2px rgba(35, 37, 46, .05), 0 8px 24px -14px rgba(35, 37, 46, .22);
    color-scheme: light;
  }
}

:root[data-theme="light"] {
  --ink: #23252e;
  --ink-soft: #595d6c;
  --ink-faint: #75798c;
  --paper: #f3f5fe;
  --card: #fdfdff;
  --line: #cfd3e5;
  --line-soft: #e4e7f5;
  --accent: #5d5294;
  --accent-dark: #423a6a;
  --accent-wash: #f5f4ff;
  --good: #2f6b50;
  --good-wash: #e9f3ee;
  --warn: #7d5e12;
  --warn-wash: #f7f0dd;
  --bad: #97403c;
  --bad-wash: #f9ebea;
  --shadow: 0 1px 2px rgba(35, 37, 46, .05), 0 8px 24px -14px rgba(35, 37, 46, .22);
  color-scheme: light;
}

/* Inter, alojada en el propio servidor. SIL Open Font License. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/inter-var.woff2") format("woff2");
}

* { box-sizing: border-box; }

/* El atributo `hidden` gana a cualquier clase.
   La regla del navegador es `[hidden] { display: none }`, que pierde en
   especificidad contra `.btn { display: inline-flex }` — así que el botón de
   silenciar el micrófono, marcado como oculto hasta que hay clase, se veía
   igualmente desde el primer momento. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

a { color: var(--accent-dark); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: var(--accent-wash); color: var(--ink); }

h1, h2, h3 { font-family: var(--font-display); font-weight: 500; line-height: 1.18; margin: 0; }
h1 { font-size: 2rem; letter-spacing: -.02em; }
h2 { font-size: 1.3rem; letter-spacing: -.01em; }
h3 { font-size: 1rem; font-weight: 600; }

p { margin: 0 0 .8em; }
p:last-child { margin-bottom: 0; }

/* --- shell ---------------------------------------------------------------- */
/* Opaque by default, translucent only where the blur behind it really works.
   The other way round — translucent always, blurred if supported — is what it
   used to be, and it meant that anywhere `backdrop-filter` does not apply the
   header was simply 85% opaque over a scrolling page. On `/admin/usuarios`
   and `/admin/mensajes` the row of pills sits exactly where the bar ends up
   after a small scroll, so it showed through and read as a second row of tabs
   riding on the first.

   Not a rare configuration: Safari before 18 needs the `-webkit-` prefix,
   which was missing, and Firefox drops the effect whenever hardware
   acceleration is off. */
.topbar {
  background: var(--paper);
  border-bottom: 1px solid var(--line-soft);
  position: sticky;
  top: 0;
  z-index: 20;
}
@supports ((backdrop-filter: blur(10px)) or (-webkit-backdrop-filter: blur(10px))) {
  .topbar {
    background: color-mix(in srgb, var(--paper) 85%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
}
.topbar__inner {
  max-width: var(--shell);
  margin: 0 auto;
  padding: .7rem 1.25rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
}
.brand {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 500;
  letter-spacing: -.01em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.brand span { color: var(--accent); }
.nav { display: flex; gap: 1.1rem; flex: 1; flex-wrap: wrap; }
.nav a {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: .92rem;
  font-weight: 500;
  padding: .2rem 0;
  border-bottom: 1px solid transparent;
}
.nav a:hover { color: var(--ink); }
.nav a.is-active { color: var(--ink); border-bottom-color: var(--accent); }

.wallet {
  display: inline-flex;
  align-items: baseline;
  gap: .35rem;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent-dark);
  background: transparent;
  border-radius: 999px;
  padding: .28rem .8rem;
  font-size: .87rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  white-space: nowrap;
}
.wallet:hover { background: var(--accent-wash); color: var(--accent-dark); }

/* El nav va vacío en la cabecera pública; sin esto se queda ocupando el hueco
   flexible y empuja los enlaces de acceso contra el borde. */
.nav:empty { display: none; }
.topbar__link {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: .92rem;
  font-weight: 500;
  white-space: nowrap;
}
.topbar__link:hover { color: var(--ink); }
.wallet small { font-weight: 400; color: var(--ink-faint); }

main { max-width: var(--shell); margin: 0 auto; padding: 2.2rem 1.25rem 5rem; }
.page-head { margin-bottom: 1.5rem; }
.page-head p { color: var(--ink-soft); max-width: 62ch; }
/* Entregado como `.page-head .kicker`; generalizado porque el hero de la
   clase en vivo lleva el mismo sobretítulo y no está dentro de `.page-head`.
   El descendiente era un subconjunto de esto, así que nada pierde estilo. */
.kicker {
  font-size: .76rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: .35rem;
}

/* --- cards ---------------------------------------------------------------- */
.card {
  background: var(--card);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 1.25rem;
  box-shadow: var(--shadow);
}
/* Separación entre bloques apilados uno tras otro.

   Una rejilla y una tarjeta son la misma cosa en el flujo de la página: un
   bloque con una caja alrededor. `.card + .card` a secas se dejaba fuera
   todos los pares en los que uno de los dos era una rejilla, y una rejilla
   seguida de una tarjeta salía a cero: dos cajas sin hueco se leen como una
   sola con una raya en medio. Pasaba en las fichas de `/admin/usuarios/{id}`
   y `/admin/mensajes/{id}`, que son las pantallas que alternan las dos. */
.card + .card,
.card + .grid,
.grid + .card,
.grid + .grid { margin-top: 1rem; }
/* …pero no dentro de una rejilla, que ya separa con `gap`. Ahí el margen
   empujaba hacia abajo a toda tarjeta que no fuera la primera y, como la
   fila se estira a la misma altura, la dejaba además 16 px más baja: dos
   cajas contiguas con distinto alto y desalineadas. */
.grid > .card + .card { margin-top: 0; }
.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: .9rem;
  flex-wrap: wrap;
}
.card--flat { box-shadow: none; background: transparent; }
.card--accent {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
              0 10px 30px -18px var(--accent);
}

.grid { display: grid; gap: 1rem; }
/* `min(Xpx, 100%)` y no `Xpx` a secas: un `minmax` con mínimo fijo no baja
   de él, así que en una pantalla más estrecha que el mínimo la rejilla se
   sale del viewport en lugar de pasar a una columna. */
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr)); }
.stack > * + * { margin-top: 1rem; }

.muted { color: var(--ink-soft); }
.faint { color: var(--ink-faint); font-size: .86rem; }
.tiny { font-size: .8rem; }

/* --- buttons -------------------------------------------------------------- */
/* El primario es un contorno del acento. Un bloque de color saturado en la
   pantalla previa a hablar añade presión donde no hace falta. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  min-height: 44px;
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  padding: .5rem 1.05rem;
  font: inherit;
  font-weight: 500;
  font-size: .93rem;
  cursor: pointer;
  text-decoration: none;
  background: transparent;
  color: var(--accent-dark);
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.btn:hover {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-dark);
  border-color: var(--accent-dark);
}
.btn:active { background: color-mix(in srgb, var(--accent) 26%, transparent); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .45;
  cursor: not-allowed;
  background: transparent;
  border-color: var(--line);
  color: var(--ink-faint);
}
.btn--ghost { border-color: var(--line); color: var(--ink); }
.btn--ghost:hover { background: var(--line-soft); border-color: var(--ink-faint); color: var(--ink); }
.btn--danger { border-color: color-mix(in srgb, var(--bad) 60%, transparent); color: var(--bad); }
.btn--danger:hover { background: var(--bad-wash); border-color: var(--bad); color: var(--bad); }
.btn--big { min-height: 56px; padding: .9rem 1.7rem; font-size: 1.05rem; }
.btn--block { width: 100%; }

/* Entrar con Google. Neutro a propósito: el botón de un tercero no compite
   con el primario de la pantalla, que es el formulario de al lado. El
   logotipo lleva sus cuatro colores porque las normas de marca de Google no
   permiten repintarlo, así que el fondo se queda en el papel de la tarjeta
   para que se vea igual de bien en claro y en oscuro. */
.btn--google {
  min-height: 48px;
  border-color: var(--line);
  color: var(--ink);
  background: var(--paper);
  gap: .6rem;
}
.btn--google:hover { background: var(--line-soft); border-color: var(--ink-faint); color: var(--ink); }
.btn--google svg { flex: none; }

/* La raya con la «o» en medio. Marca que son dos caminos al mismo sitio y no
   dos pasos del mismo. */
.or {
  display: flex;
  align-items: center;
  gap: .8rem;
  margin: 1.1rem 0;
  color: var(--ink-faint);
  font-size: .84rem;
}
.or::before, .or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-soft);
}

.actions { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }

/* --- forms ---------------------------------------------------------------- */
label { display: block; font-weight: 500; font-size: .89rem; margin-bottom: .32rem; color: var(--ink); }
.field { margin-bottom: 1.05rem; }
.field .hint { font-size: .84rem; color: var(--ink-faint); margin-top: .35rem; }
.field--error label { color: var(--bad); }
.field--error input, .field--error select, .field--error textarea { border-color: var(--bad); }
.field--error .hint { color: var(--bad); }

/* `input:not([type])` incluido a propósito: un input sin atributo `type` es
   de texto para el navegador pero no lo casa `input[type="text"]`, así que se
   quedaba sin ancho ni borde. Pasaba en cuatro sitios, uno de ellos el nombre
   en el formulario de registro. */
input:not([type]),
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="search"], select, textarea {
  width: 100%;
  min-height: 44px;
  padding: .6rem .75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--paper) 60%, var(--card));
  font: inherit;
  color: var(--ink);
}
::placeholder { color: var(--ink-faint); }
textarea { min-height: 84px; resize: vertical; }
input:hover, select:hover, textarea:hover { border-color: var(--ink-faint); }
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
input[disabled], select[disabled], textarea[disabled] { opacity: .45; cursor: not-allowed; }

/* Radios como tarjetas: una fila de radios de verdad se lee como un
   formulario que rellenar, no como una elección que tomar. */
.choices { display: flex; gap: .5rem; flex-wrap: wrap; }
.choice { position: relative; }
.choice input { position: absolute; opacity: 0; inset: 0; cursor: pointer; }
.choice span {
  display: flex;
  align-items: center;
  gap: .4rem;
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: .5rem .9rem;
  font-size: .92rem;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease, color .14s ease;
}
.choice input:hover + span { border-color: var(--ink-faint); color: var(--ink); }
.choice input:checked + span {
  border-color: var(--accent);
  background: var(--accent-wash);
  color: var(--accent-dark);
}
.choice input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.choice input[disabled] + span { opacity: .45; cursor: not-allowed; }

/* --- messages ------------------------------------------------------------- */
.alert {
  border-radius: var(--radius-sm);
  padding: .75rem 1rem;
  font-size: .92rem;
  margin-bottom: 1.1rem;
  border: 1px solid transparent;
  border-left-width: 2px;
}
.alert--bad { background: var(--bad-wash); border-color: color-mix(in srgb, var(--bad) 35%, transparent); border-left-color: var(--bad); color: var(--ink); }
.alert--good { background: var(--good-wash); border-color: color-mix(in srgb, var(--good) 32%, transparent); border-left-color: var(--good); color: var(--ink); }
.alert--warn { background: var(--warn-wash); border-color: color-mix(in srgb, var(--warn) 32%, transparent); border-left-color: var(--warn); color: var(--ink); }
.alert--info { background: var(--accent-wash); border-color: color-mix(in srgb, var(--accent) 32%, transparent); border-left-color: var(--accent); color: var(--ink); }

/* --- pills and stats ------------------------------------------------------ */
.pill {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  border-radius: 999px;
  padding: .16rem .62rem;
  font-size: .76rem;
  font-weight: 500;
  letter-spacing: .01em;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink-soft);
  white-space: nowrap;
}
.pill--live { border-color: color-mix(in srgb, var(--good) 50%, transparent); color: var(--good); }
.pill--pending { border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--warn); }
.pill--bad { border-color: color-mix(in srgb, var(--bad) 45%, transparent); color: var(--bad); }
.pill--level { border-color: color-mix(in srgb, var(--accent) 50%, transparent); color: var(--accent-dark); }

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); gap: .7rem; }
.stat {
  background: transparent;
  border: 0;
  border-left: 1px solid var(--line-soft);
  border-radius: 0;
  padding: .1rem .85rem;
}
.stat__label { font-size: .74rem; color: var(--ink-faint); font-weight: 500; text-transform: uppercase; letter-spacing: .07em; }
.stat__value { font-size: 1.9rem; font-weight: 400; font-variant-numeric: tabular-nums; letter-spacing: -.02em; line-height: 1.1; }
.stat__delta { font-size: .8rem; font-weight: 500; font-variant-numeric: tabular-nums; }
.up { color: var(--good); }
.down { color: var(--bad); }
.flat { color: var(--ink-faint); }

/* Medidores. El número solo no dice nada; la barra es lo que hace que 62 y 84
   se sientan distintos de un vistazo. */
.meter { margin-bottom: .8rem; }
.meter__top { display: flex; justify-content: space-between; font-size: .88rem; margin-bottom: .3rem; }
.meter__top strong { font-weight: 500; font-variant-numeric: tabular-nums; }
.meter__track { height: 4px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.meter__fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
  transition: width .4s ease;
}

/* The microphone level. Not a `.meter`: this one is redrawn every frame while
   somebody talks, and `.meter__fill`'s .4s transition would turn a level into
   a lag. Taller too — it is the thing being watched, not a figure's footnote. */
.mic-level { height: 10px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.mic-level__bar { display: block; height: 100%; width: 0; border-radius: 999px; background: var(--good); }
.mic-check ul { margin: .45rem 0 0; padding-left: 1.15rem; }
.mic-check li + li { margin-top: .3rem; }
.mic-check .alert :last-child { margin-bottom: 0; }
.mic-check .alert p { margin: .5rem 0 0; }

/* --- sparkline ------------------------------------------------------------ */
.spark { display: block; width: 100%; height: 56px; overflow: visible; }
.spark__line { fill: none; stroke: var(--accent); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.spark__area { fill: color-mix(in srgb, var(--accent) 14%, transparent); }
.spark__dot { fill: var(--accent-dark); }

/* --- lists and tables ----------------------------------------------------- */
.list { list-style: none; margin: 0; padding: 0; }
.list > li { padding: .7rem 0; border-bottom: 1px solid var(--line-soft); }
.list > li:last-child { border-bottom: 0; padding-bottom: 0; }
.list > li:first-child { padding-top: 0; }

.row { display: flex; gap: 1rem; align-items: center; justify-content: space-between; }
.row__main { min-width: 0; }
.row__title { font-weight: 500; }

table { width: 100%; border-collapse: collapse; font-size: .91rem; }
th, td { text-align: left; padding: .6rem .6rem; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
th { font-size: .74rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-faint); font-weight: 500; }
tbody tr:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
.table-wrap { overflow-x: auto; }

/* 360 px: la tabla de correcciones deja de ser tabla y pasa a ser una lista
   de fichas, con el nombre de la columna delante de cada valor. */
@media (max-width: 560px) {
  .table--cards thead { display: none; }
  .table--cards tr {
    display: block;
    border: 1px solid var(--line-soft);
    border-radius: var(--radius-sm);
    padding: .6rem .7rem;
    margin-bottom: .6rem;
  }
  .table--cards td { display: flex; gap: .6rem; border: 0; padding: .18rem 0; }
  .table--cards td::before {
    content: attr(data-label);
    flex: 0 0 6.5rem;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--ink-faint);
    padding-top: .2rem;
  }
  .table--cards td:empty { display: none; }
}

/* --- badges --------------------------------------------------------------- */
.badges { display: flex; flex-wrap: wrap; gap: .6rem; }
.badge {
  display: flex;
  gap: .55rem;
  align-items: flex-start;
  background: transparent;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: .6rem .8rem;
  max-width: 260px;
}
.badge__icon { font-size: 1.25rem; line-height: 1.2; filter: saturate(.75); }
.badge__title { font-weight: 500; font-size: .9rem; }

/* --- lesson-specific ------------------------------------------------------ */
/* La pantalla más emocional del producto: mucho aire, un halo del acento
   detrás del botón, ningún borde duro, ninguna cuenta atrás. */
.live-hero {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 0%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
    linear-gradient(180deg, var(--card), var(--paper));
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 3.2rem 2rem;
  text-align: center;
}
.live-hero h1 { font-size: 2.1rem; margin-bottom: .4rem; }
.live-hero p { color: var(--ink-soft); }
/* `:not(.btn--ghost)` porque el hero lleva dos botones grandes — «Entrar» y
   «Terminar» — y sin esto el descendiente gana en especificidad al modificador
   ghost: los dos salían con el halo del acento y con el mismo peso visual,
   que es justo lo contrario de lo que la jerarquía quiere decir. */
.live-hero .btn--big:not(.btn--ghost) {
  border-color: var(--accent);
  color: var(--accent-dark);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent) 10%, transparent);
}
.live-hero .btn--big:not(.btn--ghost):hover {
  box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent) 14%, transparent);
}

/* La clase dada en esta misma página, sobre una conexión directa con el
   worker. Sin Jitsi no hay ventana de videollamada, así que lo único que se
   ve del profesor es su voz: el orbe late con ella. */
.live-room { margin-top: 1.4rem; }
.live-room__status { min-height: 1.4em; margin: .9rem 0 1.1rem; color: var(--ink-soft); }
.live-room audio { display: none; }

/* `--level` lo escribe el analizador de audio de la página, 0 a 1. Sin JS se
   queda en 0 y el orbe es un círculo quieto, que es exactamente lo que debe
   verse mientras el profesor calla. */
.orb {
  --level: 0;
  width: 96px; height: 96px;
  margin: 0 auto;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%,
    color-mix(in srgb, var(--accent) 85%, white),
    var(--accent) 60%,
    color-mix(in srgb, var(--accent) 70%, black));
  transform: scale(calc(1 + var(--level) * 0.18));
  box-shadow: 0 0 calc(18px + var(--level) * 40px)
              color-mix(in srgb, var(--accent) calc(25% + var(--level) * 45%), transparent);
  /* Corto: por debajo de la duración de una sílaba, o el orbe va por detrás
     de la voz y parece otra cosa. */
  transition: transform .08s linear, box-shadow .08s linear;
}
@media (prefers-reduced-motion: reduce) {
  .orb { transform: none; transition: none; }
}

.dot {
  display: inline-block;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--good);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 55%, transparent);
  margin-right: .45rem;
  animation: pulse 2.4s ease-out infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 50%, transparent); }
  70% { box-shadow: 0 0 0 9px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .dot { animation: none; }
  .meter__fill { transition: none; }
}

/* La frase «¿qué acaba de decir?». Se lee como una cita, porque es lo que es. */
.repeat {
  margin: 0;
  padding: .85rem 1.1rem;
  border-left: 2px solid var(--accent);
  background: var(--accent-wash);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.repeat__who {
  display: block;
  font-size: .72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--accent-dark);
  margin-bottom: .25rem;
}
.repeat p { font-size: 1.1rem; line-height: 1.5; }

.correction { font-family: var(--mono); font-size: .87rem; }
.correction del { color: var(--bad); text-decoration-thickness: 1px; }
.correction ins { color: var(--good); text-decoration: none; }

.exercise { border: 1px solid var(--line-soft); border-radius: var(--radius); padding: 1.1rem; background: var(--card); }
.exercise + .exercise { margin-top: .9rem; }
.exercise__prompt { font-size: 1.05rem; margin-bottom: .8rem; }
.exercise__prompt code { background: var(--line-soft); padding: .1rem .35rem; border-radius: var(--radius-sm); font-family: var(--mono); }

.chunks { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .8rem; }
.chunk {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: .4rem .7rem;
  background: transparent;
  color: var(--ink);
  cursor: grab;
  font-size: .92rem;
  user-select: none;
}
.chunk.is-picked { background: var(--accent-wash); border-color: var(--accent); color: var(--accent-dark); }

/* --- misc ----------------------------------------------------------------- */
.auth-shell { max-width: 400px; margin: 4rem auto; }
.center { text-align: center; }
.empty { text-align: center; padding: 2.6rem 1rem; color: var(--ink-soft); }
.empty h3 { margin-bottom: .4rem; color: var(--ink); }

hr { border: 0; border-top: 1px solid var(--line-soft); margin: 1.2rem 0; }

/* --- HTMX ----------------------------------------------------------------- */
/* Los fragmentos que se cambian solos no deben saltar. Reservan su altura y
   entran con un fundido corto; el spinner ocupa sitio siempre. */
.spinner { display: inline-block; visibility: hidden; }
.htmx-request .spinner, .htmx-request.spinner { visibility: visible; }
[hx-get], [hx-post] { transition: opacity .12s ease; }
.htmx-request[hx-get], .htmx-request[hx-post] { opacity: .6; }
.htmx-settling { opacity: 0; }
.htmx-added { opacity: 0; }
[hx-swap] > * { animation: fade-in .18s ease both; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
#repeat { min-height: 5.2rem; }

@media (max-width: 640px) {
  h1 { font-size: 1.55rem; }
  main { padding: 1.25rem 1rem 4rem; }
  /* Dos filas: marca y saldo arriba, navegación y salida abajo.
     `flex-wrap` es lo que permite que el nav baje de línea — sin él su
     `order` no tiene a dónde ir y los enlaces se apilan cortados contra el
     borde. El nav no envuelve, se desplaza: cinco enlaces en tres filas
     convierten la cabecera en media pantalla. */
  .topbar__inner { gap: .6rem .8rem; flex-wrap: wrap; }
  /* Salto de línea forzado entre el saldo y la navegación. `order` sola no
     basta: ordena, pero no parte, y un nav que puede encogerse acaba
     apretujado al final de la primera fila en lugar de abrir la segunda. */
  .topbar__inner::after {
    content: "";
    flex-basis: 100%;
    height: 0;
    order: 2;
  }
  .nav {
    gap: .9rem;
    order: 3;
    /* `1 1 0` con `min-width: 0`: sin los dos, un elemento flex no baja de
       su ancho de contenido, el nav se queda con la línea entera y «Salir»
       cae a una tercera fila. Con ellos, el nav toma lo que sobra y hace
       scroll por dentro. */
    flex: 1 1 0;
    min-width: 0;
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav a { white-space: nowrap; }
  .topbar__inner form { order: 4; }
  .live-hero { padding: 2.2rem 1.1rem; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .btn--block-mobile { width: 100%; }
}

/* --- landing -------------------------------------------------------------- */
/* La única página que ve alguien sin cuenta. Construida con los componentes
   del producto: enseñar el sitio al que vas es más honesto que una foto de
   archivo, y además no puede prometer una pantalla que no existe. */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
  gap: 2.5rem;
  align-items: center;
  padding: 1rem 0 2.5rem;
}
.hero h1 { font-size: clamp(2rem, 4.4vw, 2.9rem); line-height: 1.1; margin-bottom: .9rem; }
.hero__lead { color: var(--ink-soft); font-size: 1.1rem; max-width: 46ch; margin-bottom: 1.4rem; }
.hero__demo .live-hero { padding: 2.2rem 1.5rem; }
/* La maqueta es decorativa: no debe poder recibir el foco al tabular. */
.hero__demo .btn { pointer-events: none; }

/* Dos columnas anchas. `grid--2` reparte en tres a este ancho y deja los
   cuatro pasos en 3 + 1, que se lee como si el último sobrara. */
.grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); }

main > section + section { margin-top: 3.5rem; }
main > section > .page-head h2 { font-size: 1.6rem; }

@media (max-width: 860px) {
  .hero { grid-template-columns: 1fr; gap: 1.8rem; padding-bottom: 1.5rem; }
  .hero__demo { order: -1; }
  main > section + section { margin-top: 2.5rem; }
}

/* --- legal pages and the account screen ---------------------------------- */

/* A card that is about to do something irreversible. Only the border and the
   heading are tinted: a whole red panel reads as an error that has already
   happened rather than a choice still being offered. */
.card--danger { border-color: color-mix(in srgb, var(--bad) 40%, var(--line)); }
.card--danger .card__head h2 { color: var(--bad); }

/* Two-column key/value, for the identity blocks on the legal pages and the
   account summary. Deliberately not `.table--cards`: these are two or three
   rows of plain facts, not a data table that needs to collapse. */
.table--plain th {
  width: 34%;
  text-align: left;
  font-weight: 600;
  color: var(--ink-soft);
  vertical-align: top;
  padding: .42rem .8rem .42rem 0;
}
.table--plain td { padding: .42rem 0; vertical-align: top; }
@media (max-width: 520px) {
  /* At this width a 34% label column leaves the value four characters wide. */
  .table--plain th, .table--plain td { display: block; width: auto; padding: .1rem 0; }
  .table--plain th { padding-top: .55rem; }
}

/* Long-form legal text. The rest of the app is dashboards and forms, so
   nothing existed for a page that is simply read top to bottom. */
.prose { max-width: 68ch; }
.prose h2 { font-size: 1.05rem; margin: 1.9rem 0 .6rem; }
.prose h2:first-child { margin-top: 0; }
.prose p, .prose ul, .prose table { margin-bottom: .9rem; }
.prose ul { padding-left: 1.15rem; }
.prose li { margin-bottom: .4rem; }
.prose li::marker { color: var(--ink-faint); }

/* The footer. Muted on purpose — it is a legal requirement and a way out, not
   part of the pitch. */
.footer {
  border-top: 1px solid var(--line);
  margin-top: 3rem;
  padding: 1.4rem 0 2rem;
}
.footer__inner {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: .6rem 1.4rem;
  align-items: center;
  justify-content: space-between;
  font-size: .85rem;
  color: var(--ink-faint);
}
.footer__brand { font-weight: 600; }
.footer__links { display: flex; flex-wrap: wrap; gap: .5rem 1.1rem; }
.footer__links a { color: var(--ink-faint); text-decoration: none; }
.footer__links a:hover { color: var(--ink); text-decoration: underline; }

/* The beta mark, next to the brand.

   An `<em>` inside the brand link rather than a sibling, so it travels with
   the logo and cannot end up on its own line when the topbar wraps — which is
   exactly what the nav did on a 390px screen before it was pinned down. */
.brand .beta {
  font-style: normal;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  vertical-align: .35em;
  margin-left: .4rem;
  padding: .1rem .34rem;
  border-radius: 5px;
  color: var(--accent);
  background: var(--accent-wash);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
}

/* --- the live transcript -------------------------------------------------- */

/* A `.pill` used as a control rather than a label. The element has to be a
   button — it performs an action and belongs in the tab order — and buttons
   arrive with a font and a background of their own. */
button.pill { cursor: pointer; font: inherit; font-size: .8rem; background: transparent; }
button.pill:hover { border-color: var(--accent); color: var(--accent-dark); }

/* Fixed height and its own scroll: an hour of conversation cannot be allowed
   to push the "Terminar" button off the bottom of the page. */
.transcript {
  max-height: 20rem;
  overflow-y: auto;
  padding: .9rem 1rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper);
  font-size: .92rem;
}
.transcript__line { margin: 0 0 .55rem; }
.transcript__line:last-child { margin-bottom: 0; }
.transcript__who {
  display: inline-block;
  min-width: 4.5rem;
  margin-right: .4rem;
  font-size: .78rem;
  font-weight: 600;
  color: var(--ink-faint);
}
.transcript__line--teacher .transcript__who { color: var(--accent); }
@media (max-width: 520px) {
  /* Two words of label and four of speech is not a readable line. */
  .transcript__who { display: block; min-width: 0; margin-bottom: .1rem; }
}

/* El titular de la maqueta del hero. Era un `h2` de verdad y entraba en el
   esquema de encabezados como si fuese una sección de la página. */
.demo__title { font-size: 1.5rem; font-weight: 600; margin: 0 0 .3rem; }

/* --- the help button ------------------------------------------------------ */

/* Bottom right, over everything, on every signed-in screen.

   A link and not a button: it goes to a page, so middle-click, "open in new
   tab" and a right-click menu all have to work. Styled as a circle with the
   label beside it on wide screens and the icon alone on narrow ones, where
   50px of thumb matters more than a word.

   `position: fixed` puts it outside the flow, which means nothing below it
   knows it is there — hence the footer padding further down. */
.help-fab {
  position: fixed;
  right: 1.1rem;
  bottom: 1.1rem;
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .7rem 1.05rem .7rem .85rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  font-size: .85rem;
  font-weight: 600;
  text-decoration: none;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
  transition: transform .12s ease, border-color .12s ease, color .12s ease;
}
.help-fab:hover {
  border-color: var(--accent);
  color: var(--accent-dark);
  transform: translateY(-1px);
}
/* Keyboard focus has to be obvious on something floating over the page: the
   ring is the only thing telling somebody where they are. */
.help-fab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.help-fab svg { width: 18px; height: 18px; flex: none; }

/* Room for it. Fixed elements do not reserve space, so without this the
   button sits on top of the last line of the footer on a short page. */
.footer { padding-bottom: 5rem; }

@media (max-width: 640px) {
  /* Icon only. The label is the first thing to go: on a phone this competes
     with the content for the same corner, and a circle reads as help without
     needing to say so. */
  .help-fab { padding: 0; width: 48px; height: 48px; justify-content: center; }
  .help-fab span { display: none; }
  .help-fab svg { width: 20px; height: 20px; }
}

@media (prefers-reduced-motion: reduce) {
  .help-fab { transition: none; }
  .help-fab:hover { transform: none; }
}

/* Printing a report or an invoice should not print a button. */
@media print {
  .help-fab { display: none; }
}
