/* tv.css — Cliente TV v2 (pairing + player). */

* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --bg: #0d1117;
  --fg: #f5f7fa;
  --muted: #9aa4b2;
  /* Escala de marca, la MISMA que el admin (main.tsx). Aquí no hay variables de Mantine, así
     que este es el único sitio del cliente TV donde hay hex de marca. */
  --brand-5: #5e95d8;
  --brand-6: #4282d1;
  --brand-7: #386eb2;
  --brand-9: #244773;
  --accent: var(--brand-6);   /* azul corporativo (mismo que el primario del admin) */
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--fg);
  font-family: 'Nunito', system-ui, sans-serif;
  cursor: none;
}

.hidden { display: none !important; }

.screen {
  position: fixed;
  inset: 0;
}

/* ── Rotación de la pantalla ──────────────────────────────────
   Los mismos cuatro giros que los ajustes de un monitor (0/90/180/270, 90 = horario). El
   panel va colgado girado pero el navegador sigue renderizando en horizontal, así que el
   giro lo damos aquí. En 90 y 270 las dimensiones se intercambian (ancho=100vh, alto=100vw).

   Las tres capas que llenan la pantalla giran juntas — la del contenido, la de los rótulos
   y la de los avisos —, porque las tres cuelgan del body y no una de otra. Antes cada una
   repetía el bloque entero; con `:is()` es UNA regla por giro.

   El porqué del `translate`: con el origen arriba a la izquierda, girar deja el rectángulo
   fuera del viewport, y el desplazamiento (en los ejes YA girados) lo vuelve a meter. */
/* Solo los giros de verdad: `[data-rot]` a secas casaba también con un 0 y dejaba las capas
   sin `inset` y sin tamaño — pantalla en negro. Sin giro no se toca NADA. */
body:is([data-rot='90'], [data-rot='180'], [data-rot='270']) :is(.screen, #on6-overlays, .on6-message) {
  inset: auto;
  transform-origin: top left;
}
body[data-rot='90'] :is(.screen, #on6-overlays, .on6-message),
body[data-rot='270'] :is(.screen, #on6-overlays, .on6-message) {
  width: 100vh;
  height: 100vw;
}
body[data-rot='180'] :is(.screen, #on6-overlays, .on6-message) {
  width: 100vw;
  height: 100vh;
}
body[data-rot='90'] :is(.screen, #on6-overlays, .on6-message) {
  transform: rotate(90deg) translateY(-100%);
}
body[data-rot='180'] :is(.screen, #on6-overlays, .on6-message) {
  transform: rotate(180deg) translate(-100%, -100%);
}
body[data-rot='270'] :is(.screen, #on6-overlays, .on6-message) {
  transform: rotate(-90deg) translateX(-100%);
}

/* ── Pairing / splash / sin conexión ─────────────────────────
   Mismo lenguaje que la entrada del admin: la banda de marca con su degradado, el logo en
   blanco y el dibujo. Lo que ve un cliente al enchufar la pantalla es lo mismo que verá al
   entrar a gestionarla. */

/* `.tv-info`: las pantallas que HABLAN (emparejar, sin contenido, sin conexión, ya abierta en
   otra ventana). Una clase y no una lista de ids, que era lo que se olvidaba al añadir una. */
.tv-info {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: #fff;
  background:
    radial-gradient(120% 90% at 15% 10%, var(--brand-5) 0%, transparent 55%),
    linear-gradient(150deg, var(--brand-9) 0%, var(--brand-7) 50%, var(--brand-6) 100%);
}

/* El halo que respira, como en el login. */
.tv-info::after {
  content: '';
  position: absolute;
  width: 60%;
  aspect-ratio: 1;
  right: -12%;
  bottom: -22%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, .16) 0%, transparent 65%);
  animation: respirar 14s ease-in-out infinite alternate;
}

@keyframes respirar {
  from { transform: scale(1) translate(0, 0); }
  to { transform: scale(1.18) translate(-3%, -2%); }
}

.pairing-card, .splash-card {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 4vw, 4rem);
  width: 100%;
}

/* El dibujo va DENTRO de la columna, entre quién es esta pantalla y qué le pasa: se lee de
   arriba abajo como una sola frase.
   Se mide por ALTURA y no por ancho: en una tele el ancho sobra y lo que escasea es el alto,
   y los tres dibujos tienen proporciones distintas (el del emparejamiento es muy estrecho). */
.tv-doodle {
  height: min(32vh, 340px);
  width: auto;
  flex-shrink: 0;
  filter: drop-shadow(0 12px 28px rgba(0, 0, 0, .22));
}

/* Al emparejar manda el código, que es enorme, y la columna lleva dos cosas más: el dibujo
   se aparta para que todo quepa incluso en una pantalla de 720p. */
#pairing-screen .tv-doodle { height: min(22vh, 300px); }

/* En la TV todo va centrado: se mira de lejos y de pasada, no se lee en columna como en el
   admin. */
.tv-texto {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Separación amplia entre bloques: cada cosa se lee por su cuenta desde lejos. Lo que va
     junto se agrupa (`.tv-titulos`), no se aprieta bajando este hueco. */
  gap: 3vh;
  min-width: 0;
}

/* Lo que va junto: nombre de la cuenta + nombre de la pantalla, o un mensaje y su explicación.
   Un solo bloque para el ojo, en vez de dos cosas sueltas separadas por el hueco grande. */
.tv-titulos {
  display: flex;
  flex-direction: column;
  gap: .4vh;
}

.tv-logo {
  height: clamp(2.6rem, 6vh, 5rem);
  width: auto;
  /* El aire de la columna ya lo pone su `gap`. */
}

/* ── Jerarquía de las tres pantallas ──────────────────────────
   Manda el MENSAJE: qué le pasa a esta tele. El nombre de la pantalla y el de la cuenta son
   contexto (quien está delante ya sabe qué tele mira), así que bajan de tamaño en vez de ser
   el titular. */

.tv-mensaje {
  /* En fila por el aro que gira cuando la pantalla está esperando algo. */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .5em;
  font-size: clamp(1.5rem, 3.4vw, 2.9rem);
  font-weight: 700;
  line-height: 1.2;
  /* Si no cabe de una línea, que las dos queden parecidas en vez de una larga y una corta. */
  text-wrap: balance;
}

/* La segunda línea del mensaje: explica, no titula. */
.tv-sub {
  font-size: clamp(.95rem, 1.7vw, 1.45rem);
  opacity: .8;
}

.pairing-help {
  font-size: clamp(1.15rem, 2.4vw, 2rem);
  line-height: 1.4;
  opacity: .9;
  max-width: 24ch;
}

/* El código: celdas separadas, como el campo del admin donde se teclea. Es lo único que hay
   que leer desde el otro lado de la sala. */
#pairing-code {
  display: flex;
  gap: clamp(.4rem, 1vw, 1rem);
  font-size: clamp(3rem, 9vw, 8rem);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
/* Todas las celdas iguales y cuadradas: el ancho lo manda la caja, no la letra que toque (una
   M no puede ensanchar su hueco más que un 7). */
#pairing-code span {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.2em;
  aspect-ratio: 1;
  border-radius: .12em;
  background: rgba(255, 255, 255, .14);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .25);
}

#pairing-status {
  display: flex;
  align-items: center;
  gap: .6em;
  font-size: clamp(1rem, 1.8vw, 1.5rem);
  opacity: .8;
}
#pairing-status.error { color: #ffd0d0; }

