/* ============================================================
   BITJU — Recetas visuales del overlay (bitju-obs-integration)
   ------------------------------------------------------------
   Portado desde bitju-app/public/design-editor/editor.css,
   solo las reglas de renderizado (banner/bubble/avatar/widget) —
   sin cromo de editor, sin fuentes embebidas.

   Depende de bitju-render-engine.css (@bitju/render-engine, copiado a
   public/ por copy-render-engine y cargado por index.html ANTES de este
   archivo) para los 7 .bitju-styled-box[data-style] y los 6
   .bubble[data-style] — ver el marker de Bloque 4-G más abajo. Este
   archivo sigue existiendo (no lo pisa el copy step — ver ese mismo
   marker) porque el resto de su contenido es estructural/local a este
   consumidor y nunca se migró al paquete.
============================================================ */

:root {
  --npc-size: 240px;     /* tamaño base del avatar — el styleObject lo puede
                             sobreescribir inline por elemento vía scale */
  --bubble-scale: 1;     /* escala base del globo, mismo mecanismo */
}

/* Fuente del overlay: elegible por el streamer (styleObject.tokens.font),
   nunca la fuente pixel-art del cromo de este repo (ver styles.css,
   'DotGothic16', que es solo para la UI del control-panel/overlay-shell,
   no para el contenido de un mensaje). El default acá replica el mismo
   fallback que tenía editor.css: system-ui, -apple-system, sans-serif —
   el schema's FONTS enum ya son todos fuentes de sistema, nunca las
   custom del editor. Selector de clase (no `body`) a propósito: gana por
   especificidad sin depender del orden de carga entre este archivo y
   styles.css. */
.message-with-circle,
.message-without-circle {
  font-family: var(--bitju-font, system-ui, -apple-system, sans-serif);
}

/* ── Canvas lógico de posicionamiento (Tarea 7.5) ──
   .style-canvas: position:relative + ancho/alto en píxeles reales,
   seteados inline por buildMessageElement (render-engine.js Sección
   B/E) — ver el comentario grande sobre positionElementOnCanvas ahí
   para el porqué de este wrapper dedicado en vez de reusar
   .message-wrapper. flex-shrink:0 evita que su ancestro flex-column
   más cercano (.message-wrapper, o #bubble-container en styles.css)
   intente comprimir esta medida pensando que es un item flexible más
   — el tamaño real ya viene fijado por canvasScale, no debe encogerse.

   .canvas-el: wrapper de posicionamiento absoluto para banner/bubble/
   widget/imagen/video — la escala del streamer (el.scale) va como `zoom`
   inline sobre este wrapper (2026-09-11, antes `transform: scale()`; ver
   positionElementOnCanvas en render-engine.js para el porqué), nunca
   sobre `.don-banner`/`.bubble`, que tienen su propio `transform` en CSS
   para la animación de entrada/sweep. transform-origin:0 0 solo importa
   ya para el translateX(-50%) del banner anclado por su centro. */
.style-canvas {
  position: relative;
  flex-shrink: 0;
}
.canvas-el {
  position: absolute;
  transform-origin: 0 0;
}

/* Fix (Important #2, fix wave final): el contenedor fallback
   (render-engine.js buildFallbackBubbleContainer — usado cuando el
   styleObject no trae un elemento `bubble` real, hoy solo
   control-panel.html vía synthesizeStyleObjectFromFlatFields) nunca
   llevaba ninguna clase con `position` propia, así que su
   `.progress-container` hijo (`position:absolute`, styles.css) escapaba
   al ancestro posicionado más cercano — `.style-canvas` (Tarea 7.5, casi
   todo el viewport) en vez de este contenedor — y terminaba con el ancho
   de TODA la pantalla en vez del ancho del cuadro de mensaje. `.bubble`
   (styles.css) ya resuelve esto mismo para el camino con burbuja real vía
   su propio `position: relative`; acá se replica esa misma idea para el
   fallback. `max-width` mismo criterio que `.bubble` (calc(var(--npc-size)
   * 1.75), ~420px) — el fallback no tiene avatar/npc-size propio del que
   derivarlo, así que se fija el mismo valor resultante directamente. */
.fallback-bubble {
  position: relative;
  max-width: 420px;
}

