/* ===========================================================================
   보부상 — 빌드 측 추가분 (확정본이 다루지 않은 상태만 처리)

   ⚠️ bobusang-ui.css 는 그래픽 확정본이다. 그 파일은 손대지 않는다.
      이 파일에는 확정 시안에 "없던 상태"를 굴리기 위한 것만 넣는다.
      아래 4가지가 전부이고, 손대면 깨지는 값
      (--log-bottom / --log-top / --char-h / --log-side / max-width:37%)
      은 하나도 건드리지 않는다.

   그래픽 대화에서 검토 후 확정본에 흡수하면 이 파일은 지운다.
   =========================================================================== */

/* 0 · 도트 보간 금지 ────────────────────────────────────────────────────────
   STYLE_SPEC 5절이 요구하는 image-rendering:pixelated 는 이미 .bs-screen *(ui.css:45)
   와 아이콘·배경에 개별로 걸려 있고, 게임 이미지는 전부 그 안에 있다(ART_REVIEW_0812 확인).
   다만 화면 전체 기준으로는 auto 라 .bs-screen 밖 — 타이틀 레이어처럼 — 은 사각지대였다.
   타이틀 등장 연출은 확대·이동이 계속 도는 화면이라 뭉개지면 바로 보인다. 뿌리에서 못 박는다 */
html{ image-rendering:pixelated; }

/* 0-2 · <b> 의 굵기를 절대값으로 못 박는다 ──────────────────────────────────
   브라우저 기본값은 font-weight:bolder 인데 이건 **상대값**이다 — 700 을 물려받은
   자리(HUD 칩·소문 띠)에서 한 계단 더 올라가 900 이 된다(실측: 화면에 2곳).
   Galmuri11 의 자족은 400·700 뿐이라(bobusang-font.css) 900 은 붙을 자족이 없고,
   폰트가 빠진 배포물에서는 폴백(Segoe UI)이 Black 을 골라 글자가 2% 넓어진다 —
   고정폭 도트 UI 에서 그만큼이 이름표 잘림이 된다. 2026-08-13 TASK_FONT_0813 3-1

   ⚠️ .bs-op(오프닝 카드·최후통첩 편지)도 같이 받는다 — 무대 밖 오버레이라 .bs-screen 에 안 걸린다.
      지금은 편지 본문이 400 이라 <b> 가 700 으로 떨어지지만, 본문을 700 으로 올리는 순간 900 이 된다 */
.bs-screen b, .bs-screen strong,
.bs-op b, .bs-op strong{ font-weight:700 }

/* 1 · 가로 무대 ─────────────────────────────────────────────────────────────
   확정 디자인은 16:9 전제이고 빌드는 maxWidth 720 세로였다. 협상 화면에 한해
   창 크기에 맞춰 16:9를 유지한 채 중앙에 앉힌다.
   .bs-screen 자체는 width:100%/aspect-ratio:16/9 그대로 두고, 폭은 래퍼가 정한다. */
.bs-stage{
  min-height:100vh; display:flex; align-items:center; justify-content:center;
  background:#0d0a08;
}
.bs-stage > .bs-screen{ width:min(100vw, 177.78vh); }   /* 177.78vh = 16/9 * 100vh */

/* 2 · HUD 칩 여러 줄 ────────────────────────────────────────────────────────
   확정본의 HUD는 칩 하나("골드 N · 남은 대화 N") 전제였다.
   아이템 정보(매입가·풍문 시세)를 협상 중에도 남기기로 해서 칩이 늘어난다.
   칩 자체의 모양은 확정본 .bs-chip 을 그대로 쓰고, 쌓는 방법만 여기서 정한다. */
.bs-screen .bs-hud{
  display:flex; flex-direction:column; align-items:flex-end;
  gap:clamp(3px,.6cqw,6px);
}

/* 3 · 거래 결과 뒤에도 조작 가능하게 ────────────────────────────────────────
   확정 시안에는 "성사/결렬 후 다음 손님으로" 상태가 없다. 도장은 inset:0 z-index:8
   이라 그대로 두면 아래 입력 바를 덮어 아무것도 누를 수 없다.
   도장은 순수 연출이므로 클릭을 통과시킨다. 보이는 모습은 확정본 그대로다. */
.bs-screen .bs-verdict{ pointer-events:none; }

/* 3-2 · 도장이 찍히는 순간 ──────────────────────────────────────────────────
   확정본(bobusang-ui.css 214~227)은 도장이 "이미 찍혀 있는" 정지 상태만 정의한다.
   그림(stamp.png·xmark.png)도 소리(sfx stamp)도 있는데 찍히는 움직임만 없었다.

   ⚠️ 움직이는 건 바깥 .mark 하나뿐이다. 안쪽 img 와 .txt 는 이미
      transform:rotate(-9deg) 로 비스듬히 기울어 있어서, 거기에 scale 을 얹으면
      기울기가 통째로 날아가고 도장이 반듯하게 선다.
      타이틀 레이어와 같은 원칙 — 바깥이 움직임, 안쪽이 원래 자세.

   높은 데서 크게 내려와 살짝 눌렸다가(0.94) 제자리로 튄다. 눌리는 구간이 "쾅"이다.
   딸린 어두운 막도 같이 깔려야 도장만 붕 떠 보이지 않는다. */
.bs-screen .bs-verdict{ animation:bsVeilIn .28s ease-out; }
.bs-screen .bs-verdict .mark{ animation:bsStamp .34s cubic-bezier(.15,.9,.3,1); }
/* 결렬은 쾅이 아니다. 낮은 데서 느리게 내려놓는다 — 같은 궤적, 다른 무게 */
.bs-screen .bs-verdict.fail .mark{ animation:bsStampSoft .5s ease-out; }
@keyframes bsVeilIn{ from{ background-color:rgba(0,0,0,0) } }
@keyframes bsStamp{
  0%  { opacity:0; transform:scale(2.1)  }
  55% { opacity:1; transform:scale(.94)  }
  100%{ opacity:1; transform:scale(1)    }
}
@keyframes bsStampSoft{
  0%  { opacity:0; transform:scale(1.45) }
  60% { opacity:1; transform:scale(.98)  }
  100%{ opacity:1; transform:scale(1)    }
}
/* 움직임을 싫어하는 사용자에게는 결과만 보여준다 */
@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-verdict,
  .bs-screen .bs-verdict .mark{ animation:none; }
}

/* 6 · 아이템 아이콘 크기 ────────────────────────────────────────────────────
   [개정 2026-08-14 · graphics/output/items/ICONS_APPLY.md 1절]
   아이콘이 **192×192 PNG 안에 48칸**이 됐다(예전엔 128px 안에 32칸).
   화면 크기가 48의 정수배가 아니면 칸이 깨지므로, 박스 크기는 그대로 두고 안쪽 img만 고정한다.
     도매 격자  96px (칸당 2px) — bobusang-shop.css:128 이 이미 준다. 손댈 것 없다
     봇짐 칸    48px (칸당 1px)
   ⚠️ 봇짐 칸을 96px 로 올리지 마라. .bs-bag 은 4열 minmax(0,1fr) 이라 1280 무대에서
      칸이 약 100px 이고, 그 안에 아이콘 + 이름 + 가격이 세로로 쌓인다(.bs-cell 은
      aspect-ratio:1 + overflow:hidden). 96px 을 넣으면 이름·가격이 잘려 나간다 —
      48 의 정수배 중 이 칸에 들어가는 것은 48px 뿐이다.
   ⚠️ .bs-icon img(32px)는 48의 정수배가 아니지만 지금 쓰이는 곳이 없어 그대로 둔다.
      되살릴 일이 있으면 그때 박스 크기를 보고 48 의 정수배로 정해라.                */
.bs-screen .bs-icon img{ width:32px; height:32px; display:block; image-rendering:pixelated; }
.bs-screen .bs-cell .bs-cell-icon{ width:48px; height:48px; display:block; image-rendering:pixelated; }

/* 5 · 타이틀 화면 ───────────────────────────────────────────────────────────
   2026-08-12: 한 장짜리 title_screen.png → 레이어 6장 + 등장 연출로 교체했다.
   연출·좌표·상시 움직임은 전부 bobusang-title.css 관할(그래픽 확정본)이고,
   여기서는 무대 크기만 정한다.

   ⚠️ 16:9 가 아니라 3:2 다. 배경 원본이 1536×1024 라 16:9로 자르면
      상인 발밑이 잘리고 .ti-* 좌표가 전부 어긋난다 (TITLE_INTRO_APPLY 4절).
      150vh = 3/2 * 100vh */
.bs-stage > .bs-title{ width:min(100vw, 150vh); }
.bs-title{ position:relative; width:100%; background:#0d0a08; }

/* ↓ 옛 한 장짜리 타이틀용. 되돌릴 때 필요해서 남긴다 (TITLE_INTRO_APPLY 10절).
   ⚠️ 반드시 자식 선택자(>)로 둘 것 — .bs-title img 로 두면 레이어 안쪽 img 까지
      position:absolute; object-fit:cover 를 뒤집어써서 무대가 깨진다 */
.bs-title > img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; image-rendering:pixelated; display:block;
}
.bs-title .t-hit{
  position:absolute; left:6.95%; width:27.81%; height:10.14%;
  border:0; padding:0; margin:0; background:none; font:inherit;
}
.bs-title .t-new { top:60.97%; cursor:pointer; }
.bs-title .t-new:focus-visible{ outline:3px solid #f0b03c; outline-offset:-3px; }
.bs-title .t-load{ top:71.67%; }
.bs-title .t-quit{ top:82.36%; }
/* 비활성 — 평평한 잉크 베일. 그라데이션·블러 금지 규칙을 따른다 */
.bs-title .t-off{ background:rgba(12,16,8,.55); cursor:default; }

/* 8 · 골드가 오르는 게 보이게 ───────────────────────────────────────────────
   흥정이 성사되는 순간이 이 게임에서 "돈을 벌었다"가 눈에 보이는 유일한 자리다.
   확정본에는 골드 숫자가 툭 바뀌는 것밖에 없어서 얼마를 벌었는지 화면에 안 남는다.
   숫자는 굴러 올라가고(game.jsx useCountUp), 번 액수는 HUD 바로 아래에서 떠오른다.

   ⚠️ .bs-hud 는 화면 위쪽(top:3.5%)이고 .bs-screen 은 overflow:hidden 이다.
      칩 위로 띄우면 화면 밖으로 잘린다. 그래서 HUD 아래(top:100%)에서 위로 뜬다.
      절대 배치라 칩 배열(세로 flex)을 밀지 않고, flowC 로 HUD가 옮겨가도 따라간다. */
.bs-screen .bs-goldpop{
  position:absolute; top:100%; right:0; margin-top:clamp(3px,.6cqw,7px);
  /* z 9 [FX_SPEC 6-G] — 영수증 암막(.bs-rc-wrap, z8) 위. 퇴장 팝업이 암막 걷힘과 겹쳐도 보인다 */
  white-space:nowrap; pointer-events:none; z-index:9;
  color:var(--gold); font-weight:700; letter-spacing:.04em;
  font-size:clamp(14px,2.8cqw,34px);
  text-shadow:3px 3px 0 var(--c1);   /* 밝은 배경 위에서도 읽히게. 번짐 없는 도트 그림자 */
  animation:bsGoldPop 1.4s steps(7) forwards;   /* ease-out → steps(7) [FX_SPEC 6-G] 도트 문법 */
}
@keyframes bsGoldPop{
  0%  { opacity:0; transform:translateY(45%)  }
  15% { opacity:1; transform:translateY(10%)  }
  65% { opacity:1; transform:translateY(-45%) }
  100%{ opacity:0; transform:translateY(-85%) }
}

/* ── 골드 획득 연출 [2026-08-17 · FX_SPEC_0817 6-G절 · B안] ──────────────────
   동전 3개가 영수증 「남긴 돈」 칩 → HUD 골드 칩으로 계단 비행한다.
   좌표·이동량(--gdx/--gdy)은 game.jsx 가 실측해 인라인으로 준다(무대 배율 보정 포함).
   짝: game.jsx 동전 타이머(출발 1.65s + 150ms 간격 · 도착 틱 2.10/2.25/2.40s) */
.bs-screen .bs-chip-gold{ position:relative; z-index:9; }   /* 암막(z8) 위 — 동전이 날아드는 목적지 */
.bs-screen .bs-goldcoin{
  position:absolute; width:32px; height:32px;   /* coin16 은 16칸 격자 — 32px 정수 표시 */
  z-index:9; pointer-events:none; image-rendering:pixelated;
  /* [교체 2026-08-25 · 사용자 「동전 추가되는 UI 가 엄청 깨져 보인다」 · 시안 A안 채택
      · 진단·대조: graphics\동전비행_시안_0825.html · graphics\drafts\coin_0825\coin_shatter_0825.png]
     옛 값 `.45s steps(6)` + 키프레임의 `scale(.8)` 이 셋을 한꺼번에 망가뜨리고 있었다.
     🔴 ① **깨짐(본증상)** — coin16.png 는 64px 캔버스에 16칸을 4배로 그린 그림이다
          (실측: 4px 블록 256개 전부 단색 · 색 5개). 원본 1도트가 정수 화소로 떨어지는 크기는
          **64 · 48 · 32 · 16px 넷뿐**이다. 그런데 steps(6) 이 scale 까지 함께 샘플해서 실제로 그린 크기가
          32.0 / 30.9 / 29.9 / 28.8 / 27.7 / 26.7px — **여섯 프레임 중 다섯이 깨진 크기였다.**
          HUD 코인(16px)과 출발 프레임(32px)만 멀쩡해서 「서 있을 땐 멀쩡한데 날아갈 때만 깨진다」가 됐다.
     ② **순간이동** — 영수증 칩 → HUD 칩은 523px 다(1280 무대 실측). 여섯 칸이면 한 칸 87px =
          동전 몸통의 2.7배. 창이 1920 이면 131px 다.
     ③ **칩에 안 닿음** — steps(6)=jump-end 는 진행도 5/6 에서 끝난다. 칩에서 87px 못 미친 데서
          사라지는데 숫자만 올랐다. 같은 함정이 bobusang-title.css:181~187 에 이미 기록돼 있다.

     ⚠️ 앞으로도 지킬 것 — **동전에 `transform:scale()` 을 걸지 마라.** 크기를 바꿔야 하면
        `width/height` 로, 그것도 64/48/32/16 끼리만 갈아탄다(타이틀 새에서 같은 이유로 배운 규칙).
     ⚠️ **계단 수 = 지속시간 × 60** 이다(0.5s → 30). 60fps 에 한 계단이라 눈에는 이어져 보이면서
        규칙은 계단이다. `jump-none` 이라 마지막 계단이 목적지에 **정확히 닿는다.**
     ⚠️ **86% 에 도착해서 머문다** — 이게 game.jsx 타이머와의 접점이다. 출발 1.65s + 지연(0/150/300ms)
        + 0.5s×0.86 = **2.08 / 2.23 / 2.38s 도착**, 도착 틱은 2.10/2.25/2.40s. 동전이 먼저 닿고
        20ms 뒤에 숫자가 오른다. 지속시간을 건드리면 이 순서가 뒤집히니 **game.jsx 의 도장 타이머와
        같이 고쳐라**(game.jsx:2660 근처). 지금은 game.jsx 수정이 필요 없다. */
  animation:bsGoldFly .5s steps(30, jump-none) both;
}
@keyframes bsGoldFly{
  0%  { transform:translate(0,0); opacity:1 }
  86% { transform:translate(var(--gdx),var(--gdy)); opacity:1 }   /* 칩에 닿아 잠깐 머문다 */
  93% { transform:translate(var(--gdx),var(--gdy)); opacity:.5 }  /* 소멸은 크기가 아니라 2계단 */
  100%{ transform:translate(var(--gdx),var(--gdy)); opacity:0 }
}
/* HUD 코인 바운스 — 동전이 도착할 때마다(img key 교체) 2프레임 튄다 */
.bs-screen .bs-coin.bump{ animation:bsCoinBump .15s steps(2) 1; }
@keyframes bsCoinBump{ 0%,100%{ transform:translateY(0) } 50%{ transform:translateY(-4px) } }
@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-goldpop{ animation:none; }
}

