:root {
  --bg:         #0b0908;
  --surface:    #16120f;
  --surface-2:  #1e1915;
  --surface-3:  #29221c;
  --border:     rgba(238, 214, 190, .075);
  --border-2:   rgba(238, 214, 190, .17);
  --text:       #f4ede5;
  --text-2:     #b3a599;
  --text-3:     #7f7367;
  --accent:     #e0a27a;
  --accent-2:   rgba(224, 162, 122, .14);
  --red:        #e2533e;
  --green:      #8cb87e;
  --amber:      #e8b061;
  --sans: "Inter", -apple-system, "Segoe UI", Roboto, sans-serif;
  --serif: "Newsreader", Georgia, "Times New Roman", serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background:
    radial-gradient(125% 58% at 50% -14%, #1b1511 0%, var(--bg) 62%)
    var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

::selection { background: rgba(224, 162, 122, .26); }

a { color: inherit; }

[hidden] { display: none !important; }

.container {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 20px 56px;
}

body.viewer {
  overflow: hidden;
  overscroll-behavior: none;
}

body.viewer .app {
  --pad: 12px;
  --gap: 10px;
  --vidmax: 44dvh;
  --ar: 1.7778;
  height: 100vh;
  height: 100dvh;
  display: grid;
  gap: var(--gap);
  /* Instalada en la pantalla de inicio no hay barra del navegador: la
     hora y la batería del teléfono quedan encima de la página. Este
     padding baja el contenido justo lo que mide esa franja. En el
     navegador el valor es 0 y no cambia nada. */
  padding-top: env(safe-area-inset-top, 0px);
  padding-bottom: max(var(--pad), env(safe-area-inset-bottom));
  padding-left: max(var(--pad), env(safe-area-inset-left));
  padding-right: max(var(--pad), env(safe-area-inset-right));
}

body.viewer .stage,
body.viewer .rail { display: contents; }

body.viewer .topbar   { grid-area: head; }
body.viewer .player   { grid-area: video; }
body.viewer .estado   { grid-area: estado; }
body.viewer .tabs     { grid-area: tabs; }
body.viewer #paneTele { grid-area: tele; }
body.viewer #paneAlt  { grid-area: grow; }

body.viewer .card,
body.viewer .telemetry {
  margin: 0;
  box-shadow: inset 0 1px 0 rgba(255, 236, 218, .035);
}

body.viewer .pane {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
body.viewer .card.pane { padding: 11px 13px 12px; }
body.viewer .pane > h2 { margin: 0 0 8px; }

body.viewer .estado {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 15px;
}
body.viewer .estado .phase { padding: 0; min-width: 0; }

/* La fase y, abajo, el reloj de misión. El reloj vivía en la barra de
   arriba y en un teléfono angosto la mandaba a dos renglones; acá está al
   lado de lo que mide y se lee mejor. */
body.viewer .estado .phase {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 10px;
  row-gap: 1px;
}
body.viewer .estado .met {
  grid-column: 2;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
body.viewer .estado .ptext {
  font-size: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
body.viewer .estado .bigmetric {
  border-top: 0;
  padding-top: 0;
  flex: none;
  text-align: right;
}
body.viewer .estado .bk { font-size: 10px; }
body.viewer .estado .bv { font-size: clamp(22px, 6.5vw, 30px); margin-top: 0; }

body.viewer .tabs {
  display: none;
  gap: 4px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
}
body.viewer .tabs button {
  position: relative;
  flex: 1;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-3);
  background: transparent;
  border: 0;
  border-radius: 10px;
  padding: 8px 6px;
  min-height: 38px;
  cursor: pointer;
  transition: background .15s, color .15s;
}
body.viewer .tabs button[aria-selected="true"] {
  background: var(--surface-3);
  color: var(--text);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .4), inset 0 1px 0 rgba(255, 236, 218, .05);
}
body.viewer .tabs button.hasnew::after {
  content: "";
  position: absolute;
  top: 6px; right: 8px;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

@media (max-width: 1023px), (max-height: 620px) {
  body.viewer .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto auto minmax(0, 1fr);
    grid-template-areas:
      "head"
      "video"
      "estado"
      "tabs"
      "tele"
      "grow";
  }
  body.viewer .player {
    width: min(100%, calc(var(--vidmax) * var(--ar)));
    max-height: var(--vidmax);
    justify-self: center;
  }
  body.viewer .pane:not(.on) { display: none; }

  body.viewer #paneTele .telegrid {
    flex: none;
    grid-auto-rows: minmax(54px, auto);
  }
}

@media (min-width: 520px) and (max-height: 620px) {
  body.viewer .app {
    --vidmax: 100dvh;
    grid-template-columns: minmax(0, 1fr) clamp(230px, 34%, 340px);
    grid-template-rows: auto auto auto minmax(0, 1fr);
    grid-template-areas:
      "head   head"
      "video  estado"
      "video  tabs"
      "video  tele";
  }
  body.viewer #paneAlt { grid-area: tele; }
  body.viewer .tabs { display: flex; }
  body.viewer .player {
    width: auto;
    height: 100%;
    max-width: 100%;
    max-height: none;
  }
  body.viewer #paneTele .telegrid {
    flex: 1 1 auto;
    grid-auto-rows: minmax(0, 1fr);
  }
  /* Con el teléfono acostado no hay alto para celdas de dos pisos (se
     cortaban las etiquetas y la última fila): cada dato va en un renglón,
     etiqueta a la izquierda y valor a la derecha. El título de la tarjeta
     se va porque la pestaña de arriba ya dice "Datos". */
  body.viewer .estado { padding: 8px 13px; gap: 10px; }
  body.viewer .estado .bk { font-size: 9.5px; }
  body.viewer .estado .bv { font-size: clamp(18px, 5.4vh, 25px); }
  body.viewer #paneTele .head .title { display: none; }
  body.viewer #paneTele .telegrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  body.viewer #paneTele .tele { padding: 2px 11px; }
  body.viewer #paneTele .tele .k { font-size: 8.5px; letter-spacing: .07em; }
  body.viewer #paneTele .tele .v { margin-top: 1px; font-size: clamp(13px, 3.9vh, 17px); line-height: 1.2; }
  body.viewer #paneTele .tele .v.texto { font-size: 12px; white-space: nowrap; }
}

@media (min-width: 1024px) and (min-height: 621px) {
  body.viewer .app {
    --pad: 16px;
    --gap: 12px;
    max-width: 1720px;
    margin: 0 auto;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "head"
      "stage"
      "tele";
  }
  body.viewer .stage {
    display: grid;
    grid-area: stage;
    grid-template-columns: minmax(0, 1fr) clamp(290px, 23vw, 350px);
    gap: var(--gap);
    min-height: 0;
  }
  body.viewer .rail {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
    min-width: 0;
    min-height: 0;
  }
  body.viewer .player,
  body.viewer .estado,
  body.viewer .rail > .pane { grid-area: auto; }
  body.viewer .telemetry.pane { grid-area: tele; }

  body.viewer .player {
    height: 100%;
    width: auto;
    max-width: 100%;
    justify-self: center;
  }

  body.viewer .tabs { display: none; }
  body.viewer .pane { display: flex; }

  body.viewer .estado { display: block; padding: 15px 17px; }
  body.viewer .estado .phase { padding: 0 0 11px; }
  body.viewer .estado .ptext { font-size: 17px; }
  body.viewer .estado .bigmetric { border-top: 1px solid var(--border); padding-top: 11px; text-align: left; }
  body.viewer .estado .bk { font-size: 11px; }
  body.viewer .estado .bv { font-size: clamp(28px, 3.4vh, 38px); }

  body.viewer #paneAlt { flex: 1 1 auto; }
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 0 16px;
  flex-wrap: wrap;
}

/* La barra pasa a dos renglones sola cuando no entra en uno, y recién ahí.
   Es la única salida que no le saca nada al operador: adentro no hay
   adorno —reloj de misión, espectadores, consola, compartir y estado— y
   esconder cualquiera de esos ya nos costó un enojo. El renglón de más lo
   paga la fila "grow" de la grilla, que es elástica, así que la página
   sigue entrando sin scroll. */
body.viewer .topbar {
  padding: 10px 0 2px;
  gap: 8px;
  flex-direction: row;
  flex-wrap: wrap;
  min-width: 0;
}
body.viewer .topbar .brand { flex: 0 0 auto; }
body.viewer .brand .name { font-size: 21px; }
body.viewer .statusgroup {
  flex: 1 1 auto;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
  min-width: 0;
}
body.viewer .conslink { text-decoration: none; }

@media (max-width: 860px) {
  body.viewer .brand .tag { display: none; }
}
@media (max-width: 520px) {
  body.viewer .brand .mark { width: 26px; height: 26px; }
  body.viewer .brand .name { font-size: 19px; }
}
@media (max-width: 470px) {
  body.viewer .topbar { gap: 6px; }
  body.viewer .statusgroup { gap: 6px; }
  body.viewer .brand { gap: 7px; }
  body.viewer .live-badge { padding: 5px 10px; font-size: 11.5px; }
  body.viewer .statusgroup .metric { font-size: 12px; }
}
/* Abajo de 400 px la barra entra justo. Nada de lo que hay ahí se puede
   esconder —el reloj, la cuenta de espectadores, los dos botones y la
   chapa de estado son función, no adorno—, así que lo que cede es el aire
   entre ellos. Medido con viewport real (sin shrink-to-fit, que tapa el
   desborde ensanchando el viewport en vez de mostrarlo). */
@media (max-width: 400px) {
  body.viewer .topbar { gap: 4px; }
  body.viewer .statusgroup { gap: 4px; }
  body.viewer .brand { gap: 5px; }
  body.viewer .statusgroup .metric { font-size: 11.5px; }
  body.viewer .live-badge { padding: 5px 9px; font-size: 11px; gap: 6px; }
  body.viewer #shareBtn,
  body.viewer .conslink { min-width: 36px; padding: 6px 5px; }
}
/* Abajo de 386 px el emblema es justo lo que empuja la barra al segundo
   renglón, y ese renglón sale del video. Se va el emblema y queda el
   nombre, que es lo que se lee: en un iPhone SE o un 13 mini la barra
   vuelve a entrar en una sola línea. El emblema sigue estando en grande
   en la pantalla de espera. */
@media (max-width: 385px) {
  body.viewer .brand .mark { display: none; }
}
@media (max-width: 700px) {
  body.viewer #shareTxt,
  body.viewer #consTxt { display: none; }
  body.viewer #shareBtn,
  body.viewer .conslink {
    padding: 6px;
    min-width: 40px;
    min-height: 40px;
    justify-content: center;
  }
  body.viewer #shareBtn svg,
  body.viewer .conslink svg { opacity: .85; }
}

/* La marca no se achica: si no entra, la barra pasa a dos renglones. Si en
   cambio la dejamos encoger, el nombre se desborda de su caja y se monta
   sobre lo que tenga al lado. */
.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
  flex: 0 0 auto;
}
.brand .mark {
  width: 32px; height: 32px;
  flex: none;
  display: grid;
  place-items: center;
  color: var(--accent);
  background: radial-gradient(120% 120% at 30% 20%, #241c16 0%, #120e0c 100%);
  border: 1px solid var(--border-2);
  border-radius: 50%;
  overflow: hidden;
}
.brand .mark svg { width: 100%; height: 100%; display: block; }
.brand .name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 24px;
  letter-spacing: .005em;
  white-space: nowrap;
  color: var(--text);
}
.brand .tag {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text-3);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 4px 10px;
  white-space: nowrap;
}

