/* 파츠 캐릭터 (frontend/src/components/PartsFigure.tsx).
   감춰 둔 .parts-sizer 가 크기를 정하고, 같은 이미지를 부위마다 잘라(clip-path) 겹쳐 그린다.
   걷기 @keyframes parts-walk-* 는 lib/poses.ts 의 WALK 로 화면이 만들어 넣는다. */
.parts-figure { position: relative; display: inline-block; line-height: 0; vertical-align: bottom; }
.parts-figure .parts-sizer { display: block; visibility: hidden; pointer-events: none; }
.parts-body, .parts-part { position: absolute; inset: 0; display: block; }
.parts-body { transition: translate .35s ease, rotate .35s ease; }
.parts-part {
  background-repeat: no-repeat; background-size: 100% 100%;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  transition: rotate .35s cubic-bezier(.3, .7, .35, 1.25), translate .35s ease;
}
.parts-figure.is-walking .parts-part, .is-walking .parts-figure .parts-part,
.parts-figure.is-walking .parts-body, .is-walking .parts-figure .parts-body {
  animation-duration: var(--walk-ms, 560ms); animation-timing-function: linear; animation-iteration-count: infinite;
}
.parts-figure.is-walking .part-armL, .is-walking .parts-figure .part-armL { animation-name: parts-walk-armL; }
.parts-figure.is-walking .part-armR, .is-walking .parts-figure .part-armR { animation-name: parts-walk-armR; }
.parts-figure.is-walking .part-legL, .is-walking .parts-figure .part-legL { animation-name: parts-walk-legL; }
.parts-figure.is-walking .part-legR, .is-walking .parts-figure .part-legR { animation-name: parts-walk-legR; }
.parts-figure.is-walking .part-head, .is-walking .parts-figure .part-head,
.parts-figure.is-walking .part-layer-head, .is-walking .parts-figure .part-layer-head,
.parts-figure.is-walking .part-layer-back, .is-walking .parts-figure .part-layer-back { animation-name: parts-walk-head; }
.parts-figure.is-walking .parts-body, .is-walking .parts-figure .parts-body { animation-name: parts-walk-body; }

/* 지도 위 주민: 원래 통짜 토큰 자리(높이 48px)에 그대로 */
.map-resident .parts-figure { filter: drop-shadow(0 2px 1px rgba(0, 0, 0, .25)); }
.map-resident.face-left .parts-figure { scale: -1 1; }
.map-resident.is-found .parts-figure { filter: drop-shadow(0 0 5px var(--color-sun)) drop-shadow(0 0 5px var(--color-sun)); }
.map-resident.is-sleeping .parts-figure { opacity: .85; }

/* 기념사진: .photo-body 자리(사진 높이의 48%) */
.photo-figure .parts-figure.photo-body { display: block; height: 100%; width: auto; }
.photo-figure .parts-figure .parts-sizer { height: 100%; width: auto; max-width: none; }

