/* cutout.css — companion to cutout.js. Copy both, unchanged, into any site's public/.
 *
 * Everything here is scoped under .ct-* so it cannot collide with a host page's styles, and
 * the stage inherits its size from whatever container it is mounted into (aspect-ratio keeps
 * the canvas proportion without JS measuring anything).
 *
 * The two idle loops use DELIBERATELY NON-HARMONIC periods (4.6s breathe / 7.3s sway). Equal
 * or integer-multiple periods make the pair repeat visibly every few seconds, which is the
 * exact tell that turns "alive" back into "looping GIF".
 */

.ct-stage {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  aspect-ratio: 13 / 16;            /* 832×1024, the network's canvas — overridable per site */
  background: #0e0e12;
  transform: translateZ(0);          /* own layer: the transforms below stay off the main paint */
}

/* ---- background: its own slow fade, outside the figure crossfade ---------------------- */
.ct-bg {
  position: absolute; inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 600ms ease;
}
.ct-bg.ct-has-bg { opacity: 1; }

/* ---- the figure: breathing on the outer node, sway on the inner ----------------------- */
.ct-figure {
  position: absolute; inset: 0;
  transform-origin: 50% 100%;        /* feet stay planted — a figure that scales about its
                                        centre appears to float, which reads as wrong instantly */
  animation: ct-breathe 4.6s ease-in-out infinite;
  will-change: transform;
}
.ct-sway {
  position: absolute; inset: 0;
  transform-origin: 50% 100%;
  animation: ct-sway 7.3s ease-in-out infinite;
}

@keyframes ct-breathe {
  0%, 100% { transform: scale(1) translateY(0); }
  50%      { transform: scale(1.0075) translateY(-0.35%); }
}
@keyframes ct-sway {
  0%, 100% { transform: rotate(-0.22deg) translateX(-0.15%); }
  50%      { transform: rotate(0.22deg) translateX(0.15%); }
}

/* ---- crossfade buffers ---------------------------------------------------------------- */
.ct-frame {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 420ms ease;
  pointer-events: none;
}
.ct-frame.ct-on { opacity: 1; }

/* ---- layers --------------------------------------------------------------------------- */
.ct-l {
  position: absolute;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
}
.ct-body       { inset: 0; width: 100%; height: 100%; }
.ct-expression { object-fit: contain; }
/* eyes and mouth are flashed, so they fade fast enough to read as a blink rather than a dissolve */
.ct-eyes, .ct-mouth { object-fit: contain; opacity: 0; transition: opacity 55ms linear; }
.ct-prop       { object-fit: contain; }

/* ---- fallbacks when the art is not layered yet ----------------------------------------- */
/* No eyes layer → a tiny whole-figure nudge stands in for the blink. */
.ct-nudge .ct-sway { animation: ct-nudge 260ms ease-in-out; }
@keyframes ct-nudge {
  0%, 100% { transform: rotate(0deg) scale(1); }
  50%      { transform: rotate(-0.5deg) scale(0.997); }
}
/* No mouth layer → a small continuous motion while speaking, so a reply still has a body. */
.ct-talking .ct-figure { animation: ct-breathe 2.1s ease-in-out infinite; }

/* ---- reduced motion: the state still changes, it just stops moving ---------------------- */
.ct-reduced .ct-figure,
.ct-reduced .ct-sway,
.ct-reduced.ct-talking .ct-figure { animation: none; }
.ct-reduced .ct-frame { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .ct-figure, .ct-sway, .ct-talking .ct-figure { animation: none !important; }
  .ct-frame, .ct-bg { transition: none !important; }
}
