/* =====================================================================
   OnlyBands Glitch FX — 15 efectos
   Cada bloque es autocontenido y namespaced (.fx-*): copiar el bloque
   al SCSS del componente Angular (ViewEncapsulation por defecto OK;
   los @keyframes conviene moverlos a un partial global _glitch.scss).
   Filosofía: reposo largo + ráfaga corta con steps(). Nada de bucles
   histéricos: eso es lo que separa "profesional" de "cartoon".
   ===================================================================== */


/* =====================================================================
   FX·01 — CHROMA SPLIT
   Dos pseudo-capas con los acentos de marca desplazadas ±1.5px,
   mezcla screen sobre negro. Requiere data-text con el mismo contenido.
   ===================================================================== */

.fx-chroma {
  position: relative;
  display: inline-block;
  font-weight: 900;
  text-transform: uppercase;
}
.fx-chroma::before,
.fx-chroma::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  mix-blend-mode: screen;
  pointer-events: none;
}
.fx-chroma::before {
  color: var(--ob-red50);
  transform: translate(-1.5px, 0);
  animation: fx-chroma-a 3.2s steps(1, jump-none) infinite;
}
.fx-chroma::after {
  color: var(--ob-pink50);
  transform: translate(1.5px, 0);
  animation: fx-chroma-b 3.2s steps(1, jump-none) infinite;
}
@keyframes fx-chroma-a {
  0%, 100% { transform: translate(-1.5px, 0); }
  32%      { transform: translate(-2.5px, 0.5px); }
  33%      { transform: translate(-1px, -0.5px); }
  66%      { transform: translate(-2px, 0); }
  67%      { transform: translate(-1.5px, 0.5px); }
}
@keyframes fx-chroma-b {
  0%, 100% { transform: translate(1.5px, 0); }
  24%      { transform: translate(2.5px, -0.5px); }
  25%      { transform: translate(1px, 0.5px); }
  71%      { transform: translate(2px, 0); }
  72%      { transform: translate(1.5px, -0.5px); }
}

.fx-chroma--lg { font-size: clamp(40px, 6vw, 76px); letter-spacing: -0.01em; }


/* =====================================================================
   FX·02 — SLICE SHIFT
   Dos capas clonadas recortadas en bandas con clip-path: inset() y
   desplazadas en X. La ráfaga vive en el 0–8% y 46–54% del timeline;
   el resto es reposo absoluto (clip a 100% = capa invisible).
   ===================================================================== */

.fx-slice {
  position: relative;
  display: inline-block;
}
.fx-slice::before,
.fx-slice::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  color: inherit;
  background: var(--ob-black);
  clip-path: inset(100% 0 0 0);
  pointer-events: none;
}
.fx-slice::before {
  text-shadow: -2px 0 var(--ob-red50);
  animation: fx-slice-a 5.2s steps(1, jump-none) infinite;
}
.fx-slice::after {
  text-shadow: 2px 0 var(--ob-pink50);
  animation: fx-slice-b 5.2s steps(1, jump-none) infinite;
}
@keyframes fx-slice-a {
  0%   { clip-path: inset(12% 0 78% 0); transform: translateX(-8px); }
  2%   { clip-path: inset(64% 0 24% 0); transform: translateX(6px);  }
  4%   { clip-path: inset(38% 0 52% 0); transform: translateX(-4px); }
  6%   { clip-path: inset(80% 0 8% 0);  transform: translateX(9px);  }
  8%, 45% { clip-path: inset(100% 0 0 0); transform: none; }
  46%  { clip-path: inset(22% 0 68% 0); transform: translateX(7px);  }
  48%  { clip-path: inset(56% 0 34% 0); transform: translateX(-6px); }
  50%, 100% { clip-path: inset(100% 0 0 0); transform: none; }
}
@keyframes fx-slice-b {
  0%   { clip-path: inset(72% 0 18% 0); transform: translateX(7px);  }
  2%   { clip-path: inset(28% 0 62% 0); transform: translateX(-7px); }
  4%   { clip-path: inset(88% 0 2% 0);  transform: translateX(5px);  }
  6%   { clip-path: inset(4% 0 86% 0);  transform: translateX(-9px); }
  8%, 49% { clip-path: inset(100% 0 0 0); transform: none; }
  50%  { clip-path: inset(44% 0 46% 0); transform: translateX(-8px); }
  52%  { clip-path: inset(8% 0 82% 0);  transform: translateX(6px);  }
  54%, 100% { clip-path: inset(100% 0 0 0); transform: none; }
}

