@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap');

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  font-family: 'Open Sans', sans-serif;
  background: oklch(0.98 0.003 250);
  color: oklch(0.2 0.01 250);
}

#app { height: 100%; min-height:calc(100vh - 150px)}

/* Scrollbar chica y unificada en todos los navegadores/elementos. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--border, oklch(0.8 0.008 250)) transparent;
}

::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--border, oklch(0.8 0.008 250));
  border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover { background: var(--text-faint, oklch(0.62 0.01 250)); }
::-webkit-scrollbar-corner { background: transparent; }

input::placeholder { color: oklch(0.62 0.01 250); }

button { font-family: inherit; cursor: pointer; }

/*
 * Foco unificado para inputs/textareas y los botones-dropdown (Módulo
 * padre, Tipo, filtros, cambio de estado, etc: todos comparten las clases
 * .status-dropdown-btn / .filter-toggle-btn): el borde toma el color de
 * acento del theme activo (--accent, seteado dinámicamente en
 * Dashboard.svelte según themeHue/darkMode). Fuera del shell (Login,
 * Recuperar) no hay --accent definido, así que cae al valor por defecto
 * (mismo azul/violeta de marca). No usar reglas :focus por componente para
 * esto — este selector global ya cubre cualquier input/dropdown nuevo que
 * use esas clases. Usa !important a propósito: las reglas scoped de cada
 * componente Svelte (`.clase.svelte-hash`) tienen la misma o mayor
 * especificidad y si no, lo pisan silenciosamente.
 */
input:focus, textarea:focus, select:focus,
.status-dropdown-btn:focus, .filter-toggle-btn:focus {
  outline: none !important;
  border-color: var(--accent, oklch(0.55 0.18 250)) !important;
}

.boot-loader {
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
}

.boot-logo {
  width: 56px;
  height: 56px;
  animation: boot-pulse 1.6s ease-in-out infinite;
}

.boot-bar {
  width: 160px;
  height: 4px;
  border-radius: 2px;
  background: oklch(0.9 0.005 250);
  overflow: hidden;
  position: relative;
}

.boot-bar-fill {
  position: absolute;
  top: 0;
  left: -40%;
  height: 100%;
  width: 40%;
  border-radius: 2px;
  background: linear-gradient(90deg, oklch(0.5 0.19 320), oklch(0.55 0.18 250), oklch(0.55 0.15 200));
  animation: boot-bar-slide 1.1s ease-in-out infinite;
}

.boot-label {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  color: oklch(0.55 0.012 250);
  letter-spacing: 0.02em;
}

@keyframes boot-pulse {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.08); opacity: 0.85; }
}

@keyframes boot-bar-slide {
  0% { left: -40%; }
  100% { left: 100%; }
}

/* ---------------------------------------------------------------------------
   Guardado en curso: deshabilita TODOS los botones de guardar de la app.

   Pedido explícito: "unificá todos los editores … siempre poné el botón en
   disabled". Se resuelve con un atributo `data-busy` en el <html> (lo setea
   Dashboard.svelte a partir del store `busy`, que prende `apiFetch()` en cada
   escritura) en vez de una variable `saving` por pantalla: de los 34 editores
   del backoffice, solo 6 deshabilitaban el botón y cada uno a su manera.

   El `!important` es necesario por el mismo motivo ya documentado para la
   regla de `:focus` (ver gotcha #4 en PUPILA_CMS.md §2): las reglas scoped de
   Svelte (`.primary-btn.svelte-hash`) tienen igual o más especificidad y si no
   lo tuviera lo pisarían en silencio.

   `pointer-events: none` corta el click y el `cursor: progress` del contenedor
   avisa que está trabajando. Se excluye `.saved-toast` (no es un control).
--------------------------------------------------------------------------- */
:root[data-busy] .primary-btn,
:root[data-busy] .status-dropdown-item,
:root[data-busy] .save-dropdown-menu button {
  pointer-events: none !important;
  opacity: 0.55 !important;
  cursor: progress !important;
}

