/* Crownfall 角色骨骼动画（所有角色共用：玩家皮肤 js/art/hero.js + AI 领主 js/art/lords.js）
 * 规范见 docs/ART_BIBLE.md §5.2 / §5.3。
 *
 * 结构：<g class="ch st-{状态}"><g class="ch-flip">部件…</g></g>
 *   - 部件按 §5.2 的 class 与枢轴点画，被动画的 <g> 自己不能带 transform 属性。
 *   - 整体动作（颠、跳、前倾、后退、倒地）放在 .ch-flip 上，只用独立变换属性 translate / rotate / scale，
 *     不碰 transform，所以 ART.character 给朝左角色加的 transform="scale(-1 1)" 不会被覆盖。
 *     朝左时 --fx = -1，水平位移和旋转自动镜像。
 *   - 影子在 .ch-flip 里面，整体跳起/倒地时影子做反向补偿，永远贴地。
 *
 * 外部可调：
 *   --ph  循环动画（idle walk attack cheer beg）的相位偏移，例如 style="--ph:.37s"，一群角色就不会同步呼吸。
 *   --t0  单次动画（capture hurt dead）从第几秒开始播（重建 DOM 后想接着播时用），默认 0s。
 *   祖先加 .cf-lite：只保留动作状态，关掉 idle 呼吸与皮肤光环循环（远景/低端机省电）。
 * 可选部件（AI 领主也可以用）：
 *   .ch-trail  放在 .ch-weapon 里的刀光残影（以手 (16,-27) 为圆心画在武器"来的方向"），只在 st-attack 挥砍瞬间出现。
 *   .ch-aura   放在 .ch-shadow 之后的身后光环层（史诗/传说皮肤），倒地时淡出。
 *   .ch-dust   放在 .ch-shadow 里的脚下尘土，只在 st-walk 出现。 */

/* ---------------------------------------------------------------- 枢轴点（角色坐标，原点在脚底中心） */
.ch-flip, .ch-shadow, .ch-aura, .ch-cape, .ch-armB, .ch-legB, .ch-legF, .ch-torso, .ch-head, .ch-armF, .ch-weapon, .ch-fx, .ch-trail, .ch-dust {
  transform-box: view-box;
}
.ch-flip { transform-origin: 0 0; --fx: 1; }
.ch-flip[transform] { --fx: -1; }
.ch-shadow { transform-origin: 0 0; }
.ch-cape { transform-origin: 0 -46px; }
.ch-armB { transform-origin: -12px -43px; }
.ch-legB { transform-origin: -5px -20px; }
.ch-legF { transform-origin: 5px -20px; }
.ch-torso { transform-origin: 0 -20px; }
.ch-head { transform-origin: 0 -50px; }
.ch-armF { transform-origin: 12px -43px; }
.ch-weapon { transform-origin: 16px -27px; }
.ch-trail { transform-origin: 16px -27px; opacity: 0; }
.ch-dust { opacity: 0; }

/* 缓动：重力感（上升减速 / 下落加速）、甩出（先慢后快）、弹回 */
.ch {
  --e-up: cubic-bezier(.2, .7, .4, 1);
  --e-down: cubic-bezier(.55, 0, .85, .35);
  --e-whip: cubic-bezier(.7, 0, .9, .5);
  --e-io: cubic-bezier(.45, 0, .55, 1);
  --e-sin-in: cubic-bezier(.12, 0, .39, 0);
  --e-sin-out: cubic-bezier(.61, 1, .88, 1);
}

/* ================================================================ st-idle 站岗（1.6s 循环）
 * 身体上下呼吸 1.5、头摆 ±2°、披风飘；头和手比躯干晚一点（跟随动作） */
.ch.st-idle .ch-torso { animation: cf-idle-torso 1.6s var(--e-io) infinite; }
.ch.st-idle .ch-head { animation: cf-idle-head 1.6s var(--e-io) infinite; }
.ch.st-idle .ch-armF { animation: cf-idle-armF 1.6s var(--e-io) infinite; }
.ch.st-idle .ch-armB { animation: cf-idle-armB 1.6s var(--e-io) infinite; }
.ch.st-idle .ch-cape { animation: cf-idle-cape 1.6s var(--e-io) infinite; }
.ch.st-idle .ch-shadow { animation: cf-idle-shadow 1.6s var(--e-io) infinite; }
@keyframes cf-idle-torso {
  0%, 100% { transform: translateY(0) scale(1, 1); }
  50% { transform: translateY(1.3px) scale(1.035, .975); }
}
@keyframes cf-idle-head {
  0%, 100% { transform: translateY(0) rotate(-2deg); }
  8% { transform: translateY(0) rotate(-2deg); }
  58% { transform: translateY(1.6px) rotate(2deg); }
}
@keyframes cf-idle-armF {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  55% { transform: translateY(1.5px) rotate(-4deg); }
}
@keyframes cf-idle-armB {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  55% { transform: translateY(1.5px) rotate(5deg); }
}
@keyframes cf-idle-cape {
  0%, 100% { transform: translateY(0) rotate(0deg) skewX(0deg); }
  15% { transform: translateY(0) rotate(-1deg) skewX(1deg); }
  65% { transform: translateY(1.2px) rotate(4deg) skewX(-5deg); }
}
@keyframes cf-idle-shadow {
  0%, 100% { transform: scale(1, 1); }
  50% { transform: scale(1.05, 1); }
}