/* 주민 정보 창의 큰 전신 */
.dialog-figure { display: flex; justify-content: center; align-items: flex-end; height: 170px; margin: 0 auto 6px; }
.dialog-figure .parts-figure { height: 100%; filter: drop-shadow(0 3px 2px rgba(0, 0, 0, .2)); }
.dialog-figure .parts-sizer { height: 100%; width: auto; }
.dialog-figure-name { display: block; text-align: center; font-size: .78rem; color: var(--color-muted, #777); }

/* 파츠 맞추기 (마이페이지·관리자) */
.rig-editor { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 330px), 1fr)); gap: 18px; align-items: start; }
.rig-stage { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.rig-stage figure { margin: 0; display: grid; justify-items: center; gap: 4px; }
.rig-box { position: relative; height: 230px; display: flex; align-items: flex-end; justify-content: center; padding: 8px;
  border-radius: 12px; background: repeating-conic-gradient(#f2f0ea 0 25%, #fbfaf6 0 50%) 0 0 / 16px 16px; }
.rig-box .parts-figure, .rig-cut { height: 100%; }
.rig-box .parts-sizer, .rig-cut > img { height: 100%; width: auto; display: block; }
.rig-cut { position: relative; display: inline-block; line-height: 0; }
.rig-cut svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.rig-cut line { stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.rig-cut .cut-y { stroke: #e0566a; }
.rig-cut .cut-x { stroke: #3b82c4; stroke-dasharray: 4 3; }
.rig-cut .cut-hair { fill: rgba(150, 90, 210, .18); stroke: #9a5ad2; stroke-width: 1.5; stroke-dasharray: 2 2; vector-effect: non-scaling-stroke; }
.rig-cut .cut-active { stroke-width: 3; }
.rig-pivot { position: absolute; width: 9px; height: 9px; margin: -4.5px 0 0 -4.5px; border-radius: 50%; background: #ffd34e; border: 1px solid #333; }
.rig-sliders { display: grid; gap: 6px; }
/* PSD 관절 맞추기 (PsdJointEditor): 끌 수 있는 관절 점 */
.psd-joints { margin-top: 14px; grid-template-columns: 1fr; }
.psd-joints .rig-stage { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: start; }
.psd-zoom { position: relative; overflow: hidden; width: 100%; border-radius: 10px; background: repeating-conic-gradient(#eee 0 25%, #fafafa 0 50%) 0 0 / 16px 16px; }
.psd-zoom__img { position: absolute; display: block; height: auto; }
.psd-zoom__img img { display: block; width: 100%; height: auto; user-select: none; }
.rig-pivot--drag { width: 14px; height: 14px; margin: -7px 0 0 -7px; cursor: grab; touch-action: none; z-index: 2; }
.rig-pivot--drag.is-moved { background: #ff9a3c; }
.rig-pivot--drag.is-dragging { cursor: grabbing; transform: scale(1.3); }
.rig-pivot--drag:focus-visible { outline: 2px solid #3b82c4; outline-offset: 2px; }
.rig-slider { display: grid; grid-template-columns: 8.5em 1fr 3em; gap: 8px; align-items: center; font-size: .88rem; }
.rig-slider output { text-align: right; font-variant-numeric: tabular-nums; }
.rig-poses { display: flex; flex-wrap: wrap; gap: 4px; }
.rig-poses .btn.is-current { outline: 2px solid var(--color-sun, #f5b83d); }
.expression-list { display: flex; flex-wrap: wrap; gap: 10px; margin: 8px 0; }
.expression-card { display: grid; justify-items: center; gap: 4px; width: 110px; padding: 6px; border-radius: 10px; background: var(--color-surface, #fff); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); font-size: .82rem; }
.expression-card img { height: 90px; width: auto; max-width: 100%; object-fit: contain; }
.expression-card select, .expression-card input { width: 100%; font-size: .8rem; }
@media (max-width: 720px) {
  .rig-box { height: 190px; }
}

/* PSD 리깅 캐릭터 (lib/psdRig.ts, PartsFigure RiggedParts).
   몸통 위 파츠(몸통·팔·머리·뒷머리)는 같은 주기로 함께 아주 조금 숨 쉰다 → 머리·팔이 몸통을 따라 움직인다 (부모 움직임 상속).
   뒷머리만 같은 주기로 조금 늦게 흔들린다 (머리카락 끝의 후속 움직임). 다리는 숨 쉬지 않아 발이 바닥에서 뜨지 않는다.
   0% 와 100% 가 같아서 반복할 때 튀지 않는다. */
@keyframes parts-breathe { 0%, 100% { translate: 0 0; } 50% { translate: 0 -0.45%; } }
@keyframes parts-sway { 0%, 100% { transform: rotate(-0.6deg); } 50% { transform: rotate(0.6deg); } }
.parts-figure.is-rigged .part-seg-up { animation: parts-breathe 3.6s ease-in-out infinite; }
.parts-figure.is-rigged .part-seg-back { animation: parts-breathe 3.6s ease-in-out infinite, parts-sway 3.6s ease-in-out -2.6s infinite; }
/* 감정 움직임 (리깅 캐릭터, 포즈별). 포즈 각도(--a, 몸은 --by·--br)를 기준으로 흔들어서 0%·100% 가 포즈 그대로다 → 반복해도 튀지 않는다.
   몸 전체(.parts-body)가 움직이면 모든 파츠가 함께 움직여 관절이 벌어지지 않는다. 걷기 규칙이 더 강해서 걸을 때는 꺼진다. */
@keyframes rig-hop { 0%, 100% { translate: 0 var(--by); } 40% { translate: 0 calc(var(--by) - var(--hop, 4%)); } 60% { translate: 0 calc(var(--by) - var(--hop, 4%) * .8); } }
@keyframes rig-sway { 0%, 100% { rotate: var(--br); } 25% { rotate: calc(var(--br) + var(--sw, 4deg)); } 75% { rotate: calc(var(--br) - var(--sw, 4deg)); } }
@keyframes rig-swing { 0%, 100% { rotate: var(--a); } 25% { rotate: calc(var(--a) + var(--sw, 10deg)); } 75% { rotate: calc(var(--a) - var(--sw, 10deg)); } }
@keyframes rig-nod { 0%, 100% { rotate: var(--a); } 50% { rotate: calc(var(--a) + var(--sw, 6deg)); } }
@keyframes rig-jolt { 0%, 100% { translate: 0 var(--by); } 8% { translate: 0 calc(var(--by) - 3%); } 16% { translate: 0 var(--by); } }
/* 몸 전체 */
:where(.parts-figure.is-rigged:is([data-pose="banzai"], [data-pose="heart"], [data-pose="cheer"], [data-pose="vsign"])) > .parts-body { animation: rig-hop .9s ease-in-out infinite; }
:where(.parts-figure.is-rigged[data-pose="jump"]) > .parts-body { --hop: 5%; animation: rig-hop .8s ease-in-out infinite; }
:where(.parts-figure.is-rigged[data-pose="run"]) > .parts-body { --hop: 2.5%; animation: rig-hop .42s ease-in-out infinite; }
:where(.parts-figure.is-rigged[data-pose="dance"]) > .parts-body { --sw: 7deg; animation: rig-sway 1.1s ease-in-out infinite; }
:where(.parts-figure.is-rigged:is([data-pose="shy"], [data-pose="sulk"])) > .parts-body { --sw: 3deg; animation: rig-sway 1.8s ease-in-out infinite; }
:where(.parts-figure.is-rigged[data-pose="bow"]) > .parts-body { --sw: 4deg; animation: rig-sway 2.6s ease-in-out infinite; }
:where(.parts-figure.is-rigged[data-pose="surprise"]) > .parts-body { animation: rig-jolt 1.6s ease-out infinite; }
/* 팔·머리 (숨쉬기는 그대로 함께) */
.parts-figure.is-rigged[data-pose="wave"] .part-seg-armR { --sw: 14deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-swing .7s ease-in-out infinite; }
.parts-figure.is-rigged:is([data-pose="banzai"], [data-pose="cheer"], [data-pose="heart"]) :is(.part-seg-armL, .part-seg-armR) { --sw: 6deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-swing .9s ease-in-out infinite; }
.parts-figure.is-rigged[data-pose="shrug"] :is(.part-seg-armL, .part-seg-armR) { --sw: 8deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-nod 1.2s ease-in-out infinite; }
.parts-figure.is-rigged[data-pose="sulk"] :is(.part-seg-head, .part-seg-back) { --sw: 5deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-swing 1.8s ease-in-out infinite; }
.parts-figure.is-rigged:is([data-pose="think"], [data-pose="shy"]) :is(.part-seg-head, .part-seg-back) { --sw: 4deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-nod 2.4s ease-in-out infinite; }
.parts-figure.is-rigged[data-pose="sleepy"] :is(.part-seg-head, .part-seg-back) { --sw: 7deg; animation: parts-breathe 3.6s ease-in-out infinite, rig-nod 3s ease-in-out infinite; }
/* 걷기: 리깅 캐릭터는 줄인 각도의 parts-walk-rig-* 를 쓴다 (숨쉬기는 멈춘다) */
.parts-figure.is-rigged.is-walking .parts-part, .is-walking .parts-figure.is-rigged .parts-part { animation: none; }
.parts-figure.is-rigged.is-walking .part-seg-armL, .is-walking .parts-figure.is-rigged .part-seg-armL { animation: parts-walk-rig-armL var(--walk-ms, 560ms) linear infinite; }
.parts-figure.is-rigged.is-walking .part-seg-armR, .is-walking .parts-figure.is-rigged .part-seg-armR { animation: parts-walk-rig-armR var(--walk-ms, 560ms) linear infinite; }
.parts-figure.is-rigged.is-walking .part-seg-legL, .is-walking .parts-figure.is-rigged .part-seg-legL { animation: parts-walk-rig-legL var(--walk-ms, 560ms) linear infinite; }
.parts-figure.is-rigged.is-walking .part-seg-legR, .is-walking .parts-figure.is-rigged .part-seg-legR { animation: parts-walk-rig-legR var(--walk-ms, 560ms) linear infinite; }
.parts-figure.is-rigged.is-walking :is(.part-seg-head, .part-seg-back), .is-walking .parts-figure.is-rigged :is(.part-seg-head, .part-seg-back) { animation: parts-walk-rig-head var(--walk-ms, 560ms) linear infinite; }

/* 리깅 디버그 (?rigdebug=1): 관절 점과 파츠마다 다른 색 테두리 */
.parts-pivot { position: absolute; width: 6px; height: 6px; margin: -3px 0 0 -3px; border-radius: 50%; background: #ff2d55; box-shadow: 0 0 0 1px #fff; z-index: 2; pointer-events: none; }
.parts-figure[data-rig-debug] .parts-part[data-part="head"] { filter: drop-shadow(0 0 1px #e91e63); }
.parts-figure[data-rig-debug] .parts-part[data-part="back"] { filter: drop-shadow(0 0 1px #9c27b0); }
.parts-figure[data-rig-debug] .parts-part[data-part="core"] { filter: drop-shadow(0 0 1px #2196f3); }
.parts-figure[data-rig-debug] .parts-part:is([data-part="armL"], [data-part="armR"]) { filter: drop-shadow(0 0 1px #4caf50); }
.parts-figure[data-rig-debug] .parts-part:is([data-part="legL"], [data-part="legR"]) { filter: drop-shadow(0 0 1px #ff9800); }

/* 사진(소식 기념사진 등)은 찍은 순간이라 멈춰 있다: 숨쉬기·감정 움직임·걷기 없이 포즈 그대로 */
.photo-figure .parts-figure .parts-part, .photo-figure .parts-figure .parts-body { animation: none !important; transition: none !important; }
@media (prefers-reduced-motion: reduce) {
  .parts-figure .parts-part, .parts-figure .parts-body { animation: none !important; transition: none !important; }
}
.expression-manager { margin-top: 18px; }