:root[data-busy] {
  cursor: progress;
}

/* Estilo del estado deshabilitado, global. Motivo concreto: en Plantillas el
   botón Guardar SÍ tenía `disabled={saving}`, pero de los 34 editores solo 3
   definían `.primary-btn:disabled`, así que en el resto quedaba deshabilitado
   y VISUALMENTE IGUAL — de ahí el reporte "no se pone en disabled". Con esto,
   cualquier botón con el atributo se ve deshabilitado en toda la app. Mismo
   `!important` y mismo motivo que la regla de arriba. */
.primary-btn:disabled,
.secondary-btn:disabled,
button:disabled {
  opacity: 0.55 !important;
  cursor: not-allowed !important;
}

/* ---------------------------------------------------------------------------
   El menú `⋮` de una fila no puede quedar recortado por la tabla
   ---------------------------------------------------------------------------
   Reportado: "en todos los listados, cuando hay pocos registros, el desplegable
   de los 3 puntos queda dentro del overflow de la tabla y no se ven las
   opciones".

   La causa son dos decisiones correctas que se pisan entre sí:

     - `.table-wrap` declara `overflow-x: auto` en los 29 listados — es parte de
       la convención de columnas (subgrid + scroll horizontal en pantallas
       angostas, ver `check-columnas.js`). **Por spec, si un eje es distinto de
       `visible` el otro se computa a `auto`**, así que la tabla también recorta
       en vertical. Medido: `overflow-y` computado = `auto` en los 29.
     - `.actions-menu` es `position: absolute` dentro de la fila, centrado
       (`top: 50%`) en 24 listados o colgando (`top: 100%`) en Abonos/Lectores.

   Arriba lo que sobresale lo absorbe el renglón de encabezado; abajo no hay
   nada, así que se recorta en la ÚLTIMA fila — y con uno o dos registros TODAS
   las filas son la última, que es por qué el síntoma se ve "cuando hay pocos".

   Medido en pantalla real antes de escribir esto (el menú se sale de la caja):
   General 12px · Portales 11px (1 sola fila) · Autores 13px · Ediciones 14px ·
   Servicios 14px · Perfiles 14px · Notas 26px (3 items) · Abonos 61px y
   Lectores 63px (los dos que cuelgan). Peor caso: 63px.

   El arreglo: mientras hay un menú abierto, la tabla le hace lugar. El menú
   sólo existe en el DOM cuando está abierto (`{#if menuAbierto === row.id}` en
   los 26 listados), así que `:has()` alcanza y el padding desaparece al
   cerrarlo — no queda un hueco permanente al pie de ninguna tabla.

   Por qué acá y no en cada componente: la convención está copiada a mano en 29
   archivos y ya se pagó dos veces que se despeguen entre sí (ver "Barra de
   título" y "Buscador y desplegable de Filtros" en PUPILA_CMS.md). Una sola
   regla no se puede desincronizar. No hace falta `!important` (a diferencia de
   las dos reglas de arriba): ningún `.table-wrap` declara `padding`, así que no
   compite con nada scoped.

   Se descartó `overflow: visible` bajo el mismo `:has()`: en una tabla con
   scroll horizontal activo, pasar a `visible` resetea el `scrollLeft` y la
   tabla saltaría al inicio al abrir el menú.

   Después del arreglo, medido de nuevo: de 10 menús recortados quedó **1**, y
   es de 2px ARRIBA en Notas (el único listado con 3 items). Se deja así a
   propósito: 2px se comen el borde redondeado, no un item, y taparlos pide un
   `padding-top`, que mueve la tabla entera 16px hacia abajo cada vez que se
   abre un menú — un salto visible en las 29 pantallas para tapar 2px en una.

   **Si algún día un menú suma items y vuelve a recortarse, este número es lo
   único que hay que subir** — y `.docs/devtools/browser/listado-menu-acciones.mjs`
   lo mide solo, listado por listado. */