/* ================================================================ st-walk 行军（0.5s 循环）
 * 0% / 50% 是两脚着地（身体最低），25% / 75% 是两腿交错（身体最高、摆动腿抬起） */
.ch.st-walk .ch-flip { animation: cf-walk-body .5s linear infinite; }
.ch.st-walk .ch-shadow { animation: cf-walk-shadow .5s linear infinite; }
.ch.st-walk .ch-legF { animation: cf-walk-legF .5s linear infinite; }
.ch.st-walk .ch-legB { animation: cf-walk-legB .5s linear infinite; }
.ch.st-walk .ch-armF { animation: cf-walk-armF .5s var(--e-io) infinite; }
.ch.st-walk .ch-armB { animation: cf-walk-armB .5s var(--e-io) infinite; }
.ch.st-walk .ch-torso { animation: cf-walk-torso .5s linear infinite; }
.ch.st-walk .ch-head { animation: cf-walk-head .5s linear infinite; }
.ch.st-walk .ch-cape { animation: cf-walk-cape .5s linear infinite; }
.ch.st-walk .ch-dust { animation: cf-walk-dust .5s linear infinite; }
@keyframes cf-walk-body {
  0%, 50%, 100% { translate: 0 1.2px; rotate: calc(var(--fx) * 4deg); animation-timing-function: var(--e-up); }
  25%, 75% { translate: 0 -2.4px; rotate: calc(var(--fx) * 5deg); animation-timing-function: var(--e-down); }
}
@keyframes cf-walk-shadow {
  0%, 50%, 100% { transform: rotate(-4deg) translate(0, -1.2px) scale(1, 1); animation-timing-function: var(--e-up); }
  25%, 75% { transform: rotate(-5deg) translate(0, 2.4px) scale(.88, .9); animation-timing-function: var(--e-down); }
}
@keyframes cf-walk-legF {
  0%, 100% { transform: translateY(0) rotate(-28deg); animation-timing-function: var(--e-sin-in); }
  25% { transform: translateY(0) rotate(0deg); animation-timing-function: var(--e-sin-out); }
  50% { transform: translateY(0) rotate(28deg); animation-timing-function: var(--e-sin-in); }
  75% { transform: translateY(-3.6px) rotate(0deg); animation-timing-function: var(--e-sin-out); }
}
@keyframes cf-walk-legB {
  0%, 100% { transform: translateY(0) rotate(28deg); animation-timing-function: var(--e-sin-in); }
  25% { transform: translateY(-3.6px) rotate(0deg); animation-timing-function: var(--e-sin-out); }
  50% { transform: translateY(0) rotate(-28deg); animation-timing-function: var(--e-sin-in); }
  75% { transform: translateY(0) rotate(0deg); animation-timing-function: var(--e-sin-out); }
}
@keyframes cf-walk-armF {
  0%, 100% { transform: rotate(18deg); }
  50% { transform: rotate(-22deg); }
}
@keyframes cf-walk-armB {
  0%, 100% { transform: rotate(-28deg); }
  50% { transform: rotate(26deg); }
}
@keyframes cf-walk-torso {
  0%, 50%, 100% { transform: scale(1.04, .965); animation-timing-function: var(--e-up); }
  25%, 75% { transform: scale(.98, 1.02); animation-timing-function: var(--e-down); }
}
@keyframes cf-walk-head {
  0%, 50%, 100% { transform: translateY(.9px) rotate(2.5deg); animation-timing-function: var(--e-up); }
  25%, 75% { transform: translateY(-.5px) rotate(-1.5deg); animation-timing-function: var(--e-down); }
}
@keyframes cf-walk-cape {
  0%, 50%, 100% { transform: rotate(10deg) skewX(-5deg); animation-timing-function: var(--e-up); }
  25%, 75% { transform: rotate(16deg) skewX(-10deg); animation-timing-function: var(--e-down); }
}
@keyframes cf-walk-dust {
  0%, 50%, 100% { opacity: .85; transform: translate(0, 0) scale(.6); }
  45%, 95% { opacity: 0; transform: translate(-6px, -3px) scale(1.2); }
}

/* ================================================================ st-attack 交战（0.55s 循环）
 * 举刀蓄力（0–36% 往后再拉一点 = 预备动作）→ 36–50% 甩劈到 +38°（加速）→ 定格 → 收回。
 * 躯干前冲 3、头被压扁、刀光残影只在劈下瞬间出现。 */