/* Fix de re-review (hallazgo 2, post-commit d0d3a85): .bubble-position-wrap
   (render-engine.js buildMessageElement) ahora puede contener, además de
   `.bubble`, una imagen externa (flatData.image) cuando el mensaje trae
   las dos cosas a la vez — antes de este fix esa imagen se quedaba sola en
   `outerContainer` (`.message-horizontal-container`, ver abajo) porque
   `position:absolute` (.canvas-el) salta a `.style-canvas`, el ancestro
   posicionado más cercano, dejando atrás cualquier hijo de
   `outerContainer` que no viajara DENTRO del wrapper posicionado.
   Reproduce acá el mismo `display:flex; gap:15px` que
   `.message-horizontal-container` ya tenía, para que imagen+burbuja
   sigan viéndose exactamente como antes de la Tarea 7.5 — ahora como
   grupo posicionado, en vez de como hijos sueltos de un contenedor que
   la burbuja ya no habita. */
.bubble-position-wrap {
  display: flex;
  align-items: flex-start;
  gap: 15px;
}

/* ── Banner de donación ── */
/* width: max-content (2026-09-11), mismo motivo que .drag-wrap[data-key="banner"] en el
   editor: un wrapper absoluto con solo `left` calcula su ancho como "lo que queda hasta el
   borde del canvas", así que cerca del borde derecho el navegador envolvía el texto en dos
   líneas y el alto fijo de .don-banner lo recortaba. Con max-content el ancho es siempre el
   del contenido; hacia qué lado crece lo decide el anclaje (positionBannerOnCanvas). */
.banner-wrap {
  display: flex;
  align-items: center;
  gap: 16px;
  width: max-content;
}
/* canvas de partículas alrededor del banner — se mueve solo,
   queda anclado al centro real del don-banner via JS */
.banner-fx {
  position: absolute;
  left: 50%; top: 50%;
  width: 900px; height: 440px;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.banner-inner {
  position: relative;
}
.don-banner {
  position: relative;
  height: 84px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 36px;               /* solo horizontal — el ancho varía con el mensaje */
  border-radius: 14px;
  background: linear-gradient(160deg, rgba(30,10,60,0.92), rgba(10,5,25,0.92));
  text-align: center;
  /* estado oculto = de dónde entra / hacia dónde sale: lo cargan las variables --anim-* de
     applyBespokeAnimVars() (@bitju/render-engine) según el efecto de intro/outro */
  opacity: var(--anim-opacity, 0);
  transform: translate(var(--anim-dx, 0px), var(--anim-dy, -18px)) scale(var(--anim-scale, 0.96));
  transition: opacity var(--anim-opacity-dur, 0.4s) ease, transform var(--anim-dur, 0.4s) var(--anim-ease, cubic-bezier(0.34,1.56,0.64,1)), background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
  overflow: hidden;
  min-width: 320px;
  box-sizing: border-box;
}
.don-banner.visible { opacity: 1; transform: translateY(0) scale(1); }

/* Bloque 4-G (fix de re-review) — relleno base de las cajas de Action
   Status, mismo valor hardcodeado que tenían antes de este bloque
   (Bloque 3). El estilo "gradient" del recipe compartido de abajo NO
   declara background (solo border + box-shadow, igual que el banner)
   — sin este relleno de base, "gradient" quedaba con texto blanco sobre
   fondo transparente, ilegible sobre metraje real de OBS. Selector de
   una sola clase (.as-label-box, no ".custom-action-status .as-label-box")
   a propósito: su especificidad (0,1,0) queda SIEMPRE por debajo de
   .bitju-styled-box[data-style="X"] (0,2,0, clase+atributo), así que los
   6 estilos que sí declaran su propio background (todos menos gradient)
   lo pisan sin importar el orden en la hoja — mismo mecanismo que usa
   .don-banner (clase única) contra .bitju-styled-box[data-style="X"]
   para el banner. */
.as-label-box, .as-timer-box {
  background: linear-gradient(160deg, rgba(60,12,18,0.92), rgba(22,6,9,0.92));
}

/* ============================================================
   Bloque 4-G — los 7 estilos visuales, compartidos entre banner
   y Action Status (widget) vía esta clase + data-style. Cada
   elemento trae sus propios --sc1/--sc2/--sc3 (+ variantes -rgb
   para alpha) seteados inline por su styleObject — no hay color de
   marca global, dos elementos con el mismo estilo pueden tener
   colores distintos. Simplificación deliberada: donde el estilo
   original tenía un degradé sutil de 2 tonos (ribbon, ticket),
   acá queda en un solo color por slot — 3 colores limpios y
   editables en vez de un color fijo + una sombra que no se puede
   tocar.
============================================================ */

/* Moved to @bitju/render-engine (Task 6) — the 7 .bitju-styled-box[data-style]
   recipes and the 6 .bubble[data-style] recipes (incl. the base/comic rules
   and shared mirror/tail/cursor/npc-tag rules). Wired in Task 7 via
   bitju-render-engine.css (index.html <link>, loaded before this file) —
   NOT copied into this file, since everything else here (.style-canvas,
   .canvas-el, .don-banner base, .npc, the [data-banner-style] overrides,
   etc.) is consumer-local structural CSS that was never part of the shared
   package and must survive the package's copy step untouched. */

.don-name {
  font-size: 13px; font-weight: 600; letter-spacing: 3px;
  color: rgba(255,255,255,0.75); text-transform: uppercase;
}
.don-name b { color: var(--sc2, #00E6FF); font-weight: 800; text-shadow: 0 0 12px rgba(var(--sc2-rgb, 0,230,255), 0.7); }
/* .don-amount / .coin viven en bitju-render-engine.css (2026-09-17): flex centrado y moneda como
   imagen de fondo (coin.png, copiada por copy-render-engine), igual que el editor. */

@keyframes icon-pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.15)} }

