/* ===========================================================================
   GAIA Studio — components.css
   Tarjetas, botones, formularios, badges, tablas, alertas.

   Criterio visual: mucho aire, borde fino (var(--bw), 1px) y nada de sombras
   difusas. La marca es serena — un estudio de pilates, no un tablero de
   control — así que la jerarquía la dan el espacio y la tipografía, no el peso.
   Se lee igual en el celular a plena luz en la banqueta y en modo noche a las
   6 a. m. antes de la primera clase.
   =========================================================================== */

/* ── Tarjetas ─────────────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  padding: 1.125rem;
}
.card-pad-sm { padding: .75rem; }
.card-pad-lg { padding: 1.5rem; }

.card-head {
  display: flex; align-items: center; gap: .625rem;
  margin: -.25rem 0 .875rem;
}
.card-head h2, .card-head h3 { margin: 0; }
.card-body > :last-child { margin-bottom: 0; }

/* Tarjeta con acento del contexto: barra superior del color del rol */
.card-accent { border-top: 4px solid var(--accent); }
.card-hover { transition: border-color .15s, transform .15s; }
.card-hover:hover { border-color: var(--accent); transform: translateY(-2px); }

/* Tarjeta de bienvenida del dashboard */
.card-welcome {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* ── Botones ──────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  padding: .6875rem 1.125rem;
  border: var(--bw) solid transparent;
  border-radius: var(--radius-sm);
  /* Versalitas espaciadas: es el mismo gesto tipográfico del logo y lo único
     que va en mayúsculas en toda la interfaz, junto con los badges. */
  font-weight: 600; font-size: .8125rem; line-height: 1.2;
  text-transform: uppercase; letter-spacing: .1em;
  text-decoration: none; white-space: nowrap;
  /* 44px: el mínimo táctil que se puede tocar sin fallar en un celular */
  min-height: 44px;
  transition: background-color .15s, border-color .15s, color .15s, transform .06s;
  user-select: none;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn.is-disabled { opacity: .5; cursor: not-allowed; transform: none; }

.btn-primary {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}
.btn-primary:hover:not(:disabled) { filter: brightness(1.08); }

.btn-outline {
  background: transparent; color: var(--accent); border-color: var(--accent);
}
.btn-outline:hover:not(:disabled) { background: var(--accent-soft); }

.btn-ghost { background: transparent; color: var(--text); border-color: var(--border); }
.btn-ghost:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }

