/* ═══════════════════════════════════════════════════════════════════════════
   마을 화면 그림 + 네 곳 집기   [신규 2026-08-14 · graphics/output/backgrounds/VILLAGE_HUB_APPLY.md]

   바탕을 깔고, 곳마다 **바탕을 한 번 더** 깔되 mask-image 로 그 곳만 남긴다.
   src 가 같아서 브라우저가 받아둔 것을 다시 쓴다 — 그림은 추가 다운로드가 없다.
   평소에는 바탕과 화소가 똑같이 포개져서 **안 보인다.** 마우스를 올린 것만 밝힌다.

   ⚠️ 도트 규칙: 둥근 모서리·그라데이션·블러 금지. 여긴 filter 만 쓴다.
   ═══════════════════════════════════════════════════════════════════════════ */

.bs-screen.bs-town .bs-town-pic{
  position:absolute; inset:0; overflow:hidden; cursor:default; z-index:0;
}
.bs-screen.bs-town .bs-town-pic.hot{ cursor:pointer }
.bs-screen.bs-town .bs-town-pic > img,
.bs-screen.bs-town .bs-town-lyr > img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block; pointer-events:none;
  /* [2026-08-17 · GFX_BUGS #1 · fix_result_0815c ①] 마을 그림 5레이어는 auto 로 —
     전역 pixelated(ui.css .bs-screen *)가 0.833배 같은 비정수 최근접 축소를 만들어
     계단이 깨진다. 이 그림은 도트풍 고해상도라 부드러운 리샘플이 맞다(charanim.css 주석 참조) */
  image-rendering:auto;
}

/* ⚠️ opacity 로 숨겼다 켜지 마라 — 켜지는 순간 한 번 깜빡인다.
      레이어는 늘 떠 있고 **filter 만** 바뀐다 */
.bs-screen.bs-town .bs-town-lyr{
  position:absolute; inset:0; pointer-events:none;
  transition:filter .12s steps(2), transform .12s steps(2);
}
/* 깊이(뒤→앞): 관문 → 여관 → 도매상 → 좌판 [2026-08-17 · 마스크 v2 · HUB_MASK_APPLY 2절].
   앞 건물이 뒤 건물의 금색 테두리를 가려야 맞닿은 경계가 자연스럽다 — DOM 순서
   (shop→stall→gate→inn)대로 쌓이면 도매상 테두리가 관문에 가려져 허공에 세로 금선이 생긴다.
   ⚠️ 이 값들은 .bs-town-pic(z-index:0) 안에 갇힌다 — 그 요소가 쌓임 맥락을 만들기 때문이다.
      그래서 뒤에 오는 형제 .bs-veil(34% 어둠)은 z-index 없이도 여전히 이 레이어들 위에 덮인다.
      .bs-town-pic 의 z-index:0 을 auto 로 바꾸지 마라 — 그 순간 네 레이어가 veil 을 뚫고 올라온다 */
.bs-screen.bs-town .bs-town-lyr.gate {z-index:1}
.bs-screen.bs-town .bs-town-lyr.inn  {z-index:2}
.bs-screen.bs-town .bs-town-lyr.shop {z-index:3}
.bs-screen.bs-town .bs-town-lyr.stall{z-index:4}

/* 🚨 [2026-08-17 · 마스크 v2 검증 중 발견 — z-index 와는 별개 문제]
   .bs-veil 은 game.jsx 에서 .bs-town-pic **다음 형제**라 그림 전체를 덮는다. 덮는 건 맞는데
   pointer-events 가 auto 라 **마우스 이벤트까지 삼켰다** — 그림 위 hitmap 호버(onMouseMove)와
   건물 클릭(onClick)이 한 번도 발동하지 못했다(실측: 건물 한복판에서 elementFromPoint = .bs-veil,
   실제 마우스를 올려도 호버 없음). 명패(.bs-spot)는 veil 뒤에 그려져서 그쪽 호버만 살아 있었다.
   ⚠️ 이 한 줄이 없으면 마스크 v2 도, 위 z-index 도 화면에서 볼 수 없다. 모양은 안 바뀐다 —
      veil 은 그대로 덮이고 이벤트만 통과시킨다. veil 위의 TopBar·명패는 영향 없다 */