/* El mismo aro girando que el panel (el Loader de Mantine), aquí en CSS puro. Toma el color
   del texto que acompaña, así en el aviso de "sin conexión" gira en rojo sin repetir el color. */
.tv-spinner {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
  border: .13em solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: girar .9s linear infinite;
}

@keyframes girar { to { transform: rotate(360deg); } }

.fs-btn {
  margin-top: 1vh;
  padding: 0.6em 1.4em;
  font: 700 clamp(0.9rem, 1.6vw, 1.2rem) 'Nunito', system-ui, sans-serif;
  color: #fff;
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 999px;
  cursor: pointer;
}
.fs-btn:hover { background: rgba(255, 255, 255, 0.24); }

/* Quién es esta tele: el nombre de la pantalla primero y el de la cuenta detrás, los dos por
   debajo del mensaje. Sirven para identificarla de un vistazo, no para leerlos desde la calle. */
.splash-card .splash-sub {
  font-weight: 700;
  font-size: clamp(1.1rem, 2.2vw, 1.8rem);
  line-height: 1.2;
  opacity: .85;
}
.splash-card .splash-sub:empty { display: none; }
.splash-card h1 {
  font-size: clamp(.9rem, 1.5vw, 1.25rem);
  font-weight: 600;
  opacity: .65;
}

/* Vertical: sobra altura, el dibujo se queda y hasta crece. */
@media (max-aspect-ratio: 1/1) {
  .pairing-help { max-width: 28ch; }
  .tv-doodle { height: min(30vh, 420px); }
  #pairing-screen .tv-doodle { height: min(26vh, 340px); }
}

/* Ventanas bajas (una tele apaisada muy estrecha, o el navegador a media altura): fuera el
   dibujo, todo el sitio para lo que hay que leer. */
@media (max-height: 460px) {
  .tv-doodle { display: none; }
}

/* ── Slides ──────────────────────────────────────────────── */

#slides-container {
  position: absolute;
  inset: 0;
  background: #000;
}

.slide {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  z-index: 1;
}

/* Visibilidad (estado estable o slide saliente bajo la transición) separada de
   la animación de entrada: así el saliente sigue visible mientras el entrante
   se anima por encima (solape real). */
.slide.show, .slide.enter { opacity: 1; visibility: visible; }
.slide.on-top { z-index: 2; }

/* Las transiciones de entrada (.slide.enter.t-*) viven en css/tv-transitions.css
   (compartido con la página de preview del admin). */

.slide-image, .slide-video {
  max-width: 100%;
  max-height: 100%;
  width: 100%;
  height: 100%;
  object-fit: contain;
  position: relative;
  z-index: 2;
}

.slide.media-cover .slide-image,
.slide.media-cover .slide-video { object-fit: cover; }

/* Página web subida (iframe sandbox) a pantalla completa */
.slide-html {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  z-index: 2;
}

/* Fondo ambiental difuminado para medias que no llenan la pantalla */
.slide-ambient {
  position: absolute;
  inset: -5%;
  background-size: cover;
  background-position: center;
  filter: blur(36px) brightness(0.45);
  z-index: 1;
}

/* Mismo fondo difuminado pero animado: 2º <video> en bucle (cover) tras el principal */
.slide-ambient-video {
  position: absolute;
  inset: -5%;
  width: 110%;
  height: 110%;
  object-fit: cover;
  filter: blur(36px) brightness(0.45);
  z-index: 1;
}

/* ── Widgets ─────────────────────────────────────────────── */