/* 9 · 찍히는 중이라는 표시 ──────────────────────────────────────────────────
   대사가 한 자씩 찍히는 동안 글자 끝에 붙는 커서. 이게 없으면 "타이핑 중"이
   아니라 "화면이 느린 것"으로 읽힌다. 타이틀 메뉴 커서와 같은 문법(steps 점멸)이다. */
.bs-screen .bs-caret{
  display:inline-block; width:.5em; height:.95em; margin-left:.12em;
  vertical-align:-2px; background:var(--c5);
  animation:bsCaret .8s steps(1) infinite;
}
@keyframes bsCaret{ 0%,54%{opacity:1} 55%,100%{opacity:0} }
@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-caret{ animation:none; }
}

/* 4 · 종료 후 버튼 두 개 ────────────────────────────────────────────────────
   입력 바 자리에 "다음 손님 / 좌판 접기"가 들어간다. 확정본 .bs-send 를 그대로
   쓰되 둘이 맞붙지 않게 간격만 준다. */
.bs-screen .bs-input .bs-send + .bs-send{ margin-left:clamp(4px,.8cqw,9px); }

/* 9 · 이름표 직함 축소 ──────────────────────────────────────────────────────
   데이터의 풀네임("대장장이 그레타")에서 이름만 크게, 직함은 작게 (기획 결정).
   직함은 흥정 힌트라 아예 빼지는 않는다. em 단위 — 확정본의 clamp 크기를 따라 움직인다 */
.bs-screen .bs-npc-name .bs-npc-title{
  font-size:.72em; font-weight:400; opacity:.8; margin-right:.45em;
}

/* 7 · 골드 아이콘 ───────────────────────────────────────────────────────────
   COIN_APPLY.md 의 값 그대로. coin16.png 은 16칸 격자 — 16/32/64px 정수배만 허용,
   칩 글자가 18px 이므로 16px. 마을 팔레트에 따라 bridel/groknar 가 갈린다(coinSrc). */
.bs-screen .bs-coin{
  width:16px; height:16px;
  image-rendering:pixelated;
  vertical-align:-3px;      /* 글자 기준선에 맞춘다 */
  margin:0 3px 0 2px;
}

/* 10 · 편지 화면 ────────────────────────────────────────────────────────────
   [확정 2026-08-13 · pm/OPENING_TUTORIAL_2026-08-13.md Q1~Q3]
   오프닝 카드 다음(하겐 협박장 → 야콥의 편지)과 6일차 밤(두 번째 통첩)이 쓴다.
   오버레이 무대(.bs-op/.bs-op-frame)는 bobusang-opening.css(그래픽 확정)를 빌려 쓰고,
   여기는 편지지만 입힌다. 시안: pm/opening_draft.html 탭② (PM 대화 시안 이관).
   ⚠️ 그라데이션·블러 금지, 색은 어두운 탁자(#100f0c)+양피지(#171510) — 팔레트 밖 원색 금지 */