.btn-danger { background: var(--danger); color: #fff; border-color: var(--danger); }
.btn-danger:hover:not(:disabled) { filter: brightness(1.08); }

.btn-plain { background: transparent; color: var(--muted); border-color: transparent; }
.btn-plain:hover:not(:disabled) { color: var(--text); background: var(--surface-2); }

.btn-sm  { padding: .4375rem .75rem; font-size: .8125rem; min-height: 36px; }
.btn-lg  { padding: .875rem 1.5rem; font-size: 1.0625rem; min-height: 52px; }
.btn-block { display: flex; width: 100%; }
.btn-icon { padding: .5rem; min-height: 44px; min-width: 44px; }

/* Botón cargando: el texto se atenúa y aparece un spinner */
.btn.is-loading { pointer-events: none; position: relative; color: transparent !important; }
.btn.is-loading::after {
  content: ''; position: absolute; width: 18px; height: 18px;
  border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
  animation: spin .6s linear infinite;
  color: var(--accent-ink);
}
.btn-outline.is-loading::after, .btn-ghost.is-loading::after { color: var(--accent); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Formularios ──────────────────────────────────────────────────────────── */
.field { margin-bottom: 1rem; }
.label {
  display: block; margin-bottom: .375rem;
  font-weight: 600; font-size: .875rem; color: var(--text);
}
.label .req { color: var(--danger); margin-left: .125rem; }
.label .opt { color: var(--muted); font-weight: 400; margin-left: .25rem; font-size: .8125rem; }

.input, .select, .textarea {
  width: 100%;
  padding: .6875rem .875rem;
  background: var(--surface);
  color: var(--text);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius-sm);
  min-height: 46px;
  transition: border-color .15s, box-shadow .15s;
  appearance: none;
}
.input::placeholder, .textarea::placeholder { color: var(--muted); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.input:disabled, .select:disabled, .textarea:disabled {
  background: var(--surface-2); color: var(--muted); cursor: not-allowed;
}
.textarea { min-height: 110px; resize: vertical; line-height: 1.5; }

.select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .75rem center;
  background-size: 16px;
  padding-right: 2.25rem;
}

.field.has-error .input, .field.has-error .select, .field.has-error .textarea {
  border-color: var(--danger);
}
.field-error { display: block; margin-top: .3125rem; font-size: .8125rem; color: var(--danger); font-weight: 500; }
.field-hint  { display: block; margin-top: .3125rem; font-size: .8125rem; color: var(--muted); }

/* Input con prefijo (lada telefónica, etc.) */
.input-group { display: flex; }
.input-group > .select { width: auto; flex: 0 0 auto; border-radius: var(--radius-sm) 0 0 var(--radius-sm); border-right: 0; }
/* min-width:0 es obligatorio aquí: por defecto un elemento flex no encoge por
   debajo de su min-content, y el min-content de un <input> lo fija su atributo
   `size` (~180px). Sumado al select de la lada, que no encoge, el grupo se salía
   de la pantalla en celulares de 320px. */
.input-group > .input  { min-width: 0; flex: 1 1 auto; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* Botón pegado al campo (ojo de la contraseña, "copiar", "probar"…).
   Vive AQUÍ y no en auth.css porque lo usan también /super/whatsapp,
   /super/correo y /super/almacenamiento, que no cargan el CSS del acceso: sin
   esta regla ese botón salía sin estilo en las tres pantallas. */
.input-addon {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 48px;                      /* tocable con el pulgar */
  padding: 0 .75rem;
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-left: 0;
  border-top-right-radius: var(--radius-sm);
  border-bottom-right-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
  transition: color .15s;
}
.input-addon:hover { color: var(--text); }
.input-addon[aria-pressed="true"] { color: var(--accent); }

/* Variante ENMARCADA: el borde lo dibuja el grupo, no cada pieza, para que un
   icono guía y un botón quepan dentro del mismo recuadro y se lean como un solo
   control. Va como modificador y NO sobre `.input-group` a secas a propósito: el
   grupo normal lo usa la lada + celular del registro y del perfil, donde cada
   pieza SÍ pinta su borde — dárselo a todos dejaba ese campo con doble marco. */
.input-group.-framed {
  align-items: stretch;
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius-sm);
  transition: border-color .15s, box-shadow .15s;
}
.input-group.-framed > .input {
  background: transparent;
  border: 0;
  border-radius: 0;
}
/* El campo deja de dibujar su propio foco: lo dibuja el marco entero, para que
   el anillo rodee también al icono y al botón. */
.input-group.-framed > .input:focus,
.input-group.-framed > .input:focus-visible { outline: none; box-shadow: none; }
.input-group.-framed:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.input-group.-framed > .input-addon {
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
}

/* ── Lada + número ────────────────────────────────────────────────────────
   Vive AQUÍ y no en cada módulo: lo usan el registro, el perfil y el contacto
   de emergencia del cuestionario de salud. Estaba copiado, idéntico, en dos
   archivos; el tercero habría sido el que se quedara atrás el día que alguien
   ajuste el ancho. */
.input-group .phone-code {
  flex: 0 0 7.5rem;
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
  border-right: 0;
}
.input-group .phone-code + .input {
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  border-left: var(--bw) solid var(--border);
  border-right: var(--bw) solid var(--border);
}

/* Icono guía a la izquierda del campo. Decorativo: la etiqueta de arriba es la
   que nombra el campo, así que no se anuncia ni recibe foco. */
.input-lead {
  display: inline-flex; align-items: center;
  padding-inline: .75rem .125rem;
  color: var(--muted);
  pointer-events: none;
}
.input-group.-framed:focus-within .input-lead { color: var(--accent); }

/* Checkbox / radio */
.check { display: flex; align-items: flex-start; gap: .625rem; cursor: pointer; padding: .25rem 0; }
.check input { width: 20px; height: 20px; margin: .125rem 0 0; accent-color: var(--accent); flex: 0 0 auto; cursor: pointer; }
.check span { font-size: .9375rem; }

/* Opciones tipo tarjeta (el onboarding las usa en vez de <select>: se tocan
   mejor en celular y se leen de un vistazo) */
.choice-grid { display: grid; gap: .625rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 130px), 1fr)); }
.choice {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .375rem; text-align: center;
  padding: .875rem .625rem; min-height: 76px;
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius-sm);
  font-weight: 600; font-size: .9375rem;
  cursor: pointer;
  transition: border-color .15s, background-color .15s, transform .06s;
}
.choice:hover { border-color: var(--accent); }
.choice:active { transform: scale(.98); }
.choice input { position: absolute; opacity: 0; pointer-events: none; }
.choice.is-selected, .choice:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
}
.choice-icon { font-size: 1.5rem; line-height: 1; }
.choice-sub { font-size: .75rem; font-weight: 400; color: var(--muted); }
.choice.is-selected .choice-sub { color: inherit; opacity: .8; }

/* ── Campo de dinero ───────────────────────────────────────────────────────
   El "$" pegado al campo, como una sola pieza. Lo usan el editor de planes y
   los formularios de finanzas.

   Vive AQUÍ y no en planes.css porque eso fue exactamente el fallo: finanzas
   usaba las clases sin cargar ese archivo, y el signo caía ARRIBA del campo
   —sin borde, sin fondo— como si faltara algo. Un estilo que solo existe en
   el CSS de otro módulo es un estilo que no existe. */
.money-in { display: flex; align-items: stretch; }
.money-in-sign {
  display: flex; align-items: center; justify-content: center;
  flex: 0 0 auto; min-width: 2.5rem;
  font-size: 1.1rem; font-weight: 600; color: var(--muted);
  background: color-mix(in srgb, var(--muted) 8%, transparent);
  border: var(--bw) solid var(--border); border-right: 0;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
}
/* min-width:0 es obligatorio: un elemento flex no encoge por debajo de su
   min-content, y el de un <input> lo fija su atributo `size` (~180px). En un
   modal angosto, el campo se salía por la derecha. */
.money-in .input {
  min-width: 0;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: 1.1rem; font-weight: 600;
}