.table-wrap:has(.actions-menu),
.table-wrap:has(.status-dropdown-menu) {
  /* Hacia ARRIBA se sale poco (2px en Notas, 7px en Bloques: los menús de 3
     items, que el renglón de encabezado ya casi alcanza a absorber), pero un
     `padding-top` solo movería la tabla entera hacia abajo al abrir un menú.
     El `margin-top` negativo del mismo valor lo compensa: el contenido queda
     exactamente donde estaba y lo único que se corre es el borde de la
     tarjeta, 16px hacia arriba y por debajo de la barra sticky del título,
     que tiene fondo propio. Sin salto. */
  padding-top: 16px;
  margin-top: -16px;
}

.table-wrap:has(.actions-menu) {
  padding-bottom: 72px;
}

/* LA FILA CON EL MENÚ ⋮ ABIERTO SE SUBE POR ENCIMA DE LAS QUE SIGUEN
   ------------------------------------------------------------------
   Bug real: en una fila INACTIVA, el menú de acciones quedaba debajo de las
   pastillas de estado de las filas de abajo y "Eliminar" no se podía clickear.
   Comprobado con `elementFromPoint`: el item devolvía el `estado-badge` de la
   fila siguiente, no el botón.

   No era el `z-index: 4` del menú: era que **una fila inactiva se atenúa con
   `opacity: 0.6`** (`.blocks-row.inactive` y sus 10 hermanas), y `opacity < 1`
   crea un STACKING CONTEXT. Con eso, el `z-index: 4` del menú deja de compararse
   con el resto de la tabla y solo ordena adentro de su propia fila; la fila en sí
   es `z-index: auto`, así que se pinta en orden de documento y **cualquier fila
   posterior le pasa por encima entera**, pastillas incluidas. El menú está
   centrado (`top: 50%`) y mide más que una fila, así que su parte de abajo —donde
   justo cae "Eliminar"— es la que se come la fila siguiente.

   Por eso el arreglo va en la FILA y no en el menú: subirle el `z-index` al menú
   no sirve de nada estando atrapado. Y por eso es una regla global y no una por
   componente: son 11 listados con el mismo `opacity`, y el próximo que se agregue
   lo hereda sin acordarse de esto (mismo criterio que el focus-ring y que las dos
   reglas de `.table-wrap` de acá arriba).

   `:has(> …)` con combinador de hijo es la forma barata del selector, y matchea
   como mucho una fila a la vez: solo hay un menú abierto. El `position: relative`
   va por si algún listado no lo tuviera — sin posicionar, el `z-index` no aplica.

   El 6 es a propósito mayor que el 5 del `.status-dropdown-menu`: si la fila de
   abajo tiene su desplegable de estado abierto, el menú de acciones igual gana. */
:has(> .actions-cell > .actions-menu) {
  position: relative;
  z-index: 6;
}

/* …Y LA FILA ATENUADA NO PUEDE ATENUAR SU PROPIO MENÚ
   ---------------------------------------------------
   Segunda mitad del mismo bug: con la fila inactiva en `opacity: 0.6`, el menú
   ⋮ salía translúcido. No se arregla desde el menú — **un hijo no puede
   recuperar la opacidad que le sacó el padre**: `opacity` compone al grupo
   entero y `opacity: 1` adentro solo significa "opaco DENTRO de ese grupo ya
   atenuado". Ponerle `opacity: 1` al menú no hace absolutamente nada.

   Así que se atenúa EL CONTENIDO en vez de la fila: cada celda por su cuenta,
   salvo la de acciones. El resultado se ve igual —las celdas no se superponen,
   así que no hay diferencia de composición— y de paso el botón ⋮ queda legible
   en una fila inactiva, que es donde más se lo usa (es por donde se la
   reactiva o se la borra).

   `!important` por el mismo motivo que el focus-ring de más arriba: la regla que
   se está pisando es un selector scopeado de Svelte
   (`.blocks-row.inactive.svelte-hash`, 3 clases) y sin él le gana a este.

   `:has(> .actions-cell)` es lo que identifica una fila de listado sin
   enumerar las once clases distintas que existen: es el único sitio del
   backoffice donde una fila tiene una celda de acciones. */
