:root {
  --bg: #0f1115;
  --surface: #171a21;
  --surface-2: #1f232c;
  --border: #2a2f3a;
  --text: #e7e9ee;
  --muted: #9aa1ad;
  --accent: #4f8cff;
  --ok: #3fbf7f;
  --warn: #e2a03f;
  --err: #e2554f;
  --radius: 10px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f6f7f9;
    --surface: #ffffff;
    --surface-2: #f0f2f5;
    --border: #dde1e7;
    --text: #16181d;
    --muted: #626a76;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 1.25rem;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

h1 { font-size: 1.25rem; margin: 0 0 .25rem; }
h2 { font-size: 1rem; margin: 0 0 .5rem; }
h3 { font-size: .9rem; margin: 1rem 0 .35rem; }
p  { margin: .25rem 0; }
.muted { color: var(--muted); font-size: .875rem; }

header.bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

button, .btn {
  font: inherit;
  padding: .5rem .9rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button:disabled { opacity: .5; cursor: not-allowed; }
button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
button.linklike {
  border: 0;
  background: none;
  padding: .25rem .35rem;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: .2em;
}
.danger-text { color: var(--err); }

input[type="text"], input[type="password"], input[type="url"], input[type="file"],
input[type="search"], textarea, select {
  font: inherit;
  width: 100%;
  padding: .5rem .65rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: .875rem;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: visible;
  display: flex;
  flex-direction: column;
}
.card img {
  width: 100%;
  aspect-ratio: 16/9;
  object-fit: cover;
  background: var(--surface-2);
  display: block;
  border-radius: var(--radius) var(--radius) 0 0;
}
.card .body { padding: .65rem .75rem; flex: 1; }
.card .title { font-weight: 600; font-size: .95rem; overflow-wrap: anywhere; }
.card .actions {
  padding: .5rem .75rem;
  border-top: 1px solid var(--border);
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  align-items: center;
}

.collection-card { border-left: 3px solid var(--accent); }
.collection-kind {
  display: block;
  color: var(--accent);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  margin-bottom: .25rem;
}

.badge {
  display: inline-block;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: .15rem .45rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--muted);
}
.badge.ready      { color: var(--ok);   border-color: var(--ok); }
.badge.processing,
.badge.queued     { color: var(--warn); border-color: var(--warn); }
.badge.failed     { color: var(--err);  border-color: var(--err); }
.badge.cancelled  { color: var(--muted); border-color: var(--muted); }

.notice { padding: .65rem .8rem; border-radius: var(--radius); border: 1px solid var(--border); margin-bottom: 1rem; }
.notice.error { border-color: var(--err); color: var(--err); }
.notice.ok    { border-color: var(--ok);  color: var(--ok); }
.notice.info  { border-color: var(--accent); }

/* ---- Biblioteca del profesor ---- */
.eyebrow {
  margin: 0 0 .15rem;
  color: var(--accent);
  font-size: .72rem;
  font-weight: 750;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* UNA fila y nada más, igual que el visor del alumno (ADR-022/024). El alto de
   este iframe lo decide Moodle y no se puede negociar desde dentro: cada franja
   de cromo que se añada aquí sale directamente de la lista. */
body.catalog-page {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  padding: .5rem;
}

/* El aviso flota sobre el contenido en lugar de empujarlo. Va abajo porque
   arriba está la barra de comandos, que es lo único fijo que hay. */
.catalog-page #notice {
  position: fixed;
  z-index: 80;
  bottom: 1rem;
  left: 50%;
  width: min(38rem, calc(100vw - 1.5rem));
  transform: translateX(-50%);
  margin: 0;
  background: var(--surface);
  box-shadow: 0 10px 30px rgb(0 0 0 / .35);
}

/* Sólo existe como cajón: en escritorio el lateral está siempre a la vista. */
.catalog-page #places-scrim { display: none; }

.catalog-layout {
  position: relative;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(13rem, 15rem) minmax(0, 1fr);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 2px);
  overflow: hidden;
  background: var(--surface);
}

.catalog-sidebar {
  min-height: 0;
  overflow-y: auto;
  padding: .7rem .55rem;
  border-right: 1px solid var(--border);
  background: var(--surface-2);
}
.catalog-sidebar h2 {
  margin: 0;
  padding: .8rem .45rem .3rem;
  color: var(--muted);
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.library-views { display: grid; gap: .15rem; }
.library-view {
  width: 100%;
  min-width: 0;
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr) auto;
  align-items: center;
  gap: .4rem;
  padding: .42rem .5rem;
  border: 1px solid transparent;
  background: none;
  text-align: left;
}
/* `display: grid` de autor gana al `[hidden] { display: none }` del navegador:
   sin esto, «Material de este curso» se ve incluso cuando el launch no trae
   curso y su botón no lleva a ninguna parte. */