.statusgroup {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: flex-end;
  flex: 1 1 auto;
  min-width: 0;
}
.statusgroup .metric {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.statusgroup .metric svg { opacity: .5; }

.live-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  background: rgba(238, 214, 190, .03);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 6px 13px;
  white-space: nowrap;
}
.live-badge .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  flex: none;
}
.live-badge.on {
  background: var(--red);
  border-color: transparent;
  color: #fff;
  font-weight: 600;
  box-shadow: 0 0 22px -5px rgba(226, 83, 62, .6);
}
.live-badge.on .dot {
  position: relative;
  background: #fff;
}
/* El latido es un disco que crece y se apaga, no un box-shadow animado.
   Un box-shadow que cambia obliga a repintar la página en cada cuadro:
   medido en Chrome, este punto solo se llevaba la mayor parte del trabajo
   del hilo principal durante toda la transmisión, en la consola y en cada
   espectador. Con transform y opacity lo mueve la placa de video sola. */
.live-badge.on .dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #fff;
  opacity: 0;
  animation: pulse 1.6s ease-out infinite;
}

@keyframes pulse {
  0%   { transform: scale(1); opacity: .45; }
  70%  { transform: scale(3); opacity: 0; }
  100% { transform: scale(3); opacity: 0; }
}

.player {
  position: relative;
  background: #000;
  border: 1px solid var(--border);
  border-radius: 18px;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  box-shadow: 0 30px 80px -35px rgba(0, 0, 0, .9);
}
.player video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: #000;
}