.bs-screen.bs-town .bs-veil{pointer-events:none}
.bs-screen.bs-town .bs-town-lyr > img{
  -webkit-mask-size:100% 100%;  mask-size:100% 100%;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
}

/* 마스크는 마을 x 곳 = 여덟 벌.
   ⚠️ 마스크는 **CORS 를 탄다.** 교차 출처면 차단되고 그 요소가 통째로 안 보인다
      (<img> 는 교차 출처가 되는데 마스크는 안 되는 게 함정). 상대경로로 둔다. */
.bs-screen.bs-town.bridel  .bs-town-lyr.shop  > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_bridel_shop.png);
          mask-image:url(output/backgrounds/hub/mask_bridel_shop.png) }
.bs-screen.bs-town.bridel  .bs-town-lyr.gate  > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_bridel_gate.png);
          mask-image:url(output/backgrounds/hub/mask_bridel_gate.png) }
.bs-screen.bs-town.bridel  .bs-town-lyr.inn   > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_bridel_inn.png);
          mask-image:url(output/backgrounds/hub/mask_bridel_inn.png) }
.bs-screen.bs-town.bridel  .bs-town-lyr.stall > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_bridel_stall.png);
          mask-image:url(output/backgrounds/hub/mask_bridel_stall.png) }
.bs-screen.bs-town.groknar .bs-town-lyr.shop  > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_groknar_shop.png);
          mask-image:url(output/backgrounds/hub/mask_groknar_shop.png) }
.bs-screen.bs-town.groknar .bs-town-lyr.gate  > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_groknar_gate.png);
          mask-image:url(output/backgrounds/hub/mask_groknar_gate.png) }
.bs-screen.bs-town.groknar .bs-town-lyr.inn   > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_groknar_inn.png);
          mask-image:url(output/backgrounds/hub/mask_groknar_inn.png) }
.bs-screen.bs-town.groknar .bs-town-lyr.stall > img{
  -webkit-mask-image:url(output/backgrounds/hub/mask_groknar_stall.png);
          mask-image:url(output/backgrounds/hub/mask_groknar_stall.png) }

/* ⚠️⚠️ filter 는 **바깥(.bs-town-lyr)**, mask 는 **안쪽(img)** 이다. 한 요소에 둘 다
        주지 마라. CSS 는 filter 를 먼저 적용하고 그 결과를 마스크로 자르기 때문에,
        실루엣 **바깥**에 그려지는 금색 테두리가 통째로 잘려 나간다.
   ⚠️ 위에 .bs-veil(34% 어둠)이 한 겹 덮여 있다. 그래서 밝기를 1.32 로 세게 준다 */
