/* suite-kit · characters — the shared rig for the assistant avatars.
   ============================================================================
   ONE rig, N characters. A character supplies parts + a palette; everything
   below — the slots, the state machine, every keyframe — is shared.

   Four rules this file exists to enforce, each learned the hard way:

   1. POSE AND MOTION ARE SEPARATE TIERS. Pose carries information: drooped ears
      mean the request failed. It applies with every animation off. Only motion
      is gated.
   2. MOTION IS OPT-IN, declared inside `prefers-reduced-motion: no-preference`,
      matching apps/lemma/app/static/styles.css. Never an opt-out override — a
      missed selector leaks through those.
   3. LOOPS MUST BE CSS. The suite's reduced-motion net is a CSS catch-all and
      `Element.animate()` walks straight through it (measured: a CSS animation
      clamped to 1e-05s while the identical WAAPI animation kept running at
      iterations:Infinity). JS may fire one-shots and must read matchMedia itself.
   4. AN IDLE LOOP IS NOT FREE. Measured in Chrome over 25s windows: static
      ~0.1% of a core, this rig ~4.6%, SVG transform alone ~7.2%. SVG transforms
      do NOT get the compositor fast path an HTML div transform gets. Hence the
      pause rig at the foot of this file — a requirement, not an optimisation.
   ============================================================================ */

.skc {
  display: block;
  --skc-sine:   cubic-bezier(.45, .05, .55, .95);
  --skc-settle: cubic-bezier(.22, 1, .36, 1);
  --skc-back:   cubic-bezier(.34, 1.56, .64, 1);
}

/* transform-box:fill-box makes each part rotate about itself. ⚠ It also applies
   to the SVG `transform` ATTRIBUTE, so any group used for POSITIONING must opt
   back out or `scale()` resolves about the group's own centre, not the origin. */
.skc * { transform-box: fill-box; transform-origin: center }
.skc .skc-pos, .skc .skc-pos * { transform-box: view-box }
.skc .skc-pos { transform-origin: 0 0 }

.skc .skc-body { transform-origin: 50% 100% }
.skc .skc-head { transform-origin: 50% 94%; transform: rotate(-7deg) }
.skc .skc-tail { transform-origin: 14% 88% }
.skc .skc-ear-l { transform-origin: 70% 88% }
.skc .skc-ear-r { transform-origin: 30% 88% }
.skc .skc-think { opacity: 0 }
.skc .skc-zzz { opacity: 0 }

/* Taco's head group holds only the face, so the shared swing would throw it. */
.skc[data-character="taco"] .skc-head { transform-origin: 50% 96%; transform: rotate(-4deg) }

/* ---------------------------------------------------------------- pose ----
   Always applied. These say what happened; they are not decoration.        */
.skc[data-state="thinking"] .skc-think { opacity: 1 }
.skc[data-state="happy"] .skc-eye-open { opacity: 0 }
.skc[data-state="happy"] .skc-eye-happy { opacity: 1 }
.skc[data-state="happy"] .skc-ear-l { transform: rotate(-9deg) }
.skc[data-state="happy"] .skc-ear-r { transform: rotate(9deg) }
.skc[data-state="error"] .skc-ear-l { transform: rotate(24deg) }
.skc[data-state="error"] .skc-ear-r { transform: rotate(-24deg) }
.skc[data-state="error"] .skc-head { transform: translateY(3px) rotate(-3deg) }
.skc[data-state="error"] .skc-tail { transform: rotate(-14deg) }
.skc[data-state="asleep"] .skc-eyes { transform: scaleY(.08) }
.skc[data-state="asleep"] .skc-tail { transform: rotate(2deg) }
.skc[data-state="asleep"] .skc-zzz { opacity: 1 }

/* ------------------------------------------------------------ keyframes ---
   Periods are coprime (4.1 / 5.9 / 7.3 / 9.7 / 13.1) so the composite loop
   does not visibly repeat. Easing differs per joint: one shared ease-in-out
   is what makes a rig read mechanical.                                     */
@keyframes skc-breathe {
  0%   { transform: scaleY(1)     scaleX(1)    translateY(0) }
  50%  { transform: scaleY(1.024) scaleX(.994) translateY(-1.4px) }
  100% { transform: scaleY(1)     scaleX(1)    translateY(0) }
}
@keyframes skc-bob {
  0%   { transform: translateY(0)    rotate(-7deg) }
  50%  { transform: translateY(-3px) rotate(-5.4deg) }
  100% { transform: translateY(0)    rotate(-7deg) }
}
@keyframes skc-bob-taco {
  0%   { transform: translateY(0)      rotate(-4deg) }
  50%  { transform: translateY(-1.5px) rotate(-2deg) }
  100% { transform: translateY(0)      rotate(-4deg) }
}
@keyframes skc-sway {
  0%   { transform: rotate(-5deg) }
  50%  { transform: rotate(6.5deg) }
  100% { transform: rotate(-5deg) }
}
/* fast close, brief hold, slower open — a lid, not a sine wave */
@keyframes skc-blink {
  0%, 17%    { transform: scaleY(1) }
  17.9%      { transform: scaleY(.08) }
  19.1%      { transform: scaleY(.08) }
  21.2%      { transform: scaleY(1) }
  25%, 26.1% { transform: scaleY(1) }
  26.9%      { transform: scaleY(.08) }
  29%        { transform: scaleY(1) }
  71%        { transform: scaleY(1) }
  71.9%      { transform: scaleY(.08) }
  73.1%      { transform: scaleY(.08) }
  75.4%      { transform: scaleY(1) }
  100%       { transform: scaleY(1) }
}
/* a flick out and a slow settle back */
@keyframes skc-flick {
  0%, 88% { transform: rotate(0) }
  90.5%   { transform: rotate(-13deg) }
  93%     { transform: rotate(4deg) }
  96%     { transform: rotate(-1.5deg) }
  100%    { transform: rotate(0) }
}
/* anticipation: tips AWAY before committing, overshoots, settles */
@keyframes skc-tilt {
  0%   { transform: rotate(-7deg)    translateY(0) }
  12%  { transform: rotate(-4.2deg)  translateY(.5px) }
  46%  { transform: rotate(-14deg)   translateY(-2.5px) }
  62%  { transform: rotate(-12.4deg) translateY(-2px) }
  100% { transform: rotate(-13deg)   translateY(-2.2px) }
}
@keyframes skc-pop {
  0%, 55%, 100% { transform: translateY(0) scale(.86); opacity: .3 }
  22%           { transform: translateY(-5px) scale(1.06); opacity: 1 }
  34%           { transform: translateY(-3.2px) scale(1); opacity: .9 }
}
@keyframes skc-talk {
  0%   { transform: scaleY(.7) }
  40%  { transform: scaleY(1.45) }
  100% { transform: scaleY(.7) }
}
@keyframes skc-drift {
  0%   { transform: translate(0,0) scale(.7); opacity: 0 }
  25%  { opacity: .9 }
  100% { transform: translate(9px,-20px) scale(1.1); opacity: 0 }
}