.library-view[hidden] { display: none; }
.library-view:hover:not(:disabled),
.library-view.current { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.library-view.current { border-color: color-mix(in srgb, var(--accent) 48%, var(--border)); }
.view-count, .folder-count {
  min-width: 1.35rem;
  padding: .05rem .35rem;
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: .72rem;
  text-align: center;
}
.archived-view { margin-top: .15rem; }
/* Las vistas que no son carpetas se separan del árbol con una línea: pegadas
   debajo se leían como una carpeta más de «Mis carpetas». */
.library-views-end {
  margin-top: .7rem;
  padding-top: .6rem;
  border-top: 1px solid var(--border);
}

.folder-tree { display: grid; gap: .08rem; }
.folder-card {
  min-width: 0;
  display: flex;
  align-items: center;
  padding-left: calc(var(--folder-depth, 0) * .8rem);
  border-radius: 8px;
}
.folder-card:hover,
.folder-card.current { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.folder-card.current { box-shadow: inset 2px 0 var(--accent); }
/* Desplegar y entrar son dos controles distintos, así que el triángulo tiene su
   propia zona de pulsación. `folder-twisty-space` la reserva en las hojas para
   que los nombres no bailen de sangría según tengan hijas o no. */
.folder-twisty, .folder-twisty-space {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  display: grid;
  place-items: center;
}
.folder-twisty {
  border: 0;
  padding: 0;
  background: none;
  color: var(--muted);
  font-size: .7rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 4px;
}
.folder-twisty:hover { background: var(--surface); color: var(--text); }

.folder-open {
  min-width: 0;
  flex: 1;
  border: 0;
  background: none;
  padding: .38rem .25rem .38rem .5rem;
  text-align: left;
}
.folder-label { min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: .4rem; }
.folder-text { min-width: 0; display: grid; }
.folder-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.folder-path { overflow: hidden; color: var(--muted); font-size: .68rem; text-overflow: ellipsis; white-space: nowrap; }
.folder-empty { padding: .35rem .45rem; }

.catalog-main {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
}

/* ---- La única franja de cromo ---- */
/* Por encima del cajón de ubicaciones: si el cajón la tapara, taparía también el
   botón que lo cierra. Necesita fondo propio por la misma razón. */
.catalog-bar {
  position: relative;
  z-index: 45;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .3rem;
  padding: .3rem .4rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.catalog-bar #search {
  flex: 0 1 15rem;
  min-width: 4.5rem;
  padding: .32rem .5rem;
}
.new-menu { flex: none; margin-left: .15rem; }
/* El selector va cualificado a propósito, y quitarlo rompe el botón: más abajo,
   `details.menu > summary` pinta el menú «⋯» en gris (0,1,2) y `…:hover` en gris
   sobre fondo claro (0,2,2). Un `.new-menu-trigger` a secas es (0,1,0) y pierde
   por especificidad aunque esté escrito antes, así que el botón azul salía con
   el texto oscuro y se volvía gris al pasar por encima. Cualificado gana en los
   dos casos. */
details.menu > summary.new-menu-trigger {
  white-space: nowrap;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  font-weight: 600;
}
details.menu > summary.new-menu-trigger:hover {
  background: color-mix(in srgb, #000 8%, var(--accent));
  color: #fff;
}

.crumbs {
  flex: 1 1 auto;
  min-width: 2rem;
  display: flex;
  align-items: center;
  gap: .1rem;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}
.crumbs::-webkit-scrollbar { display: none; }
.crumb { font-weight: 650; }
.crumb.current { padding: .25rem .35rem; }
.crumb-sep { color: var(--muted); }

.catalog-results { min-height: 0; overflow-y: auto; padding: 0 0 .6rem; }

/* ---- Grupos de la lista ---- */
/* Sustituyen a las pestañas y a las cabeceras de sección: una línea en lugar de
   dos franjas. Pegajosas dentro de su grupo, como en cualquier explorador. */
.group-label {
  position: sticky;
  top: 0;
  z-index: 5;
  margin: 0;
  padding: .3rem .65rem .25rem;
  background: var(--surface);
  color: var(--muted);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
}
.content-list { display: grid; }
.load-more-row { margin: .4rem 0 .2rem; text-align: center; }

/* ---- Filas densas ---- */
.catalog-page .content-list > .card {
  min-height: 2.75rem;
  display: grid;
  grid-template-columns: 3.25rem minmax(0, 1fr) auto;
  align-items: center;
  gap: .15rem;
  padding: 0 .5rem 0 0;
  border: 0;
  border-radius: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
  background: none;
  overflow: visible;
}
.catalog-page .content-list > .card:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }

/* La carpeta reutiliza su tarjeta del lateral, pero en la lista principal es una
   fila más: sin sangría de árbol y con el mismo alto que un material. */
.catalog-page .content-list > .folder-card {
  min-height: 2.75rem;
  padding-left: 0;
  padding-right: .5rem;
  border-radius: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.catalog-page .content-list > .folder-card .folder-open { padding: .45rem .25rem .45rem .55rem; }
/* El grupo ya dice que son carpetas; el glifo las distingue cuando se mezclan
   con materiales al desplazarse. Va en la etiqueta, que sí es contenedor flex. */
.catalog-page .content-list > .folder-card .folder-label::before {
  content: "▸";
  flex: none;
  color: var(--muted);
  font-size: .8rem;
}
.catalog-page .content-list > .folder-card .folder-text { flex: 1; }
.catalog-page .card img {
  width: 2.75rem;
  height: 1.6rem;
  min-height: 0;
  margin-left: .4rem;
  aspect-ratio: auto;
  border-radius: 4px;
}
.catalog-page .card .body {
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: .5rem;
  padding: .3rem .2rem;
}
.catalog-page .card .title { font-weight: 550; font-size: .9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Los metadatos ceden el sitio al título cuando la fila es estrecha. */
.catalog-page .card .body > .muted {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0;
  font-size: .78rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.catalog-page .card .badge { flex: none; font-size: .66rem; padding: .05rem .35rem; }
.catalog-page .card .collection-kind { display: none; }
.catalog-page .card .actions {
  min-width: 0;
  flex-wrap: nowrap;
  gap: .25rem;
  padding: 0;
  border: 0;
}
.catalog-page .card .actions button { white-space: nowrap; padding: .28rem .6rem; font-size: .82rem; }
.catalog-page .collection-card { border-left: 0; }
.catalog-page .content-list > .collection-card { box-shadow: inset 3px 0 var(--accent); }

/* Lo secundario aparece al apuntar o al tabular. «Insertar» no: en el selector
   de contenido es la tarea, y esconderla obligaría a cazarla fila a fila. */
.catalog-page .content-list > .card .actions > :not(.primary) { visibility: hidden; }
.catalog-page .content-list > .card:hover .actions > :not(.primary),
.catalog-page .content-list > .card:focus-within .actions > :not(.primary),
.catalog-page .content-list > .card .actions > details.menu[open] { visibility: visible; }

/* ---- Cuadrícula ---- */
.catalog-page.is-grid-view .content-list {
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: .6rem;
  padding: .5rem .65rem;
}
.catalog-page.is-grid-view .content-list > .card {
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* La fila densa centra sus columnas; aquí hay que devolver el estirado o la
     lámina y el póster se encogen al ancho de su contenido. */
  align-items: stretch;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.catalog-page.is-grid-view .card img {
  width: 100%;
  height: auto;
  margin: 0;
  aspect-ratio: 16/9;
  object-fit: cover;
  border-radius: var(--radius) var(--radius) 0 0;
}
.catalog-page.is-grid-view .card .body {
  display: grid;
  align-items: start;
  gap: .1rem;
  padding: .45rem .55rem;
}
.catalog-page.is-grid-view .card .title { white-space: normal; overflow-wrap: anywhere; }
/* En rejilla el cuerpo es una columna: sin esto el badge se estira de lado a lado. */
.catalog-page.is-grid-view .card .badge { justify-self: start; }
.catalog-page.is-grid-view .card .actions {
  flex-wrap: wrap;
  padding: .4rem .55rem;
  border-top: 1px solid var(--border);
}
.catalog-page.is-grid-view .content-list > .card .actions > :not(.primary) { visibility: visible; }
.catalog-page.is-grid-view .content-list > .folder-card { grid-column: 1 / -1; }

.collection-visual {
  display: grid;
  place-items: center;
  min-height: 5rem;
  background: color-mix(in srgb, var(--accent) 10%, var(--surface-2));
  color: var(--accent);
  font-size: 2rem;
  border-radius: var(--radius) 0 0 var(--radius);
}
/* En fila densa la lámina de la colección ocupa lo mismo que una miniatura. */
.catalog-page .content-list > .card .collection-visual {
  width: 2.75rem;
  min-height: 1.6rem;
  height: 1.6rem;
  margin-left: .4rem;
  font-size: 1rem;
  border-radius: 4px;
}
.catalog-page.is-grid-view .card .collection-visual {
  width: auto;
  height: auto;
  min-height: 5rem;
  margin: 0;
  /* Más tinte que en la fila: aquí ocupa el sitio de un póster y tiene que
     leerse como lámina, no como un hueco en blanco. */
  background: color-mix(in srgb, var(--accent) 22%, var(--surface-2));
  font-size: 1.75rem;
  border-radius: var(--radius) var(--radius) 0 0;
}

.empty-state {
  width: min(32rem, 100%);
  margin: clamp(1rem, 5vh, 2.5rem) auto;
  padding: 1.5rem;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  text-align: center;
}
.empty-state p { margin: .35rem 0 1rem; }

.help-steps { padding-left: 1.25rem; }
.help-steps li { margin: .35rem 0; }
.concept-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; margin-top: 1rem; }
.concept-cards p { display: grid; gap: .2rem; padding: .65rem; border: 1px solid var(--border); border-radius: var(--radius); }
.concept-cards span { color: var(--muted); font-size: .8rem; }

/* ---- Menú «⋯» ---- */
details.menu { position: relative; align-self: center; }
details.menu > summary {
  list-style: none;
  cursor: pointer;
  padding: .35rem .6rem;
  border-radius: var(--radius);
  font-weight: 700;
  color: var(--muted);
  user-select: none;
}
details.menu > summary::-webkit-details-marker { display: none; }
details.menu > summary:hover { background: var(--surface-2); color: var(--text); }
details.menu > .menu-list {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 30;
  min-width: 13rem;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgb(0 0 0 / .35);
  padding: .3rem;
}
details.menu > .menu-list.floating-menu {
  position: fixed;
  right: auto;
  top: 0;
  z-index: 120;
}
details.menu > .menu-list button {
  border: 0;
  background: none;
  text-align: left;
  padding: .5rem .65rem;
  border-radius: calc(var(--radius) - 4px);
}
details.menu > .menu-list button:hover { background: var(--surface-2); }

button.icon { border: 0; background: none; padding: .3rem .45rem; line-height: 1; }
button.icon:hover:not(:disabled) { background: var(--surface-2); border-radius: var(--radius); }

/* ---- Diálogos ---- */
dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  width: min(34rem, 94vw);
  max-height: 90vh;
  max-height: 90dvh;
  overflow-y: auto;
  padding: 1rem;
}
dialog::backdrop { background: rgb(0 0 0 / .5); }
dialog label { display: block; font-size: .875rem; color: var(--muted); margin-bottom: .2rem; }
.dialog-actions { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; margin-top: .8rem; }
.dialog-error {
  padding: .55rem .65rem;
  border-left: 3px solid var(--err);
  border-radius: 4px;
  background: color-mix(in srgb, var(--err) 9%, transparent);
  color: var(--err);
}

/* Importación de una carpeta: previsión antes de confirmar y progreso después. */
.import-preview {
  margin: .6rem 0;
  padding: .6rem .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.import-preview p { margin: 0; }
.import-preview details { margin-top: .5rem; }
.import-preview summary { cursor: pointer; font-size: .875rem; color: var(--muted); }
.import-skipped {
  list-style: none;
  margin: .4rem 0 0;
  padding: 0;
  max-height: 12rem;
  overflow-y: auto;
  font-size: .8125rem;
  color: var(--muted);
}
.import-skipped li { padding: .15rem 0; overflow-wrap: anywhere; }
.import-progress { margin-top: .6rem; }
.import-progress progress { width: 100%; height: .6rem; }
.import-progress p { margin: .3rem 0 0; overflow-wrap: anywhere; }

/* Informe de seguimiento. Como el editor de colecciones: ancho generoso y la
   tabla desplazándose por dentro, porque dentro del iframe de Moodle el alto
   llega recortado y «Cerrar» no puede quedar fuera de la vista. */
#report-dialog { width: min(70rem, 96vw); }
.report-tools { margin-bottom: .5rem; }
.report-tools input[type="search"] { flex: 1; min-width: 12rem; }
.report-scroll {
  max-height: clamp(12rem, 52dvh, 30rem);
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.report-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.report-table th, .report-table td {
  padding: .45rem .6rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}
.report-table th {
  position: sticky;
  top: 0;
  background: var(--surface-2);
  color: var(--muted);
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.report-table td:first-child { white-space: normal; overflow-wrap: anywhere; }
.report-identity { display: block; font-size: .78rem; }
.report-sin-acceso td { color: var(--muted); }
.report-detail { font-size: .8rem; padding: .2rem .5rem; }

/* El árbol del informe: carpeta > … > colección > materiales. Se pinta con
   <details>, así que un aula con cien materiales se puede plegar por ramas. */
.report-tree { list-style: none; margin: 0; padding-left: 0; }
.report-tree .report-tree { padding-left: 1.1rem; border-left: 1px solid var(--border); }
.report-tree-item, .report-tree-branch { padding: .2rem 0; }
.report-tree-item { display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; }
.report-tree summary { cursor: pointer; display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; }
.report-tree-kind {
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0 .35rem;
}
.report-tree .report-identity { display: inline; }

/* Lector del contrato de la API (/api/v1/docs). */
.api-description p { max-width: 70ch; }
.api-op { border-top: 1px solid var(--border); padding: .9rem 0; }
.api-op:first-of-type { border-top: 0; }
.api-op p { max-width: 70ch; }
.api-op-head { display: flex; gap: .5rem; align-items: baseline; margin: 0 0 .3rem; }
.api-method {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .06em;
  border-radius: var(--radius);
  padding: .1rem .4rem;
  border: 1px solid var(--border);
}
.api-method-get { color: var(--ok, inherit); }
.api-method-post, .api-method-put, .api-method-delete { color: var(--warn, inherit); }
.api-params p { display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; }
.api-params input { min-width: 16rem; }
.api-output {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .6rem .7rem;
  max-height: 24rem;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: .82rem;
}

#collection-dialog { width: min(62rem, 96vw); }
.collection-editor-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1rem;
  margin-top: .75rem;
}
.collection-editor-grid > section {
  min-width: 0;
  padding: .7rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}
/* Las dos columnas se desplazan por dentro. Una colección con la carpeta
   entera son decenas de filas, y dentro del iframe de Moodle —que llega ya
   recortado— dejarlas crecer empuja «Guardar» fuera de la vista. */
.collection-items, .collection-picker { max-height: clamp(10rem, 38dvh, 24rem); overflow-y: auto; }
.collection-items { padding-left: 1.25rem; margin: .25rem 0; }
.collection-items li { display: flex; align-items: center; gap: .4rem; margin: .25rem 0; }
.collection-items .item-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.collection-picker {
  list-style: none;
  margin: .25rem 0 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.collection-picker li {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .45rem .6rem;
  border-top: 1px solid var(--border);
}
.collection-picker li:first-child { border-top: 0; }
.collection-picker .item-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }

/* El selector es un árbol de carpetas, no una lista plana: la sangría es lo
   único que dice de qué carpeta cuelga cada material. */
.collection-picker li { padding-left: calc(.6rem + var(--picker-depth, 0) * .9rem); }
.picker-folder { background: var(--surface); }
.picker-folder-toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  border: 0;
  background: none;
  padding: .1rem 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.picker-folder-name { font-weight: 600; overflow-wrap: anywhere; }
.picker-twisty { flex: none; width: 1rem; color: var(--muted); font-size: .7rem; }
.picker-add-all { flex: none; }
.picker-note { display: block; }

#revision-list { list-style: none; padding: 0; }
#revision-list li {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .5rem 0; border-top: 1px solid var(--border);
}
#revision-list li span:first-child { flex: 1; }

/* ---- Visores del alumno ---- */
/* Dos filas: una de cromo y todo lo demás para el contenido. Cada franja extra
   sale del alto de lo que el alumno ha venido a estudiar, y dentro del iframe
   de Moodle ese alto ya viene recortado de casa. */
body.viewer {
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: .45rem;
  padding: .5rem;
}

.viewer-topbar {
  min-width: 0;
  display: flex;
  align-items: stretch;
  gap: .45rem;
}
.viewer-topbar .back-to-classroom {
  flex: none;
  padding: .3rem .65rem;
  font-size: .82rem;
  white-space: nowrap;
}

/* El aviso legal completo vive en un diálogo; lo que no puede desaparecer nunca
   es la advertencia de que la sesión está identificada, y eso es este chip. */
.legal-chip {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .65rem;
  text-align: left;
  border-color: color-mix(in srgb, var(--warn) 62%, var(--border));
  background: color-mix(in srgb, var(--warn) 16%, var(--surface));
  color: var(--text);
  font-size: .78rem;
  line-height: 1.3;
}
.legal-chip:hover { background: color-mix(in srgb, var(--warn) 26%, var(--surface)); }
.legal-chip-icon { flex: none; color: var(--warn); font-size: 1rem; line-height: 1; }
.legal-chip strong { flex: none; white-space: nowrap; }
.legal-chip-id {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.legal-chip-id::before { content: "· "; }
.legal-chip-cta {
  flex: none;
  margin-left: auto;
  padding-left: .5rem;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: .2em;
}

.topbar-icon {
  flex: none;
  width: 2.2rem;
  min-width: 2.2rem;
  padding: .38rem;
  display: grid;
  place-items: center;
  color: var(--muted);
}
.topbar-icon svg { display: block; width: 100%; height: 100%; fill: currentColor; }
.topbar-icon[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

.viewer-shell {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(13rem, 15rem) minmax(0, 1fr);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 2px);
  background: var(--surface);
}
/* Plegado: fuera del árbol de accesibilidad, no sólo fuera de la vista. */
body.is-panel-hidden .viewer-shell { grid-template-columns: minmax(0, 1fr); }
body.is-panel-hidden .viewer-sidebar { display: none; }

.viewer-sidebar {
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  padding: 0;
  border-right: 1px solid var(--border);
  background: var(--surface-2);
}
.viewer-sidebar > * + * { border-top: 1px solid var(--border); }
/* El título del material encabeza su panel: en la barra superior repetía lo que
   Moodle ya escribe encima del iframe y le quitaba una fila al contenido. */
.sidebar-heading { flex: none; padding: .6rem .7rem; }
.sidebar-heading .eyebrow { margin: 0 0 .1rem; }
.sidebar-heading h1 { margin: 0; font-size: 1rem; line-height: 1.3; overflow-wrap: anywhere; }
.sidebar-heading #description { margin: .2rem 0 0; font-size: .78rem; }
.resource-actions { flex: none; padding: .7rem; }
.sidebar-action { width: 100%; text-align: left; }
.viewer-sidebar .notice { margin: .8rem 0 0; font-size: .78rem; }
.viewer-status { margin-top: .8rem; overflow-wrap: anywhere; }
.error-text { color: var(--err) !important; }
.viewer-main {
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  padding: .65rem;
}
.viewer-content { width: 100%; height: 100%; min-height: 0; margin: 0 auto; }

.legal-dialog { width: min(44rem, 94vw); }
.legal-copy p { margin: 0 0 .6rem; font-size: .88rem; line-height: 1.5; }
.legal-copy p:last-child { margin-bottom: 0; }
.legal-audit {
  display: grid;
  grid-template-columns: minmax(8rem, auto) minmax(0, 1fr);
  gap: .3rem .9rem;
  margin: .35rem 0 0;
  font-size: .84rem;
}
.legal-audit dt { color: var(--muted); }
.legal-audit dd { margin: 0; overflow-wrap: anywhere; }
/* El aviso es largo y el iframe de Moodle puede ser bajo: la salida no puede
   quedar por debajo del pliegue, o el alumno se queda encerrado en el texto. */
.legal-dialog .dialog-actions {
  position: sticky;
  bottom: -1rem;
  margin: .8rem -1rem -1rem;
  padding: .75rem 1rem;
  border-top: 1px solid var(--border);
  background: var(--surface);
}

/* ---- Reproductor de vídeo ---- */
.video-view {
  position: relative;
  container: video-player / inline-size;
  width: 100%;
  height: 100%;
  min-height: 0;
  display: block;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  outline: none;
}
.video-view:focus-visible {
  box-shadow: inset 0 0 0 2px var(--accent);
}
/* Bloque, no rejilla. Con `display: grid` la fila implícita `auto` crecía hasta
   el alto intrínseco del vídeo (ancho ÷ relación de aspecto): el `height: 100%`
   del <video> se resuelve contra un área de rejilla que aún no existe al
   dimensionar las pistas, y el `overflow: hidden` recortaba por abajo todo lo
   que no fuera 16:9 (con una grabación de iPad, 1,43:1, el 22 % del fotograma).
   Con la caja definida, `object-fit: contain` centra y encaja él solo; los demás
   hijos son absolutos y `place-items` no hacía nada. Lo mide de verdad
   test/video-stage-layout.test.js. */
.video-stage {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  display: block;
  overflow: hidden;
  background: #000;
}
.video-stage video { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; }
.watermark {
  position: absolute;
  pointer-events: none;
  user-select: none;
  z-index: 5;
  color: #fff;
  opacity: .38;
  font: 600 clamp(11px, 1.4vw, 16px)/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .9);
  white-space: nowrap;
}
.video-watermark {
  /* Fallback por viewport y escala final por el ancho real del reproductor. */
  max-width: calc(96% - var(--watermark-left, 0%));
  overflow: hidden;
  font-size: clamp(18px, 2vw, 30px);
  font-size: clamp(18px, 3.2cqw, 30px);
  font-weight: 750;
  letter-spacing: .035em;
  text-overflow: ellipsis;
  text-transform: uppercase;
}
.video-loader {
  position: absolute;
  z-index: 8;
  top: 50%;
  left: 50%;
  width: 2.25rem;
  height: 2.25rem;
  margin: -1.125rem 0 0 -1.125rem;
  border: 3px solid rgb(255 255 255 / .28);
  border-top-color: #fff;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s ease;
}
.video-view.is-buffering .video-loader {
  opacity: 1;
  animation: video-spin .8s linear infinite;
}
@keyframes video-spin { to { transform: rotate(360deg); } }

.video-icon-button.video-center-play {
  position: absolute;
  z-index: 7;
  top: 50%;
  left: 50%;
  width: 3.75rem;
  height: 3.75rem;
  min-width: 3.75rem;
  padding: 1rem;
  transform: translate(-50%, -50%);
  border: 1px solid rgb(255 255 255 / .24);
  border-radius: 50%;
  background: rgb(8 10 14 / .72);
  color: #fff;
  box-shadow: 0 8px 28px rgb(0 0 0 / .35);
  transition: opacity .16s ease, transform .16s ease, background .16s ease;
}
.video-icon-button.video-center-play:hover:not(:disabled) {
  transform: translate(-50%, -50%) scale(1.06);
  background: rgb(18 22 29 / .9);
}
.video-icon-button.video-center-play svg { width: 100%; height: 100%; fill: currentColor; }
.video-view.is-playing .video-center-play,
.video-view.has-started:not(.has-ended) .video-center-play {
  visibility: hidden;
  opacity: 0;
}

.video-controls {
  --video-accent: #4f8cff;
  position: absolute;
  z-index: 10;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 3rem clamp(.55rem, 1.5vw, 1rem) .45rem;
  background: linear-gradient(to bottom, transparent, rgb(0 0 0 / .78) 58%, rgb(0 0 0 / .92));
  color: #fff;
  transition: opacity .2s ease;
  /* El degradado (padding-top 3rem) no intercepta clics ni cuenta como hover: la
     línea de tiempo y la fila de botones recuperan pointer-events justo debajo.
     El padding se queda porque .video-time se apoya en él en huecos ≤25rem. */
  pointer-events: none;
}
.video-controls > * { pointer-events: auto; }
/* Reproduciendo, la barra se retira sola (ADR-033). Sólo opacity + pointer-events:
   sigue en el árbol de accesibilidad y en el orden de tabulación; nunca
   visibility:hidden ni display:none. La marca de agua no entra aquí. */
.video-view.is-idle .video-controls { opacity: 0; }
.video-view.is-idle .video-controls,
.video-view.is-idle .video-controls > * { pointer-events: none; }
/* Cinturón: aunque el JS fallara, un control con foco de teclado nunca queda invisible. */
.video-view.is-idle .video-controls:has(:focus-visible) { opacity: 1; }
/* En el root y no en el escenario, para que cubra también :fullscreen. */
.video-view.is-idle { cursor: none; }
.video-timeline {
  --played: 0%;
  --buffered: 0%;
  display: block;
  width: 100%;
  height: 1rem;
  margin: 0;
  padding: .375rem 0;
  appearance: none;
  border: 0;
  border-radius: 0;
  background: transparent;
  cursor: pointer;
}
.video-timeline::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 999px;
  background: linear-gradient(to right,
    var(--video-accent) 0,
    var(--video-accent) var(--played),
    rgb(255 255 255 / .5) var(--played),
    rgb(255 255 255 / .5) var(--buffered),
    rgb(255 255 255 / .25) var(--buffered),
    rgb(255 255 255 / .25) 100%);
}
.video-timeline::-moz-range-track {
  height: 4px;
  border: 0;
  border-radius: 999px;
  background: linear-gradient(to right,
    var(--video-accent) 0,
    var(--video-accent) var(--played),
    rgb(255 255 255 / .5) var(--played),
    rgb(255 255 255 / .5) var(--buffered),
    rgb(255 255 255 / .25) var(--buffered),
    rgb(255 255 255 / .25) 100%);
}
.video-timeline::-webkit-slider-thumb {
  width: 13px;
  height: 13px;
  margin-top: -4.5px;
  appearance: none;
  border: 0;
  border-radius: 50%;
  background: var(--video-accent);
  box-shadow: 0 1px 5px rgb(0 0 0 / .55);
  transform: scale(.85);
  transition: transform .12s ease;
}
.video-timeline::-moz-range-thumb {
  width: 13px;
  height: 13px;
  border: 0;
  border-radius: 50%;
  background: var(--video-accent);
  box-shadow: 0 1px 5px rgb(0 0 0 / .55);
}
.video-timeline:hover::-webkit-slider-thumb,
.video-timeline:focus-visible::-webkit-slider-thumb,
.video-view.is-scrubbing .video-timeline::-webkit-slider-thumb { transform: scale(1); }
.video-timeline:disabled { cursor: wait; opacity: .55; }

.video-control-row,
.video-control-group,
.video-volume-group {
  display: flex;
  align-items: center;
}
.video-control-row { justify-content: space-between; gap: .5rem; min-width: 0; }
.video-control-group { gap: .12rem; min-width: 0; }
.video-volume-group { gap: 0; }
.video-icon-button {
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  min-width: 2.5rem;
  padding: .55rem;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: #fff;
  line-height: 1;
  transition: background .12s ease, transform .12s ease;
}
.video-icon-button:hover:not(:disabled),
.video-icon-button[aria-pressed="true"] { background: rgb(255 255 255 / .14); }
.video-icon-button:active:not(:disabled) { transform: scale(.92); }
.video-icon-button:disabled { opacity: .35; }
.video-icon-button[data-unavailable="true"] { opacity: .55; }
.video-icon-button svg { display: block; width: 100%; height: 100%; fill: currentColor; }
.video-volume {
  width: clamp(3.5rem, 8vw, 5rem);
  height: 1.25rem;
  margin: 0 .25rem 0 -.1rem;
  accent-color: #fff;
  cursor: pointer;
}
.video-time {
  margin-left: .35rem;
  color: rgb(255 255 255 / .9);
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  white-space: nowrap;
}
.video-view:fullscreen,
.video-view:-webkit-full-screen { width: 100%; height: 100%; border-radius: 0; }

@media (prefers-reduced-motion: reduce) {
  .video-view.is-buffering .video-loader { animation: none; }
  /* La barra se sigue retirando; sólo se va el fundido. */
  .video-controls,
  .video-center-play,
  .video-timeline::-webkit-slider-thumb,
  .video-icon-button { transition: none; }
}

/* ---- Visor de colección ---- */
/* Encoge y hace scroll ella sola cuando la lista es larga, pero con dos
   materiales no separa la navegación del índice con medio panel vacío. */
.collection-index { flex: 0 1 auto; min-height: 0; overflow-y: auto; padding: .65rem; }
.collection-index ol { list-style: none; margin: 0; padding: 0; }
.index-item {
  display: flex;
  gap: .5rem;
  width: 100%;
  text-align: left;
  border: 1px solid transparent;
  background: none;
  padding: .5rem;
  border-radius: var(--radius);
}
.index-item:hover:not(:disabled) { background: var(--surface); }
.index-item.current { background: var(--surface); border-color: var(--accent); }
.index-label { display: flex; flex-direction: column; min-width: 0; }
.index-title { font-weight: 600; font-size: .9rem; overflow-wrap: anywhere; }
.kind-badge { flex: none; width: 1.5rem; text-align: center; }
/* Anterior/Siguiente viven junto a la lista que ya dice dónde estás, no bajo el
   contenido: allí duplicaban el índice y costaban una fila entera de alto. */
.collection-nav {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .4rem .65rem;
}
.nav-step { padding: .25rem .7rem; font-size: .9rem; line-height: 1.4; }
.only-mobile { display: none; }

/* ---- Visor de PDF ---- */
.pdf-stage {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  background: var(--surface-2);
  border-radius: var(--radius);
  overflow: hidden;
}
.pdf-pages {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  padding: .75rem;
  scroll-behavior: smooth;
}
.pdf-page {
  position: relative;
  overflow: hidden;
  background: #fff;
  margin: 0 auto .75rem;
  max-width: min(100%, 1080px);
  min-height: 40vh;
  border-radius: 4px;
  box-shadow: 0 1px 4px rgb(0 0 0 / .35);
}
.pdf-page canvas { display: block; border-radius: 4px; }
/*
 * Marca de fondo con la identidad, repetida sobre toda la hoja.
 *
 * El equilibrio que se busca: invisible de facto al leer, presente en una foto
 * del monitor. Dos mandos, y este es sólo uno: cuánto se ve —--pdf-mark-alpha,
 * aquí— y cada cuántos píxeles se repite, que vive en `pdfMarkTile` de
 * `pdf-mark.js`. Por debajo de .10 la compresión de una cámara de móvil se la
 * puede comer, y por encima de .18 empieza a estorbar sobre texto pequeño.
 *
 * Se sirve en el suelo de esa banda: con .13 los apuntes de texto denso se leían
 * peor de lo aceptable en pantalla. Bajar más exige salir de la banda, y eso ya
 * no es un retoque de CSS sino renunciar a que la marca sobreviva a una foto.
 *
 * Negro a baja opacidad en vez de un gris fijo: así el tono lo pone la propia
 * página y la marca funciona igual sobre blanco que sobre un PDF de fondo claro
 * con color.
 */
.pdf-page-mark {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  color: #000;
  opacity: var(--pdf-mark-alpha, .10);
  pointer-events: none;
  user-select: none;
}
.pdf-page-mark text {
  font: 700 15px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .08em;
  text-transform: uppercase;
}
/* Si el usuario pide más contraste, la marca no debe ser la que se lo quite: por
   debajo del suelo de la banda, asumiendo que ahí ya puede no salir en la foto. */
@media (prefers-contrast: more) {
  .pdf-page-mark { opacity: .07; }
}
.pdf-page-legal {
  position: absolute;
  z-index: 3;
  top: 50%;
  right: 2px;
  max-height: calc(100% - 12px);
  overflow: hidden;
  color: #444;
  font: 600 6px/1.05 Arial, sans-serif;
  letter-spacing: .015em;
  writing-mode: vertical-rl;
  transform: translateY(-50%) rotate(180deg);
  white-space: nowrap;
  opacity: .9;
}
/*
 * La etiqueta flotante hereda de .watermark el blanco con sombra dura, que está
 * pensado para leerse sobre vídeo. Sobre una hoja blanca lo que se ve es la
 * sombra, y cae justo encima de un renglón: al .32 del vídeo tapaba la línea.
 * Aquí basta con insinuarla, porque debajo ya está la marca repetida.
 */
.pdf-watermark { opacity: .18; }

/* ---- Móvil ---- */
@media (max-width: 720px) {
  body { padding: .75rem; }

  body.viewer { padding: .4rem; gap: .35rem; }
  /* La barra sigue siendo UNA fila: partirla en dos es justo lo que sobraba. */
  .viewer-topbar { gap: .3rem; }
  .viewer-topbar .back-to-classroom { padding: .3rem .45rem; }
  .legal-chip { gap: .3rem; padding: .28rem .45rem; font-size: .72rem; }
  .legal-chip-cta { padding-left: .35rem; }
  .topbar-icon { width: 2rem; min-width: 2rem; }
  .viewer-shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  body.is-panel-hidden .viewer-shell { grid-template-rows: minmax(0, 1fr); }
  .viewer-sidebar {
    max-height: 13rem;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .sidebar-heading { padding: .4rem .5rem; }
  .sidebar-heading h1 { font-size: .92rem; }
  .sidebar-heading .eyebrow,
  .sidebar-heading #description { display: none; }
  .viewer-main { padding: .4rem; }

  /* El índice ya es un desplegable en móvil: las flechas caben a sus lados y la
     banda superior deja de llevarse media pantalla del alumno. */
  .collection-sidebar {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "titulo titulo titulo"
      "antes  indice despues"
      "acciones acciones acciones";
    align-items: center;
    max-height: none;
  }
  .collection-sidebar > * + * { border-top: 0; }
  .collection-sidebar .sidebar-heading {
    grid-area: titulo;
    border-bottom: 1px solid var(--border);
  }
  .collection-sidebar .collection-index { grid-area: indice; padding: .35rem .3rem; }
  .collection-sidebar .collection-index label { font-size: .72rem; }
  .collection-sidebar .collection-nav { display: contents; }
  .collection-sidebar #prev { grid-area: antes; margin-left: .45rem; }
  .collection-sidebar #next { grid-area: despues; margin-right: .45rem; }
  /* El desplegable ya numera los materiales: repetir «1 de 2» sólo ocupa. */
  .collection-sidebar #position { display: none; }
  .collection-sidebar .resource-actions {
    grid-area: acciones;
    border-top: 1px solid var(--border);
  }
  .resource-actions {
    display: grid;
    grid-template-columns: minmax(8rem, auto) minmax(0, 1fr);
    align-items: center;
    gap: .3rem .55rem;
    padding: .5rem;
  }
  .resource-actions .notice,
  .resource-actions .viewer-status { grid-column: 1 / -1; margin: .2rem 0 0; }
  .only-mobile { display: block; }
  .only-desktop { display: none; }
  .video-controls { padding: 2.6rem .4rem .3rem; }
  .video-control-row { gap: .2rem; }
  .video-control-group { gap: 0; }
  .video-volume { width: 3rem; }
  .video-time { margin-left: .2rem; font-size: .7rem; }
  .video-icon-button {
    width: 2.35rem;
    height: 2.35rem;
    min-width: 2.35rem;
    padding: .48rem;
  }
  .video-icon-button.video-center-play {
    width: 3.25rem;
    height: 3.25rem;
    min-width: 3.25rem;
    padding: .82rem;
  }
  .pdf-pages { max-height: none; padding: .4rem; }

  body.catalog-page {
    height: 100vh;
    height: 100dvh;
    padding: .35rem;
  }
  .catalog-bar { gap: .2rem; padding: .25rem .3rem; }
  .catalog-bar #search { flex: 1 1 5rem; }
  .catalog-bar #refresh { display: none; }

  /* El lateral pasa a ser un cajón sobre la lista. La banda horizontal que había
     antes se llevaba 10,5rem de alto en el sitio donde menos hay. */
  .catalog-layout { grid-template-columns: minmax(0, 1fr); }
  .catalog-sidebar {
    position: absolute;
    z-index: 40;
    inset-block: 0;
    left: 0;
    width: min(15rem, 82%);
    /* La barra queda por encima del cajón: sin este hueco, la primera ubicación
       nace debajo de ella y no se ve. */
    padding-top: calc(var(--catalog-bar-h, 3rem) + .5rem);
    border-right: 1px solid var(--border);
    box-shadow: 6px 0 24px rgb(0 0 0 / .35);
    transform: translateX(-102%);
    transition: transform .18s ease;
  }
  body.is-places-open .catalog-sidebar { transform: none; }
  #places-scrim:not([hidden]) {
    display: block;
    position: absolute;
    z-index: 35;
    inset: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: rgb(0 0 0 / .45);
  }

  .catalog-page .card .body { flex-wrap: wrap; gap: 0 .45rem; }
  .collection-editor-grid { grid-template-columns: minmax(0, 1fr); }
  .concept-cards { grid-template-columns: minmax(0, 1fr); }

  /* 16px en los campos: por debajo, iOS hace zoom automático al enfocar. */
  input[type="text"], input[type="password"], input[type="url"], input[type="file"],
  input[type="search"], textarea, select { font-size: 16px; }

  .toolbar { flex-direction: column; align-items: stretch; }
  .toolbar input[type="search"] { min-width: 0; width: 100%; }
  .toolbar-actions { justify-content: stretch; }
  .toolbar-actions button { flex: 1; }

  .grid { grid-template-columns: 1fr; }
  .folder-grid { grid-template-columns: 1fr; }

  /* Diálogos como hoja inferior: llegan con el pulgar y no bailan al escribir. */
  dialog {
    width: 100vw;
    max-width: 100vw;
    margin: auto 0 0;
    border-radius: 14px 14px 0 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    max-height: 94dvh;
  }
  .dialog-actions button { flex: 1; min-width: 8rem; }

  .collection-nav button { min-height: 44px; }
}

@media (max-width: 560px) {
  .video-volume { display: none; }
}

/* En un móvil no caben advertencia, identidad y llamada: la identidad es la que
   se puede leer entera en el diálogo, y además sigue sobre el propio contenido. */
@media (max-width: 480px) {
  .legal-chip-id { display: none; }
}

@media (max-width: 400px) {
  .video-controls { padding: 3.25rem .15rem .3rem; }
  .video-control-row { gap: 0; }
  .video-time {
    position: absolute;
    top: 2rem;
    left: .55rem;
    bottom: auto;
    margin: 0;
  }
}

/* El reproductor puede ser estrecho aunque el viewport sea ancho: el menú de
   Moodle y la colección también consumen espacio. Estas reglas miran el ancho
   real del vídeo, no sólo el de la ventana. */
@container video-player (max-width: 34rem) {
  .video-volume { display: none; }
}

@container video-player (max-width: 25rem) {
  .video-controls { padding: 3.25rem .15rem .3rem; }
  .video-control-row { gap: 0; }
  .video-time {
    position: absolute;
    top: 2rem;
    left: .55rem;
    bottom: auto;
    margin: 0;
  }
}

/* Objetivos táctiles cómodos en cualquier pantalla táctil. */
@media (pointer: coarse) {
  button, .btn { min-height: 44px; }
  button.icon, .topbar-icon, details.menu > summary { min-height: 44px; min-width: 44px; }
  .index-item { min-height: 44px; }
  /* En el árbol, ancho suficiente para acertar sin abrir la carpeta sin querer. */
  .folder-twisty { min-width: 2.2rem; }
  .video-icon-button { min-width: 44px; min-height: 44px; }
  .video-timeline { height: 2rem; padding: .875rem 0; }
  .video-volume { display: none; }
}

/* ---- Consola de administración ---- */
.admin { max-width: 1440px; margin: 0 auto; }
.admin-main { min-width: 0; }
.admin-bar nav, .admin-bar nav form, .form-actions { display: flex; gap: .65rem; align-items: center; }
.admin a { color: var(--accent); }
.admin .btn { text-decoration: none; display: inline-block; }
.admin .eyebrow { color: var(--accent); font-size: .75rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
/* Qué compilación se está mirando. Discreta pero legible sin buscarla: es lo
   que distingue test de producción y una versión de la anterior. No aparece en
   la vista de Moodle —ahí el espacio es del material— ni en el login, que es
   deliberadamente anónimo. */
.admin .build { color: var(--muted); font-size: .75rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .02em; white-space: nowrap; }
.admin .build-env { color: var(--accent); text-transform: uppercase; letter-spacing: .06em; }
.admin .build-env:empty { display: none; }
.login-page { min-height: 100vh; display: grid; place-items: center; }
.login-card { width: min(100%, 390px); padding: 1.5rem; }
.login-card form { display: grid; gap: 1rem; margin-top: 1.25rem; }
.admin label { display: grid; gap: .35rem; font-weight: 600; }
.filters { display: flex; gap: 1rem; margin-bottom: 1rem; }
.table-wrap { overflow-x: auto; }
.admin table { width: 100%; border-collapse: collapse; }
.admin th, .admin td { padding: .65rem; border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
.admin th { color: var(--muted); font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; }
.state-ok { color: var(--ok); }
.state-off { color: var(--muted); }
.form-layout { display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr); gap: 1rem; }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.field-grid .wide { grid-column: 1 / -1; }
.confirm { grid-template-columns: auto 1fr; align-items: start; font-weight: 400 !important; }
.danger { color: #fff; background: var(--err); border-color: var(--err); }
.notice.warning { border-color: var(--warn); color: var(--warn); }
.admin dl { margin: 1rem 0 0; }
.admin dt { color: var(--muted); font-size: .78rem; margin-top: .8rem; }
.admin dd { margin: .2rem 0; overflow-wrap: anywhere; }
.admin code { font-size: .82rem; }
@media (max-width: 760px) {
  .form-layout, .field-grid { grid-template-columns: 1fr; }
  .field-grid .wide { grid-column: auto; }
}