.bs-op.bs-let .bs-op-frame{ background:#100f0c; }   /* 어두운 방, 탁자 위 */
/* 편지 배경 — 압류로 텅 빈 방(o6, 브리델 그린 새벽빛). 종이가 주인공이라 반쯤 가라앉힌다 */
.bs-op.bs-let .bs-let-bg{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; object-position:center 30%; opacity:.45;
}
.bs-let-paper{
  position:absolute; inset:6% 22%; overflow:auto;
  background:#171510; border:2px solid #4a4530; box-shadow:0 0 0 6px rgba(0,0,0,.5);
  padding:clamp(12px,2.6cqw,34px) clamp(14px,3cqw,40px);
}
.bs-let-hd{
  font-size:clamp(9px,1.5cqw,19px); color:#8a7d52; letter-spacing:.22em; text-align:center;
  font-weight:700; border-bottom:1px solid #3a3524;
  padding-bottom:clamp(5px,1cqw,12px); margin-bottom:clamp(8px,1.8cqw,22px);
}
.bs-let-paper p{
  margin:0 0 clamp(7px,1.4cqw,17px); font-size:clamp(10px,1.55cqw,20px);
  line-height:1.7; color:#e4dcc2; word-break:keep-all;
}
.bs-let-paper p b{ color:#e8c46a; }   /* 빚 300골드 · 열이틀 · 30골드 — 숫자만 금색 */
.bs-let-sign{ text-align:right; color:#b9a173; font-size:clamp(9px,1.3cqw,16px); margin-top:clamp(8px,1.6cqw,20px); }
/* 하겐의 인장 — 협박장에만 찍힌다. 도장(.bs-mark)과 같은 문법: 비스듬히, 반투명 붉은 판 */
.bs-let-seal{
  position:absolute; right:clamp(12px,2.6cqw,32px); bottom:clamp(10px,2.2cqw,28px);
  width:clamp(30px,5cqw,62px); height:clamp(30px,5cqw,62px);
  border:2px solid #7a2f26; color:#a3453a; font-size:clamp(7px,1cqw,12px); font-weight:700;
  display:flex; align-items:center; justify-content:center; text-align:center;
  background:rgba(122,47,38,.12); transform:rotate(-9deg);
}
.bs-let-hint{
  position:absolute; right:clamp(14px,4cqw,50px); bottom:clamp(10px,2.4cqw,30px);
  font-size:clamp(9px,1.2cqw,14px); color:#6b6455;
  animation:bsLetBlink 1.6s steps(2) infinite;
}
@keyframes bsLetBlink{ 0%,100%{opacity:.85} 50%{opacity:.15} }
@media (prefers-reduced-motion: reduce){
  .bs-let-hint{ animation:none; }
}

/* 12 · 소문 띠가 없는 날 ────────────────────────────────────────────────────
   1일차 아침엔 소문 띠(.bs-rumor)를 렌더하지 않는다(밤을 안 지내 들은 소문이 없다).
   .bs-footbar 는 flex 이고 띠가 flex:1 로 빈자리를 먹고 있었으므로, 띠가 빠지면
   버튼 두 개가 왼쪽으로 쏠린다. 첫 자식이 버튼일 때만 밀어내 오른쪽 정렬을 유지한다.
   (띠가 있는 날은 첫 자식이 .bs-rumor 라 이 규칙이 걸리지 않는다) */
.bs-screen .bs-footbar > .bs-btn:first-child{ margin-left:auto; }

/* 11 · 응답 대기 표시 ───────────────────────────────────────────────────────
   [T1-2 · REVIEW_95 §2] 흥정 응답을 기다리는 5~6초가 "비어" 보이던 문제.
   손님이 생각 중이라는 점 세 개 — 도트 문법 그대로(사각형·steps·그라데이션 없음).
   대사판(.bs-vn) 오른쪽 아래에 붙는다. */
.bs-screen .bs-think{
  position:absolute; right:clamp(10px,1.6cqw,20px); bottom:clamp(8px,1.2cqw,16px);
  display:flex; gap:clamp(3px,.5cqw,6px); z-index:3; pointer-events:none;
}
.bs-screen .bs-think i{
  width:clamp(4px,.7cqw,9px); height:clamp(4px,.7cqw,9px); background:var(--c5);
  animation:bsThink 1.05s steps(1) infinite;
}
.bs-screen .bs-think i:nth-child(2){ animation-delay:.35s }
.bs-screen .bs-think i:nth-child(3){ animation-delay:.7s }
@keyframes bsThink{ 0%,24%{opacity:.15} 25%,74%{opacity:1} 75%,100%{opacity:.15} }
@media (prefers-reduced-motion: reduce){ .bs-screen .bs-think i{ animation:none } }

/* ═══════════════════════════════════════════════════════════════════════════
   13 · 초반 튜토리얼 + 마을 로비  [2026-08-14 · 기획\TASK_TUTORIAL_LOBBY.md 5절]
   시안: 기획\ux시안\early_flow.html — 그 스타일을 팔레트 변수(--c1~8)로 옮긴 것이다.

   여기 있는 것: .bs-town/.bs-spot(로비) · .bs-quest(목표 배지) · .bs-locked(도매 잠금)
                 .bs-tutsay(튜토리얼 대사창) · .bs-get(획득 팝업) · .bs-done(판매 정산)
   ⚠️ 전부 빌드 측 신규다. 그래픽 확정본(ui/screens/shop/title/charanim/opening)은 손대지 않았다.
   ⚠️ 도트 규칙: 둥근 모서리·그라데이션·블러 금지, 그림자는 Npx Npx 0 단색.
   ⚠️ 크기는 전부 cqw(무대 폭 기준) — 무대가 작아져도 비율이 유지된다.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 로비 ── 배경은 기존 장터 그림을 그대로 쓴다(그래픽 발주 없음).
   장터 배경은 밝아서 판이 묻힌다 — 확정본의 .bs-veil 을 한 겹 깔고 그 위에 판을 얹는다 */
/* [개정 2026-08-16 · HANDOFF_남은작업 2절] 가로 4등분 띠를 버리고 명패를 **건물 위치에 절대 배치**.
   선택자는 game.jsx 가 붙이는 s.key 클래스(shop/gate/inn/stall)다.
   시각(황동/잠김 프레임)은 bobusang-type.css 끝의 「UI 스타일」 절이 정한다 — 여긴 구조만. */
.bs-screen.bs-town .bs-spots{
  position:absolute; inset:0; display:block; z-index:4;   /* 컨테이너만 남긴다 */
}
/* [개정 2026-08-23 · graphics/output/backgrounds/EVENING_APPLY_0823.md §7-1·7-2·7-5]
   ⚠️ transform 이 **가운데 정렬**로 바뀌었다(예전엔 translateX 만이라 top 이 판의 윗변이었다).
      저녁 화면엔 명패가 gate·inn 둘뿐이라 좌표를 중심 기준으로 다시 잰 값(41/40 · 64/49)을 쓴다.
      호버·누름·등장 애니메이션의 transform 에서 이 translate(-50%,-50%) 를 빠뜨리면 명패가 반 칸 튄다.
   ⚠️ grid-template-columns 와 justify-items:stretch 는 **한 몸이다.** 긴 부제가 명패 밖으로
      삐져나오던 범인이 이 둘이었다 (실측 2026-08-23: 판 179px 인데 부제가 368px).
      · 열이 auto 면 max-content 로 벌어져 판 폭을 통째로 무시한다 → minmax(0,1fr) 로 묶는다
      · 그런데 예전 place-items:center 는 아이템을 **열보다 넓게(max-content)** 잡아 그대로 넘긴다
        → justify-items:stretch 로 열 폭에 맞춘다. 가운데 정렬은 text-align:center 가 이미 한다
      둘이 갖춰져야 .bs-spot i 의 ellipsis 가 비로소 듣는다 */
.bs-screen.bs-town .bs-spot{
  position:absolute; transform:translate(-50%,-50%);
  width:19%; min-height:9.5%;
  display:grid; grid-template-columns:minmax(0,1fr); align-items:center; justify-items:stretch; text-align:center;
  padding:0.7cqw 0.9cqw;
  cursor:pointer; color:var(--c7); line-height:1.45;
}
/* 그림 속 건물 위치 — 배경 village_hub_*_eve.png 기준 · 좌표는 판의 **중심**이다 */
/* 저녁 도매상 [추가 2026-08-23 · 기획/ux시안/저녁도매상_시안_0823.html · 사용자 확정]
   좌표는 눈대중이 아니라 **저울 간판 마스크(mask_*_shop.png)의 무게중심 실측**이다 —
   브리델 13.8%/45.0% · 그록나르 14.1%/45.0% → 공통 14%/45%. 두 마을이 같은 자리에 맞는다.
   폭 17% 는 마스크 가로폭(0~28%) 안에 들어가면서 관문 판(34%~)과 11%p 벌어지는 값 */
.bs-screen.bs-town .bs-spot.shop  { left:14%; top:45%; width:17% }   /* 저울 간판 */
/* 관문은 아치 **개구부 한복판**이다 [§3-2 실측 · 브리델 41.9% · 그록나르 39.6% → 공통 41%].
   폭 14% 는 개구부 가로폭(14.5%p)에 맞춘 값 — 넓히면 오른쪽 기둥을 덮는다 */
.bs-screen.bs-town .bs-spot.gate  { left:41%; top:40%; width:14% }   /* 아치문 */
/* 59% → 64% [§3-3 실측] — 관문이 잠기면 사유 문안이 길어 판이 x 30.9~51.2% 까지 벌어져
   59% 자리의 여관 판(50.9~67.0%)을 침범했다. 64% 면 4.7%p 벌어지고 여전히 건물 한복판이다 */
.bs-screen.bs-town .bs-spot.inn   { left:64%; top:49% }   /* 맥주잔 간판 */
.bs-screen.bs-town .bs-spot.stall { left:86%; top:49% }   /* 줄무늬 차양 (아침 동선으로 이전 · 미렌더) */
/* 잠긴 이유는 항상 붙어 있다 — 그게 곧 다음 목표라서 흐리게 두되 지우지 않는다 */
.bs-screen.bs-town .bs-spot i{
  display:block; font-style:normal; font-weight:400;
  color:var(--c5); font-size:clamp(7.5px,1.05cqw,12px);
  margin-top:clamp(2px,.4cqw,5px);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* 호버·잠김의 시각은 type.css 「UI 스타일」 절(황동/잠김 프레임)이 정한다 */
/* 갈 곳이 하나뿐인 튜토리얼 구간에만 붙는 화살표. 헤맬 일이 없게 대놓고 가리킨다 */
.bs-screen.bs-town .bs-spot.hot::after{
  content:"여기"; position:absolute; top:calc(-1 * clamp(11px,1.7cqw,19px)); left:50%;
  transform:translateX(-50%); background:var(--gold); color:#231a06;
  font-size:clamp(7px,1cqw,11px); font-weight:800; padding:0 clamp(4px,.7cqw,8px);
}
/* 방금 열린 곳 — 자물쇠가 풀리는 순간이 보상이다 (배지만 남긴다 · 테두리는 프레임 몫) */
.bs-screen.bs-town .bs-spot.fresh::after{
  content:"NEW"; position:absolute; top:calc(-1 * clamp(11px,1.7cqw,19px)); right:calc(-1 * clamp(4px,.7cqw,9px));
  background:var(--danger); color:#fff; letter-spacing:.05em;
  font-size:clamp(7px,1cqw,11px); font-weight:800; padding:0 clamp(4px,.6cqw,7px);
}
/* (spot 이 절대 배치가 되어 hot/fresh 의 position:relative 는 더 필요 없다) */

/* ── 목표 배지 ── 로비와 1일차 좌판이 같이 쓴다. HUD(오른쪽 위)를 피해 가운데 위 */
.bs-screen .bs-quest{
  position:absolute; left:50%; transform:translateX(-50%); top:4%; z-index:6;
  background:var(--c1); border:2px solid var(--gold); color:var(--c7);
  font-size:clamp(9px,1.25cqw,14px); font-weight:800; letter-spacing:.04em;
  padding:clamp(3px,.5cqw,6px) clamp(9px,1.5cqw,18px); white-space:nowrap;
}
.bs-screen .bs-quest b{ color:var(--gold); }
/* 저녁 안내 한 줄만 한 단 내린다 [개편 2026-08-23 · 하루흐름개편 【4】·【5】].
   ⚠️ 기본 top:4% 는 상단바(top:3%)와 같은 띠다. 예전엔 상단바 가운데가 비어 있어 안 겹쳤는데,
      【5】가 목표 칩을 그 가운데 자리에 넣으면서 안내 문구가 칩을 덮어 버렸다(실측 2026-08-23).
   다른 .bs-quest(튜토리얼 「물건을 팔아 보자」·「야콥에게 가자」)는 tutStep<4 라 목표 칩이
   아예 안 뜬다 — 그래서 그쪽은 건드리지 않고 이 한 줄만 내린다 */
.bs-screen .bs-quest.dusk{ top:11%; }

/* ── 도매 격자 잠금 ── ⚠️ 판을 숨기지 말고 덮기만 한다.
   판이 갑자기 생겨나면 화면이 재배치돼 눈이 헤맨다 — 같은 자리가 열리는 것이 보상이다.
   .bs-shop-list 는 position:absolute 라 inset:0 이 그 판을 정확히 덮는다 */
.bs-screen .bs-locked{
  position:absolute; inset:0; z-index:4; cursor:pointer;
  background:rgba(9,14,7,.88);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  color:var(--c4); font-size:clamp(8px,1.2cqw,14px); font-weight:700;
  text-align:center; line-height:1.6;
}
.bs-screen .bs-locked b{
  color:var(--c5); font-size:clamp(10px,1.5cqw,17px);
  letter-spacing:.18em; margin-bottom:clamp(2px,.5cqw,6px);
}

/* ── 튜토리얼 대사창 ── 하단 전폭.
   도매 화면의 상인 대사판(.bs-keepsay)은 폭 34%·글자 최대 15px 라 네 줄짜리 대사에 작다.
   ⚠️ 그래픽 확정 CSS(.bs-keepsay)의 수치를 바꾸는 대신 이 창을 따로 둔 것이다.
   자리·크기는 협상 화면 대사창(.bs-vn) 규격을 따른다 */
.bs-screen .bs-tutsay{
  position:absolute; left:2.5%; right:2.5%; bottom:4%; z-index:6; cursor:pointer;
  background:var(--c1); border:2px solid var(--c5); color:var(--c8);
  box-shadow:4px 4px 0 rgba(0,0,0,.55);
  font-size:clamp(10px,1.7cqw,20px); line-height:1.65;
  padding:clamp(11px,1.8cqw,22px) clamp(13px,2cqw,26px) clamp(12px,1.9cqw,24px);
}
.bs-screen .bs-tutsay .bs-tuttab{
  position:absolute; left:-2px; top:calc(-1 * clamp(14px,2.2cqw,21px));
  background:var(--c5); color:var(--c1); border:2px solid var(--c1);
  font-weight:700; white-space:nowrap;
  padding:1px clamp(10px,1.6cqw,18px); font-size:clamp(9px,1.45cqw,17px);
}
.bs-screen .bs-tutsay .bs-tuttab .ttl{ font-size:.72em; font-weight:700; opacity:.8; margin-right:.35em; }
.bs-screen .bs-tutsay .bs-tutnx{
  position:absolute; right:clamp(9px,1.4cqw,17px); bottom:clamp(4px,.7cqw,9px);
  font-size:clamp(7.5px,1.05cqw,12px); color:var(--c4);
  animation:bsTutNx 1s steps(1) infinite;
}
@keyframes bsTutNx{ 0%,55%{opacity:1} 56%,100%{opacity:.25} }
@media (prefers-reduced-motion: reduce){ .bs-screen .bs-tutsay .bs-tutnx{ animation:none } }

/* .bs-dim(어두운 막) — 은퇴 [2026-08-18]. 획득 팝업·정산서가 각자 배경을 갖게 됐다 */

/* ── 획득 팝업 ── 아이콘 자리는 그림이 없으니 점선 사각형 + 이름이다(그래픽 발주 없음) */
.bs-screen .bs-get{ position:absolute; inset:0; z-index:8; cursor:pointer;
  background:rgba(4,8,3,.72); display:flex; align-items:center; justify-content:center; }
.bs-screen .bs-getpop{
  background:var(--c1); border:2px solid var(--gold); box-shadow:5px 5px 0 rgba(0,0,0,.6);
  padding:clamp(12px,2cqw,26px) clamp(20px,3.2cqw,42px) clamp(10px,1.7cqw,22px);
  text-align:center; min-width:44%;
}
.bs-screen .bs-getpop .ico{
  width:clamp(40px,6.4cqw,82px); height:clamp(40px,6.4cqw,82px);
  margin:0 auto clamp(6px,1.1cqw,14px); border:2px dashed var(--c4);
  display:flex; align-items:center; justify-content:center;
  color:var(--c4); font-size:clamp(8px,1.1cqw,13px);
}
/* 그림이 든 아이콘 자리 — 점선은 "그림이 없다"는 표시였다. 그림이 오면 뗀다.
   96px = 48칸 × 2배. 자리(.ico)가 82px 이라 같이 키운다
   [2026-08-14 · ICONS_APPLY 2-2] */
.bs-screen .bs-getpop .ico.img{
  border:none; width:auto; height:auto;
}
.bs-screen .bs-getpop .ico.img img{
  width:96px; height:96px; display:block; image-rendering:pixelated;
}
.bs-screen .bs-getpop .ttl{ font-size:clamp(10px,1.5cqw,18px); font-weight:700; color:var(--c8); }
.bs-screen .bs-getpop .ttl b{ color:var(--gold); }
/* desc 는 \n 이 든 한 덩어리 문자열이다 — pre-line 이 없으면 두 줄이 한 줄로 뭉친다 */
.bs-screen .bs-getpop .dsc{
  white-space:pre-line; color:var(--c5); line-height:1.5;
  font-size:clamp(8px,1.2cqw,14px); margin-top:clamp(4px,.7cqw,9px);
}
.bs-screen .bs-getpop .nx{ color:var(--c4); font-size:clamp(7.5px,1.05cqw,12px); margin-top:clamp(7px,1.2cqw,15px); }

/* .bs-done*(판매 정산 판) — 은퇴 [2026-08-18].
   정산서는 종이 그림판 .bs-led* (bobusang-flow.css · LEDGER_UI_APPLY.md)로 갈아탔다.
   game.jsx 의 saleDone 은 살아 있지만 .bs-led-wrap 을 그린다 — 헷갈리지 마라. */

/* ═══════════════════════════════════════════════════════════════════════════
   14 · 초반 흐름 개편  [2026-08-14 · 기획\EARLY_FLOW_SPEC.md 4절]
   시안: 기획\ux시안\flow_play.html (39화면 클릭 프로토타입) — 그 스타일을 팔레트로 옮겼다.

   여기 있는 것 — 신규 그림 **0장**. 전부 CSS 다.
     .bs-daycard(DAY N)  .bs-knock(문 두드림)  봉해진 편지(.bs-let-seal-wrap)
     .bs-locked.brk(자물쇠 해제)  .bs-grid.pop(격자 등장)  .bs-bagbox.pop(봇짐 판 등장)
     .bs-cell.drop(봇짐 낙하)  .bs-slot.hot + .bs-arrow(화살표 지목)
     .bs-slot.permit(등록증 매물)  .bs-goal(목표 배지)  .bs-gate(관문 확인)
     .bs-charwrap.bs-enter(손님 등장)

   ⚠️ 도트 규칙: 둥근 모서리·그라데이션·블러 금지. 움직임은 전부 steps() 로 끊는다 —
      매끈하게 흐르면 이 요소만 다른 게임에서 온 것처럼 보인다.
   ⚠️ 이 파일은 build.css 다. 그래픽 확정본(ui/screens/shop/title/charanim/opening)은
      한 줄도 안 건드렸다. 확정본 규칙을 덮는 곳은 딱 하나 — 봉해진 편지의 봉투 크기다(아래 표시).
   ⚠️ 로드 순서상 build.css 가 shop/charanim/opening 보다 **먼저** 온다.
      그래서 덮어야 하는 값은 반드시 **더 구체적인 선택자**로 쓴다(순서로는 못 이긴다).
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── DAY N 검은 화면 ── 하루가 바뀌는 모든 자리에서 같은 형식이다.
   ⚠️ .bs-op 안에는 팔레트 변수(--c1~8)가 없다(그건 .bs-screen 에 걸려 있다).
      그래서 여기만 색을 직접 쓴다. 오프닝 CSS 도 같은 이유로 그렇게 돼 있다.
   ⚠️ 무대(.bs-op-frame)를 안 쓰므로 cqw 도 없다 — vw 로 잡는다 */
.bs-op.bs-daycard,
.bs-op.bs-knock{ flex-direction:column; text-align:center; }

/* 덮개는 **첫 프레임부터 완전히 검다.** 페이드인을 걸지 마라.
   [2026-08-15 수정] 예전엔 `animation:bsDayIn .28s steps(2) both` 였다. steps(2) 는
   불투명도를 0 → 0.5 → 1 로 두 번만 끊어 올리므로 **첫 140ms 가 opacity 0**,
   즉 완전히 투명하다. 그 사이 도착지 화면(마을 배경·이미 +1 된 일차 막대·새 매물)이
   8프레임쯤 그대로 노출돼, DAY N 카드가 알려줄 내용을 미리 흘렸다.
   goDay() 의 setPhase 와 showDay 는 같은 커밋에 그려지므로(React 18 자동 배칭)
   덮개만 즉시 불투명하면 도착 화면은 한 프레임도 안 보인다.
   ⚠️ 대신 화면 전환을 later() 로 미루지 마라 — 카드를 눌러 건너뛸 때 clearFx() 가
      그 타이머를 끊어서 이전 화면에 갇힌다(game.jsx:1593 이 경고하는 함정).
   글자 연출(bsDayBig·bsDaySub)은 자식 규칙이라 그대로 산다 — 검게 끊어 덮인 뒤
   「DAY N」이 벌어지며 뜬다.
   [2026-08-16 · 기획 결재 daycard_motion_slow.html] 카드가 1.9초 만에 즉시 컷돼
   "읽기 시작하면 사라진다"는 피드백 — 같은 연출을 두 배 천천히 돌리고(등장 1s·부제 1.6s),
   퇴장은 글자만 3단계로 끊겨 꺼진 뒤(3.0s+0.4s) 덮개가 걷힌다(즉시 컷 유지).
   ⚠️ 짝: game.jsx DAY_CARD_MS 3500. bsDayOff 지연(3s)+길이(.4s)는 그 안에 끝나야 한다. */
/* [2026-08-23 · 사용자 결재 · 원화 graphics\output\DAYCARD_APPLY_0823.md] B안 「밑에서 올라와 앉는다」
   왜 갈았나 — 옛 bsDayBig 은 불투명도와 자간(.5em→.24em)을 **한 애니에 묶어** steps(3) 으로
   끊었다. 72px·5글자에서 자간이 그만큼 줄면 글자 폭이 76px 바뀌는데 계단이 3칸뿐이라
   0.33초마다 25px 씩 뭉텅이로 줄고, 가운데 정렬이라 좌우가 세 번 크게 튀었다.
   → 자간은 최종값으로 **고정**하고(폭이 안 변한다) 움직임을 **세로 이동**으로 옮겼다.
   ⚠️ letter-spacing 을 다시 애니메이션하지 마라. 그게 부자연스러움의 원인이었다.
   ⚠️ 계단 한 칸은 60~105ms 다. 길이를 늘리려거든 steps 수도 같이 늘려라 —
      길이만 늘리면 한 칸이 길어져 옛 판보다 더 뚝뚝 끊긴다.
   ⚠️ 퇴장(bsDayOff 3s + .4s)과 game.jsx DAY_CARD_MS 3500 은 **손대지 않았다.** 짝을 유지해라.
   ⚠️ 속도는 승인본 그대로다 — ×1.5·×2 판은 사용자가 기각했다. 느리게 만들지 마라. */
/* ── 덮개가 걷힌다 ── [2026-08-23 · 사용자 결재 B안 · 원화 graphics\output\DAY_TRANS_APPLY_0823.md]
   지금까지 덮개는 **한 프레임에 사라졌다.** 그래서 도착 화면이 「검은 화면에서 배어 나오지 않고
   튀어나온다」는 지적이 나왔다(사용자 보고 0823).
   글자가 다 꺼진 3.4초부터 0.5초에 걸쳐 5단으로 걷는다. 그 0.5초 동안 도착 화면의 ①막
   (좌판 = 배경 페이드인 / 도매상 = 디더 막)이 **같이** 떠올라 이음매가 없다.
   ⚠️ 페이드**인**을 되살린 것이 아니다. 지연(3.4s) 동안은 fill 이 안 걸려 opacity 가 1 이다 —
      「덮개는 첫 프레임부터 완전히 검다」는 위 규칙은 그대로다. **fill-mode 를 both 로 바꾸지 마라.**
   ⚠️ 짝: game.jsx DAY_LIFT_MS 500 · DAY_LIFT_AT 3400 · DAY_CARD_MS 3900. 셋은 한 벌이다 —
      하나만 고치면 다 걷히기 전에 카드가 떨어지거나, 걷힌 뒤에도 검은 판이 남는다.
   ⚠️ 움직임 줄이기에서는 이 애니메이션도 함께 꺼진다 — 그때는 예전처럼 컷이다.
      JS 가 DAY_CARD_MS 에 카드를 떼므로 검은 판이 박제될 일은 없다. */
.bs-op.bs-daycard{ animation:bsDayVeil .5s steps(5) 3.4s forwards }
@keyframes bsDayVeil{ to{ opacity:0 } }

.bs-op.bs-daycard .bs-day-big{
  font-size:clamp(26px,6vw,86px); font-weight:700; color:#e8e8e0; letter-spacing:.24em;
  animation:bsDayBig .5s steps(5) both, bsDayOff .4s steps(3) 3s forwards;
}
@keyframes bsDayBig{ from{opacity:0; transform:translateY(16px)} to{opacity:1; transform:translateY(0)} }

/* 장부 괘선 — 큰 글자가 앉은 뒤 좌→우로 그어진다. 「하루가 장부에 적힌다」가 이 한 줄이다.
   짝: game.jsx 의 <i className="bs-day-rule" /> */
.bs-op.bs-daycard .bs-day-rule{
  display:block; height:2px; background:#57543f; margin:14px auto 0; width:0;
  animation:bsDayRule .36s steps(6) .42s both, bsDayOff .4s steps(3) 3s forwards;
}
@keyframes bsDayRule{ from{width:0} to{width:190px} }

.bs-op.bs-daycard .bs-day-sub{
  margin-top:12px; color:#57543f; letter-spacing:.2em;
  font-size:clamp(9px,1.1vw,17px);
  animation:bsDaySub .42s steps(4) .74s both, bsDayOff .4s steps(3) 3s forwards;
}
@keyframes bsDaySub{ from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:translateY(0)} }
@keyframes bsDayOff{ to{opacity:0} }

/* ── 문 두드림 ── 화면은 검고 소리만 난다.
   [2026-08-14 개정] 예전엔 글자가 `bsKnock .5s infinite` 로 **3초 내내** 떨었다.
   두드림은 0·190·380ms 에 세 번 나고 끝나는데 글자는 계속 떠니 소리와 그림이 따로 놀았다.
   이제 game.jsx 가 두드림 박자마다 .k1/.k2 를 번갈아 붙여 **한 번씩 튀게** 한다.
   ⚠️ k1·k2 의 내용은 같다. 나눈 이유는 같은 @keyframes 는 클래스를 다시 붙여도
      재실행되지 않기 때문이다 — 이름이 달라야 다시 걸린다. 한쪽을 지우지 마라.
   ⚠️ 화면 전체가 아니라 글자판을 흔든다. 배경이 순검정이라 흔들어도 안 보이고,
      이 화면에서 눈에 보이는 것은 글자뿐이라 **글자가 곧 화면**이다. */
.bs-op.bs-knock .bs-kn-in{ display:flex; flex-direction:column; align-items:center; }
.bs-op.bs-knock .bs-kn-in.k1{ animation:bsKnKickA .19s cubic-bezier(.2,.8,.3,1) 1 }
.bs-op.bs-knock .bs-kn-in.k2{ animation:bsKnKickB .19s cubic-bezier(.2,.8,.3,1) 1 }
@keyframes bsKnKickA{
  0%{transform:translate(0,0)} 18%{transform:translate(-7px,3px)}
  42%{transform:translate(5px,-2px)} 68%{transform:translate(-3px,1px)} 100%{transform:translate(0,0)}
}
@keyframes bsKnKickB{
  0%{transform:translate(0,0)} 18%{transform:translate(-7px,3px)}
  42%{transform:translate(5px,-2px)} 68%{transform:translate(-3px,1px)} 100%{transform:translate(0,0)}
}
/* 「쾅! 쾅! 쾅!」은 마지막 두드림에 맞춰 끊어 나타난다(game.jsx 가 .on 을 붙인다).
   ⚠️ 기본이 opacity:0 이므로 .on 이 안 붙으면 안 보인다 — 아래 reduced-motion 예외를 함께 봐라 */
.bs-op.bs-knock .bs-kn-bang{
  font-size:clamp(18px,3.4vw,50px); font-weight:700; color:#cfcabc; letter-spacing:.1em;
  opacity:0;
}
.bs-op.bs-knock .bs-kn-bang.on{ animation:bsKnBang .18s steps(2) both }
@keyframes bsKnBang{ 0%{opacity:0} 100%{opacity:1} }
.bs-op.bs-knock .bs-kn-voice{
  margin-top:clamp(10px,1.6vw,24px); color:#e8c46a;
  font-size:clamp(11px,1.7vw,26px); animation:bsKnVoice 1.6s steps(2) both;
}
@keyframes bsKnVoice{ 0%,55%{opacity:0} 100%{opacity:1} }

/* ── 봉해진 편지 ── 편지마다 뜯는 동작이 따로 있다.
   ⚠️ 확정본을 덮는 **유일한** 곳: .bs-let-closed 의 width/top.
      확정본은 46%·정중앙인데, 봉투가 무대 세로의 74% 를 먹어서 아래에 버튼 자리가 안 남는다.
      34%·top 42% 로 줄여 「편지를 뜯는다」와 한 줄 안내가 들어갈 자리를 만든 것이다.
      (봉투 그림 자체·드롭 연출은 그대로 확정본을 쓴다) */
.bs-op.bs-let .bs-let-seal-wrap{ position:absolute; inset:0; }
.bs-op.bs-let .bs-let-seal-wrap .bs-let-closed{ width:34%; top:42%; }
/* .bs-let-envseal(글자로 그린 봉랍) — 은퇴 [2026-08-18]. 봉투 그림에 인장이 들어 있다 */
.bs-op.bs-let .bs-let-from{
  position:absolute; left:0; right:0; bottom:19%; text-align:center;
  color:#b9a173; font-size:clamp(8px,1.2cqw,15px);
}
/* 자리만 봉투 **위**로 옮기는 수식 클래스 — 두 번째 봉투 안내가 쓴다
   [2026-08-15 · EARLY_FLOW_SPEC 3-1 ④]
   ⚠️ 색·크기를 다시 쓰지 않는다. 위 규칙을 그대로 물려받고 세로 자리만 뒤집는다 —
      캡션 모양을 나중에 고칠 때 두 군데를 고치는 일이 없게.
   ⚠️ bottom:auto 가 반드시 있어야 한다. 없으면 bottom:19% 가 살아남아 위아래로 늘어난다.
   top:6% = 봉투 그림(top:42% 중심·width 34%)의 윗변이 약 14% 라 그 위에 안 겹치고 들어간다.
      봉투 크기를 바꾸면 이 값도 같이 봐라(.bs-let-closed 는 바로 위 규칙). */
.bs-op.bs-let .bs-let-from.top{ bottom:auto; top:6%; }
.bs-op.bs-let .bs-let-open{
  position:absolute; left:50%; bottom:7%; transform:translateX(-50%);
  font-family:inherit; cursor:pointer; white-space:nowrap;
  background:#f0b03c; border:2px solid #231a06; color:#231a06;
  box-shadow:3px 3px 0 #000;
  font-size:clamp(9px,1.4cqw,18px); font-weight:700;
  padding:clamp(4px,.8cqw,10px) clamp(12px,2.2cqw,28px);
  animation:bsLetOpen 1.2s steps(2) infinite;
}
.bs-op.bs-let .bs-let-open:hover{ background:#ffc65c; }
@keyframes bsLetOpen{ 0%,60%{opacity:1} 61%,100%{opacity:.72} }
/* 보낸 사람 — 제목 아래, 본문 위 한 줄. 종이 안에 있으므로 글씨 색은 편지지 기준이다 */
.bs-op.bs-let .bs-let-fromline{
  color:#6b5c33; letter-spacing:.06em; flex:none;
  font-size:clamp(7px,1.05cqw,13px);
  border-bottom:1px solid #b6a578;
  padding-bottom:clamp(3px,.6cqw,7px); margin-bottom:clamp(4px,.8cqw,9px);
}
.bs-op.bs-let .bs-let-fromline b{ color:#8a5a10; }

/* ── 자물쇠가 풀린다 ── 판이 흔들리다 갈라져 사라진다.
   ⚠️ 판을 그냥 지우지 마라 — 사라지는 것이 보여야 "열렸다"가 사건이 된다 */
.bs-screen .bs-locked.brk{ cursor:default; animation:bsLockBrk 1.5s steps(3) both; }
@keyframes bsLockBrk{
  0%  { opacity:1; transform:none }
  30% { opacity:1; transform:translateX(-3px) }
  50% { opacity:1; transform:translateX(3px) }
  70% { opacity:.7; transform:scale(1.04) }
  100%{ opacity:0; transform:scale(1.15) }
}
.bs-screen .bs-locked.brk b{ color:var(--gold); }

/* ── 도매 격자 등장 ── 자물쇠가 걷히면 물건이 한 칸씩 순서대로 나타난다(80ms 간격) */
.bs-screen.bs-shop .bs-grid.pop .bs-slot{ animation:bsGridPop .6s steps(2) both; }
.bs-screen.bs-shop .bs-grid.pop .bs-slot:nth-child(2){ animation-delay:.08s }
.bs-screen.bs-shop .bs-grid.pop .bs-slot:nth-child(3){ animation-delay:.16s }
.bs-screen.bs-shop .bs-grid.pop .bs-slot:nth-child(4){ animation-delay:.24s }
.bs-screen.bs-shop .bs-grid.pop .bs-slot:nth-child(5){ animation-delay:.32s }
.bs-screen.bs-shop .bs-grid.pop .bs-slot:nth-child(6){ animation-delay:.40s }
@keyframes bsGridPop{ from{opacity:0; transform:translateY(-14%)} to{opacity:1} }

/* ── 봇짐 판 등장 ── 살짝 커졌다 제자리로. 이제 살 곳 + 담을 곳이 다 모였다 */
.bs-screen.bs-shop .bs-bagbox.pop{ animation:bsBagPop .5s steps(3) both; }
@keyframes bsBagPop{
  0%  { opacity:0; transform:scale(.86) }
  60% { opacity:1; transform:scale(1.04) }
  100%{ transform:scale(1) }
}

/* ── 매입 → 봇짐 제자리 팝 [개정 2026-08-17 · BUY_UX_GUIDE_0817 4절] ──
   낙하(bsDrop)를 대체한다. 이동하는 물체 없음 — 빈 칸에 바로 나타나며 1.28배 → 제자리.
   순차(100ms 간격)는 game.jsx 가 animationDelay 인라인으로 건다.
   backwards 채움 필수 — 자기 차례(delay) 전까지 0% 상태(투명)로 숨는다 */
.bs-screen.bs-shop .bs-bagbox .bs-cell.pop{
  animation:bsCellPop .22s steps(2) backwards;
}
@keyframes bsCellPop{
  0%  { opacity:0; transform:scale(1.28); box-shadow:inset 0 0 0 2px var(--gold) }  /* 금테 1프레임 */
  50% { opacity:1; transform:scale(1.28); box-shadow:inset 0 0 0 2px var(--gold) }
  100%{ opacity:1; transform:scale(1); box-shadow:none }
}

/* ── 화살표 지목 ── 대상 칸 위에서 ▼ 가 깜빡인다.
   ⚠️ .bs-slot 에 position 이 없어서 .hot 일 때만 붙인다 — 안 붙이면 화살표가 격자 기준으로 날아간다 */
.bs-screen.bs-shop .bs-slot.hot{ position:relative; }
.bs-screen.bs-shop .bs-slot.hot .box{ border-color:var(--gold); box-shadow:0 0 0 2px rgba(240,176,60,.4); }
.bs-screen.bs-shop .bs-slot .bs-arrow{
  position:absolute; left:50%; transform:translateX(-50%);
  top:calc(-1 * clamp(16px,2.4cqw,30px)); z-index:9;
  color:var(--gold); font-weight:700; font-size:clamp(13px,2cqw,26px); line-height:1;
  animation:bsArrow .7s steps(2) infinite;
}
@keyframes bsArrow{
  0%,100%{ transform:translateX(-50%) translateY(0) }
  50%    { transform:translateX(-50%) translateY(5px) }
}

/* ── 오크 등록증 매물 ── 목표를 말이 아니라 **매물**로 보여 준다.
   못 사는 날에도 금색으로 남는다(.no 여도 흐려지지 않는다) — 그래야 목표가 계속 눈에 띈다 */
.bs-screen.bs-shop .bs-slot.permit .box{
  border-color:var(--gold); background:#1d1608;
}
.bs-screen.bs-shop .bs-slot.permit .pr{ color:var(--gold); border-top-color:var(--gold); }
.bs-screen.bs-shop .bs-slot.permit .nm{ color:var(--gold); }
.bs-screen.bs-shop .bs-slot.permit.no .box{ border-color:var(--gold); opacity:.72; }
/* ⚠️ 등록증은 **못 사는 동안에도** 보여야 한다 — 그게 이 칸의 존재 이유다(game.jsx 의 주석).
   확정본 bobusang-shop.css:149 의 .bs-slot.no .box img(.32) 와 바로 위 .permit.no(.72) 가
   곱해져 0.23 이 된다. 글자였을 때는 .bs-permit-ico 가 img 가 아니라 안 걸리던 문제라,
   그림으로 바꾸는 순간 드러났다. 그림만 원래 진하기로 되돌린다 [2026-08-14 · ICONS_APPLY 2-1] */
.bs-screen.bs-shop .bs-slot.permit.no .box img{ opacity:1; }
/* [2026-08-23 · PERMIT_APPLY 4절] 매물 칸 그림을 획득 연출과 같은 문서(permit_doc.png)로 교체.
   ⚠️ 세로 문서(995:1368)라 shop.css 130행의 **정사각 강제**를 풀어야 한다 — 안 풀면 납작해진다.
      높이는 다른 아이콘과 같은 96px 자리를 그대로 쓴다(폭만 비율대로 70px). 더 키우지 마라 —
      칸 높이 138px 에서 아래 21px 은 가격 띠(.pr)라, 104px 부터 문서 밑단이 가격에 먹힌다.
   ⚠️ image-rendering:auto — 995px 원본이 70px 로 **연속 축소**된다. pixelated(html 전역)를
      그대로 두면 정수배가 아니라 제목 「통행 허가」의 획이 부서진다 (charanim.css 140행 규칙) */
.bs-screen.bs-shop .bs-slot.permit .box img{
  width:auto; height:clamp(32px,7.5cqw,96px); image-rendering:auto;
}
/* [2026-08-14] .bs-permit-ico(글자 판)는 지웠다 — orcpermit.png 가 들어와서 쓸 데가 없다.
   되돌릴 일이 있으면 ICONS_APPLY.md 4절(_backup_items32_0814)을 보라 */

/* ── 목표 배지 ── 로비 위쪽. 며칠을 굴려야 하는지 늘 보인다 */
.bs-screen .bs-goal{
  position:absolute; left:50%; transform:translateX(-50%); top:4%; z-index:6;
  background:var(--c1); border:2px solid var(--c5); color:var(--c6);
  font-size:clamp(8.5px,1.2cqw,14px); font-weight:700; white-space:nowrap;
  padding:clamp(3px,.5cqw,6px) clamp(9px,1.5cqw,18px);
}
.bs-screen .bs-goal b{ color:var(--gold); }
/* 살 수 있게 된 날은 배지가 켜진다 */
.bs-screen .bs-goal.ok{ border-color:var(--gold); color:var(--c8); }

/* .bs-gate*(국경 관문 비용 확인 판) — 은퇴 [2026-08-18].
   EARLY_FLOW_SPEC 3-9 로 확인 화면 자체가 없어졌다(crossGate 가 바로 건넌다). */

/* ── 손님 등장 ── 툭 나타나지 않고 서서히 걸어 들어온다.
   ⚠️ 상시 호흡(.bs-char-br)·이벤트 연출(.bs-char-fx)과 층이 겹치지 않게 가장 바깥에만 건다.
      한 요소에 몰면 등장이 끝나는 순간 손님이 튄다(charanim.css 3절이 겪은 것).
   ⚠️ translateX(-50%) 를 두 키프레임에 **반드시** 다시 적어라.
      확정본(charanim.css)이 `left:50% + transform:translateX(-50%)` 로 손님을 가운데 세운다.
      transform 은 통째로 덮어쓰는 속성이라, 여기서 translateY 만 쓰면 그 -50% 가 지워져
      손님이 제 폭의 절반만큼 오른쪽으로 밀린다(실측 2026-08-14). */
.bs-screen .bs-charwrap.bs-enter{ animation:bsCustIn 1.1s steps(4) both; }
@keyframes bsCustIn{
  from{ opacity:0; transform:translateX(-50%) translateY(5%) }
  to  { opacity:1; transform:translateX(-50%) }
}

/* ══════════════════════════════════════════════════════════════════════════
   15 · 좌판 손님 등장 연출 (3막)
   [확정 2026-08-16 · 기획\ux시안\HANDOFF_stall_intro.md · 데모 stall_intro_연출_시안.html]

   짝: game.jsx 의 SI 상수 + intro 스테이트. **한쪽만 고치면 어긋난다.**
   1막 배경(0~1800) → 2막 세팅 UI(1800~2700) → 3막 캐릭터(2700~9800)

   ⚠️ 이 절만은 도트 규칙(steps() 로 끊기)에서 벗어나 **연속 트랜지션**을 쓴다.
      손님이 다가오는 원근·색이 돌아오는 점등은 "끊긴 동작"이 아니라 공기라서,
      steps 로 끊으면 뚝뚝 끊긴 확대로 보인다(시안 v2~v4 에서 확인). 걸음 자체는
      네 단계로 끊겨 있으므로 도트 감은 거기서 지켜진다.
   ⚠️ 로드 순서상 build.css 가 charanim.css 보다 **먼저** 온다. 확정본을 덮어야 하는
      규칙은 선택자를 한 단계 더 좁혀(.bs-charwrap.bs-intro) 특정도로 이긴다.
   ══════════════════════════════════════════════════════════════════════════ */

/* 공통 — 아직 등장하지 않은 UI. 8px 아래에서 떠올라 미세 오버슈트 후 안착 */
.bs-screen .bs-ui{ transition:opacity .6s ease, transform .6s cubic-bezier(.2,.9,.3,1.04); }
.bs-screen .bs-ui.bs-pre{ opacity:0; transform:translateY(8px); pointer-events:none; }
/* 지문·아이템 칩은 "배어 나온다" — 세팅 UI 보다 느리게 */
.bs-screen .bs-slow{ transition:opacity 1.1s ease, transform 1.1s ease; }
/* 머리 위 이름표·인내심바는 가로 가운데 정렬이 걸려 있다 —
   ⚠️ translateY 만 쓰면 그 translateX(-50%) 가 지워져 오른쪽으로 밀린다(.bs-enter 주석과 같은 함정).
      확정본이 -50% 를 쓰는지 확인하고 맞춰라. 여기선 위에서 톡 떨어지는 것만 준다 */
.bs-screen .bs-drop.bs-pre{ transform:translateY(-8px); }

/* ── 1막 · 배경 ── 페이드인 + 느린 줌아웃. 장면이 천천히 자리잡는다.
   [개정 2026-08-23 · 사용자 결재 · 기획\ux시안\확정_좌판개장_타이밍.html 2절] 2.6s → **2.0s**.
   ⚠️ 줄인 만큼 game.jsx 의 SI.UI(2100) · SHELF_IN(2800)도 같이 당겼다 — **따로 고치면 어긋난다.**
      줌이 도는 중에 세팅 UI 가 얹히면 안 된다(2026-08-23 사용자 지적). 지금은 줌 끝(2030) + 70.
      되돌리려면 셋을 같이: 2.6s · SI.UI 2700 · SHELF_IN 3400.
   ⚠️ 이 규칙은 **배경이 등장하는 모든 화면**에 걸린다(.bs-bg.bs-bg-in) — 좌판만이 아니다. */
.bs-screen .bs-bg.bs-bg-in{
  opacity:0; transform:scale(1.04);
  transition:opacity 1s ease, transform 2s cubic-bezier(.2,.6,.3,1);
}
.bs-screen .bs-bg.bs-bg-in.settled{ opacity:1; transform:scale(1); }

/* ── 3막 · 손님 ──────────────────────────────────────────────────────────
   .bs-intro 가 붙는 동안은 기존 등장(.bs-enter)·상시 호흡을 끈다 —
   걷기와 호흡이 겹치면 transform 이 서로를 덮어써 손님이 튄다.
   ⚠️ transform-origin:50% 100% 가 **이 연출의 핵심**이다. 하반신이 잘린 초상이라
      기준점을 아래로 두어야 축소 상태에서도 잘린 밑단이 화면 밖에 묻힌다.
      translateY 로 띄워서 맞추려 하지 마라 — 시안 v2 의 잘림 사고가 그것이었다. */
.bs-screen .bs-charwrap.bs-intro{
  transform-origin:50% 100%;
  animation:none;                      /* .bs-enter 무력화 */
  will-change:transform, filter, opacity;
}
.bs-screen .bs-charwrap.bs-intro .bs-char-br{ animation:none; }   /* 호흡 정지 */

/* 걷기 첫 프레임 — 시작점만 그리고 트랜지션은 걸지 않는다.
   여기서 바로 목표값을 주면 트랜지션이 안 걸려 손님이 그냥 나타난다 */
.bs-screen .bs-charwrap.bs-intro.bs-walk0{
  transition:none;
  opacity:0; filter:brightness(0) blur(2px);
}
/* 실루엣 → 어스름한 색. 걷기 전체(1.7s)에 걸쳐 연속으로 돌아온다.
   ⚠️ transform 트랜지션(.42s)을 여기 같이 적어야 한다 — 걸음마다 인라인 transform 이
      갱신되는데, 목록에서 빠지면 크기가 트랜지션 없이 툭툭 튄다 */
.bs-screen .bs-charwrap.bs-intro.bs-sil{
  opacity:.8; filter:brightness(.55) saturate(.75) blur(1.2px);
  transition:opacity 1.7s ease, filter 1.7s ease,
             transform .42s cubic-bezier(.45,.05,.35,1);
}
/* 뚜벅뚜벅 — 걸음(420ms)과 1:1. 래퍼가 아니라 **그림**에 건다(래퍼는 크기 담당) */
.bs-screen .bs-charwrap.bs-intro.bs-bob .bs-char{ animation:bsWalkBob .42s ease-in-out infinite; }
@keyframes bsWalkBob{
  0%,100%{ transform:translateY(0) rotate(-.6deg) }
  50%    { transform:translateY(-1.3%) rotate(.6deg) }
}
/* 점등 ① 오버슈트 → ② 안착. 이 순간엔 이것만 뜬다 */
.bs-screen .bs-charwrap.bs-intro.bs-lit1{
  opacity:1; filter:brightness(1.12) saturate(1) blur(0);
  transition:opacity .5s ease-out, filter .5s ease-out,
             transform .42s cubic-bezier(.45,.05,.35,1);
}
.bs-screen .bs-charwrap.bs-intro.bs-lit2{
  opacity:1; filter:brightness(1) saturate(1) blur(0);
  transition:filter .3s ease-in;
}

/* ── 대사창 ── 2막부터 빈 프레임으로 떠 있다가, 대화가 시작될 때 한 번 밝아진다 */
.bs-screen .bs-vn.bs-speak{ animation:bsVnSpeak .9s ease-out 1; }
@keyframes bsVnSpeak{
  0%  { box-shadow:0 0 0 1px #8a8a58, 0 0 14px rgba(240,192,96,.28) }
  100%{ box-shadow:none }
}
/* 대사창 이름표 — 도장 찍히듯 팝 */
.bs-screen .bs-vntab.bs-pop{ transition:opacity .18s ease, transform .18s cubic-bezier(.2,1.6,.4,1); }
.bs-screen .bs-vntab.bs-pop.bs-pre{ opacity:0; transform:scale(1.25); }
/* 대사가 시작되면 지문은 뒤로 물러난다 — 화면의 주인공이 바뀐다 */
.bs-screen .bs-vnband.dim{ opacity:.55; }
/* 인내심바는 0 에서 차오른다 */
.bs-screen .bs-gauge i{ transition:width .5s cubic-bezier(.2,.7,.3,1); }
/* 입력바가 열리면 커서가 깜빡이기 시작한다 — "네 차례"의 신호 */
.bs-screen .bs-input.live input{ animation:bsInputLive 1s steps(1) infinite; }
@keyframes bsInputLive{ 0%,49%{ caret-color:currentColor } 50%,100%{ caret-color:transparent } }

/* ══════════════════════════════════════════════════════════════════════════
   16 · 거래 영수증 · 오늘의 셈 장부
   [확정 2026-08-16 · 기획\ux시안\HANDOFF_deal_ledger_art.md]
   확정 목업: graphics\drafts\ledger\ledger_ingame_mockup.html — **수치가 다르면 목업이 맞다.**
   아래 크기·색·간격은 전부 그 목업에서 그대로 옮긴 값이다.

   [개정 2026-08-17 · FX_SPEC_0817.md] 등장/퇴장을 3박자로 — **어둠이 계단으로
   내려앉는다(steps 3) → 0.2초 숨 → 종이가 나온다.** 이 두 UI 에서 ease 는 전부 제거,
   모든 움직임은 steps() 계단이다(도트 문법). "숨"이 연출이다 — 지연을 줄이지 마라.

   짝: game.jsx 의 closeReceipt(1050ms) · closeLedger(1100ms) · LED_ROW_MS(400) ·
       장부 줄 인라인 지연 시작점 1300ms.
       퇴장 길이를 여기서 고치면 game.jsx 의 타이머도 같이 고쳐라 — 안 그러면 종이가
       사라지기 전에 다음 화면이 오거나, 빈 화면이 남는다.
   ⚠️ 종이·버튼 그림은 표시 크기를 목업대로 **고정**하고 stretch(100% 100%) 로 쓴다.
      크기를 바꾸려면 9-slice 여백표(output/ui/ledger/LEDGER_UI_APPLY.md)를 따라야 한다.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 영수증 ───────────────────────────────────────────────────────────────*/
.bs-screen .bs-rc-wrap{ position:absolute; inset:0; z-index:8; }
/* 도장이 찍히는 순간(1.47s) 화면이 1px 흔들린다.
   ⚠️ .bs-screen 이나 .bs-bg 를 흔들지 마라 — 그쪽은 무대 배율(transform)이 걸려 있어
      애니메이션이 그 배율을 통째로 덮어쓴다. 이 덮개만 흔들어도 화면이 흔들려 보인다. */
.bs-screen .bs-rc-wrap:not(.out){ animation:bsRcShake .12s steps(2) 1.47s 1; }
@keyframes bsRcShake{ 0%,100%{ transform:translateY(0) } 50%{ transform:translateY(-1px) } }
/* 암막 — 3계단 디더 페이드(0.25s 버퍼 뒤). both 라 애니메이션이 안 도는 환경에선
   즉시 최종 상태(불투명) = 안전. 종이보다 0.55s 먼저 내려앉는 것이 이 연출의 핵심이다 */
.bs-screen .bs-rc-dim{ position:absolute; inset:0; background:rgba(10,8,5,.45);
  opacity:0; animation:bsDimIn .35s steps(3) .25s both; }
@keyframes bsDimIn{ from{ opacity:0 } to{ opacity:1 } }

.bs-screen .bs-rc{
  position:absolute; left:50%; top:50%; width:330px; height:450px;
  background:url("output/ui/ledger/bg_receipt.png") center/100% 100% no-repeat;
  padding:44px 36px 0; box-sizing:border-box; color:#4a3620;
  image-rendering:pixelated;
  /* ⚠️ **제자리(가운데)를 요소 자신이 갖고, 애니메이션은 거기로 들어오기만 한다.**
     animation 에 both 를 걸고 제자리를 키프레임 100% 에만 두면, 애니메이션이 돌지 않는
     환경에서 종이가 시작 위치(화면 위 -190%)에 그대로 얼어붙는다 — 실측으로 확인했다
     (백그라운드 탭에서 종이가 화면 중앙보다 527px 위에 남았다). 그러면 거래가 끝났는데
     영수증도 버튼도 안 보이는 잠금이 된다. backwards 는 **시작 전**만 채우므로 안전하다. */
  transform:translate(-50%,-50%);
  /* 등장 — 아래에서 계단으로 슬라이드업(steps 6). 암막(0.6s에 완성) + 숨 0.2s 뒤인 0.8s 시작 */
  animation:bsRcIn .35s steps(6) .8s backwards;
}
@keyframes bsRcIn{
  0%  { transform:translate(-50%,52%); opacity:0 }
  12% { opacity:1 }
  100%{ transform:translate(-50%,-50%); opacity:1 }
}
/* 퇴장 — 구겨짐(steps 3) → 낙하(steps 4). timing-function 을 키프레임 안에서 갈아 끼운다 */
.bs-screen .bs-rc.out{ animation:bsRcOut .45s both; }
@keyframes bsRcOut{
  0%  { transform:translate(-50%,-50%) rotate(0) scale(1); opacity:1; animation-timing-function:steps(3) }
  33% { transform:translate(-50%,-49%) rotate(3deg) scale(.92); opacity:1; animation-timing-function:steps(4) }
  100%{ transform:translate(-50%,58%) rotate(6deg) scale(.88); opacity:0 }
}
/* 암막 걷힘 — 종이가 사라진 뒤(0.5s) 3계단으로. transition 은 애니메이션과 싸우니 끈다 */
.bs-screen .bs-rc-wrap.out .bs-rc-dim{ transition:none; animation:bsDimOut .3s steps(3) .5s both; }
@keyframes bsDimOut{ from{ opacity:1 } to{ opacity:0 } }

.bs-screen .bs-rc h4{
  font-size:19px; text-align:center; letter-spacing:8px; margin:2px 0 12px;
  color:#3d2c18; font-weight:400;
}
.bs-screen .bs-rc-row{
  display:flex; justify-content:space-between; align-items:baseline;
  font-size:14px; margin:7px 0; position:relative; z-index:2;   /* 도장보다 위 */
}
/* 점선 리더 — 이름과 금액을 이어 준다 */
.bs-screen .bs-rc-row .d{ flex:1; border-bottom:2px dotted #a58f68; margin:0 6px 3px; }
/* 남긴 돈 — 이 종이에서 눈이 가야 할 단 한 곳 */
.bs-screen .bs-rc-row.fin{ color:#a03524; font-weight:700; font-size:16px; }
.bs-screen .bs-rc-row.fin .chip{ background:#f0b03c; color:#7c2415; padding:0 6px; border-radius:2px; }

/* 도장 — 글 **뒤**에 깔린다(z 아래). 종이가 안착하고 숨 0.2s 뒤(1.35s)에 쾅 */
.bs-screen .bs-rc-seal{
  position:absolute; left:50%; top:45%; width:120px;
  z-index:1; pointer-events:none; image-rendering:pixelated;
  /* 제자리(찍힌 상태)를 요소가 갖는다 — 위 .bs-rc 주석과 같은 이유.
     backwards 라 지연 동안만 안 보이고, 애니메이션이 없으면 그냥 찍혀 있다 */
  transform:translate(-50%,-50%); opacity:.42;
  animation:bsRcSeal .18s steps(2) 1.35s backwards;
}
@keyframes bsRcSeal{
  0%  { transform:translate(-50%,-50%) scale(1.6) rotate(4deg); opacity:0 }
  100%{ transform:translate(-50%,-50%) scale(1)   rotate(0);    opacity:.42 }
}

.bs-screen .bs-rc-btns{
  position:absolute; left:0; right:0; bottom:44px; z-index:2;
  display:flex; flex-direction:column; gap:8px; align-items:center;
  /* 도장이 찍힌 뒤에 열린다 — 연타로 연출이 통째로 넘어가는 사고 방지(지시문 26행).
     ⚠️ backwards 다. both 로 두면 애니메이션이 안 도는 환경에서 **버튼이 영영 안 눌린다** —
        영수증에서 나갈 길이 이 버튼뿐이라 그대로 게임이 멈춘다 */
  animation:bsRcBtns .01s linear 2.5s backwards;   /* 1.6 → 2.5s [FX_SPEC 6-G] 동전 비행(~2.4s) 중 연타 방지 */
}
@keyframes bsRcBtns{ from{ opacity:0; pointer-events:none } to{ opacity:1; pointer-events:auto } }
/* 동전이 안 나는 판(결렬 · 1일차)은 버튼을 일찍 연다 [2026-08-25 · 사용자 「뚝뚝 끊긴다」].
   2.5s 는 **동전 비행(1.65~2.40s) 중 연타 방지**로 잡은 값이다. 동전이 안 날면 그 근거가 없고,
   도장·흔들림이 끝나는 1.59s 뒤로 **0.91초 동안 화면에 아무 일도 안 일어난다**(실측).
   ⚠️ 1.59s(흔들림 끝)보다 앞으로 당기지 마라 — 도장이 찍히는 중에 버튼이 열린다.
   ⚠️ 짝: game.jsx 의 .bs-rc-wrap 에 붙는 nocoin 클래스. 조건은 동전 연출 훅과 **같아야** 한다 */
.bs-screen .bs-rc-wrap.nocoin .bs-rc-btns{ animation-delay:1.75s }
.bs-screen .bs-pbtn, .bs-screen .bs-nbtn{
  width:240px; height:54px; border:0; cursor:pointer;
  font-family:inherit; image-rendering:pixelated;
  /* ⚠️ background-color 를 비워 두면 버튼 UA 기본색(흰색)이 종이 그림의 투명
     모서리로 비친다 — btn_paper.png 모서리는 찢김이라 투명이다 [2026-08-17] */
  background-color:transparent;
  background-position:center; background-size:100% 100%; background-repeat:no-repeat;
}
.bs-screen .bs-pbtn{ font-size:15px; color:#f6ecd2; background-image:url("output/ui/ledger/btn_paper_primary.png"); }
.bs-screen .bs-nbtn{ font-size:14px; color:#5a4426; background-image:url("output/ui/ledger/btn_paper.png"); }
.bs-screen .bs-pbtn:hover{ background-image:url("output/ui/ledger/btn_paper_primary_hover.png"); }
.bs-screen .bs-nbtn:hover{ background-image:url("output/ui/ledger/btn_paper_hover.png"); }
/* ⚠️ press 그림은 **이미 2px 내려간 그림**이다. 여기서 translateY 를 또 주면 4px 내려간다 */
.bs-screen .bs-pbtn:active{ background-image:url("output/ui/ledger/btn_paper_primary_press.png"); }
.bs-screen .bs-nbtn:active{ background-image:url("output/ui/ledger/btn_paper_press.png"); }

/* ── 오늘의 셈 장부 ───────────────────────────────────────────────────────*/
.bs-screen .bs-led-wrap{ position:absolute; inset:0; z-index:9; cursor:pointer; }
/* 암막(깊게) — 영수증과 같은 3계단 디더. 종이보다 먼저 내려앉는다 [FX_SPEC 3절] */
.bs-screen .bs-led-dim{ position:absolute; inset:0; background:rgba(10,8,5,.72);
  opacity:0; animation:bsDimIn .35s steps(3) .25s both; }
.bs-screen .bs-led-wrap.out .bs-led-dim{ transition:none; animation:bsDimOut .3s steps(3) .55s both; }

.bs-screen .bs-led{
  position:absolute; left:50%; top:48%; width:608px; height:462px;
  transform:translate(-50%,-50%);
  background:url("output/ui/ledger/bg_ledger.png") center/100% 100% no-repeat;
  padding:38px 0 0; box-sizing:border-box; color:#4a3620;
  image-rendering:pixelated;
  /* 등장 — 가로 펼침을 5계단으로. 암막(0.6s 완성) + 숨 0.2s 뒤인 0.8s 시작.
     backwards 인 이유는 위 .bs-rc 주석과 같다 —
     both 면 애니메이션이 없을 때 장부가 scaleX(.06) 인 실선으로 얼어붙는다 */
  animation:bsLedIn .4s steps(5) .8s backwards;
}
@keyframes bsLedIn{
  from{ transform:translate(-50%,-50%) scaleX(.06) }
  to  { transform:translate(-50%,-50%) scaleX(1) }
}
/* 퇴장 — 다시 접힌다(steps 3). 뜸 0.2s 은 그대로 */
.bs-screen .bs-led.out{ animation:bsLedOut .3s steps(3) .2s both; }
@keyframes bsLedOut{
  from{ transform:translate(-50%,-50%) scaleX(1);   opacity:1 }
  to  { transform:translate(-50%,-50%) scaleX(.05); opacity:0 }
}
.bs-screen .bs-led h4{
  font-size:18px; text-align:center; letter-spacing:6px; margin:0 0 14px;
  color:#3d2c18; font-weight:400;
}
/* ⚠️ margin-left:130px — 왼쪽은 종이 그림의 **제본 구멍+실밥**이다. 글자를 그 위에 올리면
      구멍에 묻혀 안 읽힌다. 폭 352px 도 같이 지켜야 금액이 오른쪽 여백으로 안 밀린다 */
.bs-screen .bs-led-body{ width:352px; margin:0 auto 0 130px; }
.bs-screen .bs-led-row{
  display:flex; justify-content:space-between; align-items:baseline;
  font-size:14px; margin:9px 0;
  animation:bsLedRow .3s ease-out backwards;      /* 지연은 game.jsx 가 줄마다 인라인으로 준다 */
}
@keyframes bsLedRow{ from{ opacity:0; transform:translateX(-8px) } to{ opacity:1; transform:none } }
/* 점선은 짧게 끊는다(최대 36px) — 길게 이으면 이름과 금액이 멀어져 한 줄로 안 읽힌다 */
.bs-screen .bs-led-row .d{ flex:1; max-width:36px; border-bottom:2px dotted #a58f68; margin:0 6px 3px; }
.bs-screen .bs-led-row .no{ color:#8a6a4a; }

.bs-screen .bs-led-tot{
  display:flex; align-items:center; gap:10px;
  margin-top:14px; padding-top:10px; border-top:3px double #6b5334;
  animation:bsLedRow .3s ease-out backwards;
}
.bs-screen .bs-led-tot img{ width:34px; image-rendering:pixelated; }
.bs-screen .bs-led-tot .sum{
  margin-left:auto; font-size:26px; font-weight:700;
  background:#f0b03c; color:#7c2415; padding:0 8px; border-radius:2px;
}
.bs-screen .bs-led-btn{
  position:absolute; left:50%; transform:translateX(-50%); bottom:42px;
  width:260px; height:56px; border:0; cursor:pointer;
  font-family:inherit; font-size:15px; color:#f6ecd2; image-rendering:pixelated;
  background:url("output/ui/ledger/btn_paper_primary.png") center/100% 100% no-repeat;
  animation:bsLedBtn .3s ease-out backwards, bsLedPulse 2s ease-in-out infinite 1.2s;
}
@keyframes bsLedBtn{ from{ opacity:0 } to{ opacity:1 } }
@keyframes bsLedPulse{ 50%{ filter:brightness(1.18) } }
.bs-screen .bs-led-btn:hover{ background-image:url("output/ui/ledger/btn_paper_primary_hover.png"); }
.bs-screen .bs-led-btn:active{ background-image:url("output/ui/ledger/btn_paper_primary_press.png"); }
.bs-screen .bs-led-hint{
  position:absolute; left:0; right:0; bottom:1.2%; text-align:center;
  font-size:11px; color:#9a8a6a;
}

/* ── 정중히 보낸다 [2026-08-16 · PLAYTEST #7] ── 「말하기」 옆 보조 버튼.
   주 버튼과 색을 낮춰 갈라 놓는다 — 흥정의 기본 동작은 어디까지나 말하기다.
   서브라벨(평판 경고)은 버튼 안 두 번째 줄로: 결과를 알고 누르게 한다 */
.bs-screen .bs-input .bs-dismiss{
  background:#2a3020; border-color:#5a6348; color:#b9b394;
  display:flex; flex-direction:column; align-items:center; line-height:1.25;
  white-space:nowrap;
}
.bs-screen .bs-input .bs-dismiss small{ font-size:9px; color:#8a836a; display:block; }
.bs-screen .bs-input .bs-dismiss:hover:not(:disabled){ background:#3a4028; }

@media (prefers-reduced-motion: reduce){
  /* ⚠️ 장소 카드(.bs-place)는 여기 **안 걸린다** — 그쪽은 클래스가 하나 더 많아 명시도가 높다.
     짝: 이 파일 맨 끝 「장소 카드」 블록 뒤의 전용 @media 블록. 둘을 같이 고쳐라 */
  .bs-op.bs-daycard, .bs-op.bs-daycard .bs-day-big, .bs-op.bs-daycard .bs-day-sub,
  /* [2026-08-23] 괘선도 같이 끈다 — 빠뜨리면 움직임 줄이기에서 선만 혼자 그어진다.
     animation:none 이면 width:0 이 남아 선이 안 보이는데, 장식이 빠지는 게 정상이다 */
  .bs-op.bs-daycard .bs-day-rule,
  .bs-op.bs-knock .bs-kn-in, .bs-op.bs-knock .bs-kn-bang, .bs-op.bs-knock .bs-kn-voice,
  .bs-op.bs-let .bs-let-open,
  .bs-screen.bs-shop .bs-grid.pop .bs-slot, .bs-screen.bs-shop .bs-bagbox.pop,
  .bs-screen.bs-shop .bs-bagbox .bs-cell.drop, .bs-screen.bs-shop .bs-slot .bs-arrow,
  .bs-screen .bs-charwrap.bs-enter,
  /* [2026-08-23 · INN_FX_APPLY 5절] 여관 등장·호흡·퇴장 */
  .bs-screen.bs-inn .bs-innfade, .bs-screen.bs-inn .bs-innpor,
  .bs-screen.bs-inn .bs-inntable{ animation:none }
  /* 🔴 덮개는 animation:none 만 주면 **opacity 1 인 검은 판이 그대로 박제**된다 — 화면이 안 보인다 */
  .bs-screen.bs-inn .bs-innfade{ display:none }
  /* 등장 연출 — 움직임을 줄이는 설정에서는 걸음 바운스와 대사창 글로우만 끈다.
     ⚠️ 크기·색 트랜지션까지 없애지는 않는다. 그걸 끄면 손님이 그냥 튀어나오고,
        그 순간 "누가 왔다"는 사건 자체가 화면에서 사라진다 — 이건 장식이 아니다.
        대신 아래에서 길이를 확 줄여 멀미 요인만 걷어낸다 */
  .bs-screen .bs-charwrap.bs-intro.bs-bob .bs-char{ animation:none }
  .bs-screen .bs-vn.bs-speak{ animation:none }
  /* 영수증·장부 — 흔들림과 펄스만 끈다. 종이가 들어오고 나가는 것 자체는 남긴다
     (그게 없으면 화면이 툭툭 바뀌어 무슨 일이 일어났는지 안 읽힌다). 대신 짧게 */
  .bs-screen .bs-rc-wrap:not(.out){ animation:none }
  /* 암막은 즉시 불투명 — 베이스가 opacity:0 이라(bsDimIn both) 애니메이션을 끄면
     영영 투명해져 종이가 플레이 화면 위에 맨몸으로 뜬다 [FX_SPEC 개정 시 추가] */
  .bs-screen .bs-rc-wrap:not(.out) .bs-rc-dim,
  .bs-screen .bs-led-wrap:not(.out) .bs-led-dim{ opacity:1 }
  /* 동전 비행은 장식이다 — 움직임을 줄이는 설정에선 띄우지 않는다(숫자·틱은 그대로 오른다) */
  .bs-screen .bs-goldcoin{ display:none }
  .bs-screen .bs-led-btn{ animation:bsLedBtn .3s ease-out backwards }
  .bs-screen .bs-rc{ animation-duration:.2s }
  .bs-screen .bs-led{ animation-duration:.2s }
  .bs-screen .bs-input.live input{ animation:none }
  .bs-screen .bs-charwrap.bs-intro.bs-sil,
  .bs-screen .bs-charwrap.bs-intro.bs-lit1{ transition-duration:.25s }
  .bs-screen .bs-bg.bs-bg-in{ transition-duration:.3s }
  /* 자물쇠만은 예외 — 이건 장식이 아니라 "열렸다"는 사건 자체다. 대신 짧게 끝낸다 */
  .bs-screen .bs-locked.brk{ animation-duration:.4s }
  /* ⚠️ .bs-kn-bang 은 기본이 opacity:0 이고 .on 애니메이션이 띄운다.
     위에서 animation:none 을 걸었으므로 여기서 직접 보이게 해 줘야 한다 —
     안 그러면 이 설정을 켠 사람에게는 「쾅! 쾅! 쾅!」이 통째로 안 보인다. */
  .bs-op.bs-knock .bs-kn-bang{ opacity:1 }
}


/* ═══ 도매 구매 흐름 [2026-08-17 · BUY_UX_GUIDE_0817 확정판] ═══════════════════
   ① 칸 안 확인 오버레이(P1) ② 수량 줄 흔들림 ③ 지불(칩 눌림·동전 비행·−NG 팝업)
   ④ 취소(3계단 접힘·칸 흔들림) ⑤ 8/8 카운트 깜빡 · reduced-motion 처리 */

/* 오버레이가 칸 안(inset:0)에 살려면 칸이 기준점이어야 한다 (기존엔 .hot 만 relative) */
.bs-screen.bs-shop .bs-slot{ position:relative; }
.bs-screen.bs-shop .bs-slot.sel .box{ border-color:var(--gold); }

/* ── 확인 오버레이 — 칸 경계 안, 이웃 칸 위로 안 삐져나온다 (2절: inset:0 유지) ── */
.bs-screen.bs-shop .bs-buyov{
  position:absolute; inset:0; z-index:6;
  background:rgba(9,14,7,.95); border:2px solid var(--gold);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:clamp(3px,.55cqw,7px); padding:clamp(3px,.5cqw,6px);
}
/* 취소 = 3계단 접힘 — steps(3) 세로 붕괴 (1절 취소 연출 0.3s) */
.bs-screen.bs-shop .bs-buyov.closing{
  animation:bsBuyFold .3s steps(3) both; transform-origin:center top; pointer-events:none;
}
@keyframes bsBuyFold{ from{ transform:scaleY(1); opacity:1 } to{ transform:scaleY(0); opacity:0 } }

/* 수량 줄 − [n] ＋ (버튼 34px · 숫자는 격자 24px — 문서 26px 는 픽셀 규칙 위반이라 스냅) */
.bs-screen.bs-shop .bs-buyov .qty{ display:flex; align-items:center; gap:clamp(4px,.8cqw,10px); }
.bs-screen.bs-shop .bs-buyov .qty[data-sh]{ animation:bsQtyShake .18s steps(2) 2; }
@keyframes bsQtyShake{ 0%,100%{ transform:translateX(0) } 50%{ transform:translateX(2px) } }
.bs-screen.bs-shop .bs-buyov .qty b{
  min-width:1.6em; text-align:center; color:var(--c8);
  font-size:24px; font-family:"Galmuri11",system-ui,"Malgun Gothic",sans-serif; font-weight:400;
}
.bs-screen.bs-shop .bs-buyov .qb{
  width:34px; height:34px; cursor:pointer; font:inherit; font-size:24px; line-height:1;
  font-family:"Galmuri11",system-ui,sans-serif;
  background:var(--c2); color:var(--c7); border:2px solid var(--c5);
}
.bs-screen.bs-shop .bs-buyov .qb:hover{ background:var(--c3); }

/* 합계 줄 — 합계만 골드색 */
.bs-screen.bs-shop .bs-buyov .sum{ color:var(--c6); font-size:15px; white-space:nowrap; }
.bs-screen.bs-shop .bs-buyov .sum b{ color:var(--gold); font-weight:400; }

/* 버튼 줄 — 「떼기」 금테(주) | 「취소」 항상 나란히 */
.bs-screen.bs-shop .bs-buyov .btns{ display:flex; gap:clamp(4px,.7cqw,9px); }
.bs-screen.bs-shop .bs-buyov .ok{
  cursor:pointer; font:inherit; font-size:15px; padding:2px clamp(8px,1.3cqw,14px);
  background:var(--gold); color:var(--c1); border:2px solid var(--c1); box-shadow:0 0 0 1px var(--gold);
}
.bs-screen.bs-shop .bs-buyov .no2{
  cursor:pointer; font:inherit; font-size:15px; padding:2px clamp(8px,1.3cqw,14px);
  background:var(--c2); color:var(--c6); border:2px solid var(--c4);
}

/* ── 취소 — 칸 2px 2회 흔들림 (경로 3개 공통) ── */
.bs-screen.bs-shop .bs-slot.shake{ animation:bsSlotShake .18s steps(2) 2; }
@keyframes bsSlotShake{ 0%,100%{ transform:translateX(0) } 50%{ transform:translateX(2px) } }

/* ── 지불 [3절] — 소지금 칩 1프레임 눌림 · −NG 팝업 · 동전 2개 비행 ── */
.bs-screen .bs-goldchip{ position:relative; }
.bs-screen .bs-goldchip.pay{ transform:translateY(3px); }
.bs-screen .bs-goldpop.neg{ color:var(--danger-txt); }
.bs-screen.bs-shop .bs-payfly img{
  position:absolute; width:32px; height:32px; image-rendering:pixelated; z-index:9;
  animation:bsPayFly .35s steps(5) both;
}
.bs-screen.bs-shop .bs-payfly img:nth-child(2){ animation-delay:.14s; }
@keyframes bsPayFly{
  0%  { left:82%; top:6%;  opacity:1 }
  90% { opacity:1 }
  100%{ left:22%; top:42%; opacity:0 }   /* 상인 품 언저리에서 사라진다 */
}

/* ── 8/8 이 되는 구매 — 봇짐 카운트 danger 1회 깜빡 (가득 예고 · 5절) ── */
.bs-screen.bs-shop .bs-cart .cnt.flash{ animation:bsCntFlash .9s steps(2) 1; }
@keyframes bsCntFlash{ 0%,100%{ color:inherit } 50%{ color:var(--danger-txt) } }

/* ── prefers-reduced-motion — 비행·흔들림 생략, 상태 변화만 (5절) ── */
@media (prefers-reduced-motion: reduce){
  .bs-screen.bs-shop .bs-payfly img{ display:none; }
  .bs-screen.bs-shop .bs-slot.shake,
  .bs-screen.bs-shop .bs-buyov .qty[data-sh]{ animation:none; }
  .bs-screen.bs-shop .bs-buyov.closing{ animation:none; opacity:0; }
  .bs-screen.bs-shop .bs-bagbox .bs-cell.pop{ animation-duration:.01s; }
}


/* ═══ 획득 연출 강화 + NEW 배지 [2026-08-17 · 기획/ux시안/HANDOFF_get_fx.md 확정] ═══
   목업: graphics/drafts/get_fx/get_fx_mockup.html — 수치가 다르면 목업이 맞다.
   등급을 나눈다: 오크 등록증 = D안 「관문 열림」(판 전체 1회) · 그 외 획득 = B안 「도장 쾅」.
   §13 의 .bs-get / .bs-getpop 에 얹는 규칙이라 그 절을 건드리지 않고 여기 모아 둔다.

   ⚠️ 목업의 stampIn 은 transform 에 translate(-50%,-50%) 를 물고 있다 — 목업 팝업이 absolute 라서다.
      여기 .bs-getpop 은 .bs-get(flex)이 가운데로 잡는다. **scale 만** 쓴다 —
      translate 를 따라 넣으면 팝업이 왼쪽 위로 절반 밀려난다.
   ⚠️ 무대(.bs-screen)를 흔들지 마라 — 배율 transform 이 걸려 있어 애니메이션이 그걸 통째로 덮는다
      (§13 영수증 주석과 같은 함정). 덮개(.bs-get)만 흔들어도 화면이 흔들려 보인다. */

/* ── 접기 확인 판 ── [개편 2026-08-23 · 하루흐름개편 【3】] 획득 팝업 껍데기를 빌려 쓰되
   **연출은 벗긴다.** 도장 쾅(흔들림·스탬프)은 「얻었다」의 몸짓이라 「접을까?」에 붙으면
   축하처럼 읽힌다. 여긴 묻는 자리다 — 조용히 뜨고, 바깥을 눌러도 안 닫힌다(커서도 기본값). */
.bs-screen .bs-get.ask{ cursor:default; animation:bsGetDim .14s steps(2) both; }
.bs-screen .bs-get.ask .bs-getpop{ animation:none; }
.bs-screen .bs-get.ask .bs-getpop .ttl{ margin-bottom:clamp(4px,.7cqw,9px); }
.bs-screen .bs-get.ask .bs-rc-btns{ margin-top:clamp(10px,1.7cqw,22px); }

/* ── B안 · 도장 쾅 — 기본 획득 ── */
.bs-screen .bs-get{ animation:bsGetDim .18s steps(2) both, bsGetShake .2s steps(2) .25s 2; }
@keyframes bsGetDim{ from{ opacity:0 } to{ opacity:1 } }
@keyframes bsGetShake{ 0%,100%{ transform:translate(0,0) } 50%{ transform:translate(-3px,2px) } }
.bs-screen .bs-getpop{ animation:bsGetStamp .42s steps(4) both; }
@keyframes bsGetStamp{
  0%  { opacity:0; transform:scale(2.1) }
  40% { opacity:1; transform:scale(2.1) }    /* 40% 까지 떠 있다가 */
  60% { opacity:1; transform:scale(.92) }    /* 내려찍힌다 */
  80% {            transform:scale(1.04) }
  100%{ opacity:1; transform:scale(1) } }
/* 금색 링 파문 — 팝업 중앙에서 퍼진다. 20cqw 는 목업 값(무대 폭 기준) */
.bs-screen .bs-get .ring{
  position:absolute; left:50%; top:50%; width:20cqw; height:20cqw;
  border:3px solid var(--gold); pointer-events:none;
  transform:translate(-50%,-50%); opacity:0;
  animation:bsGetRing .5s steps(4) .25s; }
@keyframes bsGetRing{
  0%  { opacity:.9; transform:translate(-50%,-50%) scale(.3) }
  100%{ opacity:0;  transform:translate(-50%,-50%) scale(2.4) } }

/* ── D안 · 관문 열림 — 오크 등록증 전용(.gate) ──
   판 두 장이 빗장 풀리듯 갈라지고, 그 사이로 도매상 실내가 드러난다.
   흔들림·파문은 없다(목업). 팝업은 판이 갈라지기 시작한 뒤(0.35s) 찍힌다 */
.bs-screen .bs-get.gate{ animation:bsGetDim .2s steps(2) both; }
.bs-screen .bs-get.gate .bs-getpop{ animation:bsGetStamp .42s steps(4) .35s both; }
.bs-screen .bs-get .gl,
.bs-screen .bs-get .gr{
  position:absolute; top:0; bottom:0; width:50%; background:#0c0a08; z-index:9; }
.bs-screen .bs-get .gl{ left:0;  animation:bsGetGL .55s steps(5) forwards; }
.bs-screen .bs-get .gr{ right:0; animation:bsGetGR .55s steps(5) forwards; }
@keyframes bsGetGL{ to{ transform:translateX(-100%) } }
@keyframes bsGetGR{ to{ transform:translateX(100%) } }
.bs-screen .bs-get .unlock{
  position:absolute; left:50%; bottom:9%; z-index:10; white-space:nowrap;
  color:var(--gold); letter-spacing:3px; text-shadow:2px 2px 0 #000;
  /* 목업 2.2cqw(무대 1280 기준 28.2px) → 24px 로 스냅. Galmuri11 은 12 격자다 —
     소수점 px 는 글자가 흐려진다. 위 .bs-buyov .qty b(문서 26px → 24px 스냅)와 같은 선례 */
  font-size:24px;
  opacity:0; transform:translateX(-50%);
  animation:bsGetUnlock .4s steps(3) .95s forwards; }
@keyframes bsGetUnlock{
  from{ opacity:0; transform:translate(-50%,10px) }
  to  { opacity:1; transform:translate(-50%,0) } }

/* ── N-1 · NEW 배지 — 도매 격자 ──
   ⚠️ 칸 자체는 움직이지 않는다. 내려꽂힘(N-2)을 버린 이유가 도트를 흔들지 않기 위해서다.
      기준점(.bs-slot{position:relative})은 위 구매 흐름 절이 이미 깔아 뒀다 */
.bs-screen.bs-shop .bs-slot .bs-new{
  position:absolute; left:-1.2cqw; top:-1.2cqw; z-index:2;
  background:#a03524; color:#f0d8a0; border:2px solid var(--gold);
  box-shadow:2px 2px 0 rgba(0,0,0,.6);
  font-size:18px;            /* 목업 1.5cqw(=19.2px) → 12 격자 스냅 */
  padding:.2cqw 1cqw; line-height:1.4;
  animation:bsNewPop .5s cubic-bezier(.2,1.5,.4,1) both,
            bsNewBlink 1.6s steps(1) 1s infinite; }
@keyframes bsNewPop{
  from{ opacity:0; transform:scale(.2) rotate(-18deg) }
  to  { opacity:1; transform:scale(1)  rotate(-8deg) } }
@keyframes bsNewBlink{ 0%,70%{ filter:none } 71%,100%{ filter:brightness(1.6) } }
/* 칸 테두리 — 딱지와 같은 박자(1.6s)로 금색 점멸 */
.bs-screen.bs-shop .bs-slot.new .box{ animation:bsNewGlow 1.6s steps(2) infinite; }
@keyframes bsNewGlow{ 0%,60%{ border-color:#556031 } 61%,100%{ border-color:var(--gold) } }

@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-get,
  .bs-screen .bs-getpop,
  .bs-screen .bs-get.gate .bs-getpop,
  .bs-screen.bs-shop .bs-slot.new .box{ animation:none; }
  .bs-screen .bs-get .ring{ display:none; }
  /* 🚨 판이 안 갈라지면 화면이 검은 채로 잠긴다 — 애니메이션만 끄면 판이 그대로 남는다.
     (bobusang-town.css 진입 오버레이·build.css 암막과 같은 함정) */
  .bs-screen .bs-get .gl,
  .bs-screen .bs-get .gr{ animation:none; display:none; }
  .bs-screen .bs-get .unlock{ animation:none; opacity:1; }
  /* 딱지는 남는다 — 「새 매물」은 정보라 지우면 안 된다. 튀어나옴·점멸만 끈다 */
  .bs-screen.bs-shop .bs-slot .bs-new{ animation:none; opacity:1; transform:rotate(-8deg); }
}

/* ── 물물교환 4종 글자 칸 [2026-08-17 · NPC_CAST §5-B] ── 아이콘이 없는 물건.
   옛 오크 등록증 글자판과 같은 문법 — 점선 테두리 안에 이름 끝 두 글자 */
.bs-screen .bs-icotxt{
  display:flex; align-items:center; justify-content:center;
  width:32px; height:32px; flex:none;
  border:2px dashed var(--c4); color:var(--c6);
  font-size:15px; letter-spacing:.05em;
}
.bs-screen .bs-icotxt.big{ width:64px; height:64px; font-size:24px;
  font-family:"Galmuri11",system-ui,"Malgun Gothic",sans-serif; }

/* ── 로비 스팟 잠금 해제 [2026-08-17 · 추가 4번] ── 도매 자물쇠(bsLockBrk)와 같은 keyframes 를
   재사용한다 — 흔들리다 갈라져 사라지는 문법이 게임 전체에서 하나여야 한다.
   스팟 자체를 덮는 미니 판: 잠김 톤(빗금 팔레트)에서 1.5s 에 걷힌다. 클릭은 통과 안 시킨다 */
.bs-screen.bs-town .bs-spot .bs-spot-brk{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
  background:rgba(14,16,8,.94); color:#8e8a76;
  font-size:12px; font-family:"Galmuri11",system-ui,"Malgun Gothic",sans-serif;
  animation:bsLockBrk 1.5s steps(3) both;
}
.bs-screen.bs-town .bs-spot .bs-spot-brk b{
  font-size:15px; font-weight:400; letter-spacing:.18em; color:#c2bb96;
}
@media (prefers-reduced-motion: reduce){
  .bs-screen.bs-town .bs-spot .bs-spot-brk{ animation:bsLockBrk .01s both; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   등록증 획득 전면 연출  [2026-08-22 · 기획 C안 · 기획\지시문_등록증연출_2026-08-22.md]
   ═══════════════════════════════════════════════════════════════════════════
   네샤 퀘스트 보상 · 야콥 100G 구매 공용 (명의 텍스트만 다름 — 코드가 넣는다).
   문서·도장은 그림 발주 중(ASSET_ORDER_PERMIT_0822) — 그 전까지 CSS 양피지 대역이고,
   그림이 오면 .doc 내부를 <img> 두 장으로 교체만 하면 된다(시안 HTML .doc 마크업 그대로).
   도트 규칙: steps() 만. 스킵(.fin)은 애니를 걷어낸다 — 기저 스타일이 곧 완성 상태다. */
.bs-screen .bs-permitfx{ position:absolute; inset:0; z-index:9; cursor:pointer;
  background:rgba(10,7,5,.78);
  /* 어두운 막 0.18s + 도장이 찍히는 0.9s 시점의 화면 흔들림 — 기존 bsGetDim/bsGetShake 재사용 */
  animation:bsGetDim .18s steps(2) both, bsGetShake .2s steps(2) .9s 2; }
/* 문서 — 그림이 배경·테두리·제목·본문을 다 갖고 있다 [2026-08-22 · PERMIT_APPLY.md].
   CSS 는 자리와 펼침만 맡는다. 도장·명의는 반드시 이 상자의 **자식**이어야 한다 —
   화면 기준으로 두면 문서 밖으로 떠버린다(APPLY 3절 실측) */
/* 크기는 **세로 기준**이다 [2026-08-22 사용자 확정 CSS] — 세로 문서라 화면 높이에 걸어야
   프레임을 꽉 채운다. 폭은 그림 비율(995:1368)이 정한다: 문서 width:auto + 그림 height:100%.
   ⚠️ 폭 기준(width:clamp)으로 되돌리지 마라 — 도장·명의 %좌표가 전부 이 크기에 맞춰 잡혀 있다 */
.bs-screen .bs-permitfx .doc{ position:absolute; left:50%; top:50%; height:88%; width:auto;
  transform:translate(-50%,-50%);
  animation:bsPermUnfold .5s steps(4) .2s both; }
@keyframes bsPermUnfold{ from{ transform:translate(-50%,-50%) scaleY(.06); opacity:0 }
                         to{ transform:translate(-50%,-50%) scaleY(1); opacity:1 } }
.bs-screen .bs-permitfx .doc img.sheet{ height:100%; width:auto; display:block;
  image-rendering:auto; }   /* ⚠️ pixelated 금지 — charanim.css 140행 규칙 */
/* 명의 — 그림의 빈 밑줄 위. 문서 기준 82% 높이 */
.bs-screen .bs-permitfx .owner{ position:absolute; left:50%; top:82%; transform:translateX(-50%);
  white-space:nowrap; margin:0;
  font-size:clamp(9px,1.5cqw,15px); font-weight:700; color:#3a2c18;
  animation:bsPermName .25s steps(3) 1.4s both; }
/* 명의는 전용 키프레임이다 — 4px 밀려 올라오는데, 가운데 정렬이 transform 이라
   translateX(-50%) 를 두 프레임에 다 써야 한다(안 쓰면 글자가 오른쪽으로 튄다).
   공용 bsPermIn 에 transform 을 넣으면 .nx(좌우 0 정렬)가 같이 밀린다 */
@keyframes bsPermName{ from{ opacity:0; transform:translateX(-50%) translateY(4px) }
                       to{ opacity:1; transform:translateX(-50%) } }
.bs-screen .bs-permitfx .owner b{ color:#7a2f1e; }
/* 도장 — 그림은 정방향이다. 기울이는 건 여기서 한다. 좌표는 문서 상자 기준 % 라 같이 커진다 */
.bs-screen .bs-permitfx .stamp{ position:absolute; left:72%; top:77%; width:20%; height:auto;
  right:auto; bottom:auto;   /* 대역 시절 right/bottom 을 푼다 — 안 풀면 좌우가 동시에 걸린다 */
  image-rendering:auto;
  transform:rotate(-14deg); opacity:.88;
  animation:bsPermStamp .3s steps(3) .9s both; }
@keyframes bsPermStamp{ from{ transform:rotate(-14deg) scale(2.4); opacity:0 }
                        to{ transform:rotate(-14deg) scale(1); opacity:.88 } }
.bs-screen .bs-permitfx .nx{ position:absolute; left:0; right:0; bottom:4.5%; text-align:center;
  font-size:clamp(10px,1.5cqw,18px); color:#b7a98f;
  animation:bsPermIn .25s steps(3) 1.4s both; }
@keyframes bsPermIn{ from{ opacity:0 } to{ opacity:1 } }
/* 스킵 점프 — 클릭 시 .fin 이 붙는다. 애니만 걷어내면 위 기저 스타일이 완성 프레임과 같다 */
.bs-screen .bs-permitfx.fin,
.bs-screen .bs-permitfx.fin .doc,
.bs-screen .bs-permitfx.fin .stamp,
.bs-screen .bs-permitfx.fin .owner,
.bs-screen .bs-permitfx.fin .nx{ animation:none; }
@media (prefers-reduced-motion: reduce){
  .bs-screen .bs-permitfx,
  .bs-screen .bs-permitfx .doc,
  .bs-screen .bs-permitfx .stamp,
  .bs-screen .bs-permitfx .owner,
  .bs-screen .bs-permitfx .nx{ animation:none; }
}


/* ══ 장소 카드 ══ [2026-08-25 · 사용자 지시 「도매상으로 이동할 때 검은 화면에 브리델-야콥 도매상」]
   DAY 카드(.bs-daycard)를 **그대로 빌리고 시계만 반으로 줄인다.** 새 keyframes 는 한 줄도 없다 —
   같은 결이라야 「하루가 바뀐다」와 「자리를 옮긴다」가 한 벌로 읽힌다.

   왜 짧은가 — 도매상은 하루에 두 번(아침·저녁) 지난다. 3.9초를 그대로 받으면
   열이틀에 24번, 도합 1분 30초가 검은 화면이다. 「읽고 나면 걷힌다」선까지만 준다.
   ⚠️ 짝: game.jsx 의 PLACE_CARD_MS 2100 · PLACE_LIFT_AT 1700 · PLACE_LIFT_MS 400.
      넷은 한 벌이다 — 하나만 고치면 다 걷히기 전에 카드가 떨어지거나 검은 판이 남는다.
   ⚠️ 명시도로 이긴다(.bs-op.bs-daycard.bs-place = 클래스 3개). 순서에 기대지 마라 —
      글자 크기는 bobusang-type.css 가 뒤에서 덮는데, 그쪽은 클래스 2개라 여기가 이긴다.
   ⚠️ fill-mode 를 both 로 바꾸지 마라 — 덮개는 첫 프레임부터 완전히 검어야 한다
      (위 .bs-daycard 블록의 2026-08-15 사고 기록과 같은 함정). */
.bs-op.bs-daycard.bs-place{ animation:bsDayVeil .4s steps(5) 1.7s forwards }

/* 큰 글자는 마을 이름이다 — DAY N(72px)보다 한 급 작다.
   「DAY 3」은 다섯 글자지만 「그록나르」는 네 글자에 받침이 많아 같은 크기면 화면을 꽉 채운다 */
.bs-op.bs-daycard.bs-place .bs-day-big{
  font-size:clamp(24px,4.6vw,62px); letter-spacing:.28em;
  animation:bsDayBig .4s steps(4) both, bsDayOff .3s steps(3) 1.4s forwards;
}
.bs-op.bs-daycard.bs-place .bs-day-rule{
  animation:bsDayRule .3s steps(5) .30s both, bsDayOff .3s steps(3) 1.4s forwards;
}
/* 부제가 이 카드의 요점이다 — 「야콥 도매상」. DAY 카드 부제(#57543f)보다 밝게 둔다:
   저기선 곁들이지만 여기선 **어디로 들어가는지**를 혼자 진다 */
.bs-op.bs-daycard.bs-place .bs-day-sub{
  color:#8a8468; font-size:clamp(10px,1.3vw,19px);
  animation:bsDaySub .34s steps(4) .55s both, bsDayOff .3s steps(3) 1.4s forwards;
}

/* 움직임 줄이기 — 장소 카드 몫. 위 §@media 블록(.bs-op.bs-daycard)은 클래스가 하나 적어
   .bs-place 를 **못 이긴다.** 같은 명시도로 다시 쓰되 파일 뒤라 순서로 이긴다.
   🔴 덮개(.bs-place 자신)의 animation 을 끄면 opacity 1 인 검은 판이 남는다 —
      game.jsx 가 PLACE_CARD_MS(2100ms) 에 카드를 떼므로 박제되지는 않지만,
      그때까지 2.1초가 통째로 검다. DAY 카드도 똑같이 동작한다(위 블록 주석). */
@media (prefers-reduced-motion: reduce){
  .bs-op.bs-daycard.bs-place,
  .bs-op.bs-daycard.bs-place .bs-day-big,
  .bs-op.bs-daycard.bs-place .bs-day-rule,
  .bs-op.bs-daycard.bs-place .bs-day-sub{ animation:none }
}