.widget-slide {
  flex-direction: column;
  gap: 2vh;
  text-align: center;
  /* Fondo PROPIO (el radial del splash): sin él la diapositiva era transparente y la
     transición de entrada se "rompía" — al terminar, la diapositiva anterior se ocultaba
     de golpe y aparecía el negro del contenedor. Con fondo, el crossfade es real.
     backgroundColor de la diapositiva (inline) lo pisa; poster/YouTube lo tapan. */
  background: radial-gradient(ellipse at center, #16202c 0%, var(--bg) 75%);
}

/* Widgets con TEMA (paleta/fondo compartidos): su contenido va por encima del fondo
   animado (.widget-backdrop, iframe con z-index 0). Scoped a los tipos que lo usan. */
.widget-clock > :not(.widget-backdrop),
.widget-weather > :not(.widget-backdrop),
.widget-qr > :not(.widget-backdrop) {
  position: relative;
  z-index: 1;
}

.widget-clock .clock-time {
  font-size: clamp(5rem, 22vh, 16rem);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.widget-clock .clock-date {
  font-size: clamp(1.2rem, 4vh, 3rem);
  /* Con paleta del tema, la fecha va en su color de acento; sin tema, como siempre. */
  color: var(--w-accent, var(--muted));
  text-transform: capitalize;
}

/* Etiqueta (reloj mundial: nombre de la ciudad sobre la hora). */
.widget-clock .clock-label {
  font-size: clamp(1.1rem, 4.2vh, 3.2rem);
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--w-accent, var(--accent));
}

/* ── Reloj: tarjetas (flip) ── grupos HH · MM · [SS] en cajas. */
.widget-clock .clock-flip { display: flex; align-items: stretch; gap: clamp(0.4rem, 1.6vh, 1.6rem); }
.widget-clock .clock-flip .cf-box {
  position: relative;
  font-size: clamp(3.2rem, 18vh, 13rem);
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  padding: 0.14em 0.16em;
  border-radius: 0.14em;
  /* Tarjeta teñida con el color del texto del tema (currentColor) → va en claro y oscuro. */
  background: color-mix(in srgb, currentColor 13%, transparent);
  box-shadow: inset 0 0 0 0.02em color-mix(in srgb, currentColor 22%, transparent);
}
/* Línea central (la "bisagra" del flip). */
.widget-clock .clock-flip .cf-box::after {
  content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 0.03em;
  background: color-mix(in srgb, currentColor 26%, transparent); transform: translateY(-50%);
}
.widget-clock .clock-flip .cf-colon {
  align-self: center; font-size: clamp(2rem, 11vh, 8rem); font-weight: 900; opacity: 0.45;
}
.widget-clock .clock-flip .cf-ampm {
  align-self: flex-end; margin-bottom: 0.25em;
  font-size: clamp(1rem, 4vh, 2.6rem); font-weight: 800; color: var(--w-accent, var(--accent));
}
.widget-clock .clock-flip .cf-ampm:empty { display: none; }

/* ── Reloj: esfera analógica ── manecillas teñidas con el tema. */
.widget-clock .clock-analog { width: min(58vh, 58vw); height: auto; overflow: visible; }
/* Disco teñido con el color del texto (currentColor): sobre paleta oscura el texto es
   claro → esfera más clara; sobre paleta clara es oscuro → esfera más oscura. Bien marcado. */
.widget-clock .cf-face {
  fill: color-mix(in srgb, currentColor 17%, transparent);
  stroke: color-mix(in srgb, currentColor 34%, transparent); stroke-width: 1.2;
}
.widget-clock .cf-tick { fill: color-mix(in srgb, currentColor 45%, transparent); }
.widget-clock .cf-tick-big { fill: currentColor; }
/* Origen de giro en el centro del viewBox (50,50) para todas las manecillas. */
.widget-clock .cf-hour, .widget-clock .cf-min, .widget-clock .cf-sec {
  transform-box: view-box; transform-origin: 50px 50px; stroke-linecap: round;
}
.widget-clock .cf-hour { stroke: currentColor; stroke-width: 3.4; }
.widget-clock .cf-min { stroke: currentColor; stroke-width: 2.2; }
.widget-clock .cf-sec { stroke: var(--w-accent, var(--accent)); stroke-width: 1.1; }
.widget-clock .cf-cap { fill: var(--w-accent, var(--accent)); }

.widget-weather .weather-icon img { width: clamp(8rem, 28vh, 18rem); height: auto; }
.widget-weather .weather-temp {
  font-size: clamp(4rem, 16vh, 11rem);
  font-weight: 900;
  line-height: 1;
}
.widget-weather .weather-city { font-size: clamp(1.4rem, 5vh, 3.4rem); font-weight: 700; }
.widget-weather .weather-desc { font-size: clamp(1rem, 3vh, 2.2rem); color: var(--w-muted, color-mix(in srgb, currentColor 62%, transparent)); }
.widget-weather .weather-minmax { font-size: clamp(1rem, 3vh, 2rem); color: var(--w-muted, color-mix(in srgb, currentColor 62%, transparent)); }
.widget-weather .weather-updated { font-size: clamp(0.7rem, 1.8vh, 1.2rem); color: var(--w-muted, color-mix(in srgb, currentColor 62%, transparent)); opacity: 0.7; margin-top: 0.6vh; }
.widget-weather .weather-updated:empty { display: none; }

/* Tira de pronóstico 5 días */
.weather-forecast {
  display: flex; gap: clamp(1rem, 3vw, 3rem); justify-content: center; align-items: center;
  margin-top: 3.5vh;
  width: 100%;
}
.forecast-day { display: flex; flex-direction: column; align-items: center; gap: 0.6vh; }
.fc-name {
  font-size: clamp(0.75rem, 2vh, 1.5rem); font-weight: 700; opacity: 0.75;
  text-transform: capitalize;
}
.fc-icon { width: clamp(2rem, 5.5vh, 4.5rem); height: auto; }
.fc-temp { display: flex; gap: 0.6vw; font-size: clamp(0.7rem, 2vh, 1.5rem); font-variant-numeric: tabular-nums; }
.fc-max { font-weight: 800; }
.fc-min { opacity: 0.55; }

/* Variante weather2: fondo semitransparente sobre el canvas */
.w2-forecast {
  background: rgba(0,0,0,0.28);
  border-radius: 1.4vh;
  padding: 1.2vh 2.5vw;
  border-top: none;
  margin-top: 1.5vh;
  margin-bottom: 2vh;
  width: auto;
  align-self: center;
  backdrop-filter: blur(6px);
}

/* Clima animado (weather2) */
.widget-weather2 { position: absolute; inset: 0; overflow: hidden; }
.w2-bg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.w2-content {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  padding-bottom: 8vh; gap: 0.6vh; text-align: center; color: #fff;
}
.w2-temp {
  font-size: clamp(5rem, 20vh, 15rem); font-weight: 900; line-height: 1;
  text-shadow: 0 4px 40px rgba(0,0,0,0.45), 0 1px 4px rgba(0,0,0,0.5);
}
.w2-city {
  font-size: clamp(1.4rem, 5vh, 3.5rem); font-weight: 700;
  text-shadow: 0 2px 16px rgba(0,0,0,0.5);
}
.w2-desc {
  font-size: clamp(1.1rem, 3.8vh, 2.8rem); font-weight: 600; opacity: 0.92;
  text-shadow: 0 2px 12px rgba(0,0,0,0.5);
}
.w2-minmax {
  font-size: clamp(0.9rem, 2.8vh, 2.1rem); opacity: 0.72;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}
.w2-updated {
  font-size: clamp(0.7rem, 1.9vh, 1.3rem); opacity: 0.6; margin-top: 0.4vh;
  text-shadow: 0 2px 8px rgba(0,0,0,0.5);
}
.w2-updated:empty { display: none; }

/* YouTube: ocupa toda la slide. Fondo negro y el iframe oculto hasta que
   el vídeo está reproduciendo (evita póster/branding). */
.widget-youtube { position: absolute; inset: 0; padding: 0; background: #000; overflow: hidden; container-type: size; }
.widget-youtube iframe { width: 100%; height: 100%; border: 0; display: block; transition: opacity 0.4s ease; }
.widget-youtube.yt-hidden iframe { opacity: 0; }
.widget-youtube .yt-poster { width: 100%; height: 100%; object-fit: cover; display: block; }

/* «Cubrir»: el vídeo llena la pantalla y lo que sobra se recorta. Un vídeo de YouTube es
   apaisado, así que en una pantalla de pie el ajuste normal («contener») deja dos franjas
   negras enormes. No hay object-fit para un iframe: se agranda hasta tapar el hueco por el
   lado que se quede corto y se centra, que es lo mismo que hace object-fit: cover. */
.widget-youtube[data-fit='cover'] iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100cqw, 100cqh * 16 / 9);
  height: max(100cqh, 100cqw * 9 / 16);
  transform: translate(-50%, -50%);
}

/* ── Mensaje en vivo (toast empujado por el admin) ───────────── */
/* Capa GLOBAL (fixed), por encima de los rótulos (#on6-overlays, z-50). position:fixed +
   la regla de rotación de arriba replican el encuadre de .screen (antes se colgaba de la
   pantalla visible para heredar el giro; ahora va suelto en body). */
.on6-message {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6vmin;
  pointer-events: none;
  opacity: 0;
  background: rgba(5, 8, 14, 0);
  transition: opacity .45s ease, background-color .45s ease;
}
.on6-message.show { opacity: 1; background: rgba(5, 8, 14, 0.45); }
.on6-message.hide { opacity: 0; background: rgba(5, 8, 14, 0); }

.on6-message-card {
  max-width: 80%;
  padding: clamp(1.2rem, 4vmin, 3rem) clamp(1.6rem, 5vmin, 4rem);
  border-radius: clamp(16px, 2.5vmin, 28px);
  background: linear-gradient(160deg, rgba(28, 36, 50, 0.96), rgba(16, 22, 33, 0.96));
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  color: var(--fg);
  text-align: center;
  font-weight: 800;
  font-size: clamp(1.8rem, 6vmin, 5rem);
  line-height: 1.2;
  letter-spacing: -0.01em;
  text-wrap: balance;
  white-space: pre-wrap;
  word-break: break-word;
  transform: scale(0.92) translateY(12px);
  transition: transform .45s cubic-bezier(0.16, 1, 0.3, 1);
}
.on6-message.show .on6-message-card { transform: scale(1) translateY(0); }