.fx-slice--demo {
  font-weight: 900;
  font-size: clamp(34px, 5vw, 64px);
  text-transform: uppercase;
  letter-spacing: -0.01em;
}


/* =====================================================================
   FX·03 — CRT SCANLINES
   Overlay de líneas + banda de refresco rodante + flicker de luminancia.
   Aplicar la clase al contenedor; los hijos van en z-index < 3.
   ===================================================================== */

.fx-crt { position: relative; }
.fx-crt::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background: repeating-linear-gradient(
    0deg,
    rgba(0, 0, 0, 0.28) 0 1px,
    transparent 1px 3px
  );
}
.fx-crt::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 120px;
  z-index: 3;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    transparent 0%,
    rgba(255, 255, 255, 0.035) 45%,
    rgba(255, 26, 26, 0.05) 55%,
    transparent 100%
  );
  animation: fx-crt-roll 7s linear infinite;
}
@keyframes fx-crt-roll {
  0%   { top: -140px; }
  100% { top: 110%; }
}
/* flicker de luminancia sobre el contenido directo */
.fx-crt > * { animation: fx-crt-flicker 4s steps(1, jump-none) infinite; }
.fx-crt > .fx-noise { animation: none; }
@keyframes fx-crt-flicker {
  0%, 100% { opacity: 1; }
  53% { opacity: 0.97; }
  54% { opacity: 1; }
  81% { opacity: 0.96; }
  82% { opacity: 1; }
}


/* =====================================================================
   FX·04 — STATIC NOISE
   Textura feTurbulence en data-URI sobre una capa al 200% que salta
   de posición con steps(10). Opacidad baja: es textura, no efecto.
   ===================================================================== */

.fx-noise {
  overflow: hidden;
  pointer-events: none;
}
.fx-noise::before {
  content: "";
  position: absolute;
  inset: -100% -100% 0 0;
  width: 200%;
  height: 200%;
  opacity: 0.07;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='1'/%3E%3C/svg%3E");
  animation: fx-noise-jump 0.7s steps(10, jump-none) infinite;
}
@keyframes fx-noise-jump {
  0%   { transform: translate(0, 0); }
  10%  { transform: translate(-5%, -8%); }
  20%  { transform: translate(-12%, 3%); }
  30%  { transform: translate(6%, -14%); }
  40%  { transform: translate(-4%, 12%); }
  50%  { transform: translate(-14%, -6%); }
  60%  { transform: translate(9%, 4%); }
  70%  { transform: translate(-7%, -12%); }
  80%  { transform: translate(12%, 8%); }
  90%  { transform: translate(-10%, 2%); }
  100% { transform: translate(0, 0); }
}


/* =====================================================================
   FX·05 — GLITCH BUTTON
   Estable en reposo. Al hover/focus: ráfaga única (no loop) de slices
   + chroma + salto del bloque de sombra. animation en :hover se
   reinicia sola en cada entrada — sin JS.
   ===================================================================== */

