/* Clip-wipe title: a page-hero title block wipes in left to right on load (clip-path inset(0 100% 0 0) -> inset(0),
   1s quart.inOut = quartic in-out), then the illustration under it fades in (1s sine.inOut), starting only once the
   title is whole. CSS alone plays it on load; part.js adds hold / play / replay.
   Markup:
     <header class="clip-wipe" data-clip-wipe>
       <div class="clip-wipe__head clip-wipe__masthead"><h1>Title</h1></div>   (the masthead class is optional)
       <div class="clip-wipe__after">illustration</div>                       (optional)
     </header>
   data-clip-wipe="hold" waits (title hidden) until ClipWipe.play(el). */
.clip-wipe {
  --cw-dur: 1s;
  /* quart.inOut (8t^4 / 1 - 8(1-t)^4), sampled; 48 stops is < .0013 off anywhere */
  --cw-ease: cubic-bezier(.76, 0, .24, 1);
  --cw-from: inset(0 100% 0 0);        /* left to right; right to left = inset(0 0 0 100%) */
  --cw-fade-dur: 1s;
  --cw-fade-ease: cubic-bezier(.37, 0, .63, 1);   /* sine.inOut (within .002 everywhere) */
  --cw-fade-delay: var(--cw-dur);      /* the fade starts when the wipe ends */
  /* optional masthead rules (the source's double rules above and below the title) */
  --cw-rule: currentColor;
  --cw-rule-thick: 4px;
  --cw-rule-thin: 2px;
  --cw-rule-gap: 8px;
}
@supports (animation-timing-function: linear(0, 1)) {
  .clip-wipe { --cw-ease: linear(0, 0, 0, 0.0001, 0.0004, 0.0009, 0.002, 0.0036, 0.0062, 0.0099, 0.0151, 0.0221, 0.0312, 0.043, 0.0579, 0.0763, 0.0988, 0.1259, 0.1582, 0.1964, 0.2411, 0.2931, 0.353, 0.4217, 0.5, 0.5783, 0.647, 0.7069, 0.7589, 0.8036, 0.8418, 0.8741, 0.9012, 0.9237, 0.9421, 0.957, 0.9688, 0.9779, 0.9849, 0.9901, 0.9938, 0.9964, 0.998, 0.9991, 0.9996, 0.9999, 1, 1, 1); }
}

@media (prefers-reduced-motion: no-preference) {
  .clip-wipe .clip-wipe__head { animation: clip-wipe-in var(--cw-dur) var(--cw-ease) both; }
  .clip-wipe .clip-wipe__after { animation: clip-wipe-fade var(--cw-fade-dur) var(--cw-fade-ease) var(--cw-fade-delay) both; }
  .clip-wipe[data-clip-wipe="hold"] .clip-wipe__head,
  .clip-wipe[data-clip-wipe="hold"] .clip-wipe__after { animation-play-state: paused; }
}
@keyframes clip-wipe-in { from { clip-path: var(--cw-from); } to { clip-path: inset(0 0 0 0); } }
/* autoAlpha: hidden (not focusable, not clickable) while fully transparent */
@keyframes clip-wipe-fade { from { opacity: 0; visibility: hidden; } to { opacity: 1; visibility: visible; } }

/* Optional look: title shrink-wrapped between a thick+thin rule above and a thin+thick rule below. */
.clip-wipe__masthead { display: flex; flex-direction: column; align-items: stretch; width: fit-content; max-width: 100%; margin-inline: auto; gap: var(--cw-rule-gap); }
.clip-wipe__masthead::before,
.clip-wipe__masthead::after {
  content: ""; display: block; height: calc(var(--cw-rule-thick) + var(--cw-rule-gap) + var(--cw-rule-thin));
  box-sizing: border-box; border-style: solid; border-color: var(--cw-rule); border-inline-width: 0;
}
.clip-wipe__masthead::before { border-top-width: var(--cw-rule-thick); border-bottom-width: var(--cw-rule-thin); }
.clip-wipe__masthead::after  { border-top-width: var(--cw-rule-thin); border-bottom-width: var(--cw-rule-thick); }