body.viewer .player {
  aspect-ratio: var(--ar, 1.7778);
  min-height: 0;
  min-width: 0;
  align-self: stretch;
  border-radius: 14px;
  box-shadow: 0 18px 50px -30px rgba(0, 0, 0, .9);
}
body.viewer .player video { object-fit: contain; }

@media (max-width: 620px), (max-height: 620px) {
  body.viewer .pcontrols { gap: 8px; padding: 7px 8px; }
  body.viewer .pcbtn { width: 34px; height: 34px; border-radius: 50%; }
  body.viewer input[type="range"].pvol { width: min(92px, 26vw); }
}

.standby {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  background:
    radial-gradient(70% 90% at 50% 34%, #1c1612 0%, #0a0807 100%);
}
.standby .mark {
  width: min(30%, 21vh);
  min-width: 78px;
  max-width: 178px;
  color: var(--text-2);
  line-height: 0;
}
.standby .mark svg { width: 100%; height: auto; display: block; }

@media (max-height: 620px) {
  .standby { gap: 12px; }
  .standby .mark { width: min(26%, 17vh); min-width: 62px; }
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 16px;
  border-radius: 99px;
  background: rgba(238, 214, 190, .045);
  border: 1px solid var(--border);
  color: var(--text-2);
  font-size: 13.5px;
  font-weight: 400;
  text-align: center;
}
.pill .pdot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
  animation: breathe 2.4s ease-in-out infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: .3; }
}

.onair {
  position: absolute;
  left: 13px;
  top: 13px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--red);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  border-radius: 99px;
  padding: 6px 13px;
  box-shadow: 0 6px 20px -8px rgba(226, 83, 62, .8);
}

.pcontrols {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 13px;
  background: linear-gradient(transparent, rgba(0, 0, 0, .62));
}
.pcontrols .pspacer { flex: 1; min-width: 0; display: flex; justify-content: flex-end; }

/* Crédito del tema que suena (música Creative Commons). Dos renglones
   cortos, a la derecha, sin tapar los botones: si no entra, se corta con
   puntos suspensivos. */
.pcredito {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  min-width: 0;
  max-width: 100%;
  font-size: 11px;
  line-height: 1.35;
  color: rgba(255, 236, 218, .7);
  text-shadow: 0 1px 3px rgba(0, 0, 0, .7);
  text-align: right;
}
.pcredito > span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pcredito .pc-tema { color: var(--text); font-weight: 500; }
.pcredito a { color: inherit; text-decoration: none; }
.pcredito a:hover { text-decoration: underline; }
@media (max-width: 360px) {
  .pcredito { font-size: 10.5px; }
  body.viewer input[type="range"].pvol { width: 21vw; }
}

.pcbtn {
  width: 38px; height: 38px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid rgba(255, 236, 218, .16);
  /* Más opaco y sin backdrop-filter: estos botones están encima del video,
     y desenfocar lo que tienen atrás obligaba a recalcularlo con cada
     cuadro del video, en el teléfono de cada espectador. */
  background: rgba(16, 12, 10, .68);
  color: var(--text);
  cursor: pointer;
  transition: background .15s, border-color .15s;
}
.pcbtn:hover { background: rgba(16, 12, 10, .82); border-color: var(--border-2); }
.pcbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

input[type="range"].pvol { width: min(120px, 30vw); }

/* En un teléfono angosto el crédito no entraba al lado del volumen y la
   licencia salía cortada ("CC B…"), que es justo lo que no se puede
   cortar. Mientras hay crédito, sube a su propio renglón. */
@media (max-width: 430px) {
  body.viewer .pcontrols:has(.pcredito:not([hidden])) { flex-wrap: wrap; row-gap: 2px; }
  body.viewer .pcontrols:has(.pcredito:not([hidden])) .pspacer { order: -1; flex-basis: 100%; }
  body.viewer .pcontrols #fsBtn { margin-left: auto; }
}

.telemetry {
  margin-top: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
}
.telemetry .head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--border);
}
.telemetry .head .title {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--text-3);
}
.telemetry .head .state {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.telemetry .head .state::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--amber);
}
.telemetry .head .state.on { color: var(--green); }
.telemetry .head .state.on::before { background: var(--green); }