.fx-btn {
  position: relative;
  font-family: var(--ob-font-main);
  font-weight: 400;
  font-size: 15px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ob-black);
  background: var(--ob-white);
  border: none;
  border-radius: var(--ob-radius-sm);
  padding: 16px 32px;
  cursor: pointer;
  box-shadow: 5px 5px 0 var(--ob-red50);
  transition: box-shadow 0.12s ease, transform 0.12s ease;
}
.fx-btn:hover,
.fx-btn:focus-visible {
  transform: translate(2px, 2px);
  box-shadow: 3px 3px 0 var(--ob-red50);
  animation: fx-btn-jolt 0.36s steps(1, jump-none) 1;
}
.fx-btn::before,
.fx-btn::after {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--ob-radius-sm);
  background: inherit;
  clip-path: inset(100% 0 0 0);
  pointer-events: none;
}
.fx-btn::before { color: var(--ob-red70); }
.fx-btn::after  { color: var(--ob-pink90); }
.fx-btn:hover::before,
.fx-btn:focus-visible::before { animation: fx-btn-slice-a 0.36s steps(1, jump-none) 1; }
.fx-btn:hover::after,
.fx-btn:focus-visible::after  { animation: fx-btn-slice-b 0.36s steps(1, jump-none) 1; }

@keyframes fx-btn-jolt {
  0%  { transform: translate(2px, 2px) skewX(0); }
  15% { transform: translate(0, 3px) skewX(-2deg); }
  30% { transform: translate(4px, 1px) skewX(1.5deg); }
  45% { transform: translate(2px, 2px) skewX(0); }
}
@keyframes fx-btn-slice-a {
  0%   { clip-path: inset(8% 0 76% 0);  transform: translateX(-5px); }
  25%  { clip-path: inset(52% 0 36% 0); transform: translateX(4px);  }
  50%  { clip-path: inset(76% 0 10% 0); transform: translateX(-3px); }
  75%, 100% { clip-path: inset(100% 0 0 0); transform: none; }
}
@keyframes fx-btn-slice-b {
  0%   { clip-path: inset(64% 0 22% 0); transform: translateX(5px);  }
  25%  { clip-path: inset(18% 0 70% 0); transform: translateX(-4px); }
  50%  { clip-path: inset(40% 0 48% 0); transform: translateX(3px);  }
  75%, 100% { clip-path: inset(100% 0 0 0); transform: none; }
}

/* Variante ghost: borde blanco sobre negro, sombra rosa */
.fx-btn--ghost {
  color: var(--ob-white);
  background: var(--ob-black);
  border: 1px solid var(--ob-white);
  box-shadow: 5px 5px 0 var(--ob-pink50);
}
.fx-btn--ghost:hover,
.fx-btn--ghost:focus-visible { box-shadow: 3px 3px 0 var(--ob-pink50); }
.fx-btn--ghost::before { color: var(--ob-red50); }
.fx-btn--ghost::after  { color: var(--ob-pink50); }


/* =====================================================================
   FX·06 — TEXT SCRAMBLE  (JS: js/effects.js › initScramble)
   El CSS sólo estiliza; la resolución de caracteres es rAF.
   ===================================================================== */

.fx-scramble {
  font-weight: 900;
  font-size: clamp(28px, 4vw, 52px);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  cursor: default;
}
.fx-scramble .scr-glyph { color: var(--ob-red50); }


/* =====================================================================
   FX·07 — VHS TRACKING
   3 capas del mismo media: base + 2 bandas clip-path que patinan en X
   con text/box bleed rojo-rosa, línea de tracking y OSD.
   Estructura: .fx-vhs > .fx-vhs__frame ×3 + .fx-vhs__track + .fx-vhs__osd
   ===================================================================== */