/* ── Ventana emergente ─────────────────────────────────────────────────────
   La usan el editor de planes y la bandeja de comprobantes. Vive aquí porque
   la segunda pantalla que necesitó una habría copiado la primera, y a la
   tercera ya serían tres formas distintas de oscurecer el fondo.

   `overflow-y: auto` en la capa y `margin: auto` en la tarjeta: cuando el
   contenido es más alto que la pantalla —un formulario en un celular con el
   teclado abierto— la tarjeta se pega arriba y la capa hace scroll. Con
   `align-items: center` el borde superior quedaría inalcanzable. */
.modal {
  position: fixed; inset: 0; z-index: 120;
  display: flex; padding: 1rem; overflow-y: auto;
  background: color-mix(in srgb, #23261f 55%, transparent);
  opacity: 0; transition: opacity .18s ease;
}
.modal.show { opacity: 1; }
.modal[hidden] { display: none; }

.modal-card {
  width: 100%; max-width: 26rem; margin: auto;
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  transform: translateY(8px); transition: transform .18s ease;
}
.modal.show .modal-card { transform: none; }
.modal-card-wide { max-width: 34rem; }

/* ── Barra de semana ───────────────────────────────────────────────────────
   "‹  3 – 9 ago 2026  ›". La usan el horario de la alumna y las clases del
   día del negocio: las dos pantallas navegan de lunes a domingo y tienen que
   verse igual, así que vive aquí y no copiada en dos módulos.

   El encabezado con la fecha NO es adorno: sin él, "12" y "19" son dos
   números sueltos y nadie sabe si el segundo es de este mes o del que viene. */
.weekbar {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  margin: 1rem 0 .5rem;
}
.weekbar-t {
  margin: 0; flex: 1 1 auto; text-align: center;
  font-weight: 600; font-size: .9375rem;
}
.weekbar-nav {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  width: 40px; height: 40px;             /* se toca con el pulgar sin apuntar */
  border: var(--bw) solid var(--border); border-radius: var(--radius-sm);
  color: var(--text); text-decoration: none;
  font-size: 1.25rem; line-height: 1;
  transition: border-color .12s, color .12s;
}
.weekbar-nav:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
/* El extremo del rango se deja VISIBLE y apagado, no se quita: si el botón
   desapareciera, los otros dos se recorrerían y el que ayer estaba a la
   izquierda hoy estaría en medio. */
.weekbar-nav.is-off { opacity: .25; cursor: default; }

/* ── Badges ───────────────────────────────────────────────────────────────── */
.badge {
  display: inline-flex; align-items: center; gap: .3125rem;
  padding: .1875rem .5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .75rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .03em;
  white-space: nowrap;
}
.badge-accent  { background: var(--accent-soft);  color: var(--accent);  border-color: var(--accent); }
.badge-success { background: var(--accent-soft);  color: var(--success); border-color: var(--success); }
.badge-warning { color: var(--warning); border-color: var(--warning); }
.badge-danger  { color: var(--danger);  border-color: var(--danger); }
.badge-muted   { color: var(--muted); }
.badge-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* ── Alertas ──────────────────────────────────────────────────────────────── */
.alert {
  display: flex; align-items: flex-start; gap: .625rem;
  padding: .875rem 1rem;
  border: var(--bw) solid var(--border);
  border-left-width: 5px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: .9375rem;
}
.alert-success { border-left-color: var(--success); }
.alert-warning { border-left-color: var(--warning); }
.alert-danger  { border-left-color: var(--danger); }
.alert-info    { border-left-color: var(--info); }
.alert-icon { flex: 0 0 auto; font-size: 1.125rem; line-height: 1.4; }
.alert > div > :last-child { margin-bottom: 0; }

/* ── Tablas ───────────────────────────────────────────────────────────────── */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; border: var(--bw) solid var(--border); border-radius: var(--radius); }
.table { width: 100%; font-size: .9375rem; }
.table th, .table td { padding: .75rem .875rem; text-align: left; }
.table thead th {
  background: var(--surface-2);
  font-size: .75rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted);
  border-bottom: var(--bw) solid var(--border);
  white-space: nowrap;
}
.table tbody tr + tr td { border-top: 1px solid var(--border); }
.table tbody tr:hover td { background: var(--surface-2); }
.table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Avatar ───────────────────────────────────────────────────────────────── */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: 0 0 auto;
  border-radius: 50%;
  border: var(--bw) solid var(--border);
  background: var(--accent-soft); color: var(--accent);
  font-weight: 700; font-size: .875rem;
  overflow: hidden;
}
/* Alineado ARRIBA, no al centro. Las fotos de perfil se guardan 9:16 —de la
   cintura para arriba— y centrarlas dentro de un círculo de 40 px enseñaba el
   pecho en vez de la cara. En una foto cuadrada de las de antes no cambia nada:
   sin sobrante vertical, no hay nada que alinear. */
.avatar img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
.avatar-sm { width: 32px; height: 32px; font-size: .75rem; }
.avatar-lg { width: 64px; height: 64px; font-size: 1.25rem; }
.avatar-xl { width: 88px; height: 88px; font-size: 1.75rem; }

/* ── Foto vertical ─────────────────────────────────────────────────────────
   La foto de perfil COMPLETA, tal como se guarda: 9:16, de la cintura para
   arriba. La usan el perfil de cada quien y la ficha de la alumna, así que
   vive aquí — la segunda pantalla que la necesitó habría copiado la primera.

   `contain` y no `cover`: las fotos de antes de que existiera el recortador
   son cuadradas, y recortarlas a 9:16 les cortaría media cara. Con `contain`
   se ven enteras, con dos bandas del color del fondo, y las nuevas —que ya son
   9:16— llenan el marco exacto sin banda ninguna. */