.telegrid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}
.tele {
  padding: 15px 18px 16px;
  border-left: 1px solid var(--border);
  min-width: 0;
}
.tele:first-child { border-left: 0; }
.tele .k {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tele .v {
  font-family: var(--mono);
  font-size: 23px;
  font-weight: 400;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  letter-spacing: -.01em;
}
.tele .v .unit {
  font-size: 12px;
  color: var(--text-3);
  margin-left: 4px;
  font-weight: 400;
}
.telemetry.stale .tele .v { color: var(--text-3); }

/* Campos de estado (Network, T-Cam): muestran una palabra, no un número.
   Van en la tipografía de interfaz y más chicos — "Desconectado" en el
   mono de 23 px no entra ni cerca en una celda de 94 — y con color, que
   es lo que se lee de un vistazo desde lejos. */
.tele .v.texto {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--text-2);
  /* Las palabras de estado sí pueden ocupar dos renglones: un estado
     recortado a la mitad no se entiende, y una medición no se parte. */
  white-space: normal;
  line-height: 1.15;
  overflow: visible;
}
/* Los campos de estado no tienen unidad, pero el <span> vacío seguía
   sumando su margen: tres píxeles que alcanzaban para desbordar la celda. */
.tele .v.texto .unit,
.bigmetric .bv.texto .unit { display: none; }

.tele .v.texto.ok { color: var(--green); }
.tele .v.texto.no { color: var(--text-3); }
.telemetry.stale .tele .v.texto.ok { color: var(--text-3); }

.bigmetric .bv.texto {
  font-family: var(--sans);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -.01em;
}
.bigmetric .bv.texto.ok { color: var(--green); }
.bigmetric .bv.texto.no { color: var(--text-3); }

.telemetry .gps {
  padding: 11px 18px;
  border-top: 1px solid var(--border);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-3);
}
.telemetry .gps.off { opacity: .35; }

