/* ═══════════════════════════════════════════════════════════════════════════
   fx-sludge — the split-screen format, live.

   A phone-shaped plate: the face on top, an endless runner underneath, a
   thick ink seam between them. The runner strip is one inline SVG looped on
   CSS keyframes; fx-sludge.js only says whether it should be moving right
   now. With no JS, or under reduced motion, you get one still frame of it —
   which is the meme it is quoting (Fig. 07), so nothing is lost.
   ═══════════════════════════════════════════════════════════════════════════ */

.sludge {
  --sl-loop: 6s;    /* one obstacle, horizon to lap */
  --sl-hop:  1.2s;  /* runner changes lane this often */
  position: relative;
  width: min(100%, 340px);
  aspect-ratio: 9 / 16;
  margin-inline: auto;
  padding: 0;
  display: grid;
  grid-template-rows: 1fr calc(var(--rule-fat) + 4px) 1fr;
  /* clip, not hidden: no scroll container, and it follows .ink's wobbly radius */
  overflow: clip;
  user-select: none; -webkit-user-select: none;
  touch-action: manipulation;
}
@media (min-width: 880px) { .sludge { width: min(100%, 260px); } }
/* Only a pointer when a tap actually does something (JS booted). */
.sludge.is-live { cursor: pointer; }

/* Both halves are relative boxes with absolutely-filled children, so the
   image's intrinsic size can never stretch a 1fr row. */
.sludge__top, .sludge__strip { position: relative; min-height: 0; background: var(--paper); }
.sludge .sludge__top img {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0;
  /* crown in frame, drool running out of the bottom into the seam */
  object-fit: cover; object-position: 50% 4%;
}
/* The UA gives <figure> 40px side margins. The phone drawing lived with
   that; a phone-shaped card wants the whole column on a phone. Only when
   this card is the figure's content, so nothing else moves. */
.spec__art:has(> .sludge) { margin: 0; }

/* The seam. A straight 10px bar reads as a UI divider; a 1px wobble on
   each edge reads as a fat marker line. */
.sludge__seam {
  background: var(--ink);
  clip-path: polygon(0 12%, 18% 0, 52% 14%, 80% 2%, 100% 10%,
                     100% 92%, 70% 100%, 40% 88%, 12% 100%, 0 90%);
}

.sludge__pill {
  position: absolute; z-index: 3; left: 50%; top: 50%;
  transform: translate(-50%, -50%) rotate(-1.4deg);
  font-family: var(--font-ui); font-size: .55rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; line-height: 1;
  white-space: nowrap;
  padding: .42em .75em;
  color: var(--ink); background: var(--paper);
  border: var(--rule-thin) solid var(--ink);
  border-radius: 9px 5px 10px 6px / 5px 10px 6px 9px;
  box-shadow: 2px 2px 0 var(--ink);
}

/* ── the strip ────────────────────────────────────────────────────────────── */
.sludge__svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  fill: var(--paper); stroke: var(--ink);
  /* non-scaling strokes (set per path) mean this is a screen-px weight,
     matched to the drawing's line at this size */
  stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round;
}
.sl-track { fill: none; }
.sl-sleeper path { fill: none; stroke-width: 1.4; stroke-opacity: .35; }
/* scale about the viewBox origin — every shape is drawn around 0,0 with
   its base on y=0, so translate+scale puts its feet on the ground */
.sl-obs, .sl-sleeper, .sl-runner, .sl-runner__hop { transform-box: view-box; transform-origin: 0 0; }
/* two-frame run cycle: B is the off frame until the animation swaps them */
.sl-runner__f--b { opacity: 0; }