.ch.st-attack .ch-flip { animation: cf-atk-body .55s linear infinite; }
.ch.st-attack .ch-shadow { animation: cf-atk-shadow .55s linear infinite; }
.ch.st-attack .ch-armF { animation: cf-atk-armF .55s linear infinite; }
.ch.st-attack .ch-weapon { animation: cf-atk-weapon .55s linear infinite; }
.ch.st-attack .ch-trail { animation: cf-atk-trail .55s linear infinite; }
.ch.st-attack .ch-armB { animation: cf-atk-armB .55s linear infinite; }
.ch.st-attack .ch-head { animation: cf-atk-head .55s linear infinite; }
.ch.st-attack .ch-torso { animation: cf-atk-torso .55s linear infinite; }
.ch.st-attack .ch-legF { animation: cf-atk-legF .55s linear infinite; }
.ch.st-attack .ch-legB { animation: cf-atk-legB .55s linear infinite; }
.ch.st-attack .ch-cape { animation: cf-atk-cape .55s linear infinite; }
@keyframes cf-atk-body {
  0%, 100% { translate: 0 0; rotate: calc(var(--fx) * -2deg); animation-timing-function: var(--e-sin-out); }
  36% { translate: calc(var(--fx) * -1.5px) 0; rotate: calc(var(--fx) * -5deg); animation-timing-function: var(--e-whip); }
  50% { translate: calc(var(--fx) * 3.5px) 1px; rotate: calc(var(--fx) * 7deg); animation-timing-function: var(--e-sin-out); }
  60% { translate: calc(var(--fx) * 3px) .6px; rotate: calc(var(--fx) * 6deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-shadow {
  0%, 100% { transform: rotate(2deg) translate(0, 0); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(5deg) translate(1.5px, 0); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(-7deg) translate(-3.5px, -1px); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(-6deg) translate(-3px, -.6px); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-armF {
  0%, 100% { transform: rotate(-105deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(-128deg); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(40deg); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(33deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-weapon {
  0%, 100% { transform: rotate(20deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(28deg); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(46deg); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(40deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-trail {
  0%, 42% { opacity: 0; transform: scale(.7); }
  50% { opacity: 1; transform: scale(1); }
  58% { opacity: .55; transform: scale(1.04); }
  66%, 100% { opacity: 0; transform: scale(1.06); }
}
@keyframes cf-atk-armB {
  0%, 100% { transform: rotate(-32deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(-42deg); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(26deg); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(20deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-head {
  0%, 100% { transform: translateY(0) rotate(-3deg) scale(1, 1); animation-timing-function: var(--e-sin-out); }
  36% { transform: translateY(-.5px) rotate(-7deg) scale(.97, 1.04); animation-timing-function: var(--e-whip); }
  50% { transform: translateY(2.2px) rotate(5deg) scale(1.09, .88); animation-timing-function: var(--e-sin-out); }
  62% { transform: translateY(1px) rotate(3deg) scale(1.03, .96); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-torso {
  0%, 100% { transform: rotate(-2deg) scale(1, 1); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(-4deg) scale(.97, 1.04); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(5deg) scale(1.06, .93); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(4deg) scale(1.03, .97); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-legF {
  0%, 100% { transform: rotate(-14deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(-10deg); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(-24deg); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(-22deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-legB {
  0%, 100% { transform: rotate(14deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(10deg); animation-timing-function: var(--e-whip); }
  50% { transform: rotate(22deg); animation-timing-function: var(--e-sin-out); }
  60% { transform: rotate(20deg); animation-timing-function: var(--e-io); }
}
@keyframes cf-atk-cape {
  0%, 100% { transform: rotate(-3deg) skewX(2deg); animation-timing-function: var(--e-sin-out); }
  36% { transform: rotate(-6deg) skewX(4deg); animation-timing-function: var(--e-whip); }
  52% { transform: rotate(8deg) skewX(-6deg); animation-timing-function: var(--e-sin-out); }
  64% { transform: rotate(12deg) skewX(-9deg); animation-timing-function: var(--e-io); }
}

/* ================================================================ st-capture 占领插旗（1.2s 单次）
 * 下蹲蓄力 → 起跳拉长 → 空中举起武器（武器反向转，保持刀尖朝上）→ 落地压扁 → 回弹 → 收势 */
.ch.st-capture .ch-flip { animation: cf-cap-body 1.2s linear forwards; }
.ch.st-capture .ch-shadow { animation: cf-cap-shadow 1.2s linear forwards; }
.ch.st-capture .ch-armF { animation: cf-cap-armF 1.2s linear forwards; }
.ch.st-capture .ch-weapon { animation: cf-cap-weapon 1.2s linear forwards; }
.ch.st-capture .ch-armB { animation: cf-cap-armB 1.2s linear forwards; }
.ch.st-capture .ch-legF { animation: cf-cap-legF 1.2s linear forwards; }
.ch.st-capture .ch-legB { animation: cf-cap-legB 1.2s linear forwards; }
.ch.st-capture .ch-head { animation: cf-cap-head 1.2s linear forwards; }
.ch.st-capture .ch-cape { animation: cf-cap-cape 1.2s linear forwards; }
@keyframes cf-cap-body {
  0% { translate: 0 0; scale: 1 1; animation-timing-function: var(--e-sin-out); }
  12% { translate: 0 0; scale: 1.14 .82; animation-timing-function: var(--e-whip); }
  22% { translate: 0 -6px; scale: .88 1.14; animation-timing-function: var(--e-up); }
  38% { translate: 0 -15px; scale: 1 1; animation-timing-function: var(--e-io); }
  46% { translate: 0 -13.5px; scale: 1 1.02; animation-timing-function: var(--e-down); }
  57% { translate: 0 0; scale: 1.22 .78; animation-timing-function: var(--e-up); }
  68% { translate: 0 -2.5px; scale: .93 1.08; animation-timing-function: var(--e-io); }
  80% { translate: 0 0; scale: 1.04 .96; animation-timing-function: var(--e-io); }
  90%, 100% { translate: 0 0; scale: 1 1; }
}
@keyframes cf-cap-shadow {
  0% { transform: translate(0, 0) scale(1, 1); opacity: 1; animation-timing-function: var(--e-sin-out); }
  12% { transform: translate(0, 0) scale(1, 1); opacity: 1; animation-timing-function: var(--e-whip); }
  22% { transform: translate(0, 5.3px) scale(.85, .8); opacity: .8; animation-timing-function: var(--e-up); }
  38% { transform: translate(0, 15px) scale(.55, .55); opacity: .55; animation-timing-function: var(--e-io); }
  46% { transform: translate(0, 13.2px) scale(.6, .58); opacity: .6; animation-timing-function: var(--e-down); }
  57% { transform: translate(0, 0) scale(1, 1); opacity: 1; animation-timing-function: var(--e-up); }
  68% { transform: translate(0, 2.3px) scale(.94, .94); opacity: 1; animation-timing-function: var(--e-io); }
  80%, 100% { transform: translate(0, 0) scale(1, 1); opacity: 1; }
}
@keyframes cf-cap-armF {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-sin-out); }
  12% { transform: rotate(38deg); animation-timing-function: var(--e-whip); }
  24% { transform: rotate(-125deg); animation-timing-function: var(--e-up); }
  38% { transform: rotate(-168deg); animation-timing-function: var(--e-io); }
  57% { transform: rotate(-150deg); animation-timing-function: var(--e-up); }
  66% { transform: rotate(-162deg); animation-timing-function: var(--e-io); }
  84% { transform: rotate(-150deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-cap-weapon {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-sin-out); }
  12% { transform: rotate(-10deg); animation-timing-function: var(--e-whip); }
  24% { transform: rotate(105deg); animation-timing-function: var(--e-up); }
  38% { transform: rotate(150deg); animation-timing-function: var(--e-io); }
  57% { transform: rotate(140deg); animation-timing-function: var(--e-up); }
  66% { transform: rotate(150deg); animation-timing-function: var(--e-io); }
  84% { transform: rotate(140deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-cap-armB {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-sin-out); }
  12% { transform: rotate(40deg); animation-timing-function: var(--e-whip); }
  24% { transform: rotate(120deg); animation-timing-function: var(--e-up); }
  38% { transform: rotate(160deg); animation-timing-function: var(--e-io); }
  57% { transform: rotate(140deg); animation-timing-function: var(--e-up); }
  66% { transform: rotate(155deg); animation-timing-function: var(--e-io); }
  84% { transform: rotate(140deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-cap-legF {
  0%, 12% { transform: rotate(0deg); animation-timing-function: var(--e-whip); }
  24% { transform: rotate(-12deg); animation-timing-function: var(--e-up); }
  38% { transform: rotate(-38deg); animation-timing-function: var(--e-io); }
  50% { transform: rotate(-20deg); animation-timing-function: var(--e-down); }
  57% { transform: rotate(-14deg); animation-timing-function: var(--e-up); }
  75%, 100% { transform: rotate(0deg); }
}
@keyframes cf-cap-legB {
  0%, 12% { transform: rotate(0deg); animation-timing-function: var(--e-whip); }
  24% { transform: rotate(14deg); animation-timing-function: var(--e-up); }
  38% { transform: rotate(32deg); animation-timing-function: var(--e-io); }
  50% { transform: rotate(18deg); animation-timing-function: var(--e-down); }
  57% { transform: rotate(14deg); animation-timing-function: var(--e-up); }
  75%, 100% { transform: rotate(0deg); }
}
@keyframes cf-cap-head {
  0% { transform: translateY(0) rotate(0deg) scale(1, 1); animation-timing-function: var(--e-sin-out); }
  12% { transform: translateY(2px) rotate(5deg) scale(1.04, .95); animation-timing-function: var(--e-whip); }
  24% { transform: translateY(-1px) rotate(-4deg) scale(.97, 1.04); animation-timing-function: var(--e-up); }
  38% { transform: translateY(-.5px) rotate(-9deg) scale(1, 1); animation-timing-function: var(--e-io); }
  57% { transform: translateY(2.5px) rotate(2deg) scale(1.08, .9); animation-timing-function: var(--e-up); }
  70% { transform: translateY(-1px) rotate(-3deg) scale(.97, 1.03); animation-timing-function: var(--e-io); }
  100% { transform: translateY(0) rotate(0deg) scale(1, 1); }
}
@keyframes cf-cap-cape {
  0% { transform: rotate(0deg) skewX(0deg); animation-timing-function: var(--e-sin-out); }
  12% { transform: rotate(-4deg) skewX(3deg); animation-timing-function: var(--e-whip); }
  30% { transform: rotate(-14deg) skewX(10deg) scale(1, .9); animation-timing-function: var(--e-io); }
  50% { transform: rotate(6deg) skewX(-4deg) scale(1, 1.05); animation-timing-function: var(--e-down); }
  62% { transform: rotate(14deg) skewX(-10deg) scale(1, 1); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg) skewX(0deg) scale(1, 1); }
}

/* ================================================================ st-hurt 被打（0.35s 单次）
 * 白闪 + 后仰 + 后退，头有鞭甩（先往前甩再往后仰），最后回正 */
.ch.st-hurt { animation: cf-hurt-flash .35s linear forwards; }
.ch.st-hurt .ch-flip { animation: cf-hurt-body .35s linear forwards; }
.ch.st-hurt .ch-shadow { animation: cf-hurt-shadow .35s linear forwards; }
.ch.st-hurt .ch-head { animation: cf-hurt-head .35s linear forwards; }
.ch.st-hurt .ch-armF { animation: cf-hurt-armF .35s linear forwards; }
.ch.st-hurt .ch-armB { animation: cf-hurt-armB .35s linear forwards; }
.ch.st-hurt .ch-legF { animation: cf-hurt-legF .35s linear forwards; }
.ch.st-hurt .ch-cape { animation: cf-hurt-cape .35s linear forwards; }
@keyframes cf-hurt-flash {
  0%, 22% { filter: brightness(2.7) saturate(.25); }
  45%, 100% { filter: brightness(1) saturate(1); }
}
@keyframes cf-hurt-body {
  0% { translate: 0 0; rotate: 0deg; animation-timing-function: var(--e-up); }
  20% { translate: calc(var(--fx) * -5px) -1.5px; rotate: calc(var(--fx) * -13deg); animation-timing-function: var(--e-io); }
  55% { translate: calc(var(--fx) * -4px) 0; rotate: calc(var(--fx) * -4deg); animation-timing-function: var(--e-io); }
  100% { translate: 0 0; rotate: 0deg; }
}
@keyframes cf-hurt-shadow {
  0% { transform: rotate(0deg) translate(0, 0); animation-timing-function: var(--e-up); }
  20% { transform: rotate(13deg) translate(5px, 1.5px); animation-timing-function: var(--e-io); }
  55% { transform: rotate(4deg) translate(4px, 0); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg) translate(0, 0); }
}
@keyframes cf-hurt-head {
  0% { transform: rotate(0deg) scale(1, 1); animation-timing-function: var(--e-up); }
  12% { transform: rotate(7deg) scale(1.06, .94); animation-timing-function: var(--e-io); }
  38% { transform: rotate(-10deg) scale(.97, 1.03); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg) scale(1, 1); }
}
@keyframes cf-hurt-armF {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  22% { transform: rotate(-55deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-hurt-armB {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  22% { transform: rotate(-45deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-hurt-legF {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  20% { transform: rotate(-24deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg); }
}
@keyframes cf-hurt-cape {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  25% { transform: rotate(-12deg) skewX(8deg); animation-timing-function: var(--e-io); }
  100% { transform: rotate(0deg) skewX(0deg); }
}

/* ================================================================ st-cheer 胜利/嘲讽（0.7s 循环）
 * 蓄力压扁 → 起跳拉长 → 空中双手举起（V 字）→ 落地压扁 */
.ch.st-cheer .ch-flip { animation: cf-cheer-body .7s linear infinite; }
.ch.st-cheer .ch-shadow { animation: cf-cheer-shadow .7s linear infinite; }
.ch.st-cheer .ch-armF { animation: cf-cheer-armF .7s var(--e-io) infinite; }
.ch.st-cheer .ch-weapon { animation: cf-cheer-weapon .7s var(--e-io) infinite; }
.ch.st-cheer .ch-armB { animation: cf-cheer-armB .7s var(--e-io) infinite; }
.ch.st-cheer .ch-legF { animation: cf-cheer-legF .7s var(--e-io) infinite; }
.ch.st-cheer .ch-legB { animation: cf-cheer-legB .7s var(--e-io) infinite; }
.ch.st-cheer .ch-head { animation: cf-cheer-head .7s var(--e-io) infinite; }
.ch.st-cheer .ch-cape { animation: cf-cheer-cape .7s var(--e-io) infinite; }
@keyframes cf-cheer-body {
  0%, 100% { translate: 0 0; scale: 1.1 .88; animation-timing-function: var(--e-whip); }
  12% { translate: 0 -3px; scale: .9 1.12; animation-timing-function: var(--e-up); }
  36% { translate: 0 -11px; scale: 1 1; animation-timing-function: var(--e-down); }
  58% { translate: 0 0; scale: .96 1.05; animation-timing-function: var(--e-up); }
  70% { translate: 0 0; scale: 1.13 .86; animation-timing-function: var(--e-io); }
  86% { translate: 0 0; scale: 1.02 .98; animation-timing-function: var(--e-io); }
}
@keyframes cf-cheer-shadow {
  0%, 100% { transform: translate(0, 0) scale(1, 1); opacity: 1; animation-timing-function: var(--e-whip); }
  12% { transform: translate(0, 2.7px) scale(.9, .9); opacity: .9; animation-timing-function: var(--e-up); }
  36% { transform: translate(0, 11px) scale(.6, .6); opacity: .6; animation-timing-function: var(--e-down); }
  58% { transform: translate(0, 0) scale(1, 1); opacity: 1; animation-timing-function: var(--e-up); }
}
@keyframes cf-cheer-armF {
  0%, 100% { transform: rotate(-140deg); }
  36% { transform: rotate(-170deg); }
  70% { transform: rotate(-135deg); }
}
@keyframes cf-cheer-weapon {
  0%, 100% { transform: rotate(130deg); }
  36% { transform: rotate(155deg); }
  70% { transform: rotate(125deg); }
}
@keyframes cf-cheer-armB {
  0%, 100% { transform: rotate(140deg); }
  36% { transform: rotate(168deg); }
  70% { transform: rotate(132deg); }
}
@keyframes cf-cheer-legF {
  0%, 12%, 70%, 100% { transform: rotate(0deg); }
  36% { transform: rotate(-20deg); }
}
@keyframes cf-cheer-legB {
  0%, 12%, 70%, 100% { transform: rotate(0deg); }
  36% { transform: rotate(18deg); }
}
@keyframes cf-cheer-head {
  0%, 100% { transform: translateY(1.5px) rotate(3deg); }
  36% { transform: translateY(-1px) rotate(-6deg); }
  70% { transform: translateY(2px) rotate(4deg); }
}
@keyframes cf-cheer-cape {
  0%, 100% { transform: rotate(4deg) skewX(-3deg); }
  30% { transform: rotate(-10deg) skewX(7deg); }
  60% { transform: rotate(12deg) skewX(-8deg); }
}

/* ================================================================ st-beg 求饶（1s 循环）
 * 缩成一团、低头哈腰，双手合十在胸前上下抖（每秒 4 下），全身微微发抖 */
.ch.st-beg .ch-flip { animation: cf-beg-body 1s linear infinite; }
.ch.st-beg .ch-armF { animation: cf-beg-armF 1s var(--e-io) infinite; }
.ch.st-beg .ch-armB { animation: cf-beg-armB 1s var(--e-io) infinite; }
.ch.st-beg .ch-weapon { animation: cf-beg-weapon 1s var(--e-io) infinite; }
.ch.st-beg .ch-head { animation: cf-beg-head 1s var(--e-io) infinite; }
.ch.st-beg .ch-torso { animation: cf-beg-torso 1s var(--e-io) infinite; }
.ch.st-beg .ch-legF { animation: cf-beg-legF 1s var(--e-io) infinite; }
.ch.st-beg .ch-legB { animation: cf-beg-legB 1s var(--e-io) infinite; }
.ch.st-beg .ch-cape { animation: cf-beg-cape 1s var(--e-io) infinite; }
@keyframes cf-beg-body {
  0%, 20%, 40%, 60%, 80%, 100% { translate: 0 0; scale: 1.06 .9; }
  10%, 50%, 90% { translate: .7px 0; scale: 1.07 .89; }
  30%, 70% { translate: -.7px 0; scale: 1.05 .91; }
}
@keyframes cf-beg-armF {
  0%, 25%, 50%, 75%, 100% { transform: rotate(-44deg); }
  12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(-26deg); }
}
@keyframes cf-beg-armB {
  0%, 25%, 50%, 75%, 100% { transform: rotate(-78deg); }
  12.5%, 37.5%, 62.5%, 87.5% { transform: rotate(-62deg); }
}
@keyframes cf-beg-weapon {
  0%, 100% { transform: rotate(-18deg); }
  50% { transform: rotate(-24deg); }
}
@keyframes cf-beg-head {
  0%, 25%, 50%, 75%, 100% { transform: translateY(2.5px) rotate(6deg); }
  12.5%, 37.5%, 62.5%, 87.5% { transform: translateY(1.5px) rotate(3deg); }
}
@keyframes cf-beg-torso {
  0%, 100% { transform: rotate(5deg); }
  50% { transform: rotate(7deg); }
}
@keyframes cf-beg-legF {
  0%, 100% { transform: rotate(8deg); }
  50% { transform: rotate(10deg); }
}
@keyframes cf-beg-legB {
  0%, 100% { transform: rotate(-8deg); }
  50% { transform: rotate(-10deg); }
}
@keyframes cf-beg-cape {
  0%, 100% { transform: rotate(-2deg); }
  50% { transform: rotate(-4deg); }
}

/* ================================================================ st-dead 阵亡（1.6s 单次）
 * 一抖 → 僵直向后倒 90°（加速）→ 着地小弹一下 → 变灰 → 渐隐。腿在着地瞬间翘起来。 */
.ch.st-dead { animation: cf-dead-fade 1.6s linear forwards; }
.ch.st-dead .ch-flip { animation: cf-dead-body 1.6s linear forwards; }
.ch.st-dead .ch-shadow { animation: cf-dead-shadow 1.6s linear forwards; }
.ch.st-dead .ch-armF { animation: cf-dead-armF 1.6s linear forwards; }
.ch.st-dead .ch-armB { animation: cf-dead-armB 1.6s linear forwards; }
.ch.st-dead .ch-legF { animation: cf-dead-legF 1.6s linear forwards; }
.ch.st-dead .ch-legB { animation: cf-dead-legB 1.6s linear forwards; }
.ch.st-dead .ch-head { animation: cf-dead-head 1.6s linear forwards; }
.ch.st-dead .ch-cape { animation: cf-dead-cape 1.6s linear forwards; }
.ch.st-dead .ch-fx, .ch.st-dead .ch-aura { animation: cf-dead-fx .5s linear forwards; }
@keyframes cf-dead-fade {
  0%, 25% { filter: grayscale(0) brightness(1); opacity: 1; }
  45% { filter: grayscale(1) brightness(.92); opacity: 1; }
  72% { filter: grayscale(1) brightness(.92); opacity: 1; }
  100% { filter: grayscale(1) brightness(.92); opacity: 0; }
}
@keyframes cf-dead-body {
  0% { translate: 0 0; rotate: 0deg; animation-timing-function: var(--e-up); }
  9% { translate: 0 -5px; rotate: calc(var(--fx) * 7deg); animation-timing-function: var(--e-io); }
  18% { translate: 0 -3px; rotate: calc(var(--fx) * 2deg); animation-timing-function: var(--e-down); }
  42% { translate: calc(var(--fx) * 26px) -15px; rotate: calc(var(--fx) * -90deg); animation-timing-function: var(--e-up); }
  50% { translate: calc(var(--fx) * 27px) -19px; rotate: calc(var(--fx) * -83deg); animation-timing-function: var(--e-down); }
  58%, 100% { translate: calc(var(--fx) * 28px) -15px; rotate: calc(var(--fx) * -90deg); }
}
@keyframes cf-dead-shadow {
  0% { transform: rotate(0deg) translate(0, 0) translate(0, 0) scale(1, 1); animation-timing-function: var(--e-up); }
  9% { transform: rotate(-7deg) translate(0, 5px) translate(0, 0) scale(.8, .8); animation-timing-function: var(--e-io); }
  18% { transform: rotate(-2deg) translate(0, 3px) translate(0, 0) scale(.9, .9); animation-timing-function: var(--e-down); }
  42% { transform: rotate(90deg) translate(-26px, 15px) translate(-24px, 0) scale(2.1, 1.05); animation-timing-function: var(--e-up); }
  50% { transform: rotate(83deg) translate(-27px, 19px) translate(-24px, 0) scale(1.9, .95); animation-timing-function: var(--e-down); }
  58%, 100% { transform: rotate(90deg) translate(-28px, 15px) translate(-24px, 0) scale(2.1, 1.05); }
}
@keyframes cf-dead-armF {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  12% { transform: rotate(-70deg); animation-timing-function: var(--e-io); }
  42% { transform: rotate(-40deg); animation-timing-function: var(--e-up); }
  52% { transform: rotate(-95deg); animation-timing-function: var(--e-io); }
  66%, 100% { transform: rotate(-75deg); }
}
@keyframes cf-dead-armB {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  12% { transform: rotate(-60deg); animation-timing-function: var(--e-io); }
  42% { transform: rotate(-30deg); animation-timing-function: var(--e-up); }
  52% { transform: rotate(-80deg); animation-timing-function: var(--e-io); }
  66%, 100% { transform: rotate(-60deg); }
}
@keyframes cf-dead-legF {
  0%, 18% { transform: rotate(0deg); animation-timing-function: var(--e-io); }
  42% { transform: rotate(-8deg); animation-timing-function: var(--e-up); }
  52% { transform: rotate(-34deg); animation-timing-function: var(--e-io); }
  66%, 100% { transform: rotate(-20deg); }
}
@keyframes cf-dead-legB {
  0%, 18% { transform: rotate(0deg); animation-timing-function: var(--e-io); }
  42% { transform: rotate(4deg); animation-timing-function: var(--e-up); }
  52% { transform: rotate(-16deg); animation-timing-function: var(--e-io); }
  66%, 100% { transform: rotate(-6deg); }
}
@keyframes cf-dead-head {
  0% { transform: rotate(0deg) scale(1, 1); animation-timing-function: var(--e-up); }
  9% { transform: rotate(-6deg) scale(.96, 1.06); animation-timing-function: var(--e-io); }
  42% { transform: rotate(-4deg) scale(1, 1); animation-timing-function: var(--e-up); }
  50% { transform: rotate(8deg) scale(1.06, .94); animation-timing-function: var(--e-io); }
  62%, 100% { transform: rotate(4deg) scale(1, 1); }
}
@keyframes cf-dead-cape {
  0% { transform: rotate(0deg); animation-timing-function: var(--e-up); }
  30% { transform: rotate(-18deg) skewX(10deg); animation-timing-function: var(--e-io); }
  52% { transform: rotate(14deg) skewX(-6deg); animation-timing-function: var(--e-io); }
  70%, 100% { transform: rotate(4deg) skewX(0deg); }
}
@keyframes cf-dead-fx { to { opacity: 0; } }

/* ---------------------------------------------------------------- 相位：循环动画按 --ph 错开；单次动画按 --t0 起播 */
.ch:is(.st-idle, .st-walk, .st-attack, .st-cheer, .st-beg) :is(.ch-flip, .ch-shadow, .ch-cape, .ch-armB, .ch-legB, .ch-legF, .ch-torso, .ch-head, .ch-armF, .ch-weapon, .ch-trail, .ch-dust) {
  animation-delay: calc(var(--ph, 0s) * -1);
}
.ch:is(.st-capture, .st-hurt, .st-dead) :is(.ch-flip, .ch-shadow, .ch-cape, .ch-armB, .ch-legB, .ch-legF, .ch-torso, .ch-head, .ch-armF, .ch-weapon) {
  animation-delay: calc(var(--t0, 0s) * -1);
}
.ch:is(.st-hurt, .st-dead) { animation-delay: calc(var(--t0, 0s) * -1); }

/* ================================================================ 皮肤光环与特效小动画（hero.js 用，hr- 前缀）
 * 这些元素各自用 style="transform-origin:…" 指定圆心（角色坐标）。 */
.hr-spin, .hr-spinR, .hr-bob, .hr-pulse, .hr-twinkle, .hr-flame, .hr-rise, .hr-sway, .hr-orbit, .hr-jet {
  transform-box: view-box;
}
.hr-spin { animation: hr-spin 6s linear infinite; }
.hr-spinR { animation: hr-spin 9s linear infinite reverse; }
.hr-bob { animation: hr-bob 2.4s var(--e-io, ease-in-out) infinite; }
.hr-pulse { animation: hr-pulse 1.8s var(--e-io, ease-in-out) infinite; }
.hr-twinkle { animation: hr-twinkle 1.6s var(--e-io, ease-in-out) infinite; }
.hr-flame { animation: hr-flame .42s var(--e-io, ease-in-out) infinite alternate; }
.hr-rise { animation: hr-rise 2.2s linear infinite; }
.hr-sway { animation: hr-sway 3.2s var(--e-io, ease-in-out) infinite; }
.hr-jet { animation: hr-jet .16s linear infinite alternate; }
.hr-d1 { animation-delay: -.4s; }
.hr-d2 { animation-delay: -.8s; }
.hr-d3 { animation-delay: -1.2s; }
.hr-d4 { animation-delay: -1.6s; }
@keyframes hr-spin { to { transform: rotate(360deg); } }
@keyframes hr-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }
@keyframes hr-pulse { 0%, 100% { opacity: .55; transform: scale(.94); } 50% { opacity: 1; transform: scale(1.06); } }
@keyframes hr-twinkle { 0%, 100% { opacity: 0; transform: scale(.3) rotate(0deg); } 50% { opacity: 1; transform: scale(1) rotate(45deg); } }
@keyframes hr-flame { 0% { transform: scale(.92, .88) skewX(-4deg); } 100% { transform: scale(1.06, 1.12) skewX(5deg); } }
@keyframes hr-rise { 0% { opacity: 0; transform: translateY(0) scale(.6); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(-30px) scale(1); } }
@keyframes hr-sway { 0%, 100% { transform: rotate(-4deg); } 50% { transform: rotate(4deg); } }
@keyframes hr-jet { 0% { transform: scale(.9, .85); opacity: .85; } 100% { transform: scale(1.08, 1.15); opacity: 1; } }

/* ================================================================ 旗帜 ART.flag（hero.js）
 * 原点在旗杆底部；旗面绕挂点做"波浪"摆动，亮带横扫；.cf-flag-pop 插下去时弹一下 */
.cf-flag-cloth, .cf-flag-wave, .cf-flag, .cf-flag-shine { transform-box: view-box; }
.cf-flag-cloth { transform-origin: 2px -52px; animation: cf-flag-wave 1.5s var(--e-io, ease-in-out) infinite; animation-delay: calc(var(--ph, 0s) * -1); }
.cf-flag-shine { animation: cf-flag-shine 1.5s var(--e-io, ease-in-out) infinite; animation-delay: calc(var(--ph, 0s) * -1); }
.cf-flag-pop { transform-origin: 0 0; animation: cf-flag-pop .5s cubic-bezier(.3, 1.6, .5, 1) both; }
@keyframes cf-flag-wave {
  0%, 100% { transform: skewY(0deg) scale(1, 1); }
  25% { transform: skewY(-5deg) scale(.95, 1.02); }
  50% { transform: skewY(1deg) scale(.98, .98); }
  75% { transform: skewY(4deg) scale(.94, 1.02); }
}
@keyframes cf-flag-shine {
  0% { transform: translateX(-26px); }
  100% { transform: translateX(30px); }
}
@keyframes cf-flag-pop {
  0% { transform: translateY(-18px) scale(.4, 1.3); opacity: 0; }
  55% { transform: translateY(0) scale(1.15, .85); opacity: 1; }
  100% { transform: translateY(0) scale(1, 1); opacity: 1; }
}

/* ---------------------------------------------------------------- 省电：远景/低端机只保留动作状态 */
.cf-lite .ch.st-idle *, .cf-lite .hr-spin, .cf-lite .hr-spinR, .cf-lite .hr-bob, .cf-lite .hr-pulse, .cf-lite .hr-twinkle,
.cf-lite .hr-flame, .cf-lite .hr-rise, .cf-lite .hr-sway, .cf-lite .hr-jet, .cf-lite .cf-flag-cloth, .cf-lite .cf-flag-shine {
  animation: none;
}

/* ---------------------------------------------------------------- 减少动态效果：只留 idle 的轻微呼吸 */
@media (prefers-reduced-motion: reduce) {
  .ch, .ch *, .cf-flag, .cf-flag * { animation: none !important; }
  .ch.st-idle .ch-torso { animation: cf-idle-torso 1.6s ease-in-out infinite !important; }
  .ch.st-dead { filter: grayscale(1); opacity: .45; }
  .ch-trail { opacity: 0; }
}
