/* ── 캐릭터 기본 애니메이션 ─────────────────────────────────────────────────
   그래픽 대화 작업물. 시안: graphics/char_anim.html
   추가 에셋 0바이트 — transform/opacity 만 쓴다.

   ⚠️ 왜 래퍼가 두 겹인가
      상시 호흡(무한 반복)과 이벤트 연출(1회)이 둘 다 transform 을 쓴다.
      한 요소에 몰면 서로 덮어써서 이벤트가 끝나는 순간 캐릭터가 툭 튄다.
      바깥(.bs-char-br) = 호흡 / 안쪽(.bs-char-fx) = 이벤트 로 나눠야 한다.

   ⚠️ 위치 규칙은 원래 .bs-char 가 갖고 있었다. 그걸 래퍼로 옮겼다.
      bobusang-ui.css / bobusang-flow.css 의 .bs-char 규칙과 짝을 맞춰 둔 것이므로
      그쪽을 고치면 여기도 같이 봐야 한다.
   ────────────────────────────────────────────────────────────────────────── */

.bs-screen .bs-charwrap{
  position:absolute; left:50%; bottom:0; transform:translateX(-50%);
  height:var(--char-h); z-index:1;
}
.bs-screen.flowC .bs-charwrap{ left:23%; height:88%; }

/* 래퍼 안에서는 이미지가 흐름대로 놓인다 — 원래 있던 절대배치를 끈다 */
.bs-screen .bs-charwrap .bs-char{
  position:static; left:auto; bottom:auto; transform:none;
  height:100%; width:auto; display:block;
}
.bs-screen .bs-char-br,
.bs-screen .bs-char-fx{ display:block; height:100%; transform-origin:50% 100%; }

/* ── 상시 호흡 ──
   steps(2) 다. 도트를 부드럽게 밀면 픽셀이 반 칸 걸치며 어른거린다 */
.bs-screen .bs-char-br{ animation:bsBreathe 3.4s steps(2) infinite; }
@keyframes bsBreathe{ 0%,100%{ transform:translateY(0) } 50%{ transform:translateY(-0.9%) } }

/* ── 이벤트 ── 1회 재생 후 JS가 클래스를 뗀다 */
.bs-screen .bs-char-fx.fx-enter{ animation:bsEnter .55s cubic-bezier(.2,.9,.3,1) }
.bs-screen .bs-char-fx.fx-cower{ animation:bsCower .5s cubic-bezier(.3,.8,.4,1) }
.bs-screen .bs-char-fx.fx-anger{ animation:bsAnger .42s steps(3) }
.bs-screen .bs-char-fx.fx-spark{ animation:bsSpark .5s steps(2) }
.bs-screen .bs-char-fx.fx-deal { animation:bsDeal .6s cubic-bezier(.2,1.4,.4,1) }
.bs-screen .bs-char-fx.fx-leave{ animation:bsLeave .6s ease-in forwards }

@keyframes bsEnter{ from{ opacity:0; transform:translateX(14%) scale(.94) } to{ opacity:1; transform:none } }
/* 초상은 한 장 그림이라 실제로 뒤로 못 간다. 작아지고 위로 = 물러남으로 읽힌다 */
@keyframes bsCower{ 0%{ transform:none } 40%{ transform:translateY(-3%) scale(.955) } 100%{ transform:none } }
/* 커지고 좌우로 흔들림 = 앞으로 들이댐 */
@keyframes bsAnger{ 0%{ transform:none }
  20%{ transform:translate(-1.5%,1.5%) scale(1.05) }
  45%{ transform:translate(1.5%,1%) scale(1.05) }
  70%{ transform:translate(-1%,1.2%) scale(1.03) }
  100%{ transform:none } }