/* ── pause control ────────────────────────────────────────────────────────── */
.sludge__ctl {
  position: absolute; z-index: 3; right: 8px; bottom: 8px;
  min-height: 28px; min-width: 44px; padding: .4em .8em;
  font-size: .62rem; letter-spacing: .1em; text-transform: uppercase;
  border-radius: 8px 5px 9px 6px / 5px 9px 6px 8px;
  box-shadow: 2px 3px 0 var(--ink);
}
.sludge__ctl:hover { transform: translate(-1px, -1px); box-shadow: 3px 4px 0 var(--ink); }
.sludge__ctl:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }
/* .btn's display:inline-flex would otherwise beat the UA's [hidden] rule */
.sludge__ctl[hidden] { display: none; }
.sludge.is-held .sludge__strip { opacity: .5; }

/* ── motion ───────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
  .sludge__strip { transition: opacity var(--t-fast) var(--ease); }

  /* Obstacles and sleepers run one shared curve: an ease-in stands in for
     perspective (far = slow and small, near = fast and big). Each lane needs
     its own keyframes because the x-drift differs; --d staggers them. */
  .sludge.is-playing .sl-obs,
  .sludge.is-playing .sl-sleeper {
    animation: sl-run-m var(--sl-loop) cubic-bezier(.45, 0, .9, .4) infinite;
    animation-delay: var(--d, 0s);
  }
  .sludge.is-playing .sl-obs--l { animation-name: sl-run-l; }
  .sludge.is-playing .sl-obs--r { animation-name: sl-run-r; }

  /* lane hop: step-end holds a lane, then jumps — mid, right, mid, left */
  .sludge.is-playing .sl-runner {
    animation: sl-lane calc(var(--sl-hop) * 4) step-end infinite;
  }
  /* the hop itself, offset so its apex lands on the lane switch */
  .sludge.is-playing .sl-runner__hop {
    animation: sl-hop var(--sl-hop) infinite;
    animation-delay: calc(var(--sl-hop) * -.1);
  }
  .sludge.is-playing .sl-runner__f {
    animation: sl-frame .36s step-end infinite;
  }
  .sludge.is-playing .sl-runner__f--b { animation-delay: -.18s; }

  /* held by a tap: freeze in place rather than snap to the still frame */
  .sludge.is-held :is(.sl-obs, .sl-sleeper, .sl-runner, .sl-runner__hop, .sl-runner__f) {
    animation-play-state: paused;
  }
  /* one layer for the whole strip, only while it is actually moving */
  .sludge.is-playing:not(.is-held) .sludge__svg { will-change: transform; }
}

/* Vanishing point 90,28. t=0.05 → 1.7 of the way to the bottom edge (t=1 is
   y=156, lane centres 17.5 / 90 / 162.5), so a shape is well past the frame
   before it loops back to a dot on the horizon. */
@keyframes sl-run-l {
  from { transform: translate(86.4px, 34.4px) scale(.05); }
  to   { transform: translate(-33.25px, 245.6px) scale(1.7); }
}
@keyframes sl-run-m {
  from { transform: translate(90px, 34.4px) scale(.05); }
  to   { transform: translate(90px, 245.6px) scale(1.7); }
}
@keyframes sl-run-r {
  from { transform: translate(93.6px, 34.4px) scale(.05); }
  to   { transform: translate(213.25px, 245.6px) scale(1.7); }
}
@keyframes sl-lane {
  0%, 100% { transform: translate(90px, 118px); }
  25%      { transform: translate(141px, 118px); }
  50%      { transform: translate(90px, 118px); }
  75%      { transform: translate(39px, 118px); }
}
@keyframes sl-hop {
  0%        { transform: translateY(0);    animation-timing-function: cubic-bezier(.2, .7, .4, 1); }
  10%       { transform: translateY(-9px); animation-timing-function: cubic-bezier(.6, 0, .9, .4); }
  22%, 100% { transform: translateY(0); }
}
@keyframes sl-frame {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

/* ── optional wrapper for the "alongside" placement (option B) ─────────────
   Sits under the phone drawing in the left column of .spec at ≥880px, and
   after the list on a phone. */
.sludge-fig { margin: 0; grid-column: 1; }