body.viewer .telemetry {
  margin: 0;
  border-radius: 14px;
}
body.viewer .telemetry .head {
  padding: 7px 13px;
  gap: 10px;
  flex-wrap: nowrap;
}
body.viewer .telemetry .head .title { font-size: 10px; white-space: nowrap; }
body.viewer .telemetry .head .state { white-space: nowrap; }
body.viewer .telemetry .head .gps {
  margin-right: auto;
  padding: 0;
  border: 0;
  font-size: 11px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La posición lleva un pin en vez de la palabra "GPS": son cuatro
   caracteres menos, y en el teléfono acostado eso es la diferencia entre
   ver la coordenada entera o cortada. */
body.viewer .telemetry .head .gps::before {
  content: "";
  display: inline-block;
  width: 10px; height: 12px;
  margin-right: 6px;
  vertical-align: -1px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 24'%3E%3Cpath d='M10 0a9 9 0 0 0-9 9c0 6.6 9 15 9 15s9-8.4 9-15a9 9 0 0 0-9-9Zm0 12.5A3.5 3.5 0 1 1 10 5.5a3.5 3.5 0 0 1 0 7Z'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 24'%3E%3Cpath d='M10 0a9 9 0 0 0-9 9c0 6.6 9 15 9 15s9-8.4 9-15a9 9 0 0 0-9-9Zm0 12.5A3.5 3.5 0 1 1 10 5.5a3.5 3.5 0 0 1 0 7Z'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: .8;
}

/* En un teléfono angosto el título y el estado se comen la fila y las
   coordenadas quedaban recortadas ("GPS -34.6037, -58.38…"). Con lugar
   vertical de sobra, el GPS baja a su propio renglón y se lee entero: son
   quince píxeles de alto a cambio del dato completo. En pantalla baja
   —apaisado— no, que ahí cada píxel de alto cuesta. */
@media (max-width: 480px) and (min-height: 621px) {
  body.viewer .telemetry .head { flex-wrap: wrap; row-gap: 1px; }
  body.viewer .telemetry .head .gps {
    order: 3;
    flex-basis: 100%;
    margin-right: 0;
  }
}
body.viewer .telegrid {
  flex: 1 1 auto;
  min-height: 0;
  /* 78 px: con ocho casilleros el teléfono tiene que poder armar cuatro
     columnas, también el de 360 px; con 84 quedaban tres filas, una celda
     vacía y el gráfico aplastado. El cuerpo de la etiqueta y del valor se
     acomodan solos por container query, más abajo. */
  grid-template-columns: repeat(auto-fit, minmax(78px, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: 1px;
  background: var(--border);
}
body.viewer .tele {
  display: flex;
  flex-direction: column;
  justify-content: center;
  border: 0;
  background: var(--surface);
  padding: 7px 13px;
  min-height: 0;
}
body.viewer button.tele:hover { background: var(--surface-2); }
body.viewer .tele.pinned { background: var(--surface-2); }
body.viewer .tele .k { font-size: 9.5px; }
body.viewer .tele .v { font-size: 19px; margin-top: 3px; }
body.viewer .tele .v .unit { font-size: 11px; }

@media (max-width: 1023px) and (min-height: 621px) {
  body.viewer .tele { padding: 8px 13px; }
  body.viewer .tele .k { font-size: 10.5px; }
  body.viewer .tele .v { font-size: clamp(19px, 4.4vw, 24px); }
  body.viewer .tele .v .unit { font-size: 12px; }
}

/* "TEMPERATURA" en versalitas pide unos 94 px y la celda angosta tiene 70
   por dentro: la etiqueta se cortaba al medio. Lo que manda acá no es el
   ancho de la pantalla sino el de la celda —la grilla es auto-fit, así que
   un teléfono de 430 px puede tener celdas más angostas que uno de 375—,
   por eso va con container query sobre la celda y no con media query. */
body.viewer .tele { container-type: inline-size; }

@container (max-width: 126px) {
  body.viewer .tele { padding-left: 11px; padding-right: 11px; }
  body.viewer .tele .k { font-size: 9px; letter-spacing: .04em; }
  body.viewer .tele .v { font-size: 17px; }
  body.viewer .tele .v.texto { font-size: 12px; }
}
@container (max-width: 104px) {
  body.viewer .tele { padding-left: 9px; padding-right: 9px; }
  body.viewer .tele .k { font-size: 8.5px; letter-spacing: .02em; }
  body.viewer .tele .v { font-size: 15px; }
  body.viewer .tele .v .unit { font-size: 10px; }
  body.viewer .tele .v.texto { font-size: 10.5px; }
}
@container (max-width: 90px) {
  body.viewer .tele { padding-left: 7px; padding-right: 6px; }
  body.viewer .tele .v { font-size: 14px; letter-spacing: -.03em; }
  body.viewer .tele .v .unit { font-size: 9px; margin-left: 2px; }
  body.viewer .tele .v.texto { font-size: 10px; }
}
/* En el teléfono parado, siempre cuatro columnas: dos filas parejas. Con
   auto-fit, a 430 px entraban cinco y quedaba una fila de cinco y otra
   de tres. */
@media (max-width: 560px) and (min-height: 621px) {
  body.viewer #paneTele .telegrid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (max-height: 620px) {
  body.viewer .tele { padding: 5px 11px; }
  body.viewer .tele .v { font-size: clamp(15px, 3.2vh, 21px); }
}

@media (min-width: 1024px) and (min-height: 621px) {
  body.viewer .telegrid { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
  body.viewer .tele { padding: 10px 16px 11px; }
  body.viewer .tele .k { font-size: 10.5px; }
  body.viewer .tele .v { font-size: 21px; }
}

@keyframes valbump {
  from { color: #fff; }
  to   { color: inherit; }
}
.tele .v span:first-child.upd,
.bigmetric .bv span:first-child.upd { animation: valbump .55s ease-out; }

button.tele {
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  border-left: 1px solid var(--border);
  text-align: left;
  width: 100%;
  cursor: pointer;
  transition: background .15s;
}
button.tele:hover { background: var(--surface-2); }
button.tele:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tele.pinned {
  background: var(--surface-2);
  box-shadow: inset 0 -2px 0 0 var(--accent), inset 2px 0 0 0 var(--accent);
}
.tele.pinned .k { color: var(--accent); }
.tele.off { opacity: .38; }

.phase {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 2px 0 12px;
}
.phase .pdotbig {
  width: 8px; height: 8px;
  flex: none;
  border-radius: 50%;
  background: var(--text-3);
}
.phase .pdotbig.ok   { background: var(--green); }
.phase .pdotbig.hot  { background: var(--accent); animation: breathe 1.4s ease-in-out infinite; }
.phase .pdotbig.warn { background: var(--amber); }
.phase .ptext {
  font-family: var(--serif);
  font-size: 17px;
  font-weight: 400;
  color: var(--text);
}

.bigmetric {
  border-top: 1px solid var(--border);
  padding-top: 12px;
}
.bigmetric .bk {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--text-3);
}
.bigmetric .bv {
  font-family: var(--mono);
  font-size: 38px;
  font-weight: 400;
  line-height: 1.15;
  margin-top: 5px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.bigmetric .bv .unit {
  font-size: 14px;
  color: var(--text-3);
  margin-left: 5px;
  font-weight: 400;
}

.chartbox {
  position: relative;
  flex: 1 1 auto;
  min-height: 64px;
}
canvas#altChart {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.chartfoot {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-3);
  min-height: 14px;
  margin-top: 4px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 18px;
  margin-top: 14px;
}
.card > h2 {
  margin: 0 0 14px;
  font-family: var(--serif);
  font-size: 18px;
  font-weight: 400;
  letter-spacing: .005em;
  color: var(--text);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 2px 10px;
}
.card > h2 .sub {
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text-3);
  text-align: right;
  min-width: 0;
}

.btn {
  appearance: none;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 12px;
  padding: 11px 16px;
  min-height: 44px;
  cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.btn:hover { background: var(--surface-3); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled { opacity: .4; cursor: default; transform: none; }

.btn-primary {
  background: var(--text);
  border-color: var(--text);
  color: #16120f;
}
.btn-primary:hover { background: #fff; border-color: #fff; }

.btn-danger {
  background: var(--red);
  border-color: transparent;
  color: #fff;
}
.btn-danger:hover { background: #d2452f; }

.btn-onair {
  position: relative;
  background: var(--red);
  border-color: transparent;
  color: #fff;
}
/* El brillo está quieto y lo que respira es su opacidad: mismo efecto que
   animar el box-shadow, sin repintar en cada cuadro (ver el latido del
   punto de "En vivo"). */
.btn-onair::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 20px -2px rgba(226, 83, 62, .6);
  opacity: 0;
  pointer-events: none;
  animation: onairGlow 1.8s ease-in-out infinite;
}
@keyframes onairGlow {
  0%, 100% { opacity: 0; }
  50%      { opacity: 1; }
}

.btn-block { width: 100%; }

.row  { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.grow { flex: 1; min-width: 0; }

input[type="password"], input[type="text"] {
  width: 100%;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border-2);
  border-radius: 12px;
  padding: 11px 14px;
  min-height: 44px;
}
input[type="text"]:read-only { color: var(--text-2); }

input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

input[type="range"] {
  -webkit-appearance: none;
  width: 100%;
  height: 28px;
  background: transparent;
  cursor: pointer;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 3px;
  background: var(--surface-3);
  border-radius: 2px;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px;
  margin-top: -6.5px;
  border-radius: 50%;
  background: var(--text);
  border: none;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .55);
}
input[type="range"]::-moz-range-track {
  height: 3px; background: var(--surface-3); border-radius: 2px;
}
input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--text);
  border: none;
}
input[type="range"]:disabled { opacity: .4; cursor: default; }

.field-label {
  display: block;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 14px 0 3px;
}

.field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin: 14px 0 3px;
}
.field-head .field-label { margin: 0; }
.field-val {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-2);
  font-variant-numeric: tabular-nums;
  flex: none;
}

/* Tres opciones no entran cómodas en un teléfono angosto: el segmentado
   pasa a dos filas y el tercero ocupa el ancho entero. */
.seg3 button { font-size: 13px; padding-left: 6px; padding-right: 6px; }
@media (max-width: 420px) {
  .seg3 { flex-wrap: wrap; }
  .seg3 button { flex: 1 1 0; }
  .seg3 button:last-child { flex-basis: 100%; }
}

/* Un dato suelto debajo de un control (la dirección del video, el estado
   de la instalación). */
.dato {
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-3);
  margin: 9px 0 0;
}