.photo-v {
  flex: 0 0 auto;
  width: 96px; aspect-ratio: 9 / 16;
  border-radius: var(--radius);
  border: var(--bw) solid var(--border);
  background: var(--surface-2);
  overflow: hidden;
}
.photo-v img { display: block; width: 100%; height: 100%; object-fit: contain; }
.photo-v-lg { width: 132px; }

@media (max-width: 30rem) {
  .photo-v    { width: 78px; }
  .photo-v-lg { width: 108px; }
}

/* ── Lista de datos (etiqueta arriba, valor abajo) ────────────────────────── */
/* Apilado en vez de dos columnas: en un celular angosto una tabla parte los
   valores largos a media palabra. */
.data-list { display: grid; gap: .875rem; margin: 0; }
@media (min-width: 420px) { .data-list { grid-template-columns: repeat(2, 1fr); } }
.data-list dt {
  font-size: .6875rem; text-transform: uppercase; letter-spacing: .12em;
  font-weight: 600; color: var(--muted); margin-bottom: .125rem;
}
.data-list dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
/* ── Estado vacío ─────────────────────────────────────────────────────────── */
.empty { text-align: center; padding: 2.5rem 1rem; color: var(--muted); }
.empty-icon { font-size: 2.5rem; margin-bottom: .5rem; opacity: .6; }
.empty h3 { color: var(--text); }

/* ── Barra de progreso ────────────────────────────────────────────────────── */
.progress {
  height: 8px; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: 999px; overflow: hidden;
}
.progress-bar { height: 100%; background: var(--accent); border-radius: 999px; transition: width .35s ease; }

/* ── Skeleton (carga) ─────────────────────────────────────────────────────── */
.skeleton {
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border) 50%, var(--surface-2) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.3s infinite;
  border-radius: var(--radius-sm);
}
@keyframes shimmer { to { background-position: -200% 0; } }

/* ── Separador con texto ──────────────────────────────────────────────────── */
.divider { display: flex; align-items: center; gap: .75rem; color: var(--muted); font-size: .8125rem; }
.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: var(--border); }

/* ── Toast (avisos flotantes) ─────────────────────────────────────────────── */
.toast-host {
  /* Por encima de la hoja de la agenda (1000) y del diálogo (2000): si no,
     el aviso queda escondido detrás del modal, abajo. */
  position: fixed; z-index: 3000;
  left: 50%; transform: translateX(-50%);
  bottom: calc(env(safe-area-inset-bottom, 0px) + 5.5rem);
  display: flex; flex-direction: column; gap: .5rem;
  width: min(92vw, 420px);
  pointer-events: none;
}
@media (min-width: 900px) { .toast-host { bottom: 1.5rem; } }
.toast {
  pointer-events: auto;
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-left-width: 5px;
  border-radius: var(--radius-sm);
  padding: .75rem 1rem;
  font-size: .9375rem; font-weight: 500;
  box-shadow: 0 8px 24px rgb(0 0 0 / .16);
  animation: toast-in .2s ease-out;
}
.toast-success { border-left-color: var(--success); }
.toast-danger  { border-left-color: var(--danger); }
.toast-warning { border-left-color: var(--warning); }
.toast-info    { border-left-color: var(--info); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }

/* ── Diálogo modal propio (reemplaza alert/confirm/prompt nativos) ────────── */
.ga-dialog-ov {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center; padding: 1rem;
  background: rgba(15, 23, 42, .5); backdrop-filter: blur(3px);
  animation: bm-dialog-fade .15s ease;
}
.ga-dialog {
  width: 100%; max-width: 400px;
  background: var(--surface); border: var(--bw) solid var(--border);
  border-radius: var(--radius); padding: 1.5rem;
  box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
  animation: bm-dialog-pop .18s ease-out;
}
.ga-dialog-title { margin: 0 0 .5rem; font-size: 1.15rem; }
.ga-dialog-msg { margin: 0 0 1.125rem; color: var(--muted); line-height: 1.55; }
.ga-dialog-title + .ga-dialog-msg { margin-top: -.125rem; }
/* Desglose del diálogo: "de tal a tal fecha", "luego paga tanto". Etiqueta
   arriba y valor abajo y no en dos columnas, porque los valores son fechas y
   cantidades de largo muy distinto y en columnas quedan bailando. */
.ga-dialog-lines {
  margin: 0 0 1.125rem; padding: .75rem .875rem;
  border-radius: var(--radius-sm); background: var(--surface-2);
  display: flex; flex-direction: column; gap: .5rem;
}
.ga-dialog-lines dt {
  font-size: .6875rem; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); margin-bottom: .0625rem;
}
.ga-dialog-lines dd { margin: 0; font-size: .9375rem; color: var(--text); }

/* Cuando el título ES la cifra —cuánto tiene que pagar—, manda en la pantalla:
   es lo que la dueña va a leer en voz alta por encima del mostrador. */