/* ============================================================
   ESTILOS ALTERNATIVOS DEL BANNER SUPERIOR (selector en demo)
   gradient (default) · neon · ribbon · comic · glass · ticket
   ------------------------------------------------------------
   Adaptado: el editor original escribe estos overrides de texto/
   nombre/monto contra `.overlay[data-banner-style="X"]` — el
   `#overlay` (raíz fija del editor) no existe en este repo. En su
   lugar, la raíz real de un mensaje acá es `.message-with-circle`
   / `.message-without-circle` (ver Tarea 3 — buildMessageElement
   setea `dataset.bannerStyle` en esa raíz), así que cada selector
   se repite una vez por cada una de esas dos clases raíz.
============================================================ */

/* ── NEON OUTLINE — fondo casi negro, solo borde y texto brillan ── */
.message-with-circle[data-banner-style="neon"] .don-name,
.message-without-circle[data-banner-style="neon"] .don-name { color: rgba(255,255,255,0.6); }
.message-with-circle[data-banner-style="neon"] .don-name b,
.message-without-circle[data-banner-style="neon"] .don-name b { color: var(--sc2, #ffffff); text-shadow: 0 0 8px var(--sc2, #00E6FF), 0 0 18px var(--sc2, #00E6FF); }
.message-with-circle[data-banner-style="neon"] .don-amount,
.message-without-circle[data-banner-style="neon"] .don-amount { color: var(--sc3, #00E6FF); filter: drop-shadow(0 0 10px rgba(var(--sc1-rgb, 0,230,255),0.8)); }

/* ── RIBBON — cinta con cortes en diagonal en los extremos ── */
.message-with-circle[data-banner-style="ribbon"] .don-banner,
.message-without-circle[data-banner-style="ribbon"] .don-banner {
  border-radius: 4px;
  clip-path: polygon(2.5% 0, 97.5% 0, 100% 50%, 97.5% 100%, 2.5% 100%, 0 50%);
  padding: 0 46px;
}
.message-with-circle[data-banner-style="ribbon"] .don-name,
.message-without-circle[data-banner-style="ribbon"] .don-name { color: rgba(255,255,255,0.85); }
.message-with-circle[data-banner-style="ribbon"] .don-name b,
.message-without-circle[data-banner-style="ribbon"] .don-name b { color: var(--sc2, #ffffff); text-shadow: 0 0 10px var(--sc2, #00E6FF); }
.message-with-circle[data-banner-style="ribbon"] .don-amount,
.message-without-circle[data-banner-style="ribbon"] .don-amount { color: var(--sc3, #ffffff); filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); }

/* ── COMIC — a juego con el globo estilo cómic ── */
.message-with-circle[data-banner-style="comic"] .don-banner,
.message-without-circle[data-banner-style="comic"] .don-banner {
  border-radius: 14px;
}
.message-with-circle[data-banner-style="comic"] .don-name,
.message-without-circle[data-banner-style="comic"] .don-name { color: rgba(20,9,30,0.65); }
.message-with-circle[data-banner-style="comic"] .don-name b,
.message-without-circle[data-banner-style="comic"] .don-name b { color: var(--sc2, #7B3AED); text-shadow: none; }
.message-with-circle[data-banner-style="comic"] .don-amount,
.message-without-circle[data-banner-style="comic"] .don-amount { color: var(--sc3, #14091e); filter: none; }
.message-with-circle[data-banner-style="comic"] .don-banner::after,
.message-without-circle[data-banner-style="comic"] .don-banner::after { display: none; }

/* ── GLASS — panel translúcido con blur, sin glow ── */
.message-with-circle[data-banner-style="glass"] .don-banner,
.message-without-circle[data-banner-style="glass"] .don-banner {
  border-radius: 12px;
}
.message-with-circle[data-banner-style="glass"] .don-name,
.message-without-circle[data-banner-style="glass"] .don-name { color: rgba(255,255,255,0.65); }
.message-with-circle[data-banner-style="glass"] .don-name b,
.message-without-circle[data-banner-style="glass"] .don-name b { color: var(--sc2, #ffffff); text-shadow: none; }
.message-with-circle[data-banner-style="glass"] .don-amount,
.message-without-circle[data-banner-style="glass"] .don-amount { color: var(--sc3, rgba(255,255,255,0.95)); filter: none; font-weight: 700; }

/* ── TICKET — estilo entrada/cupón, con muescas circulares a los lados ── */
.message-with-circle[data-banner-style="ticket"] .don-banner,
.message-without-circle[data-banner-style="ticket"] .don-banner {
  border-radius: 8px;
}
.message-with-circle[data-banner-style="ticket"] .don-banner::before,
.message-without-circle[data-banner-style="ticket"] .don-banner::before {
  content: '';
  position: absolute; top: 50%; left: -11px;
  width: 22px; height: 22px; border-radius: 50%;
  background: #000;
  transform: translateY(-50%);
}
.message-with-circle[data-banner-style="ticket"] .don-name,
.message-without-circle[data-banner-style="ticket"] .don-name { color: rgba(255,255,255,0.7); }
.message-with-circle[data-banner-style="ticket"] .don-name b,
.message-without-circle[data-banner-style="ticket"] .don-name b { color: var(--sc2, #00E6FF); text-shadow: 0 0 8px rgba(var(--sc2-rgb, 0,230,255),0.6); }
.message-with-circle[data-banner-style="ticket"] .don-amount,
.message-without-circle[data-banner-style="ticket"] .don-amount { color: var(--sc3, #ffffff); filter: drop-shadow(0 0 6px rgba(var(--sc1-rgb, 123,58,237),0.5)); }

/* ── CLEAN — solo texto, sin recuadro ni fondo, bien legible
   sobre cualquier imagen gracias a una sombra de texto sutil ── */
.message-with-circle[data-banner-style="clean"] .don-banner,
.message-without-circle[data-banner-style="clean"] .don-banner {
  padding: 0 10px;
}
.message-with-circle[data-banner-style="clean"] .don-banner::after,
.message-without-circle[data-banner-style="clean"] .don-banner::after { display: none; }   /* sin sweep de luz, no aplica sin caja */
.message-with-circle[data-banner-style="clean"] .don-name,
.message-without-circle[data-banner-style="clean"] .don-name { color: var(--sc2, rgba(255,255,255,0.92)); text-shadow: 0 2px 6px rgba(0,0,0,0.8), 0 0 14px rgba(0,0,0,0.5); }
.message-with-circle[data-banner-style="clean"] .don-name b,
.message-without-circle[data-banner-style="clean"] .don-name b { color: var(--sc2, #00E6FF); text-shadow: 0 2px 6px rgba(0,0,0,0.85), 0 0 16px rgba(var(--sc2-rgb, 0,230,255),0.5); }
.message-with-circle[data-banner-style="clean"] .don-amount,
.message-without-circle[data-banner-style="clean"] .don-amount { color: var(--sc3, #ffffff); filter: drop-shadow(0 2px 6px rgba(0,0,0,0.85)) drop-shadow(0 0 12px rgba(0,0,0,0.4)); }

/* ── Avatar NPC ── */
.npc {
  width: var(--npc-size);
  height: var(--npc-size);
  transform: translateX(130%);   /* placeholder SOLO para el primer render antes de que corra JS — en cuanto el motor arranca, computeNpcTravel() calcula la posición real fuera de pantalla según dónde esté el avatar */
  will-change: transform;
}
.npc img {
  width: 100%; height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 8px 18px rgba(0,0,0,0.55));
}
.npc img.mirrored { transform: scaleX(-1); }

/* la entrada/salida del avatar NO es un @keyframes fijo en %:
   se calcula en JS en cada mensaje para garantizar que siempre
   arranca y termina fuera del borde REAL de la pantalla, sin
   importar dónde esté posicionado. La dirección
   (derecha/izquierda/arriba/abajo/fade/ninguna) viene del
   styleObject del avatar, independiente del modo espejo — ver
   playAvatarEnter()/playAvatarExit() en render-engine.js. */

/* respiración sutil (idle de NPC) */
.npc.idle { animation: npc-idle 2.6s ease-in-out infinite; }
@keyframes npc-idle {
  0%, 100% { transform: translateX(0) translateY(0) rotate(0deg); }
  50%      { transform: translateX(0) translateY(-5px) rotate(-0.6deg); }
}