/* Aviso de versión nueva. Solo aparece cuando NO se puede recargar solo
   —al aire o con música sonando—, así que tiene que verse sin tapar
   ningún control: va abajo a la izquierda y por encima de la barra de
   operación. */
.update-pill {
  position: fixed;
  left: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100vw - 24px);
  padding: 9px 10px 9px 14px;
  border-radius: 99px;
  background: rgba(30, 25, 21, .96);
  border: 1px solid var(--border-2);
  box-shadow: 0 12px 34px -12px rgba(0, 0, 0, .8);
  color: var(--text-2);
  font-size: 12.5px;
  backdrop-filter: blur(10px);
}
.update-pill button {
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: #16120f;
  background: var(--accent);
  border: 0;
  border-radius: 99px;
  padding: 7px 13px;
  min-height: 34px;
  cursor: pointer;
  white-space: nowrap;
}
.update-pill button:hover { background: #edb68f; }

/* En la consola la barra de operación vive abajo: el aviso se corre. */
body:not(.viewer) .update-pill { bottom: calc(74px + env(safe-area-inset-bottom, 0px)); }
@media (min-width: 980px) {
  body:not(.viewer) .update-pill { bottom: 12px; }
}
.dato code {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--accent);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 1px 6px;
  word-break: break-all;
}

.fieldgrid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 10px;
  align-items: end;
}
.fieldgrid .f.narrow input { width: 96px; }
.f { display: block; min-width: 0; }

.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 6px 12px;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.chip:hover { border-color: var(--border-2); }
.chip.on {
  color: var(--text);
  background: var(--surface-3);
  border-color: var(--border-2);
}
.chip.on::before {
  content: "";
  display: inline-block;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--accent);
  margin-right: 7px;
  vertical-align: 2px;
}

.teststate {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-3);
  flex: none;
  max-width: 50%;
}
.teststate.ok   { color: var(--green); }
.teststate.bad  { color: var(--red); }
.teststate.warn { color: var(--amber); }

.errline {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--red);
  margin: 10px 0 0;
}
.errline.warn { color: var(--amber); }
.errline.ok   { color: var(--green); }

.ghostbtn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  text-decoration: none;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
  background: rgba(238, 214, 190, .03);
  border: 1px solid var(--border);
  border-radius: 99px;
  padding: 6px 13px;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, color .15s, border-color .15s;
}
.ghostbtn:hover { color: var(--text); border-color: var(--border-2); background: var(--surface-2); }
.ghostbtn svg { opacity: .65; }
.ghostbtn:hover svg { opacity: .9; }

.seg {
  display: flex;
  gap: 3px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 3px;
}
.seg button {
  flex: 1;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-3);
  background: transparent;
  border: 0;
  border-radius: 9px;
  padding: 9px 10px;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.seg button.active {
  background: var(--surface-3);
  color: var(--text);
  box-shadow: inset 0 1px 0 rgba(255, 236, 218, .05);
}
.seg button:disabled { opacity: .5; cursor: default; }

.check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  color: var(--text-2);
  cursor: pointer;
  user-select: none;
}
.check input {
  width: 17px; height: 17px;
  accent-color: var(--accent);
  cursor: pointer;
}

.tracklist { display: flex; flex-direction: column; gap: 4px; }

/* ── Música por fases ───────────────────────────────────────────────
   Cada fase es una fila: a la izquierda el botón grande que la pone a
   sonar (nombre en serif, como las fases de la misión), a la derecha la
   flecha que despliega sus temas. La que suena lleva el filete cobre. */
.musica-estado {
  font-size: 12.5px;
  color: var(--text-3);
  margin: -6px 0 12px;
  line-height: 1.5;
}
.fases { display: flex; flex-direction: column; gap: 6px; }
.fase {
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  overflow: hidden;
}
.fase.on {
  border-color: var(--border-2);
  background: var(--surface-2);
  box-shadow: inset 2px 0 0 0 var(--accent);
}
.fase-head { display: flex; align-items: stretch; }
.fase-play {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: transparent;
  border: 0;
  padding: 10px 12px;
  min-height: 52px;
  cursor: pointer;
}
.fase-play:hover { background: rgba(255, 236, 218, .025); }
.fase-play:disabled { cursor: default; background: transparent; }
.fase-play:focus-visible, .fase-ver:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.fase-ico {
  flex: none;
  width: 28px; height: 28px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 11px;
  color: var(--text-2);
  background: var(--surface-3);
}
.fase.on .fase-ico { color: #16120f; background: var(--accent); }
.fase-txt { display: flex; flex-direction: column; min-width: 0; }
.fase-nombre {
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.25;
}
.fase-meta {
  font-size: 11.5px;
  color: var(--text-3);
  line-height: 1.4;
  overflow-wrap: anywhere;
}
.fase.vacia .fase-nombre { color: var(--text-3); }
.fase.vacia .fase-ico { opacity: .45; }
.fase-ver {
  flex: none;
  width: 48px;
  display: grid;
  place-items: center;
  color: var(--text-3);
  background: transparent;
  border: 0;
  border-left: 1px solid var(--border);
  cursor: pointer;
}
.fase-ver:hover { color: var(--text); }
.fase-ver svg { transition: transform .18s; }
.fase.abierta .fase-ver svg { transform: rotate(180deg); }
.fase-pistas {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0 6px 6px;
}
.fase-pistas .track { padding: 8px 10px; min-height: 40px; font-size: 13.5px; }
.track .ttl { flex: 1; min-width: 0; overflow-wrap: anywhere; display: flex; flex-direction: column; }
.track .ttl .disco {
  font-size: 11.5px;
  color: var(--text-3);
  line-height: 1.35;
}
.track .dur {
  flex: none;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.track .playing-dot { margin-left: 0; }
.track.rota { opacity: .5; }

.musica-ctl .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding-left: 10px;
  padding-right: 10px;
}
.musica-ctl .btn svg { flex: none; }
/* En un teléfono muy angosto las tres palabras no entran: quedan los
   íconos de reproductor, que se entienden solos (el nombre sigue para
   los lectores de pantalla en aria-label). */
@media (max-width: 359px) {
  .musica-ctl .btn .lbl { display: none; }
}
.track.rota .dur { font-family: var(--sans); color: var(--amber); }

.track {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  font: inherit;
  font-size: 14px;
  color: var(--text-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 11px;
  padding: 11px 12px;
  min-height: 44px;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.track:hover { background: var(--surface-2); }
.track.active {
  background: var(--surface-2);
  border-color: var(--border-2);
  color: var(--text);
}
.track .num {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--text-3);
  width: 2ch;
  flex: none;
}
.track .playing-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  margin-left: auto;
  flex: none;
  animation: breathe 1.6s ease-in-out infinite;
}

.progress-times {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--text-3);
  margin-top: -4px;
  font-variant-numeric: tabular-nums;
}