.ga-dialog.is-amount .ga-dialog-title {
  font-size: 2rem; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.ga-dialog-input { margin-bottom: 1.25rem; }
.ga-dialog-foot { display: flex; gap: .625rem; justify-content: flex-end; flex-wrap: wrap; }
.ga-dialog-foot .btn { min-width: 96px; }
@keyframes bm-dialog-fade { from { opacity: 0; } }
@keyframes bm-dialog-pop { from { transform: translateY(10px) scale(.97); opacity: 0; } }
@media (max-width: 420px) {
  .ga-dialog-foot { flex-direction: column-reverse; }
  .ga-dialog-foot .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) { .ga-dialog-ov, .ga-dialog { animation: none; } }

/* ── Sesión prestada: "estás viendo la plataforma como X" ─────────────────
   Barra fija abajo, presente en TODOS los layouts. El color es --warning y no
   --danger a propósito: no pasó nada malo, solo hay que no olvidarlo. */
body.is-impersonating { --imp-h: 52px; }

.imp-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1900;
  background: var(--warning); color: #fff;
  box-shadow: 0 -6px 20px rgba(0, 0, 0, .18);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.imp-bar-in {
  display: flex; align-items: center; gap: .75rem;
  max-width: 1180px; margin: 0 auto; padding: .5rem 1rem;
  min-height: var(--imp-h, 52px);
}
.imp-bar-eye { display: inline-flex; line-height: 1; flex: none; }
.imp-bar-eye .ic { width: 18px; height: 18px; }
.imp-bar-txt { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.imp-bar-txt strong { font-size: .9rem; font-weight: 600; }
.imp-bar-sub { font-size: .75rem; opacity: .85; }
.imp-bar form { flex: none; margin: 0; }
.imp-bar-btn {
  background: #fff; color: var(--warning);
  border: var(--bw) solid #fff; font-weight: 600; white-space: nowrap;
}
.imp-bar-btn:hover { background: rgba(255, 255, 255, .88); color: var(--warning); }

@media (max-width: 899px) {
  .imp-bar-sub { display: none; }   /* a 360 px solo cabe lo importante */

  /* Donde hay navegación inferior, el aviso se sienta ARRIBA de ella: taparla
     dejaría al super admin sin poder moverse por la plataforma que vino a
     revisar. Donde no la hay (portada, errores), va pegado abajo. */
  body:has(.bottomnav) .imp-bar {
    bottom: calc(var(--bottomnav-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: 0;
  }
  body.is-impersonating:has(.bottomnav) .shell-main {
    padding-bottom: calc(var(--bottomnav-h) + var(--imp-h) + env(safe-area-inset-bottom, 0px) + 1rem);
  }
  body.is-impersonating:has(.bottomnav) .footer {
    margin-bottom: calc(var(--bottomnav-h) + var(--imp-h) + env(safe-area-inset-bottom, 0px));
  }
  body.is-impersonating:has(.bottomnav) .toast-host {
    bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bottomnav-h) + var(--imp-h) + 1rem);
  }
}

@media (min-width: 900px) {
  body.is-impersonating .shell-main { padding-bottom: calc(var(--imp-h) + 1.5rem); }
  body.is-impersonating .footer     { margin-bottom: var(--imp-h); }
  body.is-impersonating .toast-host { bottom: calc(var(--imp-h) + 1rem); }
}

/* Layouts sin .shell (portada, acceso, errores): el aire se le da al body. */
body.is-impersonating:not(:has(.shell)) {
  padding-bottom: calc(var(--imp-h) + env(safe-area-inset-bottom, 0px));
}

/* ── Invitación a instalar la app ────────────────────────────────────────────
   Barra al pie, por encima de la navegación inferior. Se puede quitar y no
   vuelve: un aviso que reaparece en cada visita se cierra por reflejo y deja de
   leerse. */
.pw-inst {
  position: fixed; left: .75rem; right: .75rem; bottom: .75rem; z-index: 60;
  display: flex; align-items: center; gap: .875rem; flex-wrap: wrap;
  padding: .875rem 1rem;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 12px 32px -8px rgba(0,0,0,.28);
  transform: translateY(130%); opacity: 0;
  transition: transform .22s ease, opacity .22s ease;
}
.pw-inst.is-in { transform: translateY(0); opacity: 1; }
.pw-inst-txt { display: flex; flex-direction: column; flex: 1 1 12rem; min-width: 0; }
.pw-inst-txt strong { font-size: .9375rem; }
.pw-inst-txt span { font-size: .8125rem; color: var(--muted); }
.pw-inst-acts { display: flex; gap: .5rem; flex: 0 0 auto; }

/* Con la navegación inferior puesta, la barra se sube para no taparla. */
body.has-bottomnav .pw-inst { bottom: calc(var(--bottomnav-h, 60px) + .75rem); }

@media (prefers-reduced-motion: reduce) {
  .pw-inst { transition: none; }
}

/* ── Interruptor ─────────────────────────────────────────────────────────────
 *
 * No había ninguno en la plataforma: donde hacía falta un sí/no se usaba
 * `.check`, que es una casilla. Un interruptor dice «esto está encendido o
 * apagado AHORA» y una casilla dice «marca esto» — y para «También fuera», que
 * cambia quién va a ver la foto, la primera lectura es la que importa.
 *
 * El `<span class="switch-track">` tiene que ir PEGADO al input: el selector es
 * `+`, así que un elemento en medio deja el interruptor pintado como una
 * casilla suelta que no reacciona.
 */
.switch { position: relative; display: inline-flex; flex: none; }
.switch input {
  position: absolute; inset: 0;
  width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: pointer; z-index: 1;
}
.switch-track {
  display: block;
  width: 52px; height: 30px;
  border-radius: 999px;
  background: var(--border);
  transition: background-color .18s ease;
}
.switch-track::after {
  content: '';
  position: absolute; top: 3px; left: 3px;
  width: 24px; height: 24px;
  border-radius: 999px;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .28);
  transition: transform .18s ease;
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track::after { transform: translateX(22px); }
.switch input:focus-visible + .switch-track { outline: 3px solid var(--accent); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .switch-track, .switch-track::after { transition: none; }
}

/* ── Publicar: el botón flotante y su hoja ───────────────────────────────────
 *
 * Chrome de la plataforma, como la barra de abajo o el aviso de sesión
 * prestada, y por eso vive aquí y no en el módulo: sale en TODAS las pantallas
 * de la alumna. Publicar tiene que poder hacerse desde donde esté; con el
 * compositor solo dentro del muro, subir la foto de la clase cuesta dos toques
 * de más y a esos dos toques se les cae la mitad de la gente.
 */

.pub-fab {
  position: fixed; right: 1rem; z-index: 70;   /* encima de la barra (60) */
  /* Se sienta ARRIBA de la navegación inferior, con las mismas cuentas que el
     aviso de instalar y los avisos flotantes: taparla dejaría a la alumna sin
     poder moverse. */
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bottomnav-h, 62px) + 1rem);
  width: 56px; height: 56px;                   /* 56 es el mínimo cómodo para el pulgar */
  display: grid; place-items: center;
  border: 0; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink, #fff);
  box-shadow: 0 10px 24px -6px rgba(0, 0, 0, .38);
  cursor: pointer;
  transition: transform .06s ease, box-shadow .15s ease;
}
.pub-fab:active { transform: scale(.94); }
.pub-fab .ic { width: 26px; height: 26px; }