@keyframes bsSpark{ 0%,100%{ filter:none } 50%{ filter:brightness(1.45) } }
/* [개정 2026-08-23 · 인계_좌판저녁연출 4절] scale 을 뺐다 — 초상 1:1 을 깨서 얼굴이 리샘플된다 */
@keyframes bsDeal { 0%{ transform:none } 35%{ transform:translateY(2.5%) } 100%{ transform:none } }
@keyframes bsLeave{ to{ opacity:0; transform:translateX(-16%) scale(.92) } }

/* ── 도매상 ────────────────────────────────────────────────────────────────
   원본 초상 한 장을 보존하면서 두 겹의 transform 으로 상시 동작과 사건 반응을 분리한다.
   야콥(human)은 잽싸고 능청스럽게, 그로크나르(orc)는 느리고 묵직하게 움직인다. */
.bs-screen.bs-shop .bs-keeper-idle{ animation:bsKeeperHumanIdle 3.1s ease-in-out infinite; }
.bs-screen.bs-shop .bs-keeper-wrap.orc .bs-keeper-idle{ animation-name:bsKeeperOrcIdle; animation-duration:4.2s; }

@keyframes bsKeeperHumanIdle{
  0%,100%{ transform:translateY(0) rotate(0) }
  42%{ transform:translateY(-.65%) rotate(-.18deg) }
  58%{ transform:translateY(-.8%) rotate(.12deg) }
}
@keyframes bsKeeperOrcIdle{
  0%,100%{ transform:translateY(0) scaleX(1) }
  50%{ transform:translateY(-.35%) scaleX(1.008) }
}

.bs-screen.bs-shop .bs-keeper-fx.fx-enter{ animation:bsKeeperEnter .65s cubic-bezier(.16,.9,.28,1); }
.bs-screen.bs-shop .bs-keeper-fx.fx-talk{ animation:bsKeeperTalk .48s ease-out; }
.bs-screen.bs-shop .bs-keeper-fx.fx-deal{ animation:bsKeeperDeal .72s cubic-bezier(.2,1.25,.35,1); }
.bs-screen.bs-shop .bs-keeper-fx.fx-deny{ animation:bsKeeperDeny .46s steps(4); }
.bs-screen.bs-shop .bs-keeper-wrap.orc .bs-keeper-fx.fx-enter{ animation-name:bsKeeperOrcEnter; animation-duration:.78s; }
.bs-screen.bs-shop .bs-keeper-wrap.orc .bs-keeper-fx.fx-talk{ animation-name:bsKeeperOrcTalk; animation-duration:.58s; }
.bs-screen.bs-shop .bs-keeper-wrap.orc .bs-keeper-fx.fx-deal{ animation-name:bsKeeperOrcDeal; animation-duration:.82s; }

@keyframes bsKeeperEnter{
  from{ opacity:0; transform:translateX(-12%) scale(.96) }
  72%{ opacity:1; transform:translateX(1.5%) scale(1.015) }
  to{ transform:none }
}
@keyframes bsKeeperOrcEnter{
  from{ opacity:0; transform:translateX(-9%) scale(1.035) }
  65%{ opacity:1; transform:translateX(.8%) scale(.99) }
  to{ transform:none }
}
@keyframes bsKeeperTalk{
  0%,100%{ transform:none }
  28%{ transform:translateY(-1.6%) rotate(-.55deg) }
  58%{ transform:translateY(-.6%) rotate(.35deg) }
}
@keyframes bsKeeperOrcTalk{
  0%,100%{ transform:none }
  35%{ transform:translateY(-.55%) rotate(.28deg) }
  68%{ transform:translateY(.15%) rotate(-.18deg) }
}
@keyframes bsKeeperDeal{
  0%,100%{ transform:none }
  30%{ transform:translateY(1.8%) scaleX(1.025) scaleY(.98) }
  58%{ transform:translateY(-1.5%) scale(1.015) }
}
@keyframes bsKeeperOrcDeal{
  0%,100%{ transform:none }
  38%{ transform:translateY(1.1%) scaleX(1.02) scaleY(.99) }
  64%{ transform:translateY(-.55%) scale(1.006) }
}
@keyframes bsKeeperDeny{
  0%,100%{ transform:none; filter:none }
  20%{ transform:translateX(-1.2%) rotate(-.45deg) }
  45%{ transform:translateX(1.1%) rotate(.4deg); filter:contrast(1.08) }
  70%{ transform:translateX(-.7%) rotate(-.2deg) }
}