.bs-screen.bs-town .bs-town-lyr.on{
  filter: brightness(1.32)
          drop-shadow( 2px 0 0 var(--gold)) drop-shadow(-2px 0 0 var(--gold))
          drop-shadow(0  2px 0 var(--gold)) drop-shadow(0 -2px 0 var(--gold));
  transform: translateY(-2px);
}
/* 잠긴 곳은 떠오르지 않고 테두리도 회색이다 — 「누를 수 있다」로 보이면 안 된다 */
.bs-screen.bs-town .bs-town-lyr.off.on{
  filter: brightness(1.08) grayscale(.4)
          drop-shadow( 2px 0 0 #6b7355) drop-shadow(-2px 0 0 #6b7355)
          drop-shadow(0  2px 0 #6b7355) drop-shadow(0 -2px 0 #6b7355);
  transform:none;
}

/* 글자판에 마우스를 올려도 그림이 같이 밝아진다 — 「이 글자 = 저 건물」이 한 번에 읽힌다.
   ⚠️ .hot 은 튜토리얼이 「여기」를 가리킬 때 쓰는 클래스라 겹치면 안 된다. 따로 판다 */
.bs-screen.bs-town .bs-spot.lit{ border-color:var(--gold) }

@media (prefers-reduced-motion: reduce){
  .bs-screen.bs-town .bs-town-lyr{ transition:none }
  .bs-screen.bs-town .bs-town-lyr.on{ transform:none }
}

/* ── 로비 진입 연출 [확정 2026-08-17 · fx_lock_기본형_시안 · HANDOFF_lobby_fx 1절] ──
   화면 전체를 덮은 디더 오버레이가 5단 계단으로 걷힌다. 마스크 에셋이 필요 없다.
   .bs-screen.bs-town 은 ::after 를 아직 안 쓴다(2026-08-17 확인) */
.bs-screen.bs-town::after{
  content:""; position:absolute; inset:0; z-index:11; pointer-events:none;
  background-color:#0d1108;
  background-image:repeating-linear-gradient(45deg, rgba(0,0,0,.5) 0 2px, rgba(0,0,0,0) 2px 4px);
  /* [개정 2026-08-23 · 인계_좌판저녁연출 3절 ①②] 딜레이 .3s — 검은 화면을 잠깐 붙든 뒤 걷는다.
     ⚠️ forwards 를 유지해라. backwards 로 바꾸면 딜레이 동안 디더가 사라졌다 다시 나타난다 */
  animation:bsTownIn .5s steps(5) .3s forwards;
}
@keyframes bsTownIn{ from{opacity:1} to{opacity:0} }

/* ── ③ UI ── 배경이 다 걷힌 뒤 상단바가 앉는다 [2026-08-23 · 인계_좌판저녁연출 3절 ③]
   ⚠️ .bs-town 안에서만 건다 — TopBar 는 좌판·도매상에서도 쓰는 공용 부품이다.
   ⚠️ backwards 라야 딜레이 동안 opacity:0 을 붙든다. 빼면 배경보다 먼저 떠 버린다 */
.bs-screen.bs-town .bs-topbar{ animation:bsTownUi .30s steps(4) .85s backwards }
@keyframes bsTownUi{ from{ opacity:0 } to{ opacity:1 } }

/* ── ⑦ NEW ── 명패가 다 새겨진 뒤 딱지가 박힌다 [2026-08-23 · 인계_좌판저녁연출 3절 ⑦]
   선언 본체는 build.css:348 에 있다. 여기서는 animation 만 얹는다(파일 순서상 town.css 가 뒤다).
   ⚠️ scale 을 쓰지 마라 — 10px 배지가 비정수 배율로 눌려 글자가 뭉갠다.
   ⚠️ 딜레이는 `.bs-spots.in` 이 붙은 시점 기준이다(명패 셋이 다 뜬 뒤) */
.bs-screen.bs-town .bs-spot.fresh::after{
  animation:bsNewPop .25s steps(3) .75s backwards;
}
@keyframes bsNewPop{ from{ opacity:0; transform:translateY(-5px) } to{ opacity:1; transform:none } }

/* ── ① 등장 「새겨진다」 · 0.30s ── [개정 2026-08-23 · graphics/output/backgrounds/EVENING_APPLY_0823.md §6①]
   명패는 배경이 다 드러난 뒤에 뜬다 — 같이 뜨면 어디를 봐야 할지 흩어진다.
   예전 bsSpotIn(opacity 만)을 대체한다. 가로 한 줄로 그어졌다가 한 프레임 넘겨 펴진다.
   ⚠️ 전부 steps() 다 — ease/linear 를 쓰면 도트가 반 칸 걸쳐 어른거린다.
   ⚠️ transform 의 translate(-50%,-50%) 를 빠뜨리지 마라 — build.css 에서 좌표가 판의 **중심**으로 바뀌었다 */
/* 🚨 fill-mode 는 both 가 아니라 **backwards** 다. 여기서 both 를 쓰면
      끝난 애니메이션이 transform·box-shadow 를 **영구히 붙들고 있어** ③호버·④누름이
      통째로 안 먹는다(애니메이션이 일반 선언을 이기는 캐스케이드라 CSS 로는 못 이긴다).
      실측 2026-08-23: both 로 뒀더니 호버 등불(0 0 16px var(--glow))이 아예 안 켜졌다.
      backwards 면 딜레이 동안만 첫 프레임을 붙들고, 끝난 뒤에는 손을 뗀다 —
      둘 다 마지막 프레임이 「원래 모습」이라 forwards 가 필요 없다 */
/* ── ① 저녁 등장 연출 ── [개정 2026-08-23 · 기획/ux시안/저녁도매상_시안_0823.html · 사용자 확정]

   순서:  배경 걷힘 → **안내문이 한 글자씩 쳐지고** → 글이 끝나면 명패가 왼→오

     [개정 2026-08-23 · 인계_좌판저녁연출 3절] 일곱 비트로 갈랐다
     0.00~0.30  검은 화면 홀드 (bsTownIn 딜레이)
     0.30~0.80  배경 디더 걷힘 (bsTownIn)
     0.85~1.15  상단바 (bsTownUi)
     1.20~1.56  안내문 판이 왼쪽부터 펼쳐진다 (bsQuestOpen)
     1.20~2.69  「해가 진다…」 타자기 — game.jsx 의 useTypewriter(45ms/자 · DUSK_WAIT 1200)
     그 뒤       도매상 → 관문(+0.20s) → 여관(+0.40s)   ≈2.69~3.44
     +0.75      자물쇠 해제 (game.jsx SPOT_ALL_IN) · NEW 딱지 (bsNewPop)

   🚨 명패 등장은 **CSS 딜레이가 아니라 .bs-spots.in 클래스**가 방아쇠다.
      타자기는 아무 데나 누르면 즉시 완성되는데, 명패가 고정 딜레이면 글은 다 나왔는데
      갈 곳이 1초 넘게 안 뜬다 — 건너뛰기가 반만 듣는다. game.jsx 가 타이핑이 끝나는 순간 .in 을 붙인다.
   🚨 .in 이 없을 때는 **animation:none 이어야 한다.** 딜레이만 미루면 애니메이션은 계속 돌아서
      글 치는 중에 명패가 한 번 번쩍 떴다가 사라지고 .in 에서 또 뜬다 — 「튄다」는 게 이거다(실측 2026-08-23).
   ⚠️ 전부 steps() 다 — ease/linear 를 쓰면 도트가 반 칸 걸쳐 어른거린다.
   ⚠️ transform 의 translate(-50%,-50%) 를 빠뜨리지 마라 — build.css 에서 좌표가 판의 **중심**이다 */

/* 안내문 — 타자기 자리 */
.bs-screen.bs-town .bs-quest.dusk{ display:grid; text-align:left }
.bs-screen.bs-town .bs-quest.dusk > *{ grid-area:1/1 }
/* 유령 글줄 — 처음부터 최종 폭을 잡아 둔다.
   ⚠️ 빼지 마라: 없으면 판이 글자 하나마다 좌우로 벌어져 덜덜 떤다 */
.bs-screen.bs-town .bs-quest.dusk .bs-gh{ visibility:hidden }
.bs-screen.bs-town .bs-quest.dusk.pre{ opacity:0 }     /* 배경이 걷히기 전엔 판도 없다 */

/* 🚨 판이 **통째로 툭 튀어나오면 안 된다.** 유령 글줄이 최종 폭을 미리 잡고 있어서
      그냥 띄우면 글자 하나 없는 빈 판이 한 프레임에 나타난 뒤 거기에 글이 쓰인다.
      왼쪽부터 펼치면서 그 위로 글이 따라 쳐지게 한다 — 종이를 펴며 쓰는 꼴이다.
   ⚠️ scaleX 로 늘리지 마라 — 늘어나는 동안 글자가 가로로 눌려 뭉개진다.
      clip-path 로 **가리개만 걷는다**: 글자 모양은 그대로고 보이는 만큼만 늘어난다 */
.bs-screen.bs-town .bs-quest.dusk.open{ animation:bsQuestOpen .36s steps(6) backwards }
@keyframes bsQuestOpen{
  from{ clip-path:inset(0 100% 0 0) }
  to  { clip-path:inset(0 0 0 0) }
}

/* 명패 — .in 이 붙는 순간부터 왼→오. 시차는 변수다(타자기 속도와 같이 움직여야 한 호흡이다) */
.bs-screen.bs-town .bs-spots{ --st:.20s }
.bs-screen.bs-town .bs-spots .bs-spot{ opacity:0; animation:none }
.bs-screen.bs-town .bs-spots.in .bs-spot{
  opacity:1;
  animation:bsSpotCarve .30s steps(4) backwards, bsSpotGlow .42s steps(3) backwards;
  animation-delay:0s, .30s;
}
.bs-screen.bs-town .bs-spots.in .bs-spot.gate{
  animation-delay:var(--st), calc(var(--st) + .30s) }
.bs-screen.bs-town .bs-spots.in .bs-spot.inn{
  animation-delay:calc(var(--st) * 2), calc(var(--st) * 2 + .30s) }

@keyframes bsSpotCarve{
  0%  { opacity:0; transform:translate(-50%,-50%) scale(.72,.14) }
  30% { opacity:1; transform:translate(-50%,-50%) scale(1,.14) }    /* 가로 한 줄 */
  70% {            transform:translate(-50%,-50%) scale(1,1.14) }   /* 한 프레임 넘겨 편다 */
  100%{            transform:translate(-50%,-50%) scale(1,1) }
}
/* 🚨 box-shadow 를 애니메이션하면 **프레임 4겹까지 전부 다시 적어야 한다** — 빠뜨리면
      애니메이션이 도는 동안 명패 프레임이 통째로 사라진다 (type.css ⑥절 참조) */
@keyframes bsSpotGlow{
  0%  { box-shadow:inset 0 0 0 1px var(--li), inset 0 0 0 3px var(--fl),
                   inset 0 0 0 4px var(--lo), 2px 2px 0 rgba(8,10,4,.55),
                   0 0 22px rgba(240,220,160,.55) }
  100%{ box-shadow:inset 0 0 0 1px var(--li), inset 0 0 0 3px var(--fl),
                   inset 0 0 0 4px var(--lo), 2px 2px 0 rgba(8,10,4,.55) }
}

/* ── ② 닫힘 → 열림 · 총 1.85s ── [§6②]
   순서: 자물쇠 떨림 → 「철컥」 판 → 고리가 열리고 몸통이 떨어짐 → 색이 번쩍였다 앉음 → NEW.
   「철컥」 판(.bs-spot-brk · bsLockBrk)과 NEW 배지(.fresh::after)는 **이미 있다**(build.css) — 그대로 둔다.
   여기 새로 붙는 것은 **자물쇠 조각**과 **색 전환** 둘뿐이다.

   🚨 잠김에 opacity 를 쓰지 마라 (사용자 지시) — 판 농도는 열림과 똑같다.
      흐려지는 게 아니라 **색만 빠진다**(type.css .bs-spot.off 의 saturate(0)+빗금이 그 몫이다).
   🚨 자물쇠를 ::before/::after 로 만들지 마라 — .hot::after(「여기」)·.fresh::after(NEW)가 이미 쓴다.
      그래서 game.jsx 가 span 한 줄을 넣는다.
   ⚠️ 자물쇠는 판 **바깥 위**에 얹는다 — 판 안에 넣으면 글자 폭이 늘어 관문 명패가 아치를 넘는다 */
.bs-screen.bs-town .bs-spot-lk{
  position:absolute; left:50%; top:-13px; transform:translateX(-50%);
  width:3px; height:5px; border:2px solid #9a9a92; border-bottom:0; border-radius:3px 3px 0 0;
}
.bs-screen.bs-town .bs-spot-lk b{
  position:absolute; left:50%; top:5px; transform:translateX(-50%);
  width:9px; height:7px; background:#9a9a92; box-shadow:0 0 0 1px rgba(8,8,6,.8);
}
/* 해제 순간 — spotBrk 가 켜진 동안만 .unlock 이 붙는다 (game.jsx 명패 className) */
.bs-screen.bs-town .bs-spot.unlock .bs-spot-lk  { animation:bsLkRing 1.35s steps(3) both }
.bs-screen.bs-town .bs-spot.unlock .bs-spot-lk b{ animation:bsLkBody 1.35s steps(3) both }
@keyframes bsLkRing{
  0%,44%{ transform:translateX(-50%) rotate(0) }
  22%   { transform:translateX(calc(-50% - 3px)) }      /* 떨림 */
  33%   { transform:translateX(calc(-50% + 3px)) }
  62%   { transform:translateX(-50%) rotate(-38deg) }   /* 고리가 열린다 */
  100%  { transform:translateX(-50%) translate(6px,-7px) rotate(-70deg); opacity:0 }
}
@keyframes bsLkBody{
  0%,44%{ transform:translateX(-50%) }
  22%   { transform:translateX(calc(-50% - 3px)) }
  33%   { transform:translateX(calc(-50% + 3px)) }
  62%   { transform:translateX(-50%) translateY(2px) }
  100%  { transform:translateX(-50%) translateY(13px); opacity:0 }   /* 떨어진다 */
}

/* 회색 → 색. 보상 그 자체라 한 번 크게 번쩍인 뒤 제 색으로 앉는다.
   ⚠️ 등장(bsSpotCarve/Glow)과 같은 요소를 물지만 시점이 겹치지 않는다 — 해제는 로비에 들어온 뒤에 난다 */
/* ⚠️ 여기도 backwards — 위와 같은 이유다(끝난 뒤 box-shadow 를 붙들면 호버가 죽는다).
      딜레이 .95s 동안 첫 프레임(회색)을 붙드는 것이 필요한 전부다 */
.bs-screen.bs-town .bs-spot.unlock{ animation:bsUnTint .9s steps(3) backwards; animation-delay:.95s }
@keyframes bsUnTint{
  0%  { background-color:rgba(20,20,19,.86);
        box-shadow:inset 0 0 0 1px #55554e, inset 0 0 0 3px rgba(20,20,19,.86),
                   inset 0 0 0 4px #3e3e38, 2px 2px 0 rgba(8,8,6,.55) }
  34% { background-color:rgba(24,36,44,.86);
        box-shadow:inset 0 0 0 1px #9fd4f4, inset 0 0 0 3px rgba(24,36,44,.86),
                   inset 0 0 0 4px #5a7a92, 2px 2px 0 rgba(8,10,4,.55),
                   0 0 26px rgba(150,210,255,.75) }              /* 번쩍 */
  100%{ background-color:var(--fl);
        box-shadow:inset 0 0 0 1px var(--li), inset 0 0 0 3px var(--fl),
                   inset 0 0 0 4px var(--lo), 2px 2px 0 rgba(8,10,4,.55) }
}

/* 🚨 움직임 줄이기 — 이 블록을 빼면 로비가 통째로 까맣게 덮인 채 안 걷힌다.
   오버레이의 기본 opacity 는 1 이고 그걸 걷어내는 게 애니메이션이라서,
   애니메이션만 끄면 영영 불투명하게 남는다. (build.css:1008 의 암막과 같은 함정) */
/* 🚨 움직임 줄이기 — animation:none 만 걸고 끝내지 마라.
   이 절은 명패에 **opacity:0 을 기본으로 깔았고**, 안내문 판에는 .pre 로 opacity:0 을 건다.
   애니메이션만 끄면 둘 다 영영 안 보인다 — 최종 상태가 눈에 보이는지 확인하고 지나가라.
   (town.css 암막 · build.css:1008 과 같은 종류의 함정이다) */
@media (prefers-reduced-motion: reduce){
  .bs-screen.bs-town::after{ animation:none; opacity:0 }
  .bs-screen.bs-town .bs-quest.dusk.open{ animation:none }
  .bs-screen.bs-town .bs-topbar{ animation:none }              /* 기본이 보이는 상태라 안전 */
  .bs-screen.bs-town .bs-spot.fresh::after{ animation:none }   /* 〃 */
  .bs-screen.bs-town .bs-quest.dusk.pre { opacity:1 }
  .bs-screen.bs-town .bs-spots .bs-spot{ opacity:1 }
  .bs-screen.bs-town .bs-spot,
  .bs-screen.bs-town .bs-spots.in .bs-spot,
  .bs-screen.bs-town .bs-spot.unlock,
  .bs-screen.bs-town .bs-spot-lk,
  .bs-screen.bs-town .bs-spot-lk b,
  .bs-screen.bs-town .bs-spot:not(.off):active{ animation:none }
  .bs-screen.bs-town .bs-spot.unlock .bs-spot-lk{ display:none }   /* 최종 상태 = 자물쇠 없음 */
}

/* ── 불빛 연출 [2026-08-23 · LIGHT_APPLY_0823.md 3절] ──────────────────────
   ① 감광막: 지금 .bs-veil 은 화면 전체를 rgba(20,26,14,.34) 로 **똑같이** 덮는다
      (screens.css:60). 그림에는 등불·창·횃불이 그려져 있는데 막이 평평해서
      그 빛이 화면을 이끌지 못한다. 막의 농도만 자리마다 바꾼다.
   ② 숨쉬기: 배경을 한 겹 더 깔고 **같은 빛 지도**로 자른 뒤 밝힌다.
      색을 덧칠하는 게 아니라 원래 그려져 있던 화소가 밝아진다 —
      .bs-town-lyr(건물 호버 점등)이 쓰는 바로 그 문법이다(위 27~99행).
   ✅ 총량은 그대로다 — 막 평균 34.0% 로 구워져 있다(실측 확인). 바뀌는 건 분포뿐.
   ✅ 마을 그림은 1536x864 = 16:9 라 cover 로 잘리는 데가 없다 —
      막·마스크가 center 로 정확히 포개진다 (여관은 3:2 라 사정이 다르다, screens.css 참조). */

/* ── ① 감광막 ── */
.bs-screen.bs-town.bridel .bs-veil{
  background:none;
  background-image:url(output/backgrounds/darkveil_village_hub_bridel_eve.png);
  background-size:cover; background-position:center;
}
.bs-screen.bs-town.groknar .bs-veil{
  background:none;
  background-image:url(output/backgrounds/darkveil_village_hub_groknar_eve.png);
  background-size:cover; background-position:center;
}

/* ── ② 불빛 숨쉬기 ──
   ⚠️ 이 층은 반드시 .bs-veil **아래**다(DOM 에서 .bs-town-pic 안). 위로 올리면
      불투명 복제가 어둠막을 통째로 뚫어, 밝기 1.0 일 때조차 그 부분만 막이 벗겨진
      밝은 얼룩으로 남는다 [지시서 함정 1].
   ⚠️ z-index 를 주지 마라 — 올리면 .bs-town-lyr 호버의 금색 테두리를 덮는다 [함정 6]. */
.bs-screen.bs-town .bs-breath{
  position:absolute; inset:0; pointer-events:none;
  --fa:.20;                                  /* 폭 — 사용자 확정 B안 */
  animation:bsFireB 2.4s steps(1) infinite;
}
.bs-screen.bs-town .bs-breath > img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; display:block;
  image-rendering:auto;                      /* 전역 pixelated 가 비정수 축소에서 도트를 날린다 [함정 4] */
  -webkit-mask-size:cover;     mask-size:cover;
  -webkit-mask-position:center; mask-position:center;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
}
.bs-screen.bs-town.bridel .bs-breath > img{
  -webkit-mask-image:url(output/backgrounds/breath_village_hub_bridel_eve.png);
          mask-image:url(output/backgrounds/breath_village_hub_bridel_eve.png);
}
.bs-screen.bs-town.groknar .bs-breath > img{
  -webkit-mask-image:url(output/backgrounds/breath_village_hub_groknar_eve.png);
          mask-image:url(output/backgrounds/breath_village_hub_groknar_eve.png);
}

/* B 화톳불 — 이웃한 마디끼리 멀리 뛴다. 장작이 확 살았다 꺼지는 결.
   ⚠️ 마디 자리를 고르게 만들지 마라. 고르면 그 순간 불이 아니라 메트로놈이 된다.
   ⚠️ steps(1) 만 쓴다. 이징을 걸면 매끈해져서 도트 그림과 안 붙는다.
   실측: 밝기 1.010 ~ 1.200 (폭 19.0%p) · 15단 · 한 바퀴 2.4초
   ※ 여관(screens.css)도 이 @keyframes 를 그대로 쓴다 — 이름을 바꾸면 거기가 죽는다 */
@keyframes bsFireB{
  0%  {filter:brightness(calc(1 + .05*var(--fa)))}   7%{filter:brightness(calc(1 + .85*var(--fa)))}
  13% {filter:brightness(calc(1 + .20*var(--fa)))}  19%{filter:brightness(calc(1 + 1.0*var(--fa)))}
  26% {filter:brightness(calc(1 + .15*var(--fa)))}  33%{filter:brightness(calc(1 + .70*var(--fa)))}
  40% {filter:brightness(calc(1 + .30*var(--fa)))}  47%{filter:brightness(calc(1 + .95*var(--fa)))}
  55% {filter:brightness(calc(1 + .10*var(--fa)))}  62%{filter:brightness(calc(1 + .55*var(--fa)))}
  69% {filter:brightness(calc(1 + .25*var(--fa)))}  77%{filter:brightness(calc(1 + .80*var(--fa)))}
  84% {filter:brightness(calc(1 + .12*var(--fa)))}  92%{filter:brightness(calc(1 + .65*var(--fa)))}
  100%{filter:brightness(calc(1 + .05*var(--fa)))}
}

/* ── ③ 별 — 브리델 전용. 이건 막도 마스크도 아니고 진짜 2px 도트다 ──
   실측한 트인 밤하늘은 x24.6~55.8% · y0~4.6% 뿐이다. 그 아래는 성벽 흉벽 톱니다.
   ⚠️ TopBar 가 y3% 부터 덮는다 — 별을 더 아래로 내리지 마라.
   ⚠️ 그록나르는 노을 잔광이라 별이 없다. 두 마을을 가르는 공짜 신호다.
   ⚠️ 별만은 .bs-veil **위**다(z-index:2). 막 아래로 넣으면 34% 에 묻힌다 */
.bs-screen.bs-town.bridel .bs-stars{
  position:absolute; left:24.6%; top:0%; width:31.2%; height:4.6%;
  pointer-events:none; z-index:2;
}
.bs-screen.bs-town.bridel .bs-stars i{
  position:absolute; width:2px; height:2px; background:#fff;
  animation:bsTwinkle 5.6s steps(1) infinite;
}
/* 켜져 있는 시간이 훨씬 길고 꺼지는 건 잠깐이다 — 그래야 별이지 신호등이 아니다 */
@keyframes bsTwinkle{
  0%,41%{opacity:.95}  43%{opacity:.18}
  47%,72%{opacity:.95} 74%{opacity:.30}
  77%,100%{opacity:.95}
}

/* 🚨 움직임 줄이기 — 끄되 **중간값에서 멈춘다.** filter 를 초기값(밝기 1)으로 굳히면
   숨쉬는 층이 통째로 안 보여서 「불이 꺼진 화면」이 된다 [함정 5] */
@media (prefers-reduced-motion: reduce){
  .bs-screen.bs-town .bs-breath{ animation:none; filter:brightness(calc(1 + .45*var(--fa))) }
  .bs-screen.bs-town.bridel .bs-stars i{ animation:none; opacity:.7 }
}