/* Con la sesión prestada, la franja naranja se mete entre la barra y el botón:
   sin esta línea el botón queda debajo de ella y no se puede tocar. Misma
   cuenta que `.toast-host`. */
body.is-impersonating .pub-fab {
  bottom: calc(env(safe-area-inset-bottom, 0px) + var(--bottomnav-h, 62px) + var(--imp-h, 52px) + 1rem);
}

/* En escritorio no hay barra inferior: el botón baja a su esquina. */
@media (min-width: 900px) {
  .pub-fab { bottom: 1.5rem; right: 1.5rem; }
  body.is-impersonating .pub-fab { bottom: calc(var(--imp-h, 52px) + 1.5rem); }
}

/* Mientras la hoja está abierta el botón se va: es el mismo sitio de la
   pantalla y quedarían uno encima del otro. */
body.pub-abierta .pub-fab { display: none; }

/* ── La hoja ─────────────────────────────────────────────────────────────── */

.pub-hoja {
  position: fixed; inset: 0;
  /* Encima de la franja de sesión prestada (1900) y por debajo del diálogo
     (2000) y de los avisos (3000): si el aviso de «listo» quedara detrás, la
     única confirmación de que se publicó no se vería. */
  z-index: 1950;
  display: flex; align-items: flex-end; justify-content: center;
}
/* El atributo `hidden` NO gana solo: es `display: none` en la hoja del
   navegador, y en la cascada lo que escribimos nosotros va por encima sin
   mirar especificidad. Sin esta línea la hoja se ve SIEMPRE. */
.pub-hoja[hidden] { display: none; }

.pub-fondo { position: absolute; inset: 0; background: rgba(0, 0, 0, .5); }