.inactive:has(> .actions-cell) {
  opacity: 1 !important;
}
.inactive:has(> .actions-cell) > *:not(.actions-cell) {
  opacity: 0.6;
}

/* El desplegable de estado inline (Borrador/En revisión/Publicado/Programado)
   es el caso peor y NO estaba cubierto por la regla de arriba, que pedía
   `.actions-menu`: mide 142px, cuelga entero debajo de la fila y en Notas se
   salía **134px**. Lo encontró el barrido de
   `.docs/devtools/browser/desplegables-recortados.mjs`, no la lectura — es
   exactamente el mismo bug en otro control, y la razón de que ese script no
   busque clases conocidas sino cualquier flotante que se salga de su caja. */
.table-wrap:has(.status-dropdown-menu) {
  padding-bottom: 160px;
}

/* ── Encabezados que ordenan ────────────────────────────────────────────────
   Pedido: *"en todos los listados, que los titulos de las columnas de las
   tablas funcionen para ordenar los registros por esa columna"*.

   Van acá y no en cada listado por dos motivos, y el segundo es el que obliga:

   1. Son 29 listados. La convención tiene que ser una sola.
   2. **La flecha la crea `lib/ordenarTabla.js` con `createElement`, y un nodo
      creado a mano no lleva la clase de scope de Svelte** (gotcha #20): una
      regla `.th-flecha` escrita adentro de un listado no le aplicaría nunca.

   El encabezado en sí (tamaño, color, peso) lo sigue poniendo cada listado con
   su propia regla `.th`: acá solo se agrega lo que tiene que ver con ordenar.
   `background: none` y `border: 0` no hacen falta — es un `<div>` con
   `role="button"`, no un `<button>`, justamente para no tener que neutralizar
   el estilo de botón en los 29. */
.th-orden {
  cursor: pointer;
  user-select: none;
}
/* **NO se le toca el `display`.** La primera versión lo pasaba a `inline-flex`
   para acostar la flecha al lado del texto, y eso rompía la única columna
   alineada a la derecha del backoffice —«Visitas», en Notas, con
   `.th-num { text-align: right }`—: un `inline-flex` se encoge al contenido y
   deja de haber caja donde alinear. La flecha va como texto inline y el
   encabezado sigue siendo lo que cada listado dijo que era. */
.th-orden:hover {
  color: var(--accent, oklch(0.55 0.18 250));
}
.th-orden:focus-visible {
  outline: 2px solid var(--accent, oklch(0.55 0.18 250));
  outline-offset: 2px;
  border-radius: 2px;
}
.th-activo {
  color: var(--accent, oklch(0.55 0.18 250));
}
/* `inline-block` + `min-width`: el espacio de la flecha está reservado aunque
   esté vacía, así el encabezado NO cambia de ancho al elegir la columna y la
   tabla no salta. */
.th-flecha {
  display: inline-block;
  min-width: 7px;
  margin-left: 3px;
  font-size: 9px;
  line-height: 1;
  opacity: 0.85;
}

/* La columna de id de los listados. Pedido: "a todos los listados, poné el id
   de registro" — se busca un registro por su número (el bloque 39, la página
   11) y hasta ahora había que ir a buscarlo a la base.

   `tabular-nums` para que los números queden alineados entre filas y la
   columna no baile; `nowrap` porque un id partido en dos renglones no es un
   id. Va acá y no en cada listado: son 28 pantallas y la regla es una sola
   (mismo criterio que el foco de los inputs, gotcha #4). */
.td-id {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