/* -------------------------------------------------------------- motion ----
   OPT-IN. Negative delays make the head and tail LAG the body, which is what
   reads as one creature rather than several parts on a timer.              */
@media (prefers-reduced-motion: no-preference) {
  .skc .skc-body  { animation: skc-breathe 4.1s var(--skc-sine) infinite }
  .skc .skc-head  { animation: skc-bob 4.1s var(--skc-sine) -.62s infinite }
  .skc .skc-tail  { animation: skc-sway 5.9s var(--skc-settle) -1.1s infinite }
  .skc .skc-eyes  { animation: skc-blink 7.3s linear infinite }
  .skc .skc-ear-r { animation: skc-flick 9.7s var(--skc-back) infinite }
  .skc .skc-ear-l { animation: skc-flick 13.1s var(--skc-back) -4s infinite }

  .skc[data-character="taco"] .skc-head { animation-name: skc-bob-taco }

  .skc[data-state="thinking"] .skc-head { animation: skc-tilt 2.6s var(--skc-settle) infinite alternate }
  .skc[data-state="thinking"] .skc-d1 { animation: skc-pop 1.5s var(--skc-back) infinite }
  .skc[data-state="thinking"] .skc-d2 { animation: skc-pop 1.5s var(--skc-back) .14s infinite }
  .skc[data-state="thinking"] .skc-d3 { animation: skc-pop 1.5s var(--skc-back) .28s infinite }

  .skc[data-state="speaking"] .skc-head  { animation-duration: 1.5s }
  .skc[data-state="speaking"] .skc-mouth { animation: skc-talk .38s var(--skc-sine) infinite }

  .skc[data-state="happy"] .skc-ear-l,
  .skc[data-state="happy"] .skc-ear-r,
  .skc[data-state="error"] .skc-ear-l,
  .skc[data-state="error"] .skc-ear-r,
  .skc[data-state="error"] .skc-head,
  .skc[data-state="error"] .skc-tail,
  .skc[data-state="asleep"] .skc-eyes,
  .skc[data-state="asleep"] .skc-tail { animation: none }

  .skc[data-state="asleep"] .skc-body { animation-duration: 6.8s }
  .skc[data-state="asleep"] .skc-head { animation-duration: 6.8s }
  .skc[data-state="asleep"] .skc-zzz  { animation: skc-drift 3.2s ease-out infinite }
}

/* ---------------------------------------------------------------- pause ---
   Driven by IntersectionObserver + visibilitychange in the JS. CSS does the
   pausing so the reduced-motion net still governs. See note 4 at the top. */
.skc.is-paused *,
html.skc-doc-hidden .skc * { animation-play-state: paused !important }

/* --------------------------------------------------------------- picker ---
   One control, shared by every app, so the choice looks the same everywhere.
   Colours come from the kit's own tokens — this file must never introduce a
   palette, or it becomes a second design system.                           */
.skc-picker {
  display: flex; flex-wrap: wrap; gap: 10px;
}
.skc-picker-opt {
  font: inherit; cursor: pointer; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 108px; padding: 12px 8px 10px;
  background: var(--surface, #fff);
  border: 1px solid var(--rule, #E7ECF3);
  border-radius: 12px;
  color: var(--ink, #152D49);
  transition: border-color .17s, box-shadow .17s;
}
.skc-picker-opt:hover { border-color: var(--rule-blue, #C8D2E3) }
.skc-picker-opt:focus-visible { outline: 2px solid var(--brass, #A9772A); outline-offset: 2px }
.skc-picker-opt[aria-checked="true"] {
  border-color: var(--brass, #A9772A);
  box-shadow: inset 0 0 0 1px var(--brass, #A9772A);
}
.skc-picker-art {
  width: 48px; height: 48px; display: grid; place-items: center;
  border-radius: 24%; background: var(--panel, #F6F8FC); margin-bottom: 6px;
}
.skc-picker-name { font-size: 12.5px; font-weight: 600; line-height: 1.2 }
.skc-picker-sub  { font-size: 10.5px; color: var(--muted, #5A6A82); line-height: 1.25 }

/* "No character" — the plain mark. A ring, so the option reads as deliberate
   rather than as a slot that failed to load. */
.skc-picker-none {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--ink-ghost, #A7AFBE);
}