.meter-wrap {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 9px 12px;
  margin-top: 10px;
}
.meter-wrap .mic-tag {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.meter-wrap .mic-tag.on { color: var(--red); }
.meter-wrap canvas {
  flex: 1;
  height: 26px;
  min-width: 0;
  display: block;
}

.met-display {
  font-family: var(--mono);
  font-size: 34px;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  text-align: center;
  padding: 6px 0 14px;
  letter-spacing: .01em;
}
.met-display.idle { color: var(--text-3); }

.log {
  font-family: var(--mono);
  font-size: 11.5px;
  line-height: 1.75;
  color: var(--text-3);
  max-height: 170px;
  overflow: auto;
  white-space: pre-wrap;
  margin: 0;
}
.log b { color: var(--accent); font-weight: 500; }

.login {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: 20px;
  padding-top: max(20px, env(safe-area-inset-top, 0px));
  padding-bottom: max(20px, env(safe-area-inset-bottom, 0px));
}

/* Pasos para agregar la app a la pantalla de inicio en iPhone. */
.pasos {
  margin: 12px 0 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--text-2);
}
.pasos li { margin-bottom: 2px; }
.pasos b { color: var(--text); font-weight: 600; }
.pasos::marker, .pasos li::marker { color: var(--accent); }
.login-card {
  width: min(370px, 100%);
  margin: 0;
  padding: 32px 26px 26px;
  text-align: center;
}
.login-card .emblem {
  width: 92px;
  margin: 0 auto 16px;
  color: var(--text-2);
  line-height: 0;
}
.login-card .emblem svg { width: 100%; height: auto; display: block; }
.login-card .name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 26px;
  letter-spacing: .005em;
}
.login-card p {
  color: var(--text-3);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  margin: 7px 0 22px;
}
.login-card input { text-align: center; margin-bottom: 10px; }
.login-card .err {
  color: var(--red);
  font-size: 12.5px;
  min-height: 18px;
  margin: 10px 0 0;
  letter-spacing: 0;
  text-transform: none;
  font-weight: 400;
}

@media (min-width: 980px) {
  .grid {
    display: grid;
    grid-template-columns: 1.3fr 1fr;
    gap: 16px;
    align-items: start;
  }
  .grid > div > .card:first-child,
  .grid > div > .player { margin-top: 14px; }

  .grid .telegrid { grid-template-columns: repeat(3, 1fr); }
  .grid .tele:nth-child(4) { border-left: 0; }
  .grid .tele:nth-child(n+4) { border-top: 1px solid var(--border); }
  .grid .tele .v { font-size: 19px; }
}

.opsbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  padding-bottom: max(8px, env(safe-area-inset-bottom));
  /* Sin backdrop-filter: con el fondo al 94 % el desenfoque casi no se
     veía, y una barra fija desenfocando lo que pasa por debajo (el vúmetro,
     la lista al scrollear) se recalcula en cada cuadro en el teléfono. */
  background: rgba(11, 9, 8, .97);
  border-top: 1px solid var(--border-2);
}
.opsbar .btn { min-height: 48px; font-size: 15px; }
.opsbar .opsmet {
  font-family: var(--mono);
  font-size: 13px;
  flex: none;
  padding: 11px 12px;
  min-width: 76px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

@media (min-width: 980px) {
  .opsbar { display: none !important; }
}

@media (max-width: 979px) {
  body:not(.viewer) .container { padding: 0 12px 104px; }

  /* ── Consola compacta en el teléfono ─────────────────────────────────
     Medida antes de tocar nada, la consola en un teléfono de 844 px de
     alto ocupaba 4033 px: casi cinco pantallas de scroll para llegar al
     final. Se apretó todo lo que es marco —padding, márgenes, cuerpos de
     título— sin bajar de 44 px ningún blanco de toque. Los párrafos de
     explicación se sacaron el 11/10/2026 (los pidió sacar Teo). */
  body:not(.viewer) .card { padding: 12px 13px; margin-top: 8px; }
  body:not(.viewer) .card > h2 { font-size: 16px; margin-bottom: 10px; }
  body:not(.viewer) .card > h2 .sub { font-size: 11px; }

  body:not(.viewer) .field-label { font-size: 10px; margin: 10px 0 2px; }
  body:not(.viewer) .field-head { margin: 10px 0 2px; }
  body:not(.viewer) .field-val { font-size: 11.5px; }
  body:not(.viewer) .dato { font-size: 12px; line-height: 1.55; margin-top: 7px; }
  body:not(.viewer) .row { gap: 8px; }
  body:not(.viewer) .seg { padding: 2px; }
  body:not(.viewer) .meter-wrap { padding: 7px 10px; margin-top: 8px; }
  body:not(.viewer) .meter-wrap canvas { height: 22px; }
  body:not(.viewer) .met-display { font-size: 28px; padding: 2px 0 10px; }
  body:not(.viewer) .tracklist { gap: 2px; }
  body:not(.viewer) .log { max-height: 130px; }

  /* El deslizador baja de 38 a 32 px de alto: lo que se toca es el
     cabezal, que se queda en 22 px. */
  body:not(.viewer) input[type="range"] { height: 32px; }

  body:not(.viewer) .topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    /* Ver la nota del padding en .app: instalada, la franja de la hora
       se superpone y hay que correr la barra para abajo. */
    padding: calc(10px + env(safe-area-inset-top, 0px)) 0 8px;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    flex-direction: row;
    align-items: center;
    gap: 8px;
  }
  body:not(.viewer) .statusgroup { gap: 8px; }
  body:not(.viewer) .brand .tag { display: none; }
  body:not(.viewer) .brand .mark { width: 28px; height: 28px; }
  body:not(.viewer) .brand .name { font-size: 21px; }
  body:not(.viewer) .brand { gap: 8px; }

  /* En el teléfono el enlace a la transmisión queda solo con el ojo: el
     texto entero empujaba la barra y se montaba sobre el nombre. */
  body:not(.viewer) .ghostbtn .lbl { display: none; }
  body:not(.viewer) .ghostbtn {
    min-width: 40px;
    min-height: 38px;
    padding: 6px;
    justify-content: center;
  }

  .card.fold > h2 { cursor: pointer; user-select: none; }
  .card.fold > h2::after {
    content: "";
    flex: none;
    width: 7px; height: 7px;
    /* Rotada 45° la flecha ocupa 7·√2 ≈ 10 px, así que sin este margen
       derecho se sale de la caja del h2 y ensancha el área de scroll. */
    margin-left: 2px;
    margin-right: 3px;
    align-self: center;
    border-right: 1.5px solid var(--text-3);
    border-bottom: 1.5px solid var(--text-3);
    transform: rotate(45deg) translateY(-2px);
    transition: transform .15s;
  }
  .card.fold.closed > h2::after { transform: rotate(-45deg) translateY(0); }
  .card.fold.closed > h2 { margin-bottom: 0; }
  .card.fold.closed > *:not(h2) { display: none; }
  .card.fold[style*="padding:0"] > h2 { padding: 15px 15px 0; }
  .card.fold[style*="padding:0"].closed > h2 { padding: 15px; }
}

@media (max-width: 979px), (pointer: coarse) {
  input[type="text"], input[type="password"], input[type="number"],
  input[type="search"], input[type="email"], textarea, select { font-size: 16px; }
  body:not(.viewer) .btn { min-height: 46px; }
  body:not(.viewer) .seg button { min-height: 44px; }
  body:not(.viewer) .chip { padding: 9px 14px; font-size: 13px; }
  body:not(.viewer) .track { min-height: 48px; }

  /* Blancos de toque: el dedo pide 44 px. El casillero de 17 px era casi
     imposible de pegarle; ahora agranda y además toda la fila es tocable
     porque el control vive adentro de su <label>. */
  .check { min-height: 44px; }
  .check input { width: 22px; height: 22px; }

  /* Los botones del reproductor estaban en 34 px sobre el video. */
  body.viewer .pcbtn { width: 42px; height: 42px; }
  body.viewer .pcontrols { gap: 10px; padding: 9px 10px; }

  /* Con texto ("Consola", "Compartir") medían 33 px de alto. En apaisado
     el ancho da para el texto pero el dedo sigue pidiendo altura. Solo
     altura: tocarles el padding los ensancha y la barra se va a dos
     renglones en vertical. */
  body.viewer .ghostbtn { min-height: 40px; }
  input[type="range"] { height: 38px; }
  input[type="range"]::-webkit-slider-thumb {
    width: 22px; height: 22px; margin-top: -9.5px;
  }
  input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }
}

@media (max-width: 900px) {
  body:not(.viewer) .telegrid { grid-template-columns: repeat(3, 1fr); }
  body:not(.viewer) .tele:nth-child(4) { border-left: 0; }
  body:not(.viewer) .tele:nth-child(n+4) { border-top: 1px solid var(--border); }
}

@media (max-width: 520px) {
  body:not(.viewer) .telegrid { grid-template-columns: repeat(2, 1fr); }
  body:not(.viewer) .tele:nth-child(4) { border-left: 1px solid var(--border); }
  body:not(.viewer) .tele:nth-child(3) { border-left: 0; }
  body:not(.viewer) .tele:nth-child(n+3) { border-top: 1px solid var(--border); }
  body:not(.viewer) .tele .v { font-size: 19px; }
  body:not(.viewer) .topbar { flex-wrap: nowrap; }
  body:not(.viewer) .statusgroup { flex-wrap: nowrap; }
  .seg button { font-size: 12px; padding: 9px 6px; }
  .fieldgrid { grid-template-columns: 1fr; }
  .fieldgrid .f.narrow input { width: 100%; }
}

/* Fuera del aire el emblema y la pastilla de estado van centrados en el
   video, y abajo viven los controles de volumen, que siguen haciendo
   falta: la música suena antes de salir al aire. En un teléfono el video
   mide unos 200 px de alto y la pastilla caía justo debajo del control
   de volumen, tapada por la perilla. Al centrado se le reserva la franja
   de los controles (60 px) y en pantalla chica el emblema se achica. */
body.viewer .standby { padding: 8px 0 60px; }
@media (max-width: 620px), (max-height: 620px) {
  body.viewer .standby { gap: 10px; }
  body.viewer .standby .mark { width: min(24%, 17vh); min-width: 56px; }
}
@media (max-width: 360px) {
  body.viewer .standby { gap: 8px; }
  body.viewer .standby .mark { width: 52px; min-width: 52px; }
  body.viewer .standby .pill { padding: 6px 13px; font-size: 12.5px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