.fx-vhs {
  position: relative;
  min-height: 340px;
  overflow: hidden;
  filter: saturate(1.05);
}
.fx-vhs__frame { position: absolute; inset: 0; }
.fx-vhs__frame:nth-child(2) {
  mix-blend-mode: screen;
  opacity: 0.85;
  animation: fx-vhs-band-a 3.8s steps(1, jump-none) infinite;
}
.fx-vhs__frame:nth-child(3) {
  mix-blend-mode: screen;
  opacity: 0.85;
  animation: fx-vhs-band-b 3.8s steps(1, jump-none) infinite;
}
@keyframes fx-vhs-band-a {
  0%, 100% { clip-path: inset(100% 0 0 0); transform: none; filter: none; }
  10% { clip-path: inset(22% 0 66% 0); transform: translateX(10px); filter: drop-shadow(-6px 0 rgba(255,26,26,0.7)); }
  13% { clip-path: inset(30% 0 58% 0); transform: translateX(-7px); filter: drop-shadow(5px 0 rgba(255,26,26,0.7)); }
  16% { clip-path: inset(100% 0 0 0); transform: none; }
  58% { clip-path: inset(70% 0 12% 0); transform: translateX(-12px); filter: drop-shadow(6px 0 rgba(255,26,26,0.7)); }
  62% { clip-path: inset(100% 0 0 0); transform: none; }
}
@keyframes fx-vhs-band-b {
  0%, 100% { clip-path: inset(100% 0 0 0); transform: none; filter: none; }
  11% { clip-path: inset(48% 0 40% 0); transform: translateX(-9px); filter: drop-shadow(6px 0 rgba(253,173,205,0.7)); }
  14% { clip-path: inset(56% 0 30% 0); transform: translateX(8px); filter: drop-shadow(-5px 0 rgba(253,173,205,0.7)); }
  17% { clip-path: inset(100% 0 0 0); transform: none; }
  59% { clip-path: inset(6% 0 80% 0); transform: translateX(11px); filter: drop-shadow(-6px 0 rgba(253,173,205,0.7)); }
  63% { clip-path: inset(100% 0 0 0); transform: none; }
}
.fx-vhs__track {
  position: absolute;
  left: 0; right: 0;
  height: 14px;
  z-index: 4;
  background: linear-gradient(
    180deg,
    transparent,
    rgba(255, 255, 255, 0.16) 40%,
    rgba(255, 255, 255, 0.05) 60%,
    transparent
  );
  filter: blur(0.5px);
  animation: fx-vhs-track 3.8s cubic-bezier(0.7, 0, 0.3, 1) infinite;
}
@keyframes fx-vhs-track {
  0%   { top: 110%; opacity: 0; }
  8%   { opacity: 1; }
  18%  { top: -20px; opacity: 0; }
  100% { top: -20px; opacity: 0; }
}
.fx-vhs__osd {
  position: absolute;
  top: 16px; left: 20px;
  z-index: 5;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 14px;
  letter-spacing: 0.15em;
  color: var(--ob-white);
  text-shadow: 0 0 6px rgba(255, 255, 255, 0.6);
  animation: fx-vhs-osd 1.2s steps(2, jump-none) infinite;
}
@keyframes fx-vhs-osd { 50% { opacity: 0.65; } }


/* =====================================================================
   FX·08 — IMAGE GLITCH (hover)
   Limpia en reposo; al hover/focus ráfaga única de 0.4s con canales
   rojo/rosa + slices. Estructura: .fx-img > .fx-img__base (bg-image).
   Los canales se generan con ::before/::after clonando el fondo.
   ===================================================================== */

.fx-img {
  position: relative;
  width: min(420px, 90%);
  aspect-ratio: 4 / 3;
  border-radius: var(--ob-radius-sm);
  overflow: hidden;
  cursor: pointer;
  outline-offset: 4px;
}
.fx-img__base { position: absolute; inset: 0; }
.fx-img::before,
.fx-img::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  pointer-events: none;
  background: inherit;
}
/* clonamos el poster en los pseudo-elementos vía la misma clase de fondo:
   en producción, poner background-image: var(--cover) en los tres sitios */