.pub-caja {
  position: relative;
  display: flex; flex-direction: column;
  width: 100%; max-height: 92vh;
  background: var(--surface); color: var(--text);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: 0 -12px 40px -10px rgba(0, 0, 0, .45);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* ── La cabecera: cerrar y a quién se le enseña ──────────────────────────── */

.pub-cab {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .75rem 1rem;
}

/* La píldora de la audiencia. Es un BOTÓN que cambia el valor, no un
   desplegable: hay dos estados y con dos estados un menú es un toque de más
   para elegir entre lo que ya se ve. Cambia su icono —candado o mundo— y su
   texto, y está atada al mismo interruptor de abajo: las dos cosas no pueden
   decir cosas distintas porque son el mismo dato. */
.pub-aud-pill {
  display: inline-flex; align-items: center; gap: .375rem;
  min-height: 36px; padding: .375rem .75rem;
  background: transparent; color: var(--text);
  border: var(--bw) solid var(--border); border-radius: 999px;
  font: inherit; font-size: .875rem; font-weight: 600;
  cursor: pointer;
  transition: border-color .15s, background-color .15s, color .15s;
}
.pub-aud-pill:hover { border-color: var(--accent); }
.pub-aud-pill:active { transform: scale(.97); }
.pub-aud-pill.is-fuera { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.pub-aud-pill .ic { width: 18px; height: 18px; flex: none; }

/* ── El cuerpo: escribir ─────────────────────────────────────────────────── */

/* Es lo ÚNICO que se desplaza: la cabecera, la barra de acciones, la fila de
   «también fuera» y el botón se quedan quietos, así que «Publicar» está siempre
   a la vista aunque haya cuatro fotos puestas. */
.pub-cuerpo { flex: 1; overflow-y: auto; padding: 0 1rem 1rem; display: grid; gap: .875rem; }

.pub-escribe { display: flex; align-items: flex-start; gap: .75rem; }

/* El campo no parece un campo: sin borde ni fondo, como una hoja en blanco.
   Un recuadro gris en medio de una hoja que ya es un recuadro es una caja
   dentro de otra, y lo que se quiere es que se vea sitio para escribir. */
.pub-texto {
  flex: 1; min-width: 0;
  min-height: 8rem; resize: none;
  border: 0; background: transparent; padding: .25rem 0;
  font-size: 1.0625rem; line-height: 1.5;
}
/* ── El anillo de foco de este campo, y por qué es especial ──────────────────
 *
 * `base.css` manda no quitar nunca el outline, y esa regla sigue en pie: quien
 * navega con teclado tiene que ver dónde está parado.
 *
 * Pero AQUÍ el indicador de siempre se leía como un recuadro roto, y por dos
 * razones que solo se juntan en esta pantalla:
 *
 *   1. La hoja ENFOCA el campo sola al abrirse —quien tocó el botón viene a
 *      escribir—, así que el anillo salía desde el primer instante y en todas
 *      las aperturas. Un indicador permanente deja de ser un indicador: pasa a
 *      ser el borde de una caja que el diseño no tiene.
 *   2. Un `textarea` casa con `:focus-visible` también con el ratón, así que ni
 *      siquiera bastaba con esa pseudoclase.
 *
 * Se resuelve como se resolvía antes de `:focus-visible`: el anillo aparece
 * cuando de verdad se está navegando con el TABULADOR (`body.pub-teclado`, que
 * pone y quita `publicar.js`). Quien usa teclado lo ve; quien abrió la hoja con
 * el pulgar, no. No se quita el foco: se le pone la condición que le faltaba.
 *
 * Y va POR DENTRO (`outline-offset` negativo): con el de fábrica —2 px hacia
 * afuera— el anillo se salía por el borde derecho de la hoja y aparecía cortado.
 */
.pub-texto:focus { outline: none; }

body.pub-teclado .pub-texto:focus {
  outline: 3px solid var(--accent);
  outline-offset: -3px;
  border-radius: var(--radius-sm);
}

.pub-fotos {
  display: grid; gap: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
}
.pub-fotos[hidden] { display: none; }

.pub-previa {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--bg);
  border: var(--bw) solid var(--border);
}
.pub-previa img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Mientras sube, la miniatura se ve a medias: es lo que dice que todavía no
   está, sin ocupar sitio con una barra de progreso por foto. */
.pub-previa.is-subiendo img { opacity: .45; }

.pub-previa-x {
  position: absolute; top: 4px; right: 4px;
  width: 24px; height: 24px;
  display: grid; place-items: center;
  border: 0; border-radius: 999px;
  background: var(--surface); color: var(--text);
  cursor: pointer; opacity: .9; line-height: 1;
}

/* ── La barra de acciones ────────────────────────────────────────────────── */

.pub-barra {
  display: flex; align-items: center; gap: .25rem;
  padding: .5rem 1rem;
}

.pub-accion {
  display: grid; place-items: center;
  /* 44 px: por debajo de eso no se acierta con el pulgar. */
  width: 44px; height: 44px;
  border: 0; border-radius: 999px;
  background: transparent; color: var(--muted);
  cursor: pointer;
  transition: background-color .15s, color .15s;
}
.pub-accion:hover, .pub-accion[aria-expanded="true"] { background: var(--bg); color: var(--accent); }
.pub-accion .ic { width: 22px; height: 22px; }

/* El contador se va al otro extremo de la barra. */
.pub-cuenta {
  margin-left: auto;
  color: var(--muted); font-size: .8125rem; font-variant-numeric: tabular-nums;
}
.pub-cuenta.is-poco { color: var(--accent); font-weight: 700; }

/* ── También fuera ───────────────────────────────────────────────────────── */

.pub-fuera {
  display: flex; align-items: center; gap: .875rem;
  padding: .875rem 1rem;
  border-top: var(--bw) solid var(--border);
  border-bottom: var(--bw) solid var(--border);
}
.pub-fuera-ic { display: inline-flex; color: var(--muted); flex: none; }
.pub-fuera-ic .ic { width: 22px; height: 22px; }
.pub-fuera-txt { flex: 1; min-width: 0; }
.pub-fuera-t { display: block; font-size: .9375rem; font-weight: 600; }
.pub-fuera-h { display: block; color: var(--muted); font-size: .8125rem; line-height: 1.35; }

/* Encendido, la fila toma el acento: es el único cambio de la hoja que decide
   quién va a ver la foto, y tiene que notarse sin leer. */
.pub-fuera:has(input:checked) .pub-fuera-ic { color: var(--accent); }

/* ── El pie ──────────────────────────────────────────────────────────────── */

.pub-pie { display: flex; justify-content: flex-end; padding: .75rem 1rem; }
.pub-pie .btn { border-radius: 999px; padding-left: 1.75rem; padding-right: 1.75rem; }

/* Con la hoja abierta, el fondo no se desplaza. Aquí sí es una hoja de verdad
   —a pantalla completa en el teléfono—, así que se congela siempre. */
body.pub-abierta { overflow: hidden; }

/* En pantalla grande deja de ser una hoja que sube y pasa a ser un diálogo
   centrado: pegada abajo en un monitor de 27" el formulario queda en el borde. */
@media (min-width: 720px) {
  .pub-hoja { align-items: center; padding: 1.5rem; }
  .pub-caja { width: min(600px, 100%); max-height: 86vh; border-radius: var(--radius); }
  .pub-fotos { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
}

/* ── El selector de emojis ───────────────────────────────────────────────────
 *
 * Portado de Exclivia con el componente. Ver `layouts/emoji.php`.
 */

.emo {
  position: absolute;
  /* Por encima de la hoja de publicar (1950), que es desde donde se abre: un
     selector que se abre y no se ve se lee como un botón roto. Y por debajo de
     los avisos flotantes (3000), que interrumpen a todo. */
  z-index: 1975;
  width: min(20rem, calc(100vw - 1rem));
  background: var(--surface);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .18);
  overflow: hidden;
}
.emo[hidden] { display: none; }

.emo-tabs {
  display: flex; gap: .15rem;
  padding: .35rem;
  overflow-x: auto; scrollbar-width: none;
  border-bottom: var(--bw) solid var(--border);
}
.emo-tabs::-webkit-scrollbar { display: none; }

.emo-tab {
  flex: none;
  font: inherit; font-size: .72rem;
  padding: .25rem .5rem;
  border: 0; border-radius: 999px;
  background: none; color: var(--muted);
  white-space: nowrap; cursor: pointer;
}
.emo-tab.is-on { color: var(--accent); background: var(--accent-soft); }

/* Alto fijo: sin él, el grupo de 102 símbolos estiraba el panel hasta salirse
   de la pantalla y el de 49 lo dejaba diminuto — el panel cambiaba de tamaño al
   cambiar de pestaña. */
.emo-cuerpo {
  max-height: 14rem;
  overflow-y: auto;
  padding: .35rem;
  overscroll-behavior: contain;
}

.emo-grupo {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.1rem, 1fr));
  gap: .1rem;
}
.emo-grupo[hidden] { display: none; }

