/* ============================================================
   signal.css — brand cross-talk for the wordmark.

   The first syllable of the wordmark is a "signal slot": it
   normally shows the host brand ("on" on on-my.tv, "at" on
   at-my.tv) and every so often the sister station's word tears
   through it like interference on a shared frequency.

   Layout-neutral by default: the alt word is absolutely
   positioned over the home word, so the slot is exactly as wide
   as the host brand and nothing after it ever moves. Set
   data-fit="max" to size the slot to the wider of the two words
   instead (use this when the words are meant to trade places
   and stay, not just flicker).

   No JS is required to render: without signal.js the element is
   just its own text. JS only decides *when* interference hits.
   ============================================================ */

.sig {
  position: relative;
  display: inline-block;
  white-space: nowrap;
  /* effects reach outside the box; keep them from nudging layout */
  vertical-align: baseline;
}

.sig-home,
.sig-alt {
  display: block;
  /* every frame sets an explicit mask so the property never has to
     interpolate between "none" and a gradient */
  -webkit-mask-image: linear-gradient(#000 0 100%);
          mask-image: linear-gradient(#000 0 100%);
}

.sig-alt {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0;
  pointer-events: none;
  -webkit-user-select: none;
          user-select: none;
}

/* slot sized to the wider word — both layers share one grid cell */
.sig[data-fit="max"] { display: inline-grid; }
.sig[data-fit="max"] > .sig-home,
.sig[data-fit="max"] > .sig-alt { grid-area: 1 / 1; position: static; }

/* ------------------------------------------------------------
   redacted slot — for a data-alts pool of equal-length masks
   rotating inside running text, where the slot must never
   change width.

   The monospace stack makes every mask exactly as wide as every
   other one whatever glyphs it happens to contain, and the
   explicit width also covers a host word that is shorter than
   the masks. Both layers are centred so the resting word sits
   in the middle of a slot sized for the longer ones.

   Size the slot to the mask length, not to the host word:
     <span class="sig sig--redacted" style="--sig-slot: 4ch">
   Do not reach for data-fit="max" here. That cell is sized to
   whichever word is currently rendered, so a pool would nudge
   the text after it on every burst.
   ------------------------------------------------------------ */

.sig--redacted {
  font-family: var(--f-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  width: var(--sig-slot, 4ch);
  text-align: center;
}

.sig--redacted > .sig-alt {
  left: 0;
  right: 0;
  text-align: center;
}

/* the interference colours: the tally red and the link blue of the
   host theme, so the glitch never introduces a foreign hue */
.sig { --sig-r: var(--tally, #FF4438); --sig-b: var(--link, #8FB4FF); }

/* ------------------------------------------------------------
   1. scan — bands of the sister signal cut into the host word.
   Hard step timing: interference is digital, it does not ease.
   ------------------------------------------------------------ */

.sig[data-fx="scan"].sig-on > .sig-home { animation: sigScanHome var(--sig-dur, 760ms) steps(1, end) both; }
.sig[data-fx="scan"].sig-on > .sig-alt  { animation: sigScanAlt  var(--sig-dur, 760ms) steps(1, end) both; }

@keyframes sigScanHome {
  0%   { opacity: 1; transform: none;
         -webkit-mask-image: linear-gradient(#000 0 100%);
                 mask-image: linear-gradient(#000 0 100%); }
  8%   { transform: translateX(-1px);
         -webkit-mask-image: linear-gradient(#000 0 36%, transparent 36% 52%, #000 52% 100%);
                 mask-image: linear-gradient(#000 0 36%, transparent 36% 52%, #000 52% 100%); }
  18%  { transform: translateX(1px);
         -webkit-mask-image: linear-gradient(#000 0 10%, transparent 10% 20%, #000 20% 58%, transparent 58% 70%, #000 70% 100%);
                 mask-image: linear-gradient(#000 0 10%, transparent 10% 20%, #000 20% 58%, transparent 58% 70%, #000 70% 100%); }
  28%  { opacity: 0; }
  40%  { opacity: 1; transform: translateX(2px);
         -webkit-mask-image: linear-gradient(#000 0 22%, transparent 22% 34%, #000 34% 74%, transparent 74% 88%, #000 88% 100%);
                 mask-image: linear-gradient(#000 0 22%, transparent 22% 34%, #000 34% 74%, transparent 74% 88%, #000 88% 100%); }
  54%  { transform: translateX(-1px);
         -webkit-mask-image: linear-gradient(#000 0 44%, transparent 44% 60%, #000 60% 100%);
                 mask-image: linear-gradient(#000 0 44%, transparent 44% 60%, #000 60% 100%); }
  66%  { opacity: 1; transform: none;
         -webkit-mask-image: linear-gradient(#000 0 100%);
                 mask-image: linear-gradient(#000 0 100%); }
  100% { opacity: 1; }
}

@keyframes sigScanAlt {
  0%   { opacity: 0; transform: none; text-shadow: none;
         -webkit-mask-image: linear-gradient(#000 0 100%);
                 mask-image: linear-gradient(#000 0 100%); }
  8%   { opacity: 1; transform: translateX(3px); text-shadow: 2px 0 var(--sig-r), -2px 0 var(--sig-b);
         -webkit-mask-image: linear-gradient(transparent 0 36%, #000 36% 52%, transparent 52% 100%);
                 mask-image: linear-gradient(transparent 0 36%, #000 36% 52%, transparent 52% 100%); }
  18%  { opacity: 1; transform: translateX(-2px); text-shadow: -2px 0 var(--sig-r), 2px 0 var(--sig-b);
         -webkit-mask-image: linear-gradient(transparent 0 10%, #000 10% 20%, transparent 20% 58%, #000 58% 70%, transparent 70% 100%);
                 mask-image: linear-gradient(transparent 0 10%, #000 10% 20%, transparent 20% 58%, #000 58% 70%, transparent 70% 100%); }
  28%  { opacity: 1; transform: none; text-shadow: none;
         -webkit-mask-image: linear-gradient(#000 0 100%);
                 mask-image: linear-gradient(#000 0 100%); }
  40%  { opacity: 1; transform: translateX(-3px); text-shadow: 2px 0 var(--sig-b), -2px 0 var(--sig-r);
         -webkit-mask-image: linear-gradient(transparent 0 22%, #000 22% 34%, transparent 34% 74%, #000 74% 88%, transparent 88% 100%);
                 mask-image: linear-gradient(transparent 0 22%, #000 22% 34%, transparent 34% 74%, #000 74% 88%, transparent 88% 100%); }
  54%  { opacity: 1; transform: translateX(1px); text-shadow: none;
         -webkit-mask-image: linear-gradient(transparent 0 44%, #000 44% 60%, transparent 60% 100%);
                 mask-image: linear-gradient(transparent 0 44%, #000 44% 60%, transparent 60% 100%); }
  66%  { opacity: 0; transform: none;
         -webkit-mask-image: linear-gradient(#000 0 100%);
                 mask-image: linear-gradient(#000 0 100%); }
  100% { opacity: 0; }
}

/* ------------------------------------------------------------
   2. tear — the word is dragged sideways off its own baseline,
   chromatically split, and the sister word snaps into the gap.
   ------------------------------------------------------------ */

.sig[data-fx="tear"].sig-on > .sig-home { animation: sigTearHome var(--sig-dur, 880ms) linear both; }
.sig[data-fx="tear"].sig-on > .sig-alt  { animation: sigTearAlt  var(--sig-dur, 880ms) linear both; }

@keyframes sigTearHome {
  0%, 100% { opacity: 1; transform: none; text-shadow: none; }
  9%   { opacity: 1; transform: translateX(-5px) skewX(-16deg); text-shadow: 3px 0 var(--sig-r), -3px 0 var(--sig-b); }
  16%  { opacity: 0; transform: translateX(8px) skewX(12deg); }
  62%  { opacity: 0; transform: translateX(-7px) skewX(-12deg); }
  70%  { opacity: 1; transform: translateX(4px) skewX(9deg); text-shadow: -3px 0 var(--sig-r), 3px 0 var(--sig-b); }
  82%  { opacity: 1; transform: none; text-shadow: none; }
}

@keyframes sigTearAlt {
  0%, 100% { opacity: 0; transform: translateX(7px) skewX(14deg); text-shadow: none; }
  15%  { opacity: 0; transform: translateX(7px) skewX(14deg); }
  22%  { opacity: 1; transform: translateX(-4px) skewX(-11deg); text-shadow: -3px 0 var(--sig-b), 3px 0 var(--sig-r); }
  31%  { opacity: 1; transform: none; text-shadow: none; }
  55%  { opacity: 1; transform: none; text-shadow: none; }
  61%  { opacity: 1; transform: translateX(-8px) skewX(-15deg); text-shadow: 3px 0 var(--sig-b), -3px 0 var(--sig-r); }
  68%  { opacity: 0; transform: translateX(7px) skewX(14deg); }
}

/* ------------------------------------------------------------
   3. roll — vertical hold gives out: the host word rolls up out
   of frame, the sister word rolls in behind it, a bright scan
   bar rides the seam.  clip-path (not overflow) does the
   clipping, because overflow:hidden would move the inline-block
   baseline and drop the whole slot relative to "-my.tv".
   ------------------------------------------------------------ */

.sig[data-fx="roll"] { -webkit-clip-path: inset(2% -12% 2% -12%); clip-path: inset(2% -12% 2% -12%); }
.sig[data-fx="roll"].sig-on > .sig-home { animation: sigRollHome var(--sig-dur, 820ms) cubic-bezier(.6,.02,.3,1) both; }
.sig[data-fx="roll"].sig-on > .sig-alt  { animation: sigRollAlt  var(--sig-dur, 820ms) cubic-bezier(.6,.02,.3,1) both; }
.sig[data-fx="roll"]::after {
  content: "";
  position: absolute;
  left: -12%; right: -12%;
  height: 2px;
  top: 0;
  background: linear-gradient(90deg, transparent, var(--sig-b), transparent);
  opacity: 0;
  pointer-events: none;
}
.sig[data-fx="roll"].sig-on::after { animation: sigRollBar var(--sig-dur, 820ms) linear both; }

@keyframes sigRollHome {
  0%   { opacity: 1; transform: none; }
  30%  { opacity: 1; transform: translateY(-115%); }
  31%, 62% { opacity: 0; transform: translateY(-115%); }
  63%  { opacity: 1; transform: translateY(115%); }
  92%, 100% { opacity: 1; transform: none; }
}

@keyframes sigRollAlt {
  0%, 8% { opacity: 0; transform: translateY(115%); }
  9%   { opacity: 1; transform: translateY(115%); }
  38%, 55% { opacity: 1; transform: none; }
  85%  { opacity: 1; transform: translateY(-115%); }
  86%, 100% { opacity: 0; transform: translateY(-115%); }
}

@keyframes sigRollBar {
  0%   { opacity: 0; top: 100%; }
  6%   { opacity: .85; }
  34%  { opacity: .5; top: -10%; }
  35%, 59% { opacity: 0; top: -10%; }
  66%  { opacity: .75; top: 110%; }
  94%  { opacity: 0; top: -10%; }
  100% { opacity: 0; }
}

/* ------------------------------------------------------------
   4. dropout — the slot loses signal entirely for a few frames:
   a block of static, and the other station is there when it
   clears. The most abrupt of the four.
   ------------------------------------------------------------ */

.sig[data-fx="dropout"].sig-on > .sig-home { animation: sigDropHome var(--sig-dur, 700ms) steps(1, end) both; }
.sig[data-fx="dropout"].sig-on > .sig-alt  { animation: sigDropAlt  var(--sig-dur, 700ms) steps(1, end) both; }
.sig[data-fx="dropout"]::after {
  content: "";
  position: absolute;
  left: -6%; right: -6%; top: 8%; bottom: 8%;
  opacity: 0;
  pointer-events: none;
  background:
    repeating-linear-gradient(0deg,
      color-mix(in srgb, currentColor 70%, transparent) 0 1px,
      transparent 1px 2px),
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--sig-r) 55%, transparent) 0 2px,
      transparent 2px 5px,
      color-mix(in srgb, var(--sig-b) 45%, transparent) 5px 6px,
      transparent 6px 11px);
}
.sig[data-fx="dropout"].sig-on::after { animation: sigDropNoise var(--sig-dur, 700ms) steps(1, end) both; }

@keyframes sigDropHome {
  0%, 14% { opacity: 1; }
  15%, 100% { opacity: 0; }
}
@keyframes sigDropAlt {
  0%, 34% { opacity: 0; }
  35%, 100% { opacity: 1; }
}
@keyframes sigDropNoise {
  0%   { opacity: 0; transform: none; }
  16%  { opacity: .9;  transform: translateX(-2px) scaleY(1.1); }
  23%  { opacity: .45; transform: translateX(3px); }
  29%  { opacity: .95; transform: translateX(-1px) scaleY(.9); }
  36%  { opacity: .3;  transform: translateX(2px); }
  44%  { opacity: 0;   transform: none; }
  100% { opacity: 0; }
}

/* ------------------------------------------------------------
   No motion: the slot is simply the host brand's word, forever.
   (app.css already neutralises durations globally; this makes
   the intent explicit and survives that file being reused.)
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .sig.sig-on > .sig-home,
  .sig.sig-on > .sig-alt,
  .sig.sig-on::after { animation: none !important; }
  .sig > .sig-home { opacity: 1; }
  .sig > .sig-alt { opacity: 0; }
}