.fx-img::before {
  background:
    linear-gradient(0deg, rgba(255,26,26,0.55), rgba(255,26,26,0.55));
  mix-blend-mode: screen;
}
.fx-img::after {
  background:
    linear-gradient(0deg, rgba(253,173,205,0.5), rgba(253,173,205,0.5));
  mix-blend-mode: screen;
}
.fx-img:hover .fx-img__base,
.fx-img:focus-visible .fx-img__base {
  animation: fx-img-shake 0.4s steps(1, jump-none) 1;
}
.fx-img:hover::before,
.fx-img:focus-visible::before {
  animation: fx-img-ch-a 0.4s steps(1, jump-none) 1;
}
.fx-img:hover::after,
.fx-img:focus-visible::after {
  animation: fx-img-ch-b 0.4s steps(1, jump-none) 1;
}
@keyframes fx-img-shake {
  0%  { transform: translateX(-4px); }
  20% { transform: translateX(5px); }
  40% { transform: translateX(-3px); }
  60% { transform: translateX(2px); }
  80%, 100% { transform: none; }
}
@keyframes fx-img-ch-a {
  0%  { opacity: 1; clip-path: inset(10% 0 72% 0); transform: translateX(-8px); }
  25% { opacity: 1; clip-path: inset(58% 0 28% 0); transform: translateX(6px); }
  50% { opacity: 1; clip-path: inset(34% 0 54% 0); transform: translateX(-5px); }
  75%, 100% { opacity: 0; clip-path: inset(0 0 0 0); transform: none; }
}
@keyframes fx-img-ch-b {
  0%  { opacity: 1; clip-path: inset(66% 0 20% 0); transform: translateX(8px); }
  25% { opacity: 1; clip-path: inset(14% 0 74% 0); transform: translateX(-6px); }
  50% { opacity: 1; clip-path: inset(80% 0 6% 0);  transform: translateX(5px); }
  75%, 100% { opacity: 0; clip-path: inset(0 0 0 0); transform: none; }
}
.fx-img__caption {
  position: absolute;
  bottom: 12px; right: 14px;
  z-index: 3;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3em;
  color: var(--ob-grey70);
}


/* =====================================================================
   FX·09 — INTERFERENCE LINK
   Subrayado que se materializa a trozos + blip de skew único de 90ms.
   ===================================================================== */

.fx-link {
  position: relative;
  display: inline-block;
  color: var(--ob-white);
  text-decoration: none;
  font-weight: 700;
  font-size: 17px;
  letter-spacing: 0.18em;
  padding-bottom: 6px;
}
.fx-link::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--ob-red50);
  transform: scaleX(0);
  transform-origin: left;
}
.fx-link:hover,
.fx-link:focus-visible {
  animation: fx-link-blip 0.09s steps(2, jump-none) 1;
}
.fx-link:hover::after,
.fx-link:focus-visible::after {
  animation: fx-link-dash 0.32s steps(1, jump-none) 1 forwards;
}
@keyframes fx-link-blip {
  0%   { transform: skewX(-8deg) translateX(-2px); color: var(--ob-pink50); }
  100% { transform: none; }
}
@keyframes fx-link-dash {
  0%   { transform: scaleX(0.18); transform-origin: left; }
  30%  { transform: scaleX(0.55); }
  45%  { transform: scaleX(0.4); }
  70%  { transform: scaleX(0.82); }
  100% { transform: scaleX(1); }
}


/* =====================================================================
   FX·10 — CORRUPTED FRAME
   Marcos duplicados rojo/rosa que flashean desalineados 2 frames
   cada ~6s. El contenido nunca se mueve más de 1px.
   ===================================================================== */