.emo-b {
  aspect-ratio: 1;
  display: grid; place-items: center;
  /* El emoji lo dibuja el sistema con SUS colores: aquí `font-size` es lo único
     que se toca. Teñirlo no se puede y no hace falta — que traiga sus colores es
     justo lo que se busca en el contenido. */
  font-size: 1.35rem; line-height: 1;
  border: 0; border-radius: .35rem;
  background: none; cursor: pointer; padding: 0;
}
.emo-b:hover, .emo-b:focus-visible { background: var(--bg); }
/* ── Acomodar una foto ───────────────────────────────────────────────────────
 *
 * El marco donde se encuadra una foto antes de subirla. UNO para toda la
 * plataforma: lo usan la foto de perfil (9:16, sin elegir) y el muro (tres
 * formas). Estaba en `perfil.css` y subió aquí cuando lo necesitó el segundo:
 * dos recortadores se comportan distinto a los dos meses y quien los usa no
 * entiende por qué. Ver `assets/js/recorte.js` y `layouts/recorte.php`.
 */

/* Por encima de la hoja de publicar (1950), que es desde donde se abre casi
   siempre: con el 120 de `.modal` a secas, el marco se abría DETRÁS de la hoja
   y la pantalla se quedaba muerta sin que nada lo explicara. */
.crop-modal { z-index: 1970; }

.crop-card { max-width: 22rem; }

/* La forma. Las dos variables dicen lo mismo de dos maneras porque
   `aspect-ratio` necesita la razón y el ancho se calcula multiplicando por el
   número; las pone el guion al abrir. Los valores de aquí son los de fábrica
   (9:16), para que el marco no salte antes de que el guion los escriba. */
.crop-stage {
  --crop-ar: 9 / 16;
  --crop-arn: .5625;

  position: relative;
  /* El ancho sale de los DOS límites a la vez y la altura la pone el
     aspect-ratio. Con `max-height` aparte, en una pantalla baja el marco se
     achataba y dejaba de ser exacto — y entonces lo que se guarda no es lo que
     se vio en el marco. La proporción tiene que ser exacta porque el corte se
     calcula con ella. */
  width: min(20rem, calc(52vh * var(--crop-arn)), 100%);
  aspect-ratio: var(--crop-ar);
  margin: 0 auto;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface-2);
  /* Sin esto, arrastrar la foto en el celular hace scroll de la página. */
  touch-action: none;
  cursor: grab;
  user-select: none;
}
.crop-stage:active { cursor: grabbing; }

.crop-stage img {
  position: absolute; top: 0; left: 0;
  transform-origin: 0 0;
  max-width: none;   /* el reset global pone max-width:100% y aplastaría el zoom */
  pointer-events: none;
  -webkit-user-drag: none;
}

/* La tira de formas. Nace escondida y la enseña el guion solo cuando quien
   abre ofrece más de una: una sola pastilla encendida no es una elección. */
.crop-formas { display: flex; gap: .375rem; justify-content: center; margin: .75rem 0; }
.crop-formas[hidden] { display: none; }

.crop-forma {
  padding: .375rem .75rem;
  min-height: 36px;
  background: transparent; color: var(--muted);
  border: var(--bw) solid var(--border); border-radius: 999px;
  font: inherit; font-size: .8125rem; font-weight: 600;
  cursor: pointer;
  transition: border-color .15s, background-color .15s, color .15s;
}
.crop-forma[hidden] { display: none; }
.crop-forma.is-on { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }

.crop-zoom { display: flex; align-items: center; gap: .625rem; margin-top: .875rem; }
.crop-zoom .ic { flex: 0 0 auto; color: var(--muted); }
.crop-zoom input[type="range"] { flex: 1 1 auto; min-width: 0; accent-color: var(--accent); height: 32px; }