/* ============================================================
   @bitju/render-engine — shared visual recipes (banner/widget/bubble).
   See docs/superpowers/specs/2026-09-02-shared-render-engine-package-design.md
   in bitju-app for the full boundary rationale.
============================================================ */

/* ============================================================
   Bloque 1: Los 7 estilos visuales compartidos
   gradient (default) · neon · ribbon · comic · glass · ticket · clean

   Portados de editor.css:647-682. Cada elemento trae sus propios
   --sc1/--sc2/--sc3 (+ variantes -rgb para alpha) seteados inline —
   no hay color de marca global.
============================================================ */

.bitju-styled-box[data-style="gradient"] {
  border: 1px solid rgba(var(--sc1-rgb, 123,58,237), 0.55);
  box-shadow: 0 0 26px rgba(var(--sc1-rgb, 123,58,237), 0.32), inset 0 0 24px rgba(var(--sc1-rgb, 123,58,237), 0.1);
}
.bitju-styled-box[data-style="neon"] {
  background: rgba(4, 2, 10, 0.75);
  border: 1.5px solid var(--sc1, #00E6FF);
  box-shadow: 0 0 10px rgba(var(--sc1-rgb, 0,230,255),0.55), 0 0 26px rgba(var(--sc1-rgb, 0,230,255),0.25), inset 0 0 14px rgba(var(--sc1-rgb, 0,230,255),0.12);
}
.bitju-styled-box[data-style="ribbon"] {
  background: var(--sc1, #7B3AED);
  border: none;
  box-shadow: 0 4px 14px rgba(0,0,0,0.5);
}
.bitju-styled-box[data-style="comic"] {
  background: var(--sc1, #ffffff);
  border: 3px solid #14091e;
  box-shadow: 4px 5px 0 rgba(0,0,0,0.4);
}
.bitju-styled-box[data-style="glass"] {
  background: color-mix(in srgb, var(--sc1, #16102c) 45%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255,255,255,0.22);
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
}
.bitju-styled-box[data-style="ticket"] {
  background: var(--sc1, #1a0d33);
  border: 2px dashed rgba(var(--sc2-rgb, 0,230,255), 0.5);
  box-shadow: 0 4px 14px rgba(0,0,0,0.45);
}
.bitju-styled-box[data-style="clean"] {
  background: none;
  border: none;
  box-shadow: none;
}

.don-banner::after {
  content: '';
  position: absolute; top: 0; left: -60%;
  width: 40%; height: 100%;
  background: linear-gradient(100deg, transparent, rgba(var(--sc2-rgb, 0,230,255), 0.25), transparent);
  transform: skewX(-18deg);
}
.don-banner.sweep::after { animation: sweep 0.9s ease-out forwards; }
@keyframes sweep { to { left: 130%; } }

/* Alineación interna del banner (2026-09-11): el renderer real y el editor ponen data-align
   según el lateral por el que el banner está anclado (position.anchor: top-left/top-center/
   top-right, por tercios del canvas). Cerca de un lateral, el texto y el monto se pegan a ese
   lado, así un nombre/mensaje largo crece hacia adentro de la pantalla. Sin data-align (o
   center) queda el centrado de siempre que cada consumidor define en su regla base .don-banner.
   Especificidad (0,2,0) a propósito: gana a esa regla base sin depender del orden de carga. */
.don-banner[data-align="left"]  { align-items: flex-start; text-align: left; }
.don-banner[data-align="right"] { align-items: flex-end;   text-align: right; }

/* Loop "en pantalla" (timing.loopAnim), compartido por los dos consumidores (2026-09-11; antes
   vivía solo en bitju-app/editor.css, así que en OBS nunca se veía). Se activa con data-loop en
   el nodo interno que corresponda animar (.don-banner / .bubble / la imagen o el video de un
   elemento libre / .custom-action-status), nunca en el wrapper posicionado — ese wrapper es el
   que lleva la escala del streamer (transform en el editor, zoom en OBS). */
[data-loop="pulse"]  { animation: anim-loop-pulse 1.6s ease-in-out infinite; }
[data-loop="bounce"] { animation: anim-loop-bounce 1.4s ease-in-out infinite; }
[data-loop="rotate"] { animation: anim-loop-rotate 3s linear infinite; }
[data-loop="shake"]  { animation: anim-loop-shake 0.5s ease-in-out infinite; }
@keyframes anim-loop-pulse  { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
@keyframes anim-loop-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
@keyframes anim-loop-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes anim-loop-shake  { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }

/* ============================================================
   Bloque 2: Globo de diálogo estilo cómic y sus variantes
   comic (default) · pixel · cartoon · parchment · glass · cloud

   Portado de editor.css:809-999. Las reglas base (809-879) viven
   en selectores sin prefijo overlay (son el unscoped default). Los
   5 variantes explícitas (887-999) se adaptan de:
     .overlay[data-style="X"] .bubble  →  .bubble[data-style="X"]
   porque el globo lleva su propio data-style (ver comentario en
   render-engine.js Tarea 3, Paso 6).
============================================================ */

/* Globo de diálogo estilo cómic / videojuego
   Se posiciona pegado al avatar, con la cola a la altura
   de la boca. Anclado por abajo: crece hacia arriba. */
.bubble {
  max-width: calc(var(--npc-size) * 1.75 * var(--bubble-scale));
  padding: 16px 20px;
  border-radius: 18px;
  background: #fff;
  border: 3px solid #14091e;
  box-shadow: 4px 5px 0 rgba(0, 0, 0, 0.45);   /* sombra dura tipo cómic */
  font-size: clamp(12px, calc(var(--npc-size) * 0.072 * var(--bubble-scale)), 26px);
  font-weight: 600;
  line-height: 1.45;
  color: #14091e;
  /* text-align: left siempre (2026-09-11; antes right cuando la cola estaba a la derecha). Con el
     ancho del globo ya reservado por .content-placeholder, un texto alineado a la derecha hacía
     que cada línea se corriera hacia la izquierda mientras el typewriter la escribía. De qué
     lado crece el globo lo decide el anclaje del wrapper (growAwayFrom / position.anchor), no
     la alineación del texto. */
  text-align: left;
  /* Fix de re-review (Tarea 7, hallazgo 1): styles.css's viejo `.bubble` (ya
     retirado por el fix de cascada de esta misma tarea) traía estas dos
     propiedades — sin ellas, un token largo sin espacios (una URL, un
     nombre de usuario largo, un mensaje sin espacios) puede desbordar el
     borde/fondo de la burbuja en vez de cortarse dentro del `max-width`
     fijo. Van acá, en la regla base compartida, para que apliquen a las 6
     recetas por igual en vez de reintroducir una segunda fuente de estilos
     de burbuja en styles.css. */
  overflow-wrap: break-word;
  word-break: break-word;
  /* estado oculto = de dónde entra / hacia dónde sale: lo cargan las variables --anim-* que pone
     applyBespokeAnimVars() (render-engine.js) según el efecto de intro/outro; sin variables, el
     pop de siempre desde la cola. */
  opacity: var(--anim-opacity, 0);
  transform: translate(var(--anim-dx, 0px), var(--anim-dy, 0px)) scale(var(--anim-scale, 0.6));
  transform-origin: 100% 85%;   /* el pop nace desde la cola (boca) */
  transition: opacity var(--anim-opacity-dur, 0.22s) ease, transform var(--anim-dur, 0.26s) var(--anim-ease, cubic-bezier(0.34, 1.5, 0.64, 1));
}
.bubble.visible { opacity: 1; transform: scale(1); }

/* modo espejo del globo: la cola pasa al lado opuesto (útil
   cuando el avatar también está espejado hacia la izquierda),
   y con ella el sentido de crecimiento y la alineación del
   texto — siempre ancladas del lado de la cola (boca). */
.bubble.mirror-x { transform-origin: 0% 85%; text-align: left; }
.bubble.mirror-x::before { right: auto; left: -21px; border-left: 0; border-right: 21px solid #14091e; }
.bubble.mirror-x::after  { right: auto; left: -14px; border-left: 0; border-right: 16px solid #fff; }


/* cola del globo: triángulo con borde, apuntando a la boca */
.bubble::before {
  content: '';
  position: absolute;
  right: -21px; bottom: 16px;
  border: 12px solid transparent;
  border-left: 21px solid #14091e;   /* borde exterior */
  border-right: 0;
}
.bubble::after {
  content: '';
  position: absolute;
  right: -14px; bottom: 19px;
  border: 9px solid transparent;
  border-left: 16px solid #fff;      /* relleno blanco */
  border-right: 0;
}

/* Cursor parpadeante del typewriter — bug real, encontrado en vivo probando el
   tipeo (no en reposo, donde no se notaba): esto ANTES era un <span class="cursor">
   estático, hermano de `.content-typing` y ubicado DESPUÉS de `.content-placeholder`
   en el HTML. `.content-placeholder` es un <div> (bloque) que reserva el ancho/alto
   real del mensaje completo (ver el comentario grande sobre esa estructura, en
   editor.css/styles.css) — cualquier hermano en flujo normal que venga DESPUÉS de un
   bloque arranca en una línea nueva, así que el cursor (inline-block) siempre caía
   en una línea propia, debajo del placeholder entero, sin importar cuánto texto
   llevara tipeado `.content-typing` todavía. Resultado visible: el globo medía
   SIEMPRE alto para "N líneas del mensaje + 1 línea extra sólo para el cursor",
   como si el mensaje ocupara una línea de más.
   Fix: en vez de un elemento aparte, el cursor es un `::after` de
   `.content-typing` — el mismo elemento donde el typewriter escribe letra por
   letra. Un `::after` es contenido generado por CSS, no un nodo hijo real, así que
   sobrevive intacto a `textEl.textContent += char` (que si reemplazara nodos hijos,
   se comería un <span> real cada vez) y siempre fluye pegado al ÚLTIMO carácter
   YA TIPEADO, en vez de a un texto invisible que puede ser más largo. */
.bubble:not(.done) .content-typing::after {
  content: '';
  display: inline-block;
  width: 9px; height: 15px;
  margin-left: 2px;
  vertical-align: text-bottom;
  background: #7B3AED;
  animation: blink 0.7s step-start infinite;
}
@keyframes blink { 50% { opacity: 0; } }

/* tag de nombre (solo visible en estilo glass) */
.npc-tag {
  display: none;
  position: absolute;
  top: -13px; left: 16px;
  padding: 3px 12px;
  border-radius: 6px;
  background: linear-gradient(90deg, #7B3AED, #00E6FF);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 2px;
}

/* ── PIXEL ART / RETRO RPG ──
   Selector adaptado: .overlay[data-style="pixel"] .bubble → .bubble[data-style="pixel"] */
.bubble[data-style="pixel"] {
  border-radius: 0;
  background: #10142a;
  border: 4px solid #fff;
  box-shadow: 0 0 0 4px #10142a, 8px 8px 0 rgba(0,0,0,0.5);
  color: #fff;
  font-family: 'Courier New', Courier, monospace;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.bubble[data-style="pixel"]::before {
  border: none;
  width: 24px; height: 24px;
  right: -24px; bottom: 14px;
  background: #fff;
  clip-path: polygon(0 0, 50% 0, 50% 25%, 100% 25%, 100% 75%, 50% 75%, 50% 100%, 0 100%);
}
.bubble[data-style="pixel"]::after {
  border: none;
  width: 14px; height: 14px;
  right: -10px; bottom: 19px;
  background: #10142a;
  clip-path: polygon(0 0, 50% 0, 50% 25%, 100% 25%, 100% 75%, 50% 75%, 50% 100%, 0 100%);
}
.bubble[data-style="pixel"]:not(.done) .content-typing::after { background: #fff; box-shadow: none; }

/* ── CARTOON SQUISHY (trazo grueso, como el avatar) ──
   Selector adaptado: .overlay[data-style="cartoon"] .bubble → .bubble[data-style="cartoon"] */
.bubble[data-style="cartoon"] {
  border-radius: 28px;
  background: #fffdf6;
  border: 5px solid #14091e;
  box-shadow: 0 6px 0 rgba(0, 0, 0, 0.35);
}
/* el squish es el "pop" del cartoon: no corre con una intro que viaja o que no anima
   (data-anim-plain, ver applyBespokeAnimVars) — pisaría el transform —, ni con un loop activo, que
   tiene que ganarle. */
.bubble[data-style="cartoon"].visible:not([data-anim-plain]):not([data-loop]) {
  animation: squish 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes squish {
  0%   { transform: scale(0.3, 0.5); }
  55%  { transform: scale(1.12, 0.9); }
  75%  { transform: scale(0.95, 1.07); }
  100% { transform: scale(1, 1); }
}
.bubble[data-style="cartoon"]::before {
  right: -24px; bottom: 14px;
  border: 13px solid transparent;
  border-left: 24px solid #14091e;
  border-right: 0;
}
.bubble[data-style="cartoon"]::after {
  right: -14px; bottom: 18px;
  border: 9px solid transparent;
  border-left: 17px solid #fffdf6;
  border-right: 0;
}

/* ── PERGAMINO RPG ──
   Selector adaptado: .overlay[data-style="parchment"] .bubble → .bubble[data-style="parchment"] */
.bubble[data-style="parchment"] {
  background: linear-gradient(150deg, #f6e7c1, #e9d5a0);
  border: 3px solid #8b6f47;
  border-radius: 14px 22px 12px 24px / 20px 12px 24px 14px;
  box-shadow: inset 0 0 20px rgba(120, 90, 40, 0.35), 3px 4px 10px rgba(0, 0, 0, 0.45);
  color: #4a3115;
  font-family: Georgia, 'Times New Roman', serif;
}
.bubble[data-style="parchment"]::before {
  border-left-color: #8b6f47;
}
.bubble[data-style="parchment"]::after {
  border-left-color: #efdcae;
}
.bubble[data-style="parchment"]:not(.done) .content-typing::after { background: #8b5a2b; box-shadow: none; }

/* ── PANEL GLASS (juego moderno, sin cola) ──
   Selector adaptado: .overlay[data-style="glass"] .bubble → .bubble[data-style="glass"] */
.bubble[data-style="glass"] {
  background: rgba(22, 16, 44, 0.52);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 14px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  color: rgba(255, 255, 255, 0.95);
  font-weight: 500;
}
.bubble[data-style="glass"]::before,
.bubble[data-style="glass"]::after { display: none; }
.bubble[data-style="glass"] .npc-tag { display: block; }
.bubble[data-style="glass"]:not(.done) .content-typing::after { background: #00E6FF; box-shadow: 0 0 8px rgba(0,230,255,0.7); }

/* ── NUBE (thought bubble, cola de circulitos) ──
   Selector adaptado: .overlay[data-style="cloud"] .bubble → .bubble[data-style="cloud"] */
.bubble[data-style="cloud"] {
  background: #fff;
  border: 3px solid #14091e;
  border-radius: 46% 54% 48% 52% / 58% 46% 54% 42%;
  padding: 20px 26px;
  box-shadow: none;
}
.bubble[data-style="cloud"]::before {
  border: 3px solid #14091e;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #fff;
  right: -26px; bottom: 24px;
  clip-path: none;
}
.bubble[data-style="cloud"]::after {
  border: 3px solid #14091e;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: #fff;
  right: -40px; bottom: 12px;
  clip-path: none;
}
.bubble[data-style="cloud"]:not(.done) .content-typing::after { background: #14091e; box-shadow: none; }

/* ── Monto del banner (2026-09-17, spec A §3.10) ──
   Flex centrado en vez de vertical-align a mano, y la moneda es un box fijo con la misma imagen en
   el editor y en OBS (antes el editor usaba un <img> en base64 y OBS el emoji 🪙, con baselines
   distintas). coin.png viaja junto a este CSS: los scripts de copia de ambos consumidores lo llevan. */
.don-amount {
  margin-top: 4px;
  display: inline-flex; align-items: center; gap: 6px; line-height: 1;
  font-size: 26px; font-weight: 900;
  color: var(--sc3, #00E6FF);
  filter: drop-shadow(0 0 10px rgba(var(--sc1-rgb, 123,58,237), 0.55));
}
.don-amount .coin {
  display: inline-block; width: 33px; height: 33px; flex: 0 0 auto;
  background: url(./coin.png) center / contain no-repeat;
  -webkit-text-fill-color: initial;
  filter: none;
  animation: coin-bob 1.2s ease-in-out infinite;
}
.don-amount .coin img { display: none; }
@keyframes coin-bob { 0%,100%{transform:translateY(0) rotate(-8deg)} 50%{transform:translateY(-3px) rotate(8deg)} }

/* ── Cola arriba (2026-09-17, spec A §3.9) ──
   El globo anclado por arriba (position.anchor top-*) crece hacia abajo con un mensaje real más
   largo que el de muestra; con la cola fija al borde inferior, se alejaba de la boca del avatar.
   Con .tail-top la cola vive en el borde superior y no se mueve. El editor y OBS ponen la clase a
   partir del anclaje vertical. glass no tiene cola. Va al final del archivo para ganarle a las
   recetas por orden con igual especificidad. */
.bubble.tail-top { transform-origin: 100% 15%; }
.bubble.tail-top.mirror-x { transform-origin: 0% 15%; }
.bubble.tail-top::before { bottom: auto; top: 16px; }
.bubble.tail-top::after  { bottom: auto; top: 19px; }
.bubble[data-style="pixel"].tail-top::before { bottom: auto; top: 14px; }
.bubble[data-style="pixel"].tail-top::after  { bottom: auto; top: 19px; }
.bubble[data-style="cartoon"].tail-top::before { bottom: auto; top: 14px; }
.bubble[data-style="cartoon"].tail-top::after  { bottom: auto; top: 18px; }
.bubble[data-style="cloud"].tail-top::before { bottom: auto; top: 24px; }
.bubble[data-style="cloud"].tail-top::after  { bottom: auto; top: 12px; }