.fx-card {
  position: relative;
  width: min(440px, 90%);
  border: 1px solid var(--ob-grey50);
  border-radius: var(--ob-radius-sm);
  padding: 32px;
  background: var(--ob-black);
  animation: fx-card-nudge 6.4s steps(1, jump-none) infinite;
}
.fx-card::before,
.fx-card::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: var(--ob-radius-sm);
  opacity: 0;
  pointer-events: none;
}
.fx-card::before {
  border: 1px solid var(--ob-red50);
  animation: fx-card-frame-a 6.4s steps(1, jump-none) infinite;
}
.fx-card::after {
  border: 1px solid var(--ob-pink50);
  animation: fx-card-frame-b 6.4s steps(1, jump-none) infinite;
}
@keyframes fx-card-frame-a {
  0%, 100% { opacity: 0; transform: none; }
  41% { opacity: 1; transform: translate(3px, -2px); }
  42% { opacity: 1; transform: translate(-2px, 2px); }
  43% { opacity: 0; transform: none; }
  87% { opacity: 1; transform: translate(-3px, 0); }
  88% { opacity: 0; transform: none; }
}
@keyframes fx-card-frame-b {
  0%, 100% { opacity: 0; transform: none; }
  41.5% { opacity: 1; transform: translate(-3px, 2px); }
  42.5% { opacity: 1; transform: translate(2px, -2px); }
  43.5% { opacity: 0; transform: none; }
  87.5% { opacity: 1; transform: translate(3px, 1px); }
  88.5% { opacity: 0; transform: none; }
}
@keyframes fx-card-nudge {
  0%, 100% { transform: none; }
  41% { transform: translateX(1px); }
  43% { transform: none; }
  87% { transform: translateX(-1px); }
  88% { transform: none; }
}
.fx-card__kicker { font-size: 11px; font-weight: 700; letter-spacing: 0.4em; color: var(--ob-red50); margin-bottom: 12px; }
.fx-card__title { font-size: 26px; font-weight: 900; text-transform: uppercase; margin-bottom: 10px; }
.fx-card__body { color: var(--ob-grey70); font-size: 14px; line-height: 1.6; }


/* =====================================================================
   FX·11 — SVG DISPLACEMENT  (JS: js/effects.js › initDisplace)
   feTurbulence + feDisplacementMap; JS anima scale en ráfagas.
   ===================================================================== */

.fx-displace { width: min(640px, 95%); height: auto; overflow: visible; }
.fx-displace__text {
  font-family: var(--ob-font-main);
  font-weight: 900;
  font-size: 88px;
  letter-spacing: -0.01em;
  fill: var(--ob-white);
}


/* =====================================================================
   FX·12 — FLASH INVERT
   Banda horizontal que invierte la sección con mix-blend-mode:
   difference durante 2 frames cada ~9s.
   ===================================================================== */

.fx-invert { position: relative; overflow: hidden; }
.fx-invert::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 5;
  background: var(--ob-white);
  mix-blend-mode: difference;
  opacity: 0;
  pointer-events: none;
  animation: fx-invert-flash 9s steps(1, jump-none) infinite;
}
@keyframes fx-invert-flash {
  0%, 100% { opacity: 0; clip-path: inset(0 0 0 0); }
  62%   { opacity: 1; clip-path: inset(30% 0 42% 0); }
  62.6% { opacity: 1; clip-path: inset(58% 0 12% 0); }
  63.2% { opacity: 0; clip-path: inset(0 0 0 0); }
}


/* =====================================================================
   FX·13 — TYPE FLICKER  (JS: js/effects.js › initFlicker)
   Los spans por carácter los crea el JS; aquí sólo el estado "roto".
   ===================================================================== */

