/* ==========================================================================
   Sliding outline sweep — whyuseus.php.

   The motion of a rust block crossing text, applied to a thin outline
   instead. A hairline frame is drawn in from the left, crosses, and clears
   off the right. Nothing is filled, nothing is knocked out to paper,
   nothing washes over the type.

   One frame per paragraph. Headings get none: a rule crossing a heading
   competes with it rather than introducing it.

   Progressive: sweep.js adds .sw-on to the container. Without JavaScript
   the class never lands, so nothing is ever hidden and the page reads
   complete.
   ========================================================================== */

/* The frame's rust runs warmer than the ink rust, because it is a light
   crossing the page rather than a mark left on it. Pushed any brighter it
   stops reading as light and starts reading as neon. */
.case-report{ --beam: hsl(15 80% 47%) }

/* Only once JS is running does anything hide. */
.sw-on .sw{
  opacity: 0;
  transform: translateY(10px);
}
.sw-on .sw.is-in{
  opacity: 1;
  transform: none;
  /* The copy is deliberately not tied to the frame. It just arrives.
     Hooking the text to the frame's leading edge would hold a long
     paragraph hostage to a 1500ms sweep and make it read at a crawl. */
  transition: opacity 540ms cubic-bezier(.2,.8,.25,1) 60ms,
              transform 540ms cubic-bezier(.2,.8,.25,1) 60ms;
}

.case-report p.sw{ position: relative }

.sw-on .case-report p.sw::after{
  content: "";
  position: absolute;
  inset: -0.55rem -0.8rem;
  border: 1.5px solid var(--beam);
  border-radius: 0;          /* square. rounded, it reads as a UI chip */
  pointer-events: none;

  /* Zero opacity, not merely clipped. Clipped alone it still paints once,
     in full, on the tick before the animation applies: a complete orange
     box flashes around the paragraph and only then starts sweeping. */
  opacity: 0;
}
.sw-on .case-report p.sw.is-in::after{ animation: sw-sweep 1500ms }

@keyframes sw-sweep{
  /* A timing function applies between each PAIR of keyframes, not across
     the whole run. One symmetrical curve therefore decelerates into 50%
     and accelerates out of it, and the hitch lands exactly where the frame
     is fully drawn and most visible.

     So the curve is split: the first half eases in and arrives at full
     speed, the second half departs at full speed and eases out. The
     velocities match where they meet, so the two halves read as one
     continuous gesture rather than two. */
  0%{
    opacity: 1;
    clip-path: inset(0 100% 0 0);
    animation-timing-function: cubic-bezier(.4, 0, 1, 1);
  }
  50%{
    opacity: 1;
    clip-path: inset(0 0 0 0);
    animation-timing-function: cubic-bezier(0, 0, .25, 1);
  }
  100%{
    opacity: 1;
    clip-path: inset(0 0 0 100%);
  }
  /* No hold at 50%. It crosses and leaves; it never sits there. */
}

/* Everything present, nothing moving. */
@media (prefers-reduced-motion: reduce){
  .sw-on .sw{ opacity: 1; transform: none; transition: none }
  .sw-on .case-report p.sw::after,
  .sw-on .case-report p.sw.is-in::after{ display: none; animation: none }
}