/* ── Medidor de diagnóstico (overlay opcional, esquina) ───────── */
/* Ligero a propósito: sin blur (sería irónico en un player flojo). No captura eventos. */
.on6-stats {
  position: fixed;
  top: 14px;
  left: 14px;
  z-index: 2000;          /* sobre casi todo, bajo el toast .on6-message (que es modal) */
  min-width: 220px;
  padding: 9px 12px;
  border-radius: 10px;
  background: rgba(6, 10, 16, 0.82);
  border: 1px solid rgba(120, 210, 165, 0.28);
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
  color: #e8f0f5;
  font: 12px/1.5 ui-monospace, "SF Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace;
  pointer-events: none;
  user-select: none;
}
.on6-stats-h { font-weight: 700; color: #57e39a; margin-bottom: 5px; letter-spacing: 0.02em; }
.on6-stats-r { display: flex; justify-content: space-between; gap: 18px; }
.on6-stats-r span { color: #7c8b99; }
.on6-stats-r b { color: #eef4f8; font-weight: 600; }

/* ── Rótulos (sobreimpresos de esquina, capa persistente) ─────── */
/* Capa GLOBAL de rótulos (fuera de las secciones): se pinta también en el splash, porque
   los rótulos de Pantalla y de Playlist no dependen del contenido. position:fixed + la
   regla de rotación de arriba replican el encuadre de .screen (incluido el giro). */
#on6-overlays {
  position: fixed;
  inset: 0;
  z-index: 50;            /* sobre slides (1-10), bajo .on6-message (1000) */
  pointer-events: none;
}

.on6-rotulo {
  position: absolute;
  width: var(--on6-rotulo-size, 20vmin);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1vmin;
  pointer-events: none;
}

/* 9 anclas; margen del borde vía variable (overscan-safe). */
.on6-rotulo.r-tl { top: var(--on6-rotulo-margin, 4vmin); left: var(--on6-rotulo-margin, 4vmin); }
.on6-rotulo.r-tc { top: var(--on6-rotulo-margin, 4vmin); left: 50%; transform: translateX(-50%); }
.on6-rotulo.r-tr { top: var(--on6-rotulo-margin, 4vmin); right: var(--on6-rotulo-margin, 4vmin); }
.on6-rotulo.r-ml { top: 50%; left: var(--on6-rotulo-margin, 4vmin); transform: translateY(-50%); }
.on6-rotulo.r-mc { top: 50%; left: 50%; transform: translate(-50%, -50%); }
.on6-rotulo.r-mr { top: 50%; right: var(--on6-rotulo-margin, 4vmin); transform: translateY(-50%); }
.on6-rotulo.r-bl { bottom: var(--on6-rotulo-margin, 4vmin); left: var(--on6-rotulo-margin, 4vmin); }
.on6-rotulo.r-bc { bottom: var(--on6-rotulo-margin, 4vmin); left: 50%; transform: translateX(-50%); }
.on6-rotulo.r-br { bottom: var(--on6-rotulo-margin, 4vmin); right: var(--on6-rotulo-margin, 4vmin); }

/* Imagen (logo): la pinta tal cual; su aspecto/fondo es del propio PNG. */
.on6-rotulo > img.on6-rotulo-content { display: block; width: 100%; height: auto; }

/* Reloj/clima COMPACTOS para rótulos: markup propio (control total) renderizado DIRECTO en
   el rótulo. El tamaño se toma del ANCHO del rótulo con container queries (cqw = 1% del
   ancho): escala con el rótulo, se centra y NO se recorta; la caja se ajusta al contenido. */
/*
 * El reloj y el clima COMPACTOS, con su propio markup.
 *
 * Valen en un rótulo de esquina y en un hueco de plantilla, que son la misma cosa: un widget
 * pintado encima de otro contenido, en una caja de cualquier tamaño. Estaban atados a
 * `.on6-rotulo`, así que en un hueco no aplicaba ninguna de estas reglas y el compacto salía
 * sin maquetar.
 *
 * El tamaño sale del ANCHO de la caja con consultas de contenedor (cqw = 1% del ancho): escala
 * con ella, se centra y no se recorta.
 */
:is(.on6-rotulo, .plantilla-hueco) { container-type: inline-size; }
:is(.on6-rotulo, .plantilla-hueco) .on6-rotulo-widget {
  width: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center;
}

/* Reloj: hora protagonista + fecha corta pequeña opcional debajo. La fuente se ajusta al
   formato: 12h añade "p. m." y los segundos alargan → menor tamaño para que quepa. */
:is(.on6-rotulo, .plantilla-hueco) .clock-inline { gap: 1.5cqw; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline .clock-time { font-size: 32cqw; font-weight: 900; line-height: 1; white-space: nowrap; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline[data-seconds="1"] .clock-time { font-size: 21cqw; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline[data-format="12h"] .clock-time { font-size: 16cqw; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline[data-format="12h"][data-seconds="1"] .clock-time { font-size: 12cqw; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline .clock-date { font-size: 9cqw; line-height: 1.1; white-space: nowrap; opacity: 0.92; }
:is(.on6-rotulo, .plantilla-hueco) .clock-inline[data-date="0"] .clock-date { display: none; }

/*
 * Lo secundario del reloj, en el color de SU CAJA.
 *
 * A pantalla completa la fecha va en gris y la etiqueta en el color de acento, los dos del
 * cliente de TV. Encima de otra cosa eso es mentira: el rótulo o el hueco traen su propio
 * color —en una plantilla, el que eligió el diseñador— y la hora lo cogía, pero la fecha se
 * quedaba en el gris de siempre. La jerarquía la sigue haciendo la opacidad, que no depende de
 * ningún color.
 *
 * Va sobre `.widget-clock` y no sobre `.clock-inline` porque en un hueco el reloj NO es el
 * compacto: la plantilla decide el formato, y el de pantalla completa cabe igual. Atarlo al
 * compacto fue el primer intento y no se aplicaba nunca.
 */
:is(.on6-rotulo, .plantilla-hueco) .widget-clock .clock-date { color: var(--w-accent, currentColor); }
:is(.on6-rotulo, .plantilla-hueco) .widget-clock .clock-label { color: var(--w-accent, currentColor); }

/* Clima: icono GRANDE + temperatura en una fila, y la ciudad centrada debajo.
   (.weather-inline hereda columna+centrado de .on6-rotulo-widget.) */
:is(.on6-rotulo, .plantilla-hueco) .weather-inline { gap: 0; }
:is(.on6-rotulo, .plantilla-hueco) .weather-inline .wi-top {
  display: flex; flex-direction: row; align-items: center; justify-content: center;
  gap: 2cqw; max-width: 100%;
}
:is(.on6-rotulo, .plantilla-hueco) .weather-inline .wi-icon { width: 44cqw; height: 44cqw; object-fit: contain; flex: 0 0 auto; }
:is(.on6-rotulo, .plantilla-hueco) .weather-inline .wi-temp { font-size: 30cqw; font-weight: 900; line-height: 1; flex: 0 0 auto; }
:is(.on6-rotulo, .plantilla-hueco) .weather-inline .wi-city {
  font-size: 13cqw; font-weight: 700; line-height: 1; max-width: 100%; margin-top: -8cqw;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* En un hueco la caja tiene alto propio, así que el widget lo llena y se centra dentro; en un
   rótulo la caja se ajusta al contenido y no debe estirarse. */
.plantilla-hueco > .on6-rotulo-widget { height: 100%; }

/*
 * Un widget A PANTALLA COMPLETA metido en un hueco de plantilla.
 *
 * Aquí no siempre entra el compacto: la plantilla elige el formato, y un clima «completo» o un
 * reloj normal caben perfectamente en una caja grande. El problema es que sus tamaños están en
 * `vh` —del CARTEL ENTERO, 720 px— así que en una caja pequeña el reloj se salía por abajo: la
 * hora se dibujaba de 158 px dentro de un hueco de 120. Medía la pantalla, no su caja.
 *
 * Se arregla dándole a la caja las DOS dimensiones (`container-type: size`, que puede porque su
 * alto se lo pone la plantilla) y midiendo con `min(alto, ancho)`: manda el eje que se quede
 * corto, que es lo que hace que quepa tanto en una caja apaisada como en una vertical.
 */
.plantilla-hueco { container-type: size; }

.plantilla-hueco .widget-clock .clock-time { font-size: min(50cqh, 26cqw); }
.plantilla-hueco .widget-clock .clock-date { font-size: min(13cqh, 8cqw); }
.plantilla-hueco .widget-clock .clock-label { font-size: min(14cqh, 9cqw); }

.plantilla-hueco .widget-weather .weather-icon img { width: min(30cqh, 30cqw); }
.plantilla-hueco .widget-weather .weather-temp { font-size: min(26cqh, 22cqw); }
.plantilla-hueco .widget-weather .weather-city { font-size: min(9cqh, 9cqw); }
.plantilla-hueco .widget-weather .weather-desc,
.plantilla-hueco .widget-weather .weather-minmax { font-size: min(6cqh, 6cqw); }
.plantilla-hueco .widget-weather .weather-updated { font-size: min(4cqh, 4cqw); margin-top: 0.5cqh; }
.plantilla-hueco .weather-forecast { gap: 2cqw; margin-top: 2cqh; }
.plantilla-hueco .forecast-day { gap: 0.5cqh; }
.plantilla-hueco .fc-name,
.plantilla-hueco .fc-max,
.plantilla-hueco .fc-min { font-size: min(4.5cqh, 3cqw); }
.plantilla-hueco .forecast-day img { width: min(9cqh, 7cqw); height: auto; }

/* QR widget — su aspecto (blanco + pie) es GLOBAL: vale a pantalla completa
   (diapositiva / galería de widgets) y dentro de un rótulo. A pantalla completa
   se centra y se limita el tamaño; el rótulo lo sobreescribe para llenar la cajita. */
.widget-qr {
  width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2vmin; box-sizing: border-box; padding: 4vmin;
}
.widget-qr .qr-img {
  width: min(56vmin, 90%);
  background: #fff;
  border-radius: 2vmin;
  padding: 1.4vmin;
  box-shadow: 0 0.6vmin 2vmin rgba(0, 0, 0, 0.45);
}
.widget-qr .qr-img svg { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; }

/* Tamaños elegibles (config.size, slider del editor). 'm' = el ancho base de arriba.
   Solo afectan a pantalla completa: las reglas .on6-rotulo de abajo pisan el ancho. */
.widget-qr[data-size="s"] .qr-img { width: min(38vmin, 70%); }
.widget-qr[data-size="l"] .qr-img { width: min(68vmin, 92%); }
.widget-qr[data-size="xl"] .qr-img { width: min(80vmin, 94%); }
.widget-qr[data-size="s"] .qr-caption { font-size: 2.4vmin; }
.widget-qr[data-size="l"] .qr-caption { font-size: 3.4vmin; }
.widget-qr[data-size="xl"] .qr-caption { font-size: 3.8vmin; }
.widget-qr .qr-caption {
  font-weight: 800; font-size: 3vmin; line-height: 1.15; color: #fff; text-align: center;
  max-width: 90%; padding: 0.6vmin 1.6vmin; border-radius: 1vmin; background: rgba(0, 0, 0, 0.55);
}

/* Dentro de un rótulo (esquina): llena la cajita; su tamaño lo da .on6-rotulo. */
.on6-rotulo .widget-qr { height: auto; padding: 0; gap: 1vmin; }
.on6-rotulo .widget-qr .qr-img { width: 100%; border-radius: 1.6vmin; padding: 0.7vmin; }
.on6-rotulo .widget-qr .qr-caption { font-size: 2vmin; padding: 0.5vmin 1.2vmin; max-width: 100%; }

/* ── Cartel con producto (widget 'scene') ─────────────────────────────
   Dos capas de la MISMA foto: la escena entera de fondo y el sujeto recortado encima.
   Comparten caja y object-fit, así que el producto cae exactamente donde estaba y sigue
   apoyado en su superficie, con la sombra original (que nunca se quitó).
   Todo se dimensiona en cqw para que valga igual en la TV, en el preview y en un rótulo. */
.widget-scene, .widget-animate {
  /* `size` y no `inline-size`: con inline-size las unidades cqh (que usa el bloque de
     texto para no desbordar) no se resuelven contra este contenedor. Aquí es seguro
     porque el alto no depende del contenido: lo fija el inset. */
  position: absolute; inset: 0; width: 100%; height: 100%;
  overflow: hidden; container-type: size;
  background: #0d0906;
}

/*
 * El cartel dentro de la pantalla, con LA FORMA DEL CARTEL y no la de la pantalla.
 *
 * Cuando las dos coinciden (lo normal: pantalla apaisada, cartel apaisado) el lienzo llena
 * todo y no se nota. En una pantalla VERTICAL, antes se ampliaba el cartel hasta llenarla y
 * solo se veía su franja central; ahora se ve entero y centrado, igual que hace la tele con
 * cualquier otra imagen.
 *
 * `min()` en vez de `aspect-ratio` + max-width/max-height: con esos dos, el ancho no encoge
 * cuando manda la altura y la caja se deforma. Aquí las dos medidas salen de la misma
 * proporción, así que siempre cuadran.
 *
 * `--sc-ar` (ancho ÷ alto) lo escribe el render al cargar la imagen; 16:9 mientras tanto,
 * que es la forma de casi todos los carteles.
 */
.sc-lienzo {
  position: absolute; inset: 0; margin: auto;
  width: min(100cqw, 100cqh * var(--sc-ar, 1.7778));
  height: min(100cqh, 100cqw / var(--sc-ar, 1.7778));
  /* El texto del cartel se mide en cq: contra el CARTEL, no contra la pantalla, o en
     vertical saldría diminuto y fuera de sitio. */
  container-type: size;
}
.widget-scene .scene-plate { position: absolute; inset: 0; overflow: hidden; }
.widget-scene .scene-plate img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
/* Lo que se mueve va en su propia capa de GPU: sin esto el navegador vuelve a rasterizar la
   foto (1536 px) en cada fotograma del zoom. */
.widget-scene .sc-stage,
.widget-scene .sc-cut,
.widget-scene .sc-flash { will-change: transform; backface-visibility: hidden; }

/* ── Los cuatro niveles ────────────────────────────────────────────
   El movimiento SIEMPRE va en el envoltorio (.sc-stage) o en escalas HACIA ARRIBA del
   producto. Motivo: el fondo es la foto entera y el recorte está tapando a su propio
   original, así que desplazarlo o encogerlo lo dejaría asomar. Agrandarlo, en cambio, tapa
   MÁS: por eso el latido crece y nunca baja de su tamaño real. */

.widget-scene .sc-stage { position: absolute; inset: 0; }
/* El producto crece SIEMPRE desde un punto que cae dentro de él (`--sc-anchor`, el
   centro-base del sujeto que calculó el recorte). Con el origen en el centro del lienzo,
   al escalar se desplazaba hacia su lado y por el contrario asomaba el original. */
.widget-scene .sc-cut { transform-origin: var(--sc-anchor, 74% 62%); }

/* 1 · Quieto — una foto fija. */
.widget-scene[data-motion="ninguna"] .sc-stage,
.widget-scene[data-motion="ninguna"] .sc-cut,
.widget-scene[data-motion="ninguna"] .sc-flash,
.widget-scene[data-motion="ninguna"] .sc-halo,
.widget-scene[data-motion="ninguna"] .sc-spark { animation: none; will-change: auto; }
.widget-scene[data-motion="ninguna"] .sc-halo,
.widget-scene[data-motion="ninguna"] .sc-spark { display: none; }

/* 2 · Suave — la escena se acerca al entrar y luego deriva muy despacio. El producto
   respira, apenas. Para sitios donde un cartel dando saltos molesta. */
.widget-scene[data-motion="suave"] .sc-stage {
  animation: sc-in 1.8s cubic-bezier(.2, .8, .3, 1) both,
             sc-drift 30s ease-in-out 1.8s infinite alternate;
}
.widget-scene[data-motion="suave"] .sc-cut {
  animation: sc-fade 1.4s ease-out both,
             sc-breath 9s ease-in-out 2s infinite;
}

/* 3 · Vibrante — el recorrido del fondo se nota, y el producto late cada 5 s con un
   golpe de luz. Es el punto medio de verdad, no un "normal" disimulado. */
.widget-scene[data-motion="vibrante"] .sc-stage {
  animation: sc-in 1.5s cubic-bezier(.2, .8, .3, 1) both,
             sc-drift-fuerte 16s ease-in-out 1.5s infinite alternate;
}
.widget-scene[data-motion="vibrante"] .sc-cut {
  animation: sc-fade 1.2s ease-out both,
             sc-latido 5s cubic-bezier(.34, 1.56, .64, 1) 1.6s infinite;
}
.widget-scene[data-motion="vibrante"] .sc-halo { animation: sc-halo 5s ease-in-out 1.6s infinite; }

/* 4 · Espectáculo — para llamar la atención desde la otra punta del local: el fondo no
   para, el producto pega un latido grande con destello, cruza un brillo por el cartel,
   el precio salta y suben chispas. */
.widget-scene[data-motion="espectaculo"] .sc-stage {
  animation: sc-in 1.2s cubic-bezier(.2, .8, .3, 1) both,
             sc-drift-fuerte 11s ease-in-out 1.2s infinite alternate;
}
.widget-scene[data-motion="espectaculo"] .sc-cut {
  animation: sc-entrada 1.3s cubic-bezier(.16, 1.5, .3, 1) both,
             sc-latidon 4s cubic-bezier(.34, 1.7, .64, 1) 1.4s infinite,
             sc-shine 4s ease-in-out 1.4s infinite;
}
.widget-scene[data-motion="espectaculo"] .sc-halo { animation: sc-halo 4s ease-in-out 1.4s infinite; }
.widget-scene[data-motion="espectaculo"] .sc-flash { animation: sc-flash 8s ease-in-out 3s infinite; }

/* Movimiento de la escena */
@keyframes sc-in { from { transform: scale(1.24); } to { transform: scale(1.06); } }
@keyframes sc-drift {
  from { transform: scale(1.06) translate(0, 0); }
  to { transform: scale(1.11) translate(-1.2%, -.6%); }
}
/* OJO: el primer fotograma tiene que ser EXACTAMENTE el último de la entrada
   (scale(1.06) sin desplazar). Si no, al acabar el zoom la escena pega un tirón: es lo que
   pasaba al empezar la deriva ya desplazada. */
@keyframes sc-drift-fuerte {
  from { transform: scale(1.06) translate(0, 0); }
  to { transform: scale(1.18) translate(-2.6%, -1.5%); }
}

/* El producto. Ninguna escala baja de 1: por debajo asomaría el original del fondo. */
@keyframes sc-fade { from { opacity: .5; } to { opacity: 1; } }
@keyframes sc-entrada { from { opacity: .3; transform: scale(1.12); } to { opacity: 1; transform: scale(1); } }
@keyframes sc-breath { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.012); } }
@keyframes sc-latido {
  0%, 62%, 100% { transform: scale(1); }
  70% { transform: scale(1.045); }
  78% { transform: scale(1.012); }
  84% { transform: scale(1.028); }
}
@keyframes sc-latidon {
  0%, 55%, 100% { transform: scale(1); }
  64% { transform: scale(1.085); }
  72% { transform: scale(1.02); }
  80% { transform: scale(1.05); }
  88% { transform: scale(1.008); }
}

/* Halo: la misma silueta detrás, difuminada, que se enciende con el latido. */
/* El halo lleva un desenfoque GRANDE, y animar su escala obligaría a recalcularlo en cada
   fotograma (es de lo más caro que hay). Se deja quieto y solo se anima la opacidad, que la
   resuelve la GPU sin repintar. El efecto de "encenderse" se nota igual. */
.widget-scene .sc-halo {
  filter: blur(2.2cqw) saturate(1.8) brightness(1.5);
  opacity: 0; transform: scale(1.06); transform-origin: var(--sc-anchor, 74% 62%);
  will-change: opacity;
}
@keyframes sc-halo {
  0%, 55%, 100% { opacity: 0; }
  66% { opacity: .9; }
  80% { opacity: .3; }
}

/* Solo brillo y saturación: el drop-shadow animado que había aquí recalculaba la sombra de
   la silueta en cada fotograma. El resplandor de color ya lo pone el halo. */
@keyframes sc-shine {
  0%, 55%, 100% { filter: none; }
  64% { filter: brightness(1.4) saturate(1.3); }
  76% { filter: brightness(1.12) saturate(1.1); }
}

/* Chispas que suben (solo en Espectáculo) */
.widget-scene .sc-spark {
  position: absolute; bottom: 12cqh; z-index: 3;
  width: .55cqw; height: .55cqw; border-radius: 50%;
  /* Degradado en vez de blur: mismo aspecto difuso y sin filtro que recalcular. */
  background: radial-gradient(circle,
    color-mix(in srgb, var(--sc-main) 70%, #fff) 35%, transparent 70%);
  opacity: 0; pointer-events: none; will-change: transform, opacity;
  animation: sc-spark 8s ease-in-out infinite;
}
@keyframes sc-spark {
  0% { transform: translateY(0) scale(.5); opacity: 0; }
  15% { opacity: .95; }
  100% { transform: translateY(-30cqh) scale(1.15); opacity: 0; }
}

/* Las entradas empiezan CUANDO SE VE la diapositiva, no cuando se monta.
   El carrusel construye los widgets por adelantado (warmSlide, para que un player flojo no
   tenga que montar varios iframes de golpe): sin esto, la entrada de 1-2 s se consumía
   quince segundos antes de que la diapositiva llegara a la pantalla y el cartel aparecía ya
   colocado. Se pausa solo DENTRO de una diapositiva que aún no se muestra: en la vista
   previa del admin el widget no vive en un `.slide`, así que corre normal.
   Vale para las dos familias de cartel.

   🔴 `!important` A PROPÓSITO: las capas declaran su animación con la FORMA CORTA
   (`animation: nombre dur …`), que reescribe `animation-play-state` a `running`, y su
   selector empata en especificidad con este — al ir después en la hoja, ganaba y la
   diapositiva llegaba con su entrada ya gastada. Esto es un estado que tiene que pisar a
   cualquier animación, la de hoy y la que se añada mañana. */
.slide:not(.show):not(.enter) .widget-scene *,
.slide:not(.show):not(.enter) .widget-animate * { animation-play-state: paused !important; }

/* ── Imagen animada: catálogo de animaciones por capa ──────────────
   Cada capa (fondo, producto, rótulo) se mueve por su cuenta en dos ejes: una ENTRADA que
   ocurre una vez y un BUCLE que sigue después. El catálogo vive en
   backend/src/widgets/animations.ts y de ahí salen estos nombres de clase; añadir una
   animación es añadirla allí y sus keyframes aquí.

   Reglas que no se rompen:
   · Solo se anima `transform` y `opacity`. Nada de `filter` ni `box-shadow` animados: en un
     player modesto cada fotograma obliga a repintar la imagen entera (1536 px).
   · Ninguna entrada deja la capa desplazada o escalada al terminar: todas acaban en
     `transform: none`, o sea en el sitio exacto que ocupaban en el cartel original. Si el
     fondo se quedara ampliado, se comería sus bordes y descolocaría al rótulo. */

.widget-animate .sc-capa {
  position: absolute; inset: 0;
  will-change: transform, opacity; backface-visibility: hidden;
}
.widget-animate .sc-capa img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
}
/* El producto crece desde un punto que cae DENTRO de él (lo mide el recorte). */
.widget-animate .sc-cut { transform-origin: var(--sc-anchor, 50% 50%); }

/*
 * El movimiento de las capas: las reglas que convierten `data-in`/`data-loop` en animación, y sus
 * keyframes. Lo escribe `backend/scripts/generar-css-animaciones.mjs` desde
 * `backend/src/widgets/animations.ts`, que es la ÚNICA fuente — de ahí salen también el catálogo
 * que se ofrece en el panel y el CSS que viaja dentro del cartel de una plantilla (que vive en un
 * iframe con origen opaco y no ve esta hoja).
 *
 * NO EDITAR A MANO entre los marcadores: se pierde al regenerar. Para cambiar un efecto o añadir
 * uno, se toca el módulo y se lanza `npm run gen:animaciones`.
 *
 * Los tres tiempos llegan en SEGUNDOS desde el estilo del elemento (`--in-delay`, `--in-dur`,
 * `--loop-dur`), no como niveles con una tabla de equivalencias aquí.
 */
/* __ON6_ANIMACIONES__ — GENERADO por backend/scripts/generar-css-animaciones.mjs. No editar a mano. */
.widget-animate .sc-capa{--in-dur:1.4s;--loop-dur:6s;--in-delay:0s;}
.widget-animate .sc-capa[data-in="fundido"]{--in-name:an-fundido;}
.widget-animate .sc-capa[data-in="acercar"]{--in-name:an-acercar;}
.widget-animate .sc-capa[data-in="alejar"]{--in-name:an-alejar;}
.widget-animate .sc-capa[data-in="abajo"]{--in-name:an-abajo;}
.widget-animate .sc-capa[data-in="arriba"]{--in-name:an-arriba;}
.widget-animate .sc-capa[data-in="izquierda"]{--in-name:an-izquierda;}
.widget-animate .sc-capa[data-in="derecha"]{--in-name:an-derecha;}
.widget-animate .sc-capa[data-in="volando"]{--in-name:an-volando;--in-ease:cubic-bezier(.16,1.5,.3,1);}
.widget-animate .sc-capa[data-in="salto"]{--in-name:an-salto;--in-ease:cubic-bezier(.16,1.7,.3,1);}
.widget-animate .sc-capa[data-loop="flota"]{--loop-name:an-flota;}
.widget-animate .sc-capa[data-loop="late"]{--loop-name:an-late;}
.widget-animate .sc-capa[data-loop="baila"]{--loop-name:an-baila;}
.widget-animate .sc-capa[data-loop="balancea"]{--loop-name:an-balancea;}
.widget-animate .sc-capa[data-loop="deriva"]{--loop-name:an-deriva;}
.widget-animate .sc-capa[data-loop="parpadea"]{--loop-name:an-parpadea;}
.widget-animate .sc-capa[data-in]:not([data-in="ninguna"]){animation:var(--in-name) var(--in-dur) var(--in-ease,cubic-bezier(.2,.8,.3,1)) var(--in-delay) both;}
.widget-animate .sc-capa[data-loop]:not([data-loop="ninguno"]){animation:var(--in-name,an-nada) var(--in-dur) var(--in-ease,cubic-bezier(.2,.8,.3,1)) var(--in-delay) both,var(--loop-name) var(--loop-dur) ease-in-out calc(var(--in-delay) + var(--in-dur)) infinite;}
.widget-animate .sc-capa[data-in="ninguna"][data-loop]:not([data-loop="ninguno"]){animation:var(--loop-name) var(--loop-dur) ease-in-out infinite;}
.widget-animate .sc-capa[data-adorno]::before,.widget-animate .sc-capa[data-adorno]::after{content:"";position:absolute;pointer-events:none;border-radius:inherit;}
.widget-animate .sc-capa[data-adorno="barrido"]{overflow:hidden;}
.widget-animate .sc-capa[data-adorno="barrido"]::after{top:0;bottom:0;left:0;width:42%;background:linear-gradient(100deg,transparent,var(--adorno-color,#fff),transparent);opacity:.72;animation:an-barrido var(--adorno-dur,4.5s) ease-in-out infinite;}
.widget-animate .sc-capa[data-adorno="halo"]::after{inset:0;background:radial-gradient(ellipse at center,transparent 62%,var(--adorno-color,#fff) 112%);animation:an-halo var(--adorno-dur,3.2s) ease-in-out infinite;}
.widget-animate .sc-capa[data-adorno="chispas"]::before{inset:0;background-repeat:no-repeat;background-image:radial-gradient(circle,var(--adorno-color,#fff) 0 38%,transparent 72%),radial-gradient(circle,var(--adorno-color,#fff) 0 38%,transparent 72%);background-size:2.6cqh 2.6cqh,1.9cqh 1.9cqh;background-position:14% 24%,78% 66%;animation:an-chispa var(--adorno-dur,3.6s) ease-in-out infinite;}
.widget-animate .sc-capa[data-adorno="chispas"]::after{inset:0;background-repeat:no-repeat;background-image:radial-gradient(circle,var(--adorno-color,#fff) 0 38%,transparent 72%),radial-gradient(circle,var(--adorno-color,#fff) 0 38%,transparent 72%);background-size:2.1cqh 2.1cqh,3cqh 3cqh;background-position:62% 18%,28% 74%;animation:an-chispa calc(var(--adorno-dur,3.6s) * 1.35) ease-in-out -1.4s infinite;}
@keyframes an-nada{from{opacity:1}to{opacity:1}}
@keyframes an-fundido{from{opacity:0}to{opacity:1}}
@keyframes an-acercar{from{opacity:.35;transform:scale(1.08)}to{opacity:1;transform:none}}
@keyframes an-alejar{from{opacity:.35;transform:scale(.9)}to{opacity:1;transform:none}}
@keyframes an-abajo{from{opacity:0;transform:translateY(12%)}to{opacity:1;transform:none}}
@keyframes an-arriba{from{opacity:0;transform:translateY(-12%)}to{opacity:1;transform:none}}
@keyframes an-izquierda{from{opacity:0;transform:translateX(-12%)}to{opacity:1;transform:none}}
@keyframes an-derecha{from{opacity:0;transform:translateX(12%)}to{opacity:1;transform:none}}
@keyframes an-volando{from{opacity:0;transform:translateX(-14%) rotate(-6deg) scale(.86)}to{opacity:1;transform:none}}
@keyframes an-salto{from{opacity:0;transform:translateY(16%) scale(.88)}to{opacity:1;transform:none}}
@keyframes an-flota{0%,100%{transform:translateY(0)}50%{transform:translateY(-1.6%)}}
@keyframes an-late{0%,62%,100%{transform:scale(1)}70%{transform:scale(1.05)}78%{transform:scale(1.01)}86%{transform:scale(1.03)}}
@keyframes an-baila{0%,55%,100%{transform:translateY(0) scale(1) rotate(0deg)}64%{transform:translateY(-2.6%) scale(1.05) rotate(-1.2deg)}76%{transform:translateY(-.6%) scale(1.01) rotate(.6deg)}86%{transform:translateY(-1.4%) scale(1.03) rotate(0deg)}}
@keyframes an-balancea{0%,100%{transform:rotate(-.8deg)}50%{transform:rotate(.8deg)}}
@keyframes an-deriva{0%{transform:scale(1.06) translate(0,0)}100%{transform:scale(1.14) translate(-2%,-1.2%)}}
@keyframes an-parpadea{0%,70%,100%{opacity:1}80%{opacity:.45}90%{opacity:1}}
@keyframes an-barrido{0%{transform:translateX(-160%)}55%,100%{transform:translateX(320%)}}
@keyframes an-halo{0%,100%{opacity:.35}50%{opacity:.9}}
@keyframes an-chispa{0%,100%{opacity:.3}35%{opacity:1}70%{opacity:.35}}
/* __ON6_ANIMACIONES__ fin */

/* Velo bajo los textos: sin él, un titular claro sobre una escena clara no se lee. */
.widget-scene .sc-veil { position: absolute; inset: 0; z-index: 2; }
.widget-scene[data-side="izquierda"] .sc-veil {
  background: linear-gradient(96deg, rgba(20, 10, 2, .9) 2%, rgba(20, 10, 2, .5) 34%, rgba(20, 10, 2, 0) 60%);
}
.widget-scene[data-side="derecha"] .sc-veil {
  background: linear-gradient(264deg, rgba(20, 10, 2, .9) 2%, rgba(20, 10, 2, .5) 34%, rgba(20, 10, 2, 0) 60%);
}

.widget-scene .sc-flash {
  position: absolute; inset: -20%; z-index: 3; pointer-events: none;
  background: linear-gradient(105deg, transparent 38%, rgba(255, 245, 220, .5) 47%, transparent 56%);
  opacity: 0;
}
@keyframes sc-flash {
  0%, 72%, 100% { transform: translateX(-60%); opacity: 0; }
  74% { opacity: 1; }
  86% { transform: translateX(60%); opacity: 0; }
}

/* Textos */
.widget-scene .sc-copy {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 4;
  display: flex; flex-direction: column; max-width: 46cqw;
  /* El texto no puede desbordar, pero CORTARLO es peor que encogerlo: antes había aquí un
     max-height con overflow oculto y se comía las últimas líneas. Ahora se parte por
     palabras y cada bloque limita sus líneas; el tamaño ya lo ajusta el renderer al largo. */
  overflow-wrap: anywhere; word-break: break-word; hyphens: auto;
}
.widget-scene[data-side="izquierda"] .sc-copy { left: 5.5cqw; align-items: flex-start; text-align: left; }
.widget-scene[data-side="derecha"] .sc-copy { right: 5.5cqw; align-items: flex-end; text-align: right; }

.widget-scene {
  /* Los pisa el renderer con la paleta elegida (config.colors). `--sc-main` es el color de
     la paleta tal y como se ve en el selector: es el que manda en el texto. */
  --sc-main: #f2960f;
  --sc-accent: #ffd35e;
  --sc-ink: #12100c;
}
.widget-scene .sc-kicker {
  letter-spacing: .3em; text-transform: uppercase; font-weight: 700;
  color: color-mix(in srgb, var(--sc-main) 55%, #fff); animation: sc-slide .8s cubic-bezier(.2, 1.3, .35, 1) .15s both;
}
@keyframes sc-slide { from { opacity: 0; transform: translateX(-3cqw); } to { opacity: 1; transform: none; } }

.widget-scene .sc-title {
  line-height: .88; font-weight: 900; text-transform: uppercase;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden;
  margin-top: .5cqw; text-wrap: balance;
  background: linear-gradient(177deg, #fff 12%,
    color-mix(in srgb, var(--sc-main) 45%, #fff) 46%, var(--sc-main) 94%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(.3cqw .48cqw 0 rgba(0, 0, 0, .78));
  animation: sc-title-in .95s cubic-bezier(.16, 1.2, .3, 1) .3s both;
}
@keyframes sc-title-in { from { opacity: 0; transform: translateY(1.6cqw); } to { opacity: 1; transform: none; } }

.widget-scene .sc-price {
  position: relative; overflow: hidden; margin-top: 2cqw; align-self: inherit;
  /* El mismo degradado que enseña el selector de paletas: color principal → realce. */
  background: linear-gradient(160deg, var(--sc-main), var(--sc-accent)); color: var(--sc-ink);
  font-weight: 900; line-height: 1.1; padding: .1cqw 2.6cqw; max-width: 100%;
  border-radius: .8cqw; box-shadow: .45cqw .8cqw 0 rgba(46, 14, 0, .55);
  animation: sc-price-in 1s cubic-bezier(.16, 1.7, .3, 1) .7s both;
}
@keyframes sc-price-in { from { opacity: 0; transform: rotate(12deg) scale(.5); } to { opacity: 1; transform: rotate(-2.5deg) scale(1); } }
.widget-scene[data-motion="vibrante"] .sc-price,
.widget-scene[data-motion="espectaculo"] .sc-price {
  animation: sc-price-in 1s cubic-bezier(.16, 1.7, .3, 1) .7s both,
             sc-price-call 5s ease-in-out 1.8s infinite;
}
.widget-scene[data-motion="espectaculo"] .sc-price {
  animation: sc-price-in 1s cubic-bezier(.16, 1.7, .3, 1) .7s both,
             sc-price-call 4s ease-in-out 1.6s infinite;
}
@keyframes sc-price-call {
  0%, 58%, 100% { transform: rotate(-2.5deg) scale(1); }
  66% { transform: rotate(2deg) scale(1.14); }
  74% { transform: rotate(-5deg) scale(1.04); }
  82% { transform: rotate(-1deg) scale(1.07); }
  90% { transform: rotate(-2.5deg) scale(1); }
}
.widget-scene .sc-price::after {
  content: ''; position: absolute; inset: 0; width: 26%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .9), transparent);
  transform: translateX(-160%) skewX(-18deg);
}
.widget-scene[data-motion="vibrante"] .sc-price::after,
.widget-scene[data-motion="espectaculo"] .sc-price::after { animation: sc-sheen 6s ease-in-out 2s infinite; }
@keyframes sc-sheen {
  0%, 84%, 100% { transform: translateX(-160%) skewX(-18deg); }
  96% { transform: translateX(400%) skewX(-18deg); }
}

.widget-scene .sc-foot {
  letter-spacing: .18em; text-transform: uppercase;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
  color: color-mix(in srgb, var(--sc-main) 32%, #fff);
  margin-top: 1.5cqw; font-weight: 600;
  animation: sc-slide .8s cubic-bezier(.2, 1.3, .35, 1) 1.05s both;
}

.widget-scene .scene-missing, .widget-animate .scene-missing {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: #c9bfae; font-size: 3cqw; text-align: center; padding: 6cqw;
}

@media (prefers-reduced-motion: reduce) {
  .widget-scene *, .widget-animate * { animation: none !important; }
}