/* 거래 때 금속 장식 두 곳이 짧게 반짝인다. 캐릭터별 장식 위치만 다르게 둔다. */
.bs-screen.bs-shop .bs-keeper-glint{
  position:absolute; display:block; width:1.1cqw; aspect-ratio:1; pointer-events:none;
  opacity:0; transform:translate(-50%,-50%) scale(.2) rotate(45deg);
}
.bs-screen.bs-shop .bs-keeper-glint::before,
.bs-screen.bs-shop .bs-keeper-glint::after{
  content:""; position:absolute; left:50%; top:50%; background:#fff1a8; box-shadow:0 0 .45cqw #d7a93b;
  transform:translate(-50%,-50%);
}
.bs-screen.bs-shop .bs-keeper-glint::before{ width:100%; height:2px; }
.bs-screen.bs-shop .bs-keeper-glint::after{ width:2px; height:100%; }
.bs-screen.bs-shop .bs-keeper-wrap.human .glint-a{ left:84%; top:77%; }
.bs-screen.bs-shop .bs-keeper-wrap.human .glint-b{ left:47%; top:90%; }
.bs-screen.bs-shop .bs-keeper-wrap.orc .glint-a{ left:61%; top:46%; }
.bs-screen.bs-shop .bs-keeper-wrap.orc .glint-b{ left:45%; top:87%; }
.bs-screen.bs-shop .bs-keeper-fx.fx-deal .glint-a{ animation:bsKeeperGlint .5s .16s ease-out; }
.bs-screen.bs-shop .bs-keeper-fx.fx-deal .glint-b{ animation:bsKeeperGlint .5s .28s ease-out; }
@keyframes bsKeeperGlint{
  0%,100%{ opacity:0; transform:translate(-50%,-50%) scale(.2) rotate(45deg) }
  45%{ opacity:1; transform:translate(-50%,-50%) scale(1) rotate(45deg) }
}

/* ── 스케일링 ────────────────────────────────────────────────────────────────
   ⚠️ 초상·배경에는 image-rendering:pixelated 를 걸면 안 된다.

   왜냐 — .bs-screen 이 min(100vw,177.78vh) 라 표시 크기가 창을 따라 연속으로 변한다.
     작은 창  : 초상 400px → 277px  (0.69배 축소)
     풀HD     : 초상 400px → 886px  (2.2배 확대)
   둘 다 정수배가 아니다. pixelated 는 최근접 이웃이라 이때 픽셀 행·열을
   불규칙하게 버리거나 겹쳐서 계단이 들쭉날쭉해진다 — 도트가 깨져 보이는 원인이다.

   게다가 초상·배경은 **진짜 픽셀아트가 아니다.** 픽셀 런 길이가 2~7로 흩어져 있는
   도트풍 고해상도 그림이라, 부드러운 리샘플링이 맞다.

   pixelated 는 격자가 진짜 일정한 것에만 남긴다 — 아이템 아이콘(32칸), 코인(16칸),
   UI 9분할. 그것들은 정수배 크기로 띄우므로 pixelated 가 이득이다.
   ────────────────────────────────────────────────────────────────────────── */
.bs-screen .bs-char,
.bs-screen .bs-bg,
.bs-screen .bs-keeper{ image-rendering:auto; }   /* keeper 도 같은 계열 — 0.926배로 뜬다 [GFX_BUGS #2] */

/* 움직임을 싫어하는 사용자 */
@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-char-br,
  .bs-screen .bs-char-fx,
  .bs-screen.bs-shop .bs-keeper-idle,
  .bs-screen.bs-shop .bs-keeper-fx,
  .bs-screen.bs-shop .bs-keeper-glint{ animation:none !important }
}