.fx-flicker {
  font-weight: 900;
  font-size: clamp(26px, 3.6vw, 48px);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.fx-flicker .flk-char { display: inline-block; white-space: pre; }
.fx-flicker .flk-char.is-broken {
  font-family: var(--ob-font-display);
  color: var(--ob-red50);
  transform: translateY(-1px) skewX(-6deg);
}


/* =====================================================================
   FX·14 — PIXEL SORT
   Columnas de píxel estiradas con gradientes verticales por franjas
   y deriva lenta steps(). Fondo abstracto para heros.
   ===================================================================== */

.fx-pixelsort {
  position: relative;
  min-height: 340px;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  background:
    /* vetas de acento, muy espaciadas */
    linear-gradient(180deg, transparent 20%, rgba(255, 26, 26, 0.5) 45%, transparent 70%) 12% 0 / 3px 100% no-repeat,
    linear-gradient(180deg, transparent 55%, rgba(253, 173, 205, 0.35) 75%, transparent 95%) 71% 0 / 2px 100% no-repeat,
    linear-gradient(180deg, transparent 10%, rgba(255, 26, 26, 0.35) 30%, transparent 60%) 88% 0 / 2px 100% no-repeat,
    /* columnas grises estiradas */
    repeating-linear-gradient(90deg,
      #07080a 0 14px, #0d0f13 14px 22px, #07080a 22px 38px,
      #12151a 38px 41px, #07080a 41px 66px, #0a0c0f 66px 90px,
      #14171c 90px 93px, #07080a 93px 128px),
    var(--ob-black);
}
.fx-pixelsort::before {
  content: "";
  position: absolute;
  inset: -20% 0;
  background: inherit;
  opacity: 0.55;
  mix-blend-mode: screen;
  animation: fx-pixelsort-drift 11s steps(24, jump-none) infinite alternate;
}
@keyframes fx-pixelsort-drift {
  0%   { transform: translateY(-4%) scaleY(1.04); }
  100% { transform: translateY(4%) scaleY(1.1); }
}
.fx-pixelsort__label {
  position: relative;
  z-index: 2;
  padding: 24px 28px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.55em;
  color: var(--ob-grey50);
}


/* =====================================================================
   FX·15 — SIGNAL LOSS
   Carta de ajuste en paleta de marca. En reposo, quieta; al
   hover/focus las barras patinan con steps() + ruido + NO SIGNAL.
   ===================================================================== */

.fx-signal {
  position: relative;
  min-height: 340px;
  overflow: hidden;
  cursor: pointer;
  outline-offset: -4px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.fx-signal__bars {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    var(--ob-grey10) 0 12.5%,  var(--ob-pink50) 12.5% 25%,
    var(--ob-grey50) 25% 37.5%, var(--ob-red50) 37.5% 50%,
    var(--ob-grey70) 50% 62.5%, var(--ob-pink90) 62.5% 75%,
    var(--ob-red90) 75% 87.5%,  var(--ob-grey90) 87.5% 100%);
}
.fx-signal:hover .fx-signal__bars,
.fx-signal:focus-visible .fx-signal__bars {
  animation: fx-signal-slip 0.5s steps(6, jump-none) infinite;
  filter: saturate(1.3) contrast(1.1);
}
@keyframes fx-signal-slip {
  0%   { transform: translateX(0); }
  20%  { transform: translateX(-3%) skewX(-1deg); }
  40%  { transform: translateX(2%); }
  60%  { transform: translateX(-1.5%) skewX(1deg); }
  80%  { transform: translateX(3%); }
  100% { transform: translateX(0); }
}
.fx-signal::before {
  /* ruido sólo en hover */
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E");
  pointer-events: none;
  transition: opacity 0.15s steps(3, jump-none);
}
.fx-signal:hover::before,
.fx-signal:focus-visible::before { opacity: 0.25; }
.fx-signal__label {
  position: relative;
  z-index: 3;
  font-size: clamp(28px, 4vw, 48px);
  font-weight: 900;
  color: var(--ob-white);
  background: var(--ob-black);
  padding: 12px 28px;
  opacity: 0;
  transition: opacity 0.1s steps(2, jump-none);
}
.fx-signal:hover .fx-signal__label,
.fx-signal:focus-visible .fx-signal__label { opacity: 1; }
.fx-signal__hint {
  position: absolute;
  bottom: 14px; left: 0; right: 0;
  z-index: 3;
  text-align: center;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.3em;
  color: var(--ob-black);
  mix-blend-mode: screen;
  background: rgba(255, 255, 255, 0.75);
  width: fit-content;
  margin-inline: auto;
  padding: 4px 10px;
}


/* =====================================================================
   ACCESIBILIDAD — congelar todo con prefers-reduced-motion
   ===================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
  }
}
