/* 도깨비 마을(index.html) - 04_자료\앱자료\도깨비 마을 맵.png(RW-079 마스터 배경아트)을
   실제 배경으로 쓰는 아이소메트릭 마을 씬. 2026-08-19 사람 직접 지시로 CSS 도형
   placeholder(옛 두번째씬_마을맵_기획.md 1차 스펙)를 실사용 배경아트+실사용 미니미
   스프라이트로 교체했다 - 신규 이미지 애셋은 결재를 거쳐 이미 확보된 것만 쓰고,
   그 위의 동적 연출(문 글로우·분수 물결·미니미 이동·낮밤)은 여전히 CSS/JS만으로
   비용 0원 구현한다(cover.css 기법 재사용 원칙 유지). */

/* ★ 2026-08-21 사람 지시 "글자는 상단으로, 사진은 더 크게".
   머리말이 먹던 세로 공간을 **변수 하나**(--vg-head)로 모았다 — 그림 높이 계산과
   같은 숫자를 두 곳에 적어 두면 한쪽만 고쳤을 때 그림이 잘리거나 뜬다.
   실측(2026-08-21 이전): 뒤로가기 14~58 · 제목 72~113 · 설명 121~141 · 지도 176 시작
   → 머리말이 176px 을 먹고 있었다. 아래로 줄여 그만큼 그림에 준다. */
/* ★ 머리말 압축(사람 지시 "글자는 상단으로") — 줄인 만큼이 전부 그림 높이로 간다.
   --vg-head 는 village-ambient.js 가 **실제로 재서** 덮어쓴다(여기 값은 첫 프레임용 기본값).
   ★ 기본은 **두 줄**이다: 1행 = 뒤로가기 pill + 은화 배지 · 2행 = 제목 전폭.
     좁은 화면에서 제목을 pill 과 같은 줄에 두면 가운데 정렬 제목이 양옆에 눌려
     **여러 줄로 꺾인다**(390px 실측: 쓸 폭이 90px 밖에 안 남아 글자 아래끝이 112px 까지 내려갔다).
     그래서 한 줄 압축은 폭이 넉넉한 화면(>=700px)에서만 한다 — 아래 미디어쿼리. */
.village-body{ position:relative; padding-top:52px; --vg-head:104px; }

/* ============================================================
   시간대 UI 테마 — 낮 · 노을 · 밤 (2026-08-21 사람 지시 `새 폴더\0821.txt` 2항)
   ------------------------------------------------------------
   ★ 지시문이 [배경 이미지만 바꾸지 말고 화면 전체 분위기가 바뀌게] 라고 했다.
     배경 3장 교차(.vg-art)는 지도 **안**만 바꾼다 — 지도 밖 머리말·뒤로가기·배지·
     시트·모달은 그대로 낮이라, 밤에 밝은 크림색 띠가 화면 위아래에 남는다.
     그래서 앱 공통 토큰(--bg/--text/--muted/--border/--accent/--card)을
     **이 두 화면(마을·놀이터)에서만** 시간대에 맞춰 갈아 끼운다.

   ★ 색은 감으로 정하지 않았다(CLAUDE.md §1-7). `village-time-*.jpg` 3장의
     **하늘 대역(위 0~15%) 평균색**을 실측해 색상(H)을 그대로 가져오고,
     명도만 대비 기준에 맞게 잡았다 — 2026-08-21 실측:
        낮   #C0C9D1 (H208 S0.08 V0.82)   ← 앱 기본 크림 톤을 그대로 둔다
        노을 #DEA382 (H 21 S0.41 V0.87)
        밤   #333C70 (H231 S0.54 V0.44)
     대비 실측(글자/배경 · 보조/배경 · 강조/배경 · 강조/흰글자, 앱 현행 기준값은
     각각 12.23 / 5.19 / 4.57 / 4.92):
        노을(밝은테마) 10.74 / 5.41 / 4.64 / 5.83
        밤  (밝은테마) 14.94 / 8.52 / 4.13 / 4.25
        노을(어두운테마) 13.35 / 7.24 / 4.11 / 4.07
        밤  (어두운테마) 15.15 / 7.64 / 4.28 / 4.27
     --accent 는 style.css:73 에서 **흰 글자 버튼의 배경**으로도 쓰이므로
     [배경 대비]와 [흰 글자 대비]를 동시에 넘겨야 한다 — 위 네 번째 숫자가 그것이다.

   ★ 낮은 일부러 [앱 기본값 그대로]다. 사용자의 밝게/어둡게 설정을 낮에도 무시하면
     낮·노을·밤이 아니라 [테마가 세 번 뒤집히는 화면]이 된다. 밤은 어느 설정에서도
     어둡다 — 그것이 밤의 정의라서 그렇다.
   ============================================================ */
:root{
  --vg-bg:#faf6ef; --vg-fg:#3a2e22; --vg-muted:#766553;
  --vg-line:#e8ddc9; --vg-accent:#8f6a2e; --vg-card:#ffffff;
  --ui-dur:1.2s;                     /* UI 색 전환 — 그림 교차(--dn-dur)보다 조금 짧게 */
  /* ★ 2026-09-04 #165 ④ — [하늘 → 환경색 → 창문 → 등불 → 별] 순서의 한 칸.
     --dn 은 낮 .08 / 노을 .50 / 밤 .92 로 **툭 뛰는 값**이라(game-time.js:82) 층마다 문턱이
     달라도 전환은 다섯이 **동시에** 시작했다 — 그래서 순서가 실제로는 없었다. 순서는
     transition-delay 로만 생긴다. 한 칸 = --dn-dur/8 이라 마지막 층(별)이 첫 층(하늘)의
     **절반쯤**에서 출발한다(4칸 = 1/2). 총 전환 = 4칸 + --dn-dur = 0.8 + 1.6 = 2.4s 로
     #165 완료 기준의 1.5~2.5초 밴드 안이다. 계약 = 08_작업1팀\산출물u_상태빛_2026-09-04\계약.json */
  --vg-dn-step:calc(var(--dn-dur, 2.4s) / 8);
}
:root[data-phase="sunset"]{
  --vg-bg:#f6e2d2; --vg-fg:#43281a; --vg-muted:#7a5238;
  --vg-line:#dfb894; --vg-accent:#a34c18; --vg-card:#fff3e9;
}
:root[data-phase="night"]{
  --vg-bg:#141731; --vg-fg:#e8ecff; --vg-muted:#a9b3de;
  --vg-line:#2e3568; --vg-accent:#566ef5; --vg-card:#1e2244;
}
@media (prefers-color-scheme: dark){
  :root{
    /*  2026-09-12 사람 지시 [곳곳에 남은 브라운 창 모두 삭제] — 다크모드 표면을 브라운에서 남색으로.
        옛 값 --bg:#211a12 / --card:#2c2318 / --border:#40331f 는 리뉴얼 전 갈색 테마의 잔재였고,
        리뉴얼된 화면들(phys-landing #100c1c · 사주집 랜딩 navy · village night)이 그 위를 덮고 있어
        **안 덮은 자리에서만 갈색이 드러났다**(2026-09-12 실측: 청운 화면 픽셀 89.9% 가 #211a12).
        새 값은 지어낸 색이 아니라 이 앱이 이미 쓰는 village.css 의 [night] 팔레트 그대로다.
        대비는 오히려 좋아졌다(재계산): text 14.30→15.20 · muted 7.47→7.94 · accent 4.11→4.37.
        ★ 금색 계열(--accent/--accent2)은 그대로 둔다 — 샴페인골드는 이 앱의 정체성이고
          사람이 지운 것은 [브라운 창(표면)]이지 금색 포인트가 아니다. */
    --vg-bg:#10132a; --vg-fg:#f3e9d8; --vg-muted:#b9a98c;
    --vg-line:#262c57; --vg-accent:#a37236; --vg-card:#1a1f3d;
  }
  :root[data-phase="sunset"]{
    --vg-bg:#10132a; --vg-fg:#ffe0c8; --vg-muted:#cfa184;
    --vg-line:#262c57; --vg-accent:#a17458; --vg-card:#1a1f3d;
  }
  :root[data-phase="night"]{
    --vg-bg:#10132a; --vg-fg:#e4e9ff; --vg-muted:#9ba6d2;
    --vg-line:#262c57; --vg-accent:#5c70e6; --vg-card:#1a1f3d;
  }
}

/* 갈아 끼우는 자리는 여기 한 곳이다 — 요소마다 색을 새로 적으면 다음에 하나를 빠뜨린다.
   .village-body 는 마을·놀이터 두 화면에만 붙는 클래스라, 다른 화면(오늘의 카드·리딩 등)은
   앱 기본 테마 그대로 남는다. */
.village-body{
  --bg:var(--vg-bg); --text:var(--vg-fg); --muted:var(--vg-muted);
  --border:var(--vg-line); --accent:var(--vg-accent); --card:var(--vg-card);
  transition:background-color var(--ui-dur) ease-in-out, color var(--ui-dur) ease-in-out;
}
/* 색이 [한 번에 툭] 바뀌면 지시문 3항(튀거나 깜빡이지 않게)에 걸린다 — 지도 안 그림과
   같은 리듬으로 함께 넘어가야 한 장면으로 보인다. */
.village-body .village-back,
.village-body .village-title,
.village-body > .sub,
.village-body .pg-badge,
.village-body .village-map{
  transition:color var(--ui-dur) ease-in-out,
             border-color var(--ui-dur) ease-in-out,
             background-color var(--ui-dur) ease-in-out;
}

/* 첫 페인트 — game-time.js 가 <html> 에 두 프레임만 붙였다 뗀다.
   화면에 들어오는 순간 1.2초짜리 색 전환이 재생되면 [들어올 때마다 시간이 처음부터]
   로 보인다(지시문 4항). 값은 이미 맞은 상태로 들어가고, 전환만 건너뛴다. */
:root.gt-boot .village-body,
:root.gt-boot .village-body *,
:root.gt-boot .village-body *::before,
:root.gt-boot .village-body *::after{ transition:none !important; }

.village-back{
  position:absolute; top:4px; left:14px; color:var(--muted); font-size:.82rem;
  text-decoration:none; border:1px solid var(--border); border-radius:999px; padding:6px 14px;
  z-index:5;
  /* M6-4 신규 - 터치영역 44px 하한(M6-3 §4 신규조문). 폰트 렌더링에 따라 padding만으론
     정확한 44px을 보장 못 해 min-height+flex 중앙정렬로 결정적으로 만든다(pill 모양·글자는 그대로). */
  min-height:44px; box-sizing:border-box; display:inline-flex; align-items:center;
}
.village-back:hover{ color:var(--accent); border-color:var(--accent); }

/* (2026-08-20 정리) audio.js 위젯이 전 화면 공통 좌하단(배너 위)으로 이동해
   더 이상 이 페이지 전용 회피(top:56px)가 필요 없다 — 남겨두면 top+bottom이 동시에
   걸려 위젯이 세로로 늘어난다. */

/* #105 사이클 별건(2026-08-20 실측): style.css의 배지 회피용 h1 padding-right:160px가
   여기서도 제목 가용폭을 160px로 깎아 "도깨비 마을"·"운명의 놀이터"가 2줄로 꺾이고,
   왼쪽으로 밀린 글자가 .village-back pill(y14~58px)과 겹쳤다 — index 216px²·playground 229px².
   #96이 today·cards·history에 쓴 처방(#98-B 2단 구성)과 같은 것을 같은 숫자로 적용한다:
   1행 = 뒤로가기 pill + 은화 배지, 2행 = 제목 전폭. 제목 상단 72px = pill 하단 58 + 14.
   (은화 배지는 y12~48이라 제목과 세로로 안 겹친다 — 실측 겹침 0px², 그래서 우측 여백 불요) */
/* 제목을 위로 — pill 이 top:4 + min-height:44 라 아래끝이 48px 이고,
   padding-top:46 + margin-top:2 = 제목 상단 48px 로 딱 그 아래에서 시작한다.
   (좁은 화면에서 가운데 정렬 제목이 왼쪽 pill 과 세로로 겹치지 않게 하는 것이 #105 의 조문이다) */
/* 좁은 화면: 제목은 pill 아래 전폭(2행). 여백만 바짝 줄인다. */
.village-title{ margin:0 0 1px; padding-right:0; font-size:1.12rem; line-height:1.4; }
.village-body > .sub{ margin:0 0 4px; font-size:.72rem; opacity:.75; line-height:1.25; }

/* 넓은 화면(>=700px): pill·배지 옆으로 제목이 들어갈 자리가 남는다 — 한 줄로 올린다.
   좌우 150px 은 [왼쪽 pill · 오른쪽 은화 배지] 자리를 비워 두는 것이다. */
@media (min-width:700px){
  .village-body{ padding-top:6px; }
  .village-title{ padding:0 150px; line-height:1.5; }
}

/* 배경아트 자체가 실제 그림이라 그 비율에 맞춰 컨테이너를 맞춘다 - 임의로 자르면
   6샵 배치가 어긋나므로 aspect-ratio를 그림 원본 비율 그대로 고정.
   2026-08-20 사람 직접 지시: 배경을 사람이 생성한 세로형 원화(도깨비 마을.png →
   assets/village/village-main.jpg, 1024x1536 = 2:3)로 교체. 세로형이라 폰 화면을
   위아래로 꽉 채우는 풀블리드가 비율 그대로 가능해졌다(M10의 목표). 히트박스·미니미
   % 좌표(index.html doors/.shop, village.js DOORS)는 이 그림 기준으로 전부 재계약됨. */
.village-map{
  position:relative; aspect-ratio:1024/1536; width:auto; max-width:none; flex-shrink:0;
  /* 배너에 짤리지 않게 설계(사람 직접 지시): 씬 높이는 [화면 - 실측 배너 높이 - 머리글]
     안에서만 잡는다 — 어떤 기기에서도 씬 하단이 배너 뒤로 들어가지 않는다.
     세로형 원화라 대부분의 폰에서 dvh 쪽이 선택돼 사실상 전체 화면이 된다.
     body가 flex(center)라 폭이 넘치면 좌우 균등 크롭. */
  height:min(calc(100vw * 1536 / 1024), calc(100dvh - var(--banner-ad-height,0px) - var(--vg-head,104px)));
  min-height:220px;
  /* 2026-08-20 STEP4: 배경을 CSS background 1장에서 <img> 3장(낮·노을·밤)으로 옮겼다.
     여기 남는 것은 그림이 로드되기 전 깜빡임을 막는 바탕색뿐이다. */
  background:#100c17;
  border-radius:0; border:none; border-top:1px solid var(--border); border-bottom:1px solid var(--border);
  overflow:hidden; box-shadow:var(--shadow);
}

/* ============================================================
   낮/밤 — 하나의 변수 --dn (0=낮 … 1=깊은 밤) 이 전부를 제어한다
   ------------------------------------------------------------
   ★ 옛 방식(검은 그라디언트 1장을 mix-blend-mode:multiply 로 덮기)은 **버렸다**.
     곱하기는 모든 픽셀을 같은 비율로 어둡게 만든다 — 창문도 네온도 같이 어두워져서
     [광원]이 생길 수가 없다. 사람 지시가 정확히 그것을 하지 말라고 했다.
   ★ 대신 사람이 만든 원화 3장(낮·노을·밤)이 **구도가 완전히 동일**해서(셋 다 1024x1536,
     건물·길·분수가 같은 좌표) opacity 교차만으로 [같은 장소에서 시간이 흐른다]가 된다.
     다른 맵으로 바뀌는 느낌이 원천적으로 불가능하다.
   ★ 그 위에 광원 레이어를 screen(더하기) 으로 얹어 [각 광원이 따로 밝아진다]를 만든다.
     밤 원화에 이미 불이 켜져 있으므로 이 레이어는 **번짐(bloom)과 깜빡임**만 담당한다 —
     그래서 최대 불투명도가 낮다. 세게 주면 그림이 타 버린다.
   ============================================================ */
.vg-art{ position:absolute; inset:0; z-index:0; pointer-events:none; }
.vg-art img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  display:block; user-select:none; -webkit-user-drag:none;
  /* #165 ④ 0칸 — 하늘이 가장 먼저 움직인다 */
  transition:opacity var(--dn-dur, 2.4s) ease-in-out;
  /* 2026-09-05 메인화면 1차 UI 지시 §1 [전체 색감 강화] — 원화 재생성 대신 부분 필터로 결정했다.
     재생성은 문·광원 30여 곳·걷기영역이 전부 지금 그림의 실측 픽셀 기준이라 다시 그리면 그 전부를
     재계약해야 한다(리스크·시간 큼). 이 필터는 배경 레이어(.vg-art img) 한 곳에만 걸린다 —
     "전체 화면에 필터 금지"는 UI까지 덮는 화면 전체 워시를 금지한 것이므로 위반이 아니다.
     saturate 로 회색기를 줄이고 contrast 로 깊이를 준다. brightness 는 1을 넘지 않는다
     (지시 [너무 어둡게도, 너무 밝게도 하지 않는다] — 낮·노을·밤 세 장에 같은 값을 건다).
     2026-09-06 지시 [실기기 색감 선명도 개선] — 실기기로 보니 그래도 흐릿하다는 재지적.
     화이트 veil·backdrop-filter·오버레이는 다시 찾아봤지만 없었다(.vg-ambient 는 --dn>0.45
     낮에는 0, .vg-lights 는 additive screen 이라 탈색이 아니라 오히려 채도를 더한다) — 그래서
     범위 안에서 값만 올린다. 이전 saturate(1.16) contrast(1.05) → saturate(1.18) contrast(1.06)
     brightness(0.98)(허용 범위 sat 1.12~1.22 · contrast 1.03~1.08 · brightness 0.97~1.00).
     되돌리려면 이 한 줄만 이전 값으로 바꾸면 된다. */
  filter:none;   /* 2026-09-29 마을톤조정 — 등급을 원화에 구웠다. 되돌리려면 saturate(1.18) contrast(1.06) brightness(.98) */
}
/* 낮 : --dn 0.25 까지 1 → 0.45 에서 0 */
/* 2026-09-12 ORIGINAL NIGHT MASTER 복원 — 사람 직접 지시: "DAY 이미지를 NIGHT의 주
   배경으로 사용하지 마세요 · NIGHT 상태 완료 뒤에도 DAY 가 90% 남아 있는 구조는
   제거한다 · DAY → crossfade → NIGHT 이어야 한다". 2026-09-11~12 사이에 얹었던
   floor(.90→.84)·마스크·밤 레이어 감쇄(1→.66)를 전부 걷어내고, §165 원안(낮은
   dn 0.45 에서 완전히 0, 밤은 dn 0.75 에서 완전히 1)으로 되돌렸다. */
.vg-art-day{ z-index:1; opacity:clamp(0, calc((0.45 - var(--dn,0)) / 0.2), 1); }
/* 노을 : 0.25 에서 오르기 시작 → 0.45~0.55 최대 → 0.75 에서 0 (종 모양) */
.vg-art-sunset{
  z-index:2;
  opacity:clamp(0, min( calc((var(--dn,0) - 0.25) / 0.2), calc((0.75 - var(--dn,0)) / 0.2) ), 1);
}
/* 밤 : 0.55 에서 오르기 시작 → 0.75 에서 1(원안 그대로 — 위 낮 opacity 와 대칭인
   크로스페이드). mask-image 도 걷어냈다 — ORIGINAL NIGHT MASTER 원화 전체가 밤의
   주인공이다(사람 지시 "배경 원화 자체가 어떻게 보이는지 먼저 확인"). */
.vg-art-night{ z-index:3; opacity:clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1); }
/* 2026-09-12 ORIGINAL NIGHT MASTER 복원 §1단계 — "brightness(1) saturate(1) contrast(1)
   hue-rotate(0)에 가까운 상태에서 원본 그대로 렌더링해 확인". 낮·노을과 같은 공통
   filter(:198, saturate 1.18·contrast 1.06·brightness .98)조차 밤에는 끄고 원화
   그대로 보인다 — 비교 캡처 뒤 필요하면 이 한 줄만 다시 조정한다. */
/* 2026-09-12 NIGHT MASTER FINAL POLISH §4 — "벚꽃이 일부 죽어 있다, lavender pink/muted
   rose/dusty pink 색 정보가 조금 더 읽히게, 밝기 상승은 최소화". saturate 를 0.85→0.97
   로 올려 채도(색 정보)를 살리고, contrast 를 1.08→1.03 으로 살짝 낮춰 어두운 쪽이
   뭉개지지 않게 했다 — brightness 는 0.62→0.64, 거의 그대로다(전체를 다시 밝히지
   않는다는 금지 조항을 지킨다). */
.vg-art img.vg-art-night{ filter:brightness(0.64) saturate(0.97) contrast(1.03); }

/* ════════════════════════════════════════════════════════════════
   2026-09-12 [NPC DEPTH / CTA SAFE ZONE 2차 보완] §1~§3 — 건물 4곳
   FOREGROUND OCCLUSION. 새 그림이 아니다 — 같은 배경 원화 3장(day·
   sunset·night)을 **한 번 더** 그리고 그중 건물 처마 아래 상자만
   clip-path 로 남긴다(나머지는 원래 배경과 완전히 같은 픽셀이라
   [보이지 않는] 크롭이다 — 이중선·흰 틈·검은 틈이 날 수가 없다,
   구조적으로 같은 그림을 같은 자리에 겹쳐 그릴 뿐이다).
   ★ z-index 만 NPC(village.js `10+top*3`)보다 높게 준다 — 그 상자
   안에서 NPC 의 top 이 건물의 [처마 아래선](t2, village-walkgraph.js
   구멍 배열의 이미 실측된 값)보다 작을 때만 그 NPC 의 z 가 이 값보다
   낮아 **자동으로** 가려진다(밖에서 top 이 크면 자동으로 앞선다 —
   매 프레임 JS 가 켜고 끄는 게 아니라 이미 있는 z 공식이 그대로 한다).
   좌표 출처 — 사주/타로/도사 세 문(box)은 village-walkgraph.js 구멍
   배열의 실측 그대로(새로 안 잰다). 처마선(위 경계)만 이번에 1%p
   격자 스크린샷으로 새로 쟀다(사주 t36 · 타로 t39 · 도사 t39 — 셋 다
   지붕이 그 위, 문·창이 그 아래). 정문은 이미 있는 P0 실측(구멍
   "정문 왼기둥/오른기둥" t69 = 처마 아래선)에 지붕 폭 t65~69 만 얹었다
   (기둥 t69~74 는 안 넣는다 — NPC 가 l41.5~46·54~58.5 를 절대 안
   걷는다, 걸어도 되는 칸은 항상 l46~54 뿐이라 기둥을 가릴 일 자체가
   없다). 상자보다 넓게 안 잡는다 — 넓게 잡을수록 [건물과 무관한 먼
   NPC 도 이 좁은 화면 자리를 지날 때만 우연히 영향받는다]는 안전판이
   좁아진다(원리는 같은 그림 겹침이라 안전하지만, 자리를 정확히 좁게
   잡는 편이 왜 이 값인지 다음 사람이 읽기 쉽다). ════════════════════ */
/* ★ 마크업은 <div class="vg-art vg-art-occ vg-art-occ-*"> — `.vg-art` 를 그대로 물려받아
   (position/inset/pointer-events · img 의 position/size/object-fit/transition · 밤 filter ·
   day/sunset/night 크로스페이드 opacity 식) 한 줄도 새로 안 만든다. 여기서는 z-index 와
   clip-path 딱 둘만 얹는다 — `.vg-art{z-index:0}` 보다 **뒤**에 와야 이긴다(원본이 위에 있다). */
/* 2026-09-14 [VISUAL P0 — 미니미 도로 통과 시 캐릭터 잘림 FOREGROUND OCCLUSION 정밀 수정] —
   사주관만 수정(사람 지시 범위 — tarot/dosa/gate 무변경). z-index(134)·NPC z 공식(10+top*3)은
   그대로 둔다 — 원인은 z 값이 아니라 [발 앵커가 처마선보다 한참 남쪽(t43~44, 일반 도로)에
   있어도 캐릭터 스프라이트의 시각적 높이(실측 물리픽셀 h≈158px ≈ 지도높이의 6.3%)가 이
   고정 clip 사각형 아래쪽까지 닿는 것]이었다 — clip-path 는 스프라이트 높이를 전혀 모른다.
   실기기 A/B 렌더 비교(t43/l50·t44/l52 두 지점, occlusion 개별 on/off): bottom 58.8~60.5%
   전부 여전히 머리/상체가 잘렸고, **61.5%에서 두 지점 모두 완전히 정상**이었다(그 아래
   61.0%는 기기 디스플레이 셀렉션 결함으로 검증 못함 — 61.5%는 육안 확인 2회로 확정).
   top(36%)·right(43%)·left(43%)는 실측 그대로 안 건드린다 — 처마 아래 실제 가림(문 앞·
   문턱·처마 뒤)은 그대로 유지된다(사주 현관 t41.67 은 새 경계 t39.0 보다 훨씬 남쪽이라
   entry/exit 트윈이 쓰는 문턱(threshold, village.js §7 의 z 공식 기반 — 이 파일과 무관)도
   영향 없다). */
/* 2026-09-19 [NEW VILLAGE WORLD SYSTEM — 실기기 실측으로 발견한 이중 클리핑 버그 수정]
   이 clip-path 는 2026-09-17 [LIGHTING PASS 4차 수정](:317 아래 주석)에서 "clip-path 는
   다시 안 쓴다"고 이미 폐기된 값인데 실제로는 지워지지 않고 남아 있었다 — box(top/right/
   bottom/left, :330 아래)가 이미 작게 잘라놓은 요소 위에 같은 비율의 clip-path 를 자기
   자신 기준으로 또 적용해 실제 보이는 영역이 원래 의도의 15%도 안 남았다(실기기 픽셀 스캔:
   기대 85x85 CSS px, 실측 35x19 device px). z-index 만 남기고 clip-path 는 완전히 지운다. */
.vg-art-occ-saju{  z-index:134; }
/* 2026-09-15 [사람 지시 #4 — 타로집·도사집 미니미 통과] — 위 사주관과 **같은 원인**
   [clip-path 는 스프라이트 실제 시각적 높이를 모른다]. 1차 시도(+2%p)로는 부족해서
   실기기에서 진짜 입장 시퀀스를 붙잡아(village.js 의 `_건물출입` 을 60ms 폴링) 그
   순간의 캐릭터 실측 상자를 직접 쟀다(2026-09-15 재확인) — 도사: z 가 occZ(156) 밑으로
   내려간 마지막 프레임에서 캐릭터 상자가 **t48.49~54.81%(세로 6.32%p)** 인데, 그때
   가림 사각형은 t39~50.5% 까지만 있어 **아래쪽 4.3%p 가 그대로 노출**됐다(다리만 보이고
   상체가 사라지는 그 착시). 타로도 문턱(47.82%)이 비슷해 같은 폭(≈6.3%p)이 필요하다고
   보고 같은 만큼 늘렸다 — bottom-inset 을 50%/49.5% 에서 **44%** 로(가림 아래 경계
   56%, 실측 필요치 54.8%보다 1.2%p 더 여유) 낮췄다. top·left·right(문 앞·문턱 가림)는
   실측 그대로 안 건드렸다. */
/* 2026-09-17 버그수정 [미니미가 건물을 그냥 통과하는 것처럼 보임] — 이 clip 창(y 39~56%)이
   실측(위 §)대로 세로 17%p 로 넓어, **입장하지 않고 그냥 지나가거나 잠깐 서 있는** 보통 NPC
   도 그 안에 들어간다. n-타로대기(t49.30)·n-타로현관(t48.96) 의 z(10+round(top*3))는
   157~158 인데 occZ 154 와 겨우 1~4 차이라, 반올림·벌리기(옆사람과 간격 벌릴 때 top 도
   같이 밀리는 조문13)만으로도 occZ 밑으로 떨어져 **입장 시퀀스가 아닌 보통 대기/통행
   중에도** 하반신이 가려 보였다(실기기 신고 스크린샷 — 몸은 앞인데 다리만 벽 속). 입장
   문턱(village.js 건물입장표준비)은 occZ 에서 자동으로 다시 계산되므로 여기 숫자만 내리면
   같이 안전해진다 — 문턱 좌표·클릭·문 애니메이션은 한 글자도 안 건드린다. 마진을 154→1
   에서 144→13(현관 z157 기준)으로 늘렸다. */
.vg-art-occ-tarot{ z-index:144; }
.vg-art-occ-dosa{  z-index:144; }
/* 2026-09-17 버그수정 [대문 통과 — 머리만 잘리고 몸은 그대로 보임] — 실측(실기기 CDP,
   VillageNpcDebug.위치옮기기 로 t60~74 강제 배치 후 getBoundingClientRect): 미니미 실제
   세로 길이가 화면 65~72px(지도 % 로 약 7%p)인데 가림 창은 65~69%(4%p)뿐이었다 — 몸 전체가
   가려질 수 없는 폭이라 z 가 occZ 밑으로 내려가도 머리(위쪽 일부)만 창 안에 걸려 잘리고
   나머지 몸은 창 밖이라 그대로 노출됐다(첨부 스크린샷과 실측 일치). 창을 몸 높이보다
   넉넉히 키운다(65~80%, 15%p) — 문·기둥 좌표(left/right 41.5%)는 그대로 둔다. */
/* 2026-09-19 [GATE OCCLUSION — Z-ORDER ONLY HOTFIX 사람 지시] — 옛 217은 NPC z 공식
   (자리쓰기(): z = 10 + round(top*3))과 무관한 매직넘버였다. 이 상자의 하단 depth가
   top:71.77%(=100-28.23, 아래 .vg-art-occ-gate 좌표) 이므로 그 depth의 NPC z 는
   10+round(71.77*3)=225 다. 217 < 225 라 NPC가 이 상자 하단부(top 69~71.77%, z 217~225)를
   지날 때 z가 옥클루전보다 높아져 지붕 위를 걷는 것처럼 보였다(+12% NEW MINI 스케일로
   상체가 이 상자 영역에 실제로 걸치면서 드러남). 상자 top/bottom·walkgraph·NPC 배율은
   손대지 않고 이 z 하나만 225→226(경계 타이 여유 1) 으로 올린다 — 999 같은 큰 값은 쓰지
   않는다(그러면 게이트 남쪽/앞쪽 NPC까지 지붕 뒤로 숨는다). */
/* 2026-09-20 [MAIN GATE z 250 — 사람 결정] 가림층이 #village-npc-layer 안으로 들어가 NPC z 와 실제로
   비교되자(v0.1.748) top 72~74 NPC 가 z226 을 넘어 [지붕 위에 선] 그림이 됐다. 게이트의 실제 지면 접점
   (기둥 받침 아래선)은 ≈ top 80% — 같은 공식 10+round(80*3)=250. 전역 NPC z 공식은 안 바꾼다. */
.vg-art-occ-gate{  z-index:250; }

/* 2026-09-17 [LIGHTING PASS 중 실기기 발견 — occlusion 버그, 4차 수정] — 조명 작업과
   무관한 기존 결함(실기기 SM-F966N WebView 전용, 헤드리스 크롬에선 재현 안 됨). 앞선
   세 시도(loading=lazy 제거 · clip-path→작은요소 · <img>→background-image, v0.1.706·
   707·709)는 전부 원화를 [몇 백~4000%까지 확대해 그리는 것] 자체는 그대로 둔 채 CSS
   기법만 바꿨다 — 셋 다 실기기 재현이 그대로여서 원인이 바로 그 확대 자체라고 판단했다.
   이번엔 index.html 의 img src 를 `occ-<건물>-<시간대>.jpg`(12장, app/tools 로 미리
   원화에서 정확히 잘라 둔 것 — 손으로 안 잘랐다)로 바꿨다. 이 작은 그림은 이 작은
   상자에 **거의 1배 축척**으로 들어가 극단적 확대가 사라진다. 컨테이너는 §2 시도의
   [작은 상자] 그대로 재사용(제일 가볍다) — clip-path 는 다시 안 쓴다. */
.vg-art-occ-saju,  .vg-art-occ-tarot, .vg-art-occ-dosa, .vg-art-occ-gate{
  /* position:absolute 는 옛 마크업의 `vg-art` 클래스가 주던 것 — 이번에도 그 클래스를
     뺐으므로(마크업이 <img> 만 남아 더 안 어울려서) 직접 채운다. 안 채우면 §3 에서
     겪은 그 회귀(지도 레이아웃 전체 붕괴)가 그대로 재현된다 — 꼭 있어야 하는 줄이다. */
  position:absolute; overflow:hidden;
  /* 2026-09-19 [LIVE DEVICE FINAL QA] pointer-events:none 도 옛 `.vg-art` 클래스가 주던
     것인데 같이 빠졌다 — 실기기 CDP 실측(elementsFromPoint): 타로/도사 히트박스 중심이
     이 장식 레이어(z144)에 가려 클릭이 .shop-1/.shop-5 까지 안 뚫렸다. 되돌린다. */
  pointer-events:none;
}
.vg-art-occ-saju{  top:23.0%;  right:35.18%; bottom:74.5%;  left:38.79%; }
.vg-art-occ-tarot{ top:35.0%;  right:63.34%; bottom:56.04%; left:11.16%; }
.vg-art-occ-dosa{  top:35.0%;  right:14.98%; bottom:56.34%; left:58.98%; }
/* 2026-09-17 버그수정 시도 되돌림 [대문 머리 잘림] — 상자를 65~80%로 키웠더니(직전 커밋)
   사전에 좁은 상자(65~69%) 기준으로 잘라 둔 occ-gate-night.jpg 가 늘어나며 큰 검은
   사각형으로 깨졌다(실기기 스크린샷 확인 — 남자 캐릭터 하반신을 덮는 검은 박스).
   이미지가 새 상자보다 먼저 정리돼야 해서 **원래 크기로 되돌린다** — 이 사이클엔 손대지
   않는다. 대문 머리잘림 수정은 occ-gate-*.jpg 세 장(day/sunset/night)을 이 새 상자
   비율(65~80%, 41.5~58.5%)에 맞게 다시 잘라 온 뒤에 상자를 키워야 한다(다른 세션이
   만든 사전크롭 도구를 찾거나 새로 만들어야 함 — 다음 사이클 몫). */
/* 2026-09-19 [MAIN GATE OCCLUSION — FINAL RECROP FIX 사람 지시] — 941x1672 WORLD MASTER
   원화를 직접 실측(그리드 오버레이, app/tools 임시 스크립트): 옛 상자(top62.8~bottom28.23%
   =화면 1049.8~1199.9px)는 자체 상단 40px가 이미 지붕이 아니라 길바닥 색이었고(가짜 가림),
   그러면서도 실제 지붕 최고점(처마 끝 ~y1065)보다도 낮아 +12% NEW MINI 머리를 못 덮었다.
   원화 실측 결과 처마 좌우 끝 ~y1065, 지붕 능선 ~y1095, 보/공포 하단 ~y1225 (마스터 941x1672
   기준, x330~610). 이 실측 박스로 교체 — 사각형 그대로 쓰지 않고 처마 곡선을 근사하는 파라볼라
   알파마스크(occ-gate-*.png, RGBA)를 새로 구웠다(app/www/assets/village/occ-gate-*.png) —
   하늘/길/꽃밭이 사각형으로 덮이지 않는다. z-index(226)·SAJU/TAROT/DOSA는 손대지 않는다. */
.vg-art-occ-gate{  top:63.40%;  right:35.18%; bottom:26.44%; left:35.07%; }
/* 2026-09-20 [OCCLUSION STACKING CONTEXT FIX] 가림층이 #village-npc-layer 안으로 들어가 `.village-npc-layer img`(:921,
   NPC 밝기 filter·transition:filter)와 명시도(0,1,1)가 같아졌다 — 뒤에 오는 그쪽이 이기면 원화 filter 와 opacity
   크로스페이드가 사라진다. .vg-art-occ 를 겹쳐 (0,2,1)로 올린다. 밤 규칙(:367, 같은 명시도·뒤)은 그대로 이긴다. */
.vg-art-occ.vg-art-occ-saju img, .vg-art-occ.vg-art-occ-tarot img, .vg-art-occ.vg-art-occ-dosa img, .vg-art-occ.vg-art-occ-gate img{
  /* 이 img 들은 이미 그 상자 크기에 맞춰 잘려 있다(occ_crop.py) — 그러므로
     width/height:100% 는 그냥 그 상자를 채우는 것뿐, .vg-art img(:190)의 [지도 전체를
     100%/100%로 늘려 그리기]와는 다르다(그래서 확대율이 거의 1배다). object-fit:cover
     는 반올림 오차 1px 안쪽만 완충한다. position/inset/opacity(day/sunset/night
     크로스페이드)는 :208~223 의 클래스 선택자가 그대로 준다(새 전환 로직 0줄). */
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:opacity var(--dn-dur, 2.4s) ease-in-out;
  filter:none;   /* 2026-09-29 마을톤조정 — 등급을 원화에 구웠다. 되돌리려면 saturate(1.18) contrast(1.06) brightness(.98) */   /* .vg-art img 공통 필터(:206)와 동일 */
}
.vg-art-occ-saju img.vg-art-night, .vg-art-occ-tarot img.vg-art-night,
.vg-art-occ-dosa img.vg-art-night, .vg-art-occ-gate img.vg-art-night{
  filter:brightness(0.64) saturate(0.97) contrast(1.03);  /* .vg-art img.vg-art-night(:233)와 동일 */
}

/* 2026-09-13 [MAIN PREMIUM POLISH PHASE 3 — LAKE LIVING REFLECTION] — 2026-09-12
   PREMIUM LIVING WORLD §3 이 만든 이 요소를 그대로 재사용한다(새 레이어 0개 · 자리
   top/height/left/width 는 그 실측 그대로 안 바꾼다 — §1·§11). 그날은 [밤에만 나타나는
   옅은 색조] 하나였는데(최대 .34, dn>0.55 에서만), 이번 지시는 DAY·SUNSET·NIGHT 셋 다
   각자의 색으로 항상(아주 옅게) 있어야 한다 — 그래서 세 그라디언트를 **한 `background`
   안에 콤마로 겹쳐** 넣었다(새 DOM 0개, §4 "복잡한 다중 레이어 금지"는 소스 하나·형태
   하나를 유지하는 것으로 지킨다). 각 그라디언트의 투명도는 opacity 가 아니라 색상 자체의
   알파(`rgb(r g b / calc(...))`)에 굽는다 — **그래야 element 의 `opacity` 속성이
   비어 있어** 아래 §5~§7 "숨쉬기" 애니메이션이 그 자리를 그대로 쓸 수 있다(같은 속성을
   두 목적이 다투지 않는다). 세 가중치 식은 배경 원화 크로스페이드(:214·:218·:223)와
   **글자 그대로 같다** — 그래서 낮→노을→밤 전환이 이미 검증된 그 타이밍에 자동으로
   맞물린다(§8, 새 전환 로직 0줄).
   색 방향(§2) — DAY: soft gold(#F3D9A0)+peach(#F4C4A8) · SUNSET: gold(#E8C46E)+
   rose·약한 pink 혼합(#E19AAA, 정적 70/30 사전 혼합값) · NIGHT: muted lavender·약한
   rose-gold 혼합(#CAB6DC, 정적 85/15 사전 혼합값)+silver(#B8C6E6, 기존 값 그대로).
   피크 알파(§3) — DAY .06 · SUNSET .07 · NIGHT .34(2026-09-17 원복 — 09-12 FINAL POLISH 가
   .06 으로 낮췄던 것을 원안으로 되돌림, 회귀 감사 A~F 보고 참고). 가운데(44~56%)는
   여전히 비워 둔다(금빛 중심 반사 자리, 안 건드림). */
.vg-lake-glow{
  position:absolute; top:19%; height:8%; left:16%; width:68%;
  pointer-events:none; z-index:3;
  background:
    linear-gradient(to right,
      transparent 0%,
      rgb(243 217 160 / calc(clamp(0, calc((0.45 - var(--dn,0)) / 0.2), 1) * .06)) 16%,
      rgb(244 196 168 / calc(clamp(0, calc((0.45 - var(--dn,0)) / 0.2), 1) * .06)) 34%,
      transparent 44%, transparent 56%,
      rgb(244 196 168 / calc(clamp(0, calc((0.45 - var(--dn,0)) / 0.2), 1) * .06)) 66%,
      rgb(243 217 160 / calc(clamp(0, calc((0.45 - var(--dn,0)) / 0.2), 1) * .06)) 84%,
      transparent 100%),
    linear-gradient(to right,
      transparent 0%,
      rgb(232 196 110 / calc(clamp(0, min(calc((var(--dn,0) - 0.25) / 0.2), calc((0.75 - var(--dn,0)) / 0.2)), 1) * .07)) 16%,
      rgb(225 154 170 / calc(clamp(0, min(calc((var(--dn,0) - 0.25) / 0.2), calc((0.75 - var(--dn,0)) / 0.2)), 1) * .07)) 34%,
      transparent 44%, transparent 56%,
      rgb(225 154 170 / calc(clamp(0, min(calc((var(--dn,0) - 0.25) / 0.2), calc((0.75 - var(--dn,0)) / 0.2)), 1) * .07)) 66%,
      rgb(232 196 110 / calc(clamp(0, min(calc((var(--dn,0) - 0.25) / 0.2), calc((0.75 - var(--dn,0)) / 0.2)), 1) * .07)) 84%,
      transparent 100%),
    linear-gradient(to right,
      transparent 0%,
      rgb(202 182 220 / calc(clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1) * .34)) 16%,
      rgb(184 198 230 / calc(clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1) * .34)) 34%,
      transparent 44%, transparent 56%,
      rgb(184 198 230 / calc(clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1) * .34)) 66%,
      rgb(202 182 220 / calc(clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1) * .34)) 84%,
      transparent 100%);
  background-size:140% 100%; background-position:0% 0%;
  mix-blend-mode:soft-light;
  opacity:1;
  /* 2026-09-17 사람 지시 — 실기기에서 이 띠의 위/아래 경계가 "CSS overlay 박스"처럼
     또렷이 보인다는 지적(스크린샷 확인 — .vg-lake-glow 의 실측 rect 가 그 경계와 정확히
     일치했다, y143~220). 원인은 배경이 **가로 방향 그라디언트뿐**이라 위/아래는 요소
     자체의 사각형 테두리에서 뚝 끊기는 것이었다 — 그라디언트·색·알파(§2~§8 값)는 한
     자리도 안 건드리고, mask 로 위/아래만 부드럽게 페더링한다(라디알로 바꾸면 가로
     방향의 기존 다단 그라디언트 로직을 전부 다시 짜야 해서 더 위험하다). */
  -webkit-mask-image:linear-gradient(to bottom, transparent 0%, black 35%, black 65%, transparent 100%);
  mask-image:linear-gradient(to bottom, transparent 0%, black 35%, black 65%, transparent 100%);
}
/* 숨쉬기(§5~§7) — 38초(권장 30~45s 안), 6개 불균등 키프레임이라 왕복처럼 안 읽힌다.
   opacity 는 위 그라디언트 알파와 곱해지므로(별도 속성) 0.93~1.04 의 미세한 곱셈만
   더한다 — 실제 화면 알파는 여전히 §3 피크(.06~.07)의 ±수%대다. background-position
   은 ±1%p 안(§6, 기존 14s/±7% 에서 줄였다) — background-size:140% 라 범위 밖으로 나가도
   가장자리가 드러나지 않는다(그라디언트는 정의역 밖에서 자기 끝 색 그대로 이어진다). */
@media (prefers-reduced-motion: no-preference){
  .vg-lake-glow{ animation:vg-lake-breathe 38s ease-in-out infinite; }
  @keyframes vg-lake-breathe{
    0%   { background-position:0%    0%; opacity:1;    }
    20%  { background-position:0.9%  0%; opacity:0.93; }
    42%  { background-position:0.3%  0%; opacity:1.04; }
    61%  { background-position:-0.8% 0%; opacity:0.96; }
    83%  { background-position:-0.3% 0%; opacity:1.02; }
    100% { background-position:0%    0%; opacity:1;    }
  }
}
.village-map.vg-lite .vg-lake-glow{ animation:none; }

/* 2026-09-12 NIGHT MASTER FINAL POLISH §3 — "중앙 PLAYABLE AREA(돌길·놀이터·정문~세
   건물 동선)만 8~15% 부드럽게, 흰색 overlay 금지, 달빛+등불 간접광처럼, 가장자리 숲·
   꽃밭은 깊은 밤 유지". 새 div 하나(.vg-night-corridor) — 따뜻한 달빛색 radial-gradient
   를 soft-light 로 아주 약하게 얹는다(핵심 .14 뿐이다). 중심은 사주관~정문 축(t56%)·
   건물 3채 폭(l50%) 이고, 타원 반경 밖(가장자리 숲·꽃밭)에서는 자연히 0 이 된다. */
/* 2026-09-17 [LIGHTING/VISIBILITY REMASTER] 사람 지시 §2·§7 — 배경 원화·건물·등불
   좌표는 전혀 안 건드리고(재생성 금지) 이 오버레이 하나만 조정한다. ① 타원을 살짝
   넓혀(36→40% · 27→31%) 세 건물 앞(문·처마 아래)까지 닿게 한다 — §3 "입구가 클릭
   가능함을 인지"를 새 레이어 없이 지원한다. ② 핵심 알파를 .14→.19 로 올려 돌길·NPC
   가독성을 8~15%→조금 더 보탠다(사람 지시 "+15~20% 체감", 여전히 §8 상한인 .34
   보다 한참 낮다 — 호수 반사 피크값과 안 겹치게). ③ 바깥 stop 색만 ivory→ivory-
   lavender 로 틀어 "soft ivory-lavender ambient moonlight"(사람 지시 §2)를 만든다 —
   중심(돌길과 맞닿는 자리)은 그대로 따뜻한 ivory 로 남겨 §2 "warm stone" 요청과
   §8 "보라색 tint 금지"를 동시에 지킨다(라벤더는 가장자리 전환에서만, 아주 옅게). */
/* 2026-09-17 2차 재지시 — "중앙 전체가 흰 안개처럼 밝아짐, ambient light 크게 축소".
   위 REMASTER 판(타원 40/31·핵심 .19)이 과했다 — 원안(36/27·.14)보다도 더 낮춘다. */
.vg-night-corridor{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  background:radial-gradient(ellipse 33% 24% at 50% 56%,
    rgba(255,246,222,.11) 0%,
    rgba(250,240,230,.06) 50%,
    rgba(224,214,232,.03) 78%,
    transparent 100%);
  mix-blend-mode:soft-light;
  opacity:clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1);
  transition:opacity var(--dn-dur, 2.4s) ease-in-out;
}

/* 2026-09-17 [LIGHTING PASS — BUILDING LIGHT] 사람 재지시 — "밝기 자체보다 광원의 계층이
   없는 것". 위 .vg-night-corridor(중앙광장 달빛)와 .vg-walk(돌길)만으로는 세 건물이
   정원과 비슷한 명도로 남는다는 지적. 건물 원화·문·창문·좌표는 전혀 새로 안 잰다 — 이미
   있는 문 좌표(.door[data-door] top/left · index.html:270~271)와 건물 상자(.shop-1/-3/-5
   top/left/width/height · index.html:347·357·365)를 그대로 쓴다.
   ★★ 첫 판은 soft-light 로 짰는데, 실측(헤드리스 캡처 픽셀 대조 — 글로우 중심 화소가
   before/after 완전 동일값)으로 **어두운 배경 위에서 soft-light 는 사실상 0에 가까운
   효과**임을 확인했다(soft-light 공식은 낮은 base 값일수록 보정폭이 줄어드는 성질이라,
   이미 어두운 야간 배경 화소에서는 알파를 올려도 거의 안 보인다). 그래서 이 앱이 실제
   창문/가로등/등불(.vg-light 전부)에 **이미 쓰고 있는** mix-blend-mode:screen 으로
   바꿨다 — 새 블렌드 기법이 아니라 이미 검증된 것을 그대로 재사용한 것이다. "네온처럼
   되면 안 된다"는 블렌드 모드가 아니라 **채도 낮은 색 + 작은 반경 + 낮은 알파**로 지킨다
   (.vg-light.is-neon 이 이미 이 방식으로 "네온 금지"를 지키고 있다 — 같은 원리).
   ★★ z-index 4(=1차 시도)로 두니 화소 대조에서 도사집·타로집 글로우 중심이 before/after
   완전 동일값이 나왔다 — 원인은 밝기가 아니라 **가려짐**이었다. NPC 가림용 occlusion
   레이어(.vg-art-occ-saju/tarot/dosa, z134~156 · :274~286)가 처마 아래~문 앞 대역을
   [원화를 다시 그려] 덮는데, 그 대역이 하필 이 글로우들의 자리와 거의 겹친다(타로집은
   occlusion 상자가 건물 앞면 대부분을 덮는다 — 문 좌표 자체가 그 안에 있다). 그 좁은
   대역을 피해 좌표를 다시 잡는 대신(건물마다 모양이 달라 안전 영역이 들쭉날쭉하다),
   이 레이어의 z-index 를 occlusion 최댓값(217, 정문) 보다 위로 올린다 — occlusion 이
   있든 없든 항상 보이게 된다. NPC 가 그 좁은 대역을 지나는 순간에만 이 옅은 빛(알파
   .05~.16)이 NPC 위에 살짝 겹칠 수 있으나, 세기가 낮아 실사용에서 거의 안 보인다. */
.vg-entry-glow{
  position:absolute; inset:0; z-index:230; pointer-events:none;
  mix-blend-mode:screen;
  opacity:clamp(0, calc((var(--dn,0) - 0.45) / 0.25), 1);
  transition:opacity var(--dn-dur, 2.4s) ease-in-out;
}
/* 사주관 — 문(.door[data-door="shop-3"] top38.5 left49.7) 위에서 돌계단 쪽(남쪽)으로
   타원을 길게 늘여 "현관→돌계단 짧은 빛 번짐"을 준다. 기존 사주관 창문 색(--c:#FFD6AB·
   #FFE9A9, index.html:200~201)과 같은 warm champagne/amber 계열 — 새 색 0개.
   screen 은 soft-light 보다 훨씬 세게 들어오므로 알파를 낮췄다(.32→.16 코어). */
/* 2026-09-19 [NEW VILLAGE WORLD SYSTEM] 위치만 새 사주집 문(.door[data-door="shop-3"]
   top23.33 left49.95, index.html:284) 기준으로 재배치 — 문→돌계단(남쪽) 델타(+2.5%p)는
   그대로 유지, 타원 크기·색·알파는 한 글자도 안 건드렸다. */
.vg-entry-glow.vg-entry-saju{
  background:radial-gradient(ellipse 9% 13% at 49.95% 25.83%,
    rgba(255,221,175,.16) 0%, rgba(255,221,175,.07) 45%, transparent 78%);
}
/* 타로집·도사집용 vg-entry-tarot·vg-entry-dosa-gold·vg-entry-dosa-teal 은 2026-09-17
   실기기 재확인에서 "건물 자체가 아니라 주위가 안개처럼 밝아진다"는 지적으로 걷어냈다
   (DOM 도 index.html 에서 제거됨). 그 대신 아래 is-window/is-neon/is-lantern 의 색·
   반경·세기(그 밑 [FINAL BUILDING LIGHT PASS] 절)를 직접 조정하는 쪽으로 되돌렸다 —
   "기존 레이어가 있으면 새 레이어를 만들지 말고 그것을 조정한다"(사람 지시). */

/* ── 보이는 산책로 (village-walkpath.js 가 그린다) ─────────────
   2026-09-02 사람 지시 §2·§3 — [보이는 길 = 미니미가 걷는 영역].
   정본은 `village-walkgraph.js` 하나고, 같은 그래프를 `걷는영역굽기.py` 가 굽는다.

   ★ z-index 3 — 원화(.vg-art z0) 위, 광원(.vg-lights z4) **아래**다.
     광원은 mix-blend-mode:screen 이라 [자기 아래에 깔린 것]에 섞인다. 길을 광원보다
     아래 두어야 밤에 등불이 **길을 비춘다.** 위로 올리면 길만 등불빛 밖에 뜬다.
     (.vg-art 안의 z1~3 은 .vg-art 가 만든 스택 문맥 안쪽 값이라 여기와 겹치지 않는다)
   ★ 색은 원화에서 잰 값이다(8px 평균 · 2026-09-02, 옛 원화 기준):
     길 #D5BCA6 · 잔디 #75776E · 밤 길 #5D5762. 오버레이는 원화보다 밝지 않다 —
     덧칠이 아니라 [밟아 쓸린 자리]로 읽히는 세기여야 한다.
   ★★ 2026-09-10 재보정 — PREMIUM MASTER 배경 교체(자산\이미지로그.md 2026-09-10
     [메인 마을 배경 — PREMIUM MASTER로 전면 교체]) 뒤 이 옛 색이 새 배경 위에서 탁한
     회갈색 얼룩으로 떠 보인다는 지적(사람 신고 — 청록 천막 아래~중앙 광장~정문 위).
     새 배경(village-day-20260910.jpg)에서 같은 8px(중앙값) 방식으로 길을 다시 쟀다:
     길 #BC8F5F(188,143,95). soil·rim 은 옛 판이 길 대비 갖던 밝기비(soil ≈0.70·0.71·0.65,
     rim ≈0.66·0.64·0.57 — R·G·B 채널별)를 새 길색에 그대로 곱해 만들었다 — 세 겹의
     관계(밟아 쓸린 순서)는 그대로 두고 색상만 새 배경에 맞춘다. 오버레이 자체를 없애지
     않는다 — 지시 [원인이 UI면 그 요소만 최소 수정, 배경/다른 UI는 그대로].
   ★ 세기를 바꾸려면 --vg-walk-a 하나만 만진다(0 이면 안 보이고, 걷는 영역은 그대로다).
   ★★★ 2026-09-10 (2차) — 색만 고친 뒤에도 [문기둥 위부터 얼룩덜룩]이 남는다는 재지적.
     원인은 색이 아니라 **겹침**이다 — village-walkpath.js 가 길을 soil/body/rim 세 겹의
     타원·경로로 그리는데, 정문 바로 위(원이 좁은 문 진입로에서 넓은 원형 광장으로 벌어지는
     자리)에서 그 타원들이 여러 장 겹쳐 반투명이 반투명 위에 또 쌓인다 — 격리해서(.vg-art 를
     끄고 이 층만) 찍어 보면 그 겹침이 둥근 안개 뭉치로 또렷이 보인다(재현 확인).
     원화·문·광원·걷기 로직은 그대로 두고 **전체 세기 하나만** 낮춘다 — 겹치는 절대량이
     줄면 겹친 자리와 안 겹친 자리의 차이도 같이 준다. 1 → .5 (그 아래 .13/.22/.26 은
     상대 관계 유지, 실측: 문기둥 위 안개 뭉치 사라짐 · 길은 여전히 [밟아 쓸린 자리]로 보임). */
.vg-walk{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  opacity:var(--vg-walk-a, .5);
  /* 밤이 되면 원화와 같이 어두워진다 — --dn 은 <html> 에 있고 원화 교차가 쓰는 그 변수다.
     2026-09-17 [LIGHTING/VISIBILITY REMASTER] 사람 지시 §2·§7 "돌길 가독성 우선 개선 ·
     warm stone/muted moonlight 계열로". 계수만 낮춘다(0.52→0.40 · 0.30→0.20) — 길
     타일 자체(soil/body/rim 세 겹 색)·낮 밝기·opacity 상한은 그대로다. 밤(dn≈0.92)
     기준 brightness 0.52→0.63(+21%)·saturate 0.72→0.82 로, 원화보다 밝아지지 않는
     §373 원칙 안에서(옛 밝기 0.52 도 원화보다 어두웠다) 돌길만 살짝 더 보인다. */
  filter:brightness(calc(1 - var(--dn,0) * 0.40)) saturate(calc(1 - var(--dn,0) * 0.20));
  transition:opacity var(--dn-dur, 2.4s) ease-in-out;
}
.vg-walk .vgw-soil{ stroke:rgba(132,101,62,.13); fill:rgba(132,101,62,.13); }
.vg-walk .vgw-body{ stroke:rgba(188,143,95,.22); fill:rgba(188,143,95,.22); }
/* 잔디와 만나는 선. 길이 [길]로 읽히게 하는 것은 밝기가 아니라 이 선이다 —
   2026-09-10 재보정 — 새 배경의 길 가장자리 흙빛(#7C5B36 언저리)으로 다시 맞췄다. */
.vg-walk .vgw-rim{ fill:rgba(124,91,54,.26); }
.vg-walk .vgw-core{ stroke:rgba(236,218,192,.13); fill:none; }
.vg-walk .vgw-stone{ fill:rgba(246,234,212,.30); }
/* 저사양/애니메이션 끄기에서는 필터 하나라도 줄인다(페인트 비용). 길은 그대로 보인다. */
.village-map.vg-lite .vg-walk{ filter:none; }

/* ── 광원 레이어 ────────────────────────────────────────────
   위치·색은 감으로 정하지 않았다 — `village-time-night.jpg` 를 48x72 격자로 훑어
   [주변보다 밝은 칸]을 뽑아 그 좌표와 평균색을 그대로 썼다(2026-08-20 실측).
   상점 네온 6색만 사람이 준 값(요청문 4절)을 쓴다.                                */
.vg-lights{ position:absolute; inset:0; z-index:4; pointer-events:none; }
.vg-light{
  position:absolute; left:var(--x); top:var(--y); width:var(--r,14%); aspect-ratio:1;
  /* 2026-09-12 NIGHT MASTER FINAL POLISH §6 — "떠다니는 광점 대폭 정리 · 큰 halo 금지".
     어제 얹은 bloom 꼬리(32→58→88%)를 걷어내고 원래 2단 그라데이션(0→32→72%)으로
     되돌린 데다, 크기 자체도 0.8배로 줄인다(scale) — ORIGINAL NIGHT MASTER 위에서
     어두워진 배경과 맞물려 [크고 흐릿한 빛덩어리]로 보이던 것을 [창가에 붙은 작은
     불빛]으로 되돌린다. 좌표(--x/--y)·반경(--r) 은 실측값이라 안 건드린다. */
  transform:translate(-50%,-50%) scale(.8); border-radius:50%;
  background:radial-gradient(circle closest-side,
              var(--c) 0%,
              color-mix(in srgb, var(--c) 55%, transparent) 32%,
              transparent 72%);
  mix-blend-mode:screen;
  /* #165 ④ 2칸 — 창문. 등불 갈래(is-lamp·is-lantern·is-neon)는 아래에서 3칸으로 덮는다 */
  transition:opacity var(--dn-dur, 2.4s) ease-in-out calc(var(--vg-dn-step, 0s) * 2);
  will-change:opacity;
}
.vg-light.is-lamp, .vg-light.is-lantern, .vg-light.is-neon{
  transition-delay:calc(var(--vg-dn-step, 0s) * 3);   /* #165 ④ 3칸 — 등불 */
}
/* 2026-09-12 PREMIUM LIVING WORLD §2 — DAY→NIGHT 점등 순서(요청 "1.입구 석등 2.길가
   가로등 3.건물 창문과 처마 등불"). 위 두 규칙(§165, :vg-dn-step 배수)은 원경 전체의
   느린 하루-주기 크로스페이드(--dn-dur 가 길 수 있다) 몫이라 그대로 두고, 이 세 줄만
   더 구체적인 선택자로 덮어 [1~2초 안에 순서대로] 를 **고정 지속시간**으로 만든다 —
   여전히 opacity transition 이라(새 keyframe 아님) dn 이 그 문턱을 지나는 그 한 번만
   움직인다(요청 "애니메이션은 1회만" · "랜덤 깜빡임 금지" 그대로 지켜진다). 이 레이어
   전체가 이미 pointer-events:none(aria-hidden, index.html §134)이라 클릭·터치는
   애초에 막지 않는다. */
.vg-light.is-lantern[data-zone="정문"]{ transition:opacity .6s ease-in-out 0s; }
.vg-light.is-lamp{ transition:opacity .6s ease-in-out .5s; }
.vg-light.is-window,
.vg-light.is-lantern:not([data-zone="정문"]),
.vg-light.is-neon{ transition:opacity .7s ease-in-out 1s; }
/* 브라우저가 color-mix 를 모르면(구 WebView) 위 gradient 전체가 무효가 된다 — 그때 쓸 밑판 */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .vg-light{ background:radial-gradient(circle closest-side, var(--c) 0%, transparent 70%); }
}

/* 켜지는 구간이 종류마다 다르다 — 이것이 "각 광원이 독립적으로 변한다"의 실체다.
   창문이 먼저 들어오고, 가로등이 따라오고, 네온이 가장 늦게 최대가 된다. */
/* 2026-09-12 NIGHT MASTER FINAL POLISH §6 — "떠다니는 광점 대폭 정리, 현재 보이는 양의
   25~35% 수준부터 테스트". 어제(메인 NIGHT 재수정) .55→.66·.60→.72·.65→.78 로 올렸던
   것을, 그 값 기준 약 30%로 되돌린다(.66→.20 · .72→.22 · .78→.24) — 원래 §165 원안
   (.55·.60·.65)보다도 낮다. ORIGINAL NIGHT MASTER 자체가 이미 어둡게 그려져 있어(창문에
   불이 칠해진 그림) 오버레이 쪽은 [작게 보태는 정도]면 충분하다(사람 지시 §5 "원화 자체의
   조명을 먼저 최대한 살린다"). data-boost-zone 순간 반짝임(zone-boost 항)도 같이 줄였다. */
/* 2026-09-13 [MAIN PREMIUM POLISH PHASE 4 — BUILDING BREATH + TOUCH RESPONSE] 이
   아래 두 항(`--vg-breath-boost`·`--vg-touch-boost`)을 각 식에 더했다. 기존
   `--vg-zone-boost`(진짜 상점 진입 클릭 반응, village.js 건물반응() 소관 — 이 PHASE가
   손대지 않는다)와는 **완전히 다른 변수**다 — 상담/상점 진입 로직 한 글자도 안 건드리고
   그 옆에 항만 추가했다. 가중치는 BREATH가 TOUCH보다 더 작다(§3 "2~5%" 는 BREATH,
   TOUCH는 "조금 더 즉각적이어도" 라 살짝 크게 — 그래도 기존 zone-boost(.02~.04)보다는
   항상 작다, 진짜 입장 반응이 가장 커야 한다는 원칙을 지킨다). 새 그림·새 색 0개 — 이
   광원들의 기존 `--c`(사주집=amber/gold 톤 · 타로집=gold+연라벤더 톤 · 도사집=amber+
   muted teal/olive 톤, index.html 광원 좌표 실측값 그대로)가 이미 §2 가 요구하는 건물별
   색 성격과 맞아 새로 칠하지 않는다(§1). */
/* 2026-09-17 사람 지시 — 09-12 FINAL POLISH 가 원안(§165, 09-03: .55/.60/.65)보다도 낮은
   .20/.22/.24 로 줄여 실기기에서 "야간 조명이 대부분 사라진 것처럼" 보였다(회귀 감사
   A~F 보고 참고). 새 레이어·새 디자인 없이 배수값만 원안으로 되돌린다 — 나머지(전환 순서·
   색·zone/breath/touch 가산항)는 한 글자도 안 건드린다. */
.vg-light.is-window { opacity:calc((clamp(0, calc((var(--dn,0) - 0.30) / 0.25), 1) * .55 + var(--vg-zone-boost, 0) * .04 + var(--vg-breath-boost, 0) * .008 + var(--vg-touch-boost, 0) * .012) * var(--vg-streak-boost, 1)); }
/* 2026-09-17 3차 재지시 — "건물 창문·현관 > 실제 가로등·석등 > 별빛/반딧불" 위계를
   만들라는 지시. is-lamp(길·놀이터·고민저장소·풍선마당 — 건물에 안 붙은 환경 조명)는
   위계상 2단계라 1단계(창문)보다 낮게 보여야 한다. 알파 .60→.44(-27%), 크기도
   scale(.8)→scale(.62, 아래 별도 규칙, -22%)로 낮췄다 — 좌표·개수·색은 안 건드렸다.
   ★ is-lantern 은 손대지 않는다 — index.html 실측으로 확인하니 이 클래스를 쓰는 자리는
   정문(4) · 도사집(1, 별도 override 로 이미 처리) **둘뿐**이다. 정문 lantern 은 "마을
   입구 조명 유지"(사람 보호 대상 §5) 그 자체라 base 를 건드리면 바로 그걸 어기게 된다
   — 그래서 §165 원안 .65 그대로 둔다(한 글자도 안 바꿈). */
.vg-light.is-lamp   { opacity:calc((clamp(0, calc((var(--dn,0) - 0.35) / 0.25), 1) * .44 + var(--vg-zone-boost, 0) * .03 + var(--vg-breath-boost, 0) * .006 + var(--vg-touch-boost, 0) * .010) * var(--vg-streak-boost, 1)); }
.vg-light.is-lantern{ opacity:calc(clamp(0, calc((var(--dn,0) - 0.32) / 0.25), 1) * .65 + var(--vg-zone-boost, 0) * .03 + var(--vg-breath-boost, 0) * .006 + var(--vg-touch-boost, 0) * .010); }
.vg-light.is-lamp{ transform:translate(-50%,-50%) scale(.62); }
/* 2026-09-12 PREMIUM LIVING WORLD §1 — 도사집 포인트 3개가 이 클래스다. "아주 약한 포인트"
   라 .70(네온급)을 .40 으로 낮췄었다 — 2026-09-17 그 .40 으로 되돌린다(.12 는 과도했다). */
.vg-light.is-neon   { opacity:calc((clamp(0, calc((var(--dn,0) - 0.45) / 0.25), 1) * .40 + var(--vg-zone-boost, 0) * .02 + var(--vg-breath-boost, 0) * .004 + var(--vg-touch-boost, 0) * .007) * var(--vg-streak-boost, 1)); }

/* 2026-09-17 [LIGHTING PASS] 실기기 재확인(v0.1.702) — "실기기 확인하면 도사집 타로집
   아직 어두운데" 사람 직접 지적. 헤드리스 시뮬레이션에서는 이 정도 세기면 사주관과 균형이
   맞아 보였는데, 실기기 화면(다른 색 프로파일·밝기)에서는 두 건물이 확연히 처졌다. 공유
   공식(바로 위 세 줄)을 고치면 정문·길·놀이터 등 다른 자리도 같이 움직이므로, 이 두
   건물만 속성 선택자로 세기를 보탠다 — 색·좌표·개수는 그대로(새 색 0개), 배수만 올린다. */
/* 2026-09-17 2차 ART DIRECTION 재지시 — "밝기 방향은 맞지만 지금은 광원이 아니라 화면
   위에 얹은 빛 구슬처럼 보인다 · 불빛 개수/크기 50~60% 축소 · 청록은 보조색으로만".
   위 §(v0.1.702)에서 올린 알파(.78~.96)와 scale(1.25)가 과했다 — 알파를 원안(§594 위
   is-neon .40 등)보다도 낮추고, 크기 확대(scale 1.25 · index.html 의 --r 확대)는
   전부 걷는다(아래에서 index.html --r 도 원래 값으로 되돌림). 사주집엔 안 건드린
   .vg-entry-saju 스팟 하나가 이미 "제일 중요한 건물"의 존재감을 준다는 사람 확인을
   받았으니 그 톤에 맞춘다 — 창문은 살짝만, 도사집 청록은 뚜렷이 낮춘다. */
/* 2026-09-17 3차 재지시 — "사주관 창문 10~15% 미세 강화 · 도사집 청록 한 단계 더 축소 ·
   나머지 구조는 그대로". 사주집엔 처음으로 zone 전용 override 를 준다(기존엔 base .55
   그대로였다) — 요청한 +10~15% 그대로 .62(+12.7%). 타로집은 지난 차수 값(.62) 유지
   (건드리지 말라는 지시). 도사집 네온만 .28→.20(-29%, 요청한 25~35% 구간 안). */
.vg-light.is-window[data-zone="사주집"]{
  opacity:calc((clamp(0, calc((var(--dn,0) - 0.30) / 0.25), 1) * .62 + var(--vg-zone-boost, 0) * .04 + var(--vg-breath-boost, 0) * .008 + var(--vg-touch-boost, 0) * .012) * var(--vg-streak-boost, 1));
}
.vg-light.is-window[data-zone="타로집"]{
  opacity:calc((clamp(0, calc((var(--dn,0) - 0.30) / 0.25), 1) * .62 + var(--vg-zone-boost, 0) * .04 + var(--vg-breath-boost, 0) * .008 + var(--vg-touch-boost, 0) * .012) * var(--vg-streak-boost, 1));
}
.vg-light.is-neon[data-zone="도사집"]{
  opacity:calc((clamp(0, calc((var(--dn,0) - 0.45) / 0.25), 1) * .20 + var(--vg-zone-boost, 0) * .02 + var(--vg-breath-boost, 0) * .004 + var(--vg-touch-boost, 0) * .007) * var(--vg-streak-boost, 1));
}
.vg-light.is-neon[data-zone="도사집"]{ transform:translate(-50%,-50%) scale(.65); }
.vg-light.is-lantern[data-zone="도사집"]{
  opacity:calc(clamp(0, calc((var(--dn,0) - 0.32) / 0.25), 1) * .70 + var(--vg-zone-boost, 0) * .03 + var(--vg-breath-boost, 0) * .006 + var(--vg-touch-boost, 0) * .010);
}
.vg-light.is-window[data-zone="도사집"]{
  opacity:calc((clamp(0, calc((var(--dn,0) - 0.30) / 0.25), 1) * .62 + var(--vg-zone-boost, 0) * .04 + var(--vg-breath-boost, 0) * .008 + var(--vg-touch-boost, 0) * .012) * var(--vg-streak-boost, 1));
}
.vg-light.is-moon   { opacity:calc(clamp(0, calc((var(--dn,0) - 0.50) / 0.20), 1) * .50); }
/* 2026-09-30 #293 [마을 등불] 판정 반영 — 연속 일수는 광원(.vg-light 창문·가로등·네온)의 opacity 상한(--vg-streak-boost)으로 나타낸다:
   0일 35% · 1~2일 50% · 3~4일 70% · 5~6일 85% · 7일~ 100%. 원화 자체의 불빛은 못 끄므로 광원 레이어만 움직인다.
   후광(.vg-streak-halo)은 3일부터 단계로(3일 7일의 55% · 5일 80% · 7일 100%) — 각 광원 자리에 형제 요소로 깔아(부모 opacity 와 무관) 밝기를 따로 정한다.
   밤에만 보인다(--dn 문턱). JS(village-streak-lantern.js)가 만들고 지운다. 손잡이 = --vg-streak-halo-mul(크기 배수) · --vg-streak-halo-a(세기). */
.vg-streak-halos{ position:absolute; inset:0; z-index:8; pointer-events:none; }
.vg-streak-halo{
  position:absolute; left:var(--x); top:var(--y);
  width:calc(var(--r, 3%) * var(--vg-streak-halo-mul, 6.48) * var(--vg-streak-halo-s, 1)); aspect-ratio:1;
  transform:translate(-50%,-50%); border-radius:50%; pointer-events:none;
  /* 2026-09-30 후광 재조정(희야 판정) — 7일이 안개처럼 과해서: 호박색(#ffcf7a · 원화 창문 불빛색) · 지름 72%(=60%의 1.2배 · 희야 확정) · 세기는 평균 밝기 목표(+4.5)에 맞춰 100%(60%로는 +1.1) · 70% 지점에서 완전 투명(중심만 밝게). 단계 3일 55% · 5일 80% · 7일 100%(세기 --vg-streak-halo-a · 크기 -s) */
  background:radial-gradient(circle closest-side,
              rgba(255,207,122,1) 0%,
              rgba(255,207,122,.72) 22%,
              rgba(255,207,122,.30) 46%,
              rgba(255,207,122,.08) 60%,
              rgba(255,207,122,0) 70%);
  mix-blend-mode:screen;
  opacity:calc(clamp(0, calc((var(--dn,0) - 0.40) / 0.25), 1) * var(--vg-streak-halo-a, 1) * var(--vg-streak-halo-k, 1));
  transition:opacity 1.2s ease-in-out, width 1.2s ease-in-out;
}
.vg-streak-halo.is-off{ opacity:0; }
.vg-streak-halo.is-lamp{ --vg-streak-halo-mul:5.4; }
.vg-streak-halo.is-neon{ --vg-streak-halo-mul:5.94; }


/* BUILDING BREATH(§A) — "normal → 아주 약간 밝음 → normal" 한 번, 1.8~3.5초(JS가
   --vg-breath-dur 로 매번 다르게 준다). blink/두세번 깜빡임 금지라 vg-zone-glow 의
   [오르고→유지→내리기] 3단 대신 **단봉(0→1→0) 한 번**만 쓴다 — 정점이 짧게 머물지
   않고 바로 다시 내려가 "숨"에 가깝다. reduced-motion 에서는 이 규칙 자체가 없어
   BREATH 가 아예 안 뜬다(§22). data-breath-zone 은 village-building-breath.js 가
   village-ambient-director.js 의 대여(lease)를 받은 뒤에만 짧게 건다(한 번에 최대
   1개 건물 — 그 스케줄러가 자체적으로 보장, §5). */
@property --vg-breath-boost{ syntax:"<number>"; inherits:true; initial-value:0; }
@property --vg-touch-boost{ syntax:"<number>"; inherits:true; initial-value:0; }
@media (prefers-reduced-motion: no-preference){
  @keyframes vg-building-breath{ 0%{ --vg-breath-boost:0; } 50%{ --vg-breath-boost:1; } 100%{ --vg-breath-boost:0; } }
  .vg-lights[data-breath-zone="사주집"] .vg-light[data-zone="사주집"],
  .vg-lights[data-breath-zone="타로집"] .vg-light[data-zone="타로집"],
  .vg-lights[data-breath-zone="도사집"] .vg-light[data-zone="도사집"]{
    animation:vg-building-breath var(--vg-breath-dur, 2.4s) ease-in-out 1;
  }
}
/* TOUCH RESPONSE(§B) — 120~200ms, 단봉 한 번. reduced-motion 에서도 살아 있어야 하는
   입력 피드백이라(§22) 위 BREATH 처럼 media 게이트 안에 두지 않는다 — 대신
   village-building-breath.js 가 reduced-motion 일 때 더 짧고(140ms) 더 각진(linear)
   타이밍을 인라인으로 준다(같은 키프레임 재사용, 새 키프레임 안 만든다). */
@keyframes vg-building-touch{ 0%{ --vg-touch-boost:0; } 45%{ --vg-touch-boost:1; } 100%{ --vg-touch-boost:0; } }
.vg-lights[data-touch-zone="사주집"] .vg-light[data-zone="사주집"],
.vg-lights[data-touch-zone="타로집"] .vg-light[data-zone="타로집"],
.vg-lights[data-touch-zone="도사집"] .vg-light[data-zone="도사집"]{
  animation:vg-building-touch var(--vg-touch-dur, 170ms) var(--vg-touch-ease, ease-in-out) 1;
}

/* 2026-09-11 STEP2 [건물 접근/터치 반응] — village.js 건물반응() 이 `.vg-lights` 에
   `data-boost-zone` 을 짧게 건다. 처음엔 [기본 0.4-4.5초 transition] 을 그대로 빌려 쓰려
   했는데, 등불 갈래(is-lamp·is-lantern·is-neon)는 낮/밤 교차용 자기 지연(`--vg-dn-step*3`
   · village.css:272 · 실측 0.6s)이 있어 transition-duration 만 줄여도 **지연 뒤에야**
   움직여 500ms 안에는 [반응 없음]으로 보였다(실측 — 문제를 이 주석에 남긴다). 그래서
   transition 이 아니라 **독립된 키프레임 애니메이션**으로 바꿨다 — 지연·듀레이션을 다른
   규칙과 공유하지 않아 등불·창문·네온 전부 0.7초 안에서 스스로 끝난다.
   ★ 커스텀 프로퍼티를 애니메이션시키려면 `@property` 로 타입을 등록해야 부드럽게
   보간된다(등록 안 하면 이산 전환) — 아주 오래된 WebView는 이 등록을 무시하고 [순간
   전환]으로 떨어질 뿐 깨지지는 않는다(그레이스풀 디그레이드). */
@property --vg-zone-boost{ syntax:"<number>"; inherits:true; initial-value:0; }
@media (prefers-reduced-motion: no-preference){
  @keyframes vg-zone-glow{ 0%,100%{ --vg-zone-boost:0; } 35%,65%{ --vg-zone-boost:1; } }
  .vg-lights[data-boost-zone="사주집"] .vg-light[data-zone="사주집"],
  .vg-lights[data-boost-zone="타로집"] .vg-light[data-zone="타로집"],
  .vg-lights[data-boost-zone="도사집"] .vg-light[data-zone="도사집"]{
    animation:vg-zone-glow .7s ease-in-out 1;
  }
  /* 2026-09-11 STEP3 [숨은 터치 반응 §9 — 가로등 터치] — village-tap.js 가로등반응() 이
     그 등 하나에만 붙였다 뗀다. 새 키프레임을 또 만들지 않는다 — 바로 위 vg-zone-glow
     (요청 §9 예시의 "0.5초 밝아졌다 되돌아옴"과 이 0.7초 목례류 리듬이 사실상 같다). */
  .vg-light.vg-egg-lamp{ animation:vg-zone-glow .7s ease-in-out 1; }
}

/* ★ 2026-09-02 — 지도 원화가 4판(village-day-0902.jpg)으로 갈리면서 위 30개 광원의 좌표가
   **전부 옛 마을 기준**이 됐다. 새 마을은 건물 자리가 다르다(타로집 x31~40 · 사주집 x43~57 ·
   도사집 x58~70 · 놀이터 x36~63 · 정문 x44~58 실측). 그대로 두면 밤에 **빈 풀밭이 빛난다.**
   그래서 좌표를 다시 잴 때까지(전체계획 #201) 이 층만 껐었다 — DOM(index.html)은 지우지 않았다.
   ★ 2026-09-03 #165 P1-① — **다시 켰다.** 좌표 30개를 4판 밤 원화에서 다시 구웠기 때문이다
     (`app\tools\광원좌표굽기.py` · 근거는 index.html 광원 레이어 주석). 끄고 싶으면
     `display:none` 을 되살리는 것이 아니라 그 도구로 다시 굽는다 — 끈 채로 두면
     [건물이 밤에 안 켜진다]가 되어 #165 완료 기준 첫 줄에 그대로 걸린다. */

/* 네온만 아주 미세하게 흔들린다(요청 11절 "네온사인 미세한 Glow").
   밝기를 ±6% 만 흔든다 — 더 주면 고장난 간판처럼 보인다. */
@media (prefers-reduced-motion: no-preference){
  .vg-light.is-neon{ animation:vg-neon-breathe 4.2s ease-in-out infinite; }
  .vg-light.is-lantern{ animation:vg-lantern-sway 5.6s ease-in-out infinite; }
}
@keyframes vg-neon-breathe{ 0%,100%{ filter:brightness(1); } 50%{ filter:brightness(1.06); } }
@keyframes vg-lantern-sway{ 0%,100%{ filter:brightness(.96); } 50%{ filter:brightness(1.05); } }

/* 전역 색조 — 아주 약하게만. 밤 원화가 이미 자기 톤을 갖고 있어서 여기서 더 덮으면 탁해진다. */
.vg-ambient{
  position:absolute; inset:0; z-index:5; pointer-events:none;
  background:radial-gradient(120% 80% at 50% 12%, rgba(120,150,255,.30) 0%, rgba(30,20,70,.10) 60%, transparent 100%);
  mix-blend-mode:soft-light;
  opacity:clamp(0, calc((var(--dn,0) - 0.45) / 0.3), 1);
  /* #165 ④ 1칸 — 하늘 바로 다음이 환경색이다 */
  transition:opacity var(--dn-dur, 2.4s) ease-in-out var(--vg-dn-step, 0s);
}

/* 밤 안개·빛 입자 — 파티클 상한 12개(요청 13절). 화면 밖에서는 JS 가 애니메이션을 끈다. */
.vg-fog{ position:absolute; inset:0; z-index:6; pointer-events:none;
  opacity:clamp(0, calc((var(--dn,0) - 0.65) / 0.3), 1);
  transition:opacity var(--dn-dur, 2.4s) ease-in-out calc(var(--vg-dn-step, 0s) * 4); }
.vg-mote{
  position:absolute; left:var(--x); top:var(--y);
  width:var(--s,4px); height:var(--s,4px); border-radius:50%;
  background:radial-gradient(circle, rgba(255,236,190,.85) 0%, rgba(255,220,150,0) 70%);
  mix-blend-mode:screen;
}
@media (prefers-reduced-motion: no-preference){
  .vg-mote{ animation:vg-mote-drift var(--d,9s) ease-in-out infinite; animation-delay:var(--delay,0s); }
}
@keyframes vg-mote-drift{
  0%   { transform:translate3d(0,0,0)      scale(.7); opacity:.15; }
  50%  { transform:translate3d(6px,-14px,0) scale(1);  opacity:.75; }
  100% { transform:translate3d(0,0,0)      scale(.7); opacity:.15; }
}

/* 화면 밖·저사양이면 통째로 끈다 (JS 가 붙인다) */
.village-map.vg-lite .vg-fog,
.village-map.vg-lite .vg-light.is-window{ display:none; }
.village-map.vg-paused .vg-light,
.village-map.vg-paused .vg-mote{ animation-play-state:paused; }
/* #170 (3)/#171 진입 시점 조문: 저장된 밤 상태는 [첫 프레임부터 밤]이어야 한다 —
   위 1.4s 페이드가 새로고침마다 재생되면 [매번 해가 지는] 화면이 되어 불합격(채점 4항).
   village.js 가 저장값을 적용하는 순간에만 이 클래스를 붙였다가 다음 프레임에 뗀다.
   새 숫자를 만들지 않는다 — 전환 자체를 잠깐 끄는 것뿐이고 이후 탭은 그대로 2.4s.
   2026-08-20 STEP4: ::before 오버레이가 사라지고 레이어가 여럿이 됐으므로 대상도 함께 넓힌다.
   하나라도 빠뜨리면 [배경만 즉시 밤인데 네온만 서서히 켜지는] 어긋난 첫 프레임이 된다. */
.village-map.vg-no-anim .vg-art img,
.village-map.vg-no-anim .vg-light,
.village-map.vg-no-anim .vg-ambient,
.village-map.vg-no-anim .vg-fog,
.village-map.vg-no-anim .vg-star,
.village-map.vg-no-anim .village-npc-layer img{ transition:none !important; }

.village-daynight{
  position:absolute; top:10px; right:10px; z-index:20; width:32px; height:32px;
  border-radius:50%; border:1px solid rgba(255,255,255,.45); background:rgba(20,15,40,.5);
  color:#fff; font-size:.95rem; cursor:pointer; display:flex; align-items:center;
  justify-content:center; padding:0;
  /* #164/#165 ②(2026-08-20 03_디자인팀 확정): 전역 button{min-height:44px}(style.css:75,
     RW-074)가 위 height:32px 를 이겨 실제로는 32x44 로 렌더돼 border-radius:50% 와 겹치면
     원이 아니라 세로로 긴 타원으로 보였다(04_품질팀 실측). height 와 같은 숫자를 적어 두면
     한쪽만 고쳤을 때 어긋남이 드러난다 — 그래서 min-height:0 이 아니라 32px 이다. */
  min-height:32px;
}
/* 시각은 32px 그대로 두고 히트 영역만 44px 로 넓힌다 — 가로 히트가 32px 뿐이던
   비대칭(#164 ②)도 여기서 함께 닫힌다.
   ★ 스펙이 준 값은 inset:-6px 이지만 이 사이클 실측 결과 그 값은 42x42 를 만든다(x=313,
   w=42). 절대배치 의사요소의 기준 상자는 border-box(32)가 아니라 **padding-box**(32 -
   테두리 1px*2 = 30)라 30 + 6*2 = 42 다. 계약 조문 3 은 [시각 32 유지 · 히트 44 확보]
   이므로 목적(44)을 지키고 산술만 바로잡는다: 30 + 7*2 = 44. 실측 x=312 w=44 로 확인.
   같은 산식을 쓰는 #155 ⚙️(.village-settings)도 테두리가 있으면 -7px 이어야 한다 — 대장 #170. */
.village-daynight::after{ content:""; position:absolute; inset:-7px; }
.village-map.is-night .village-daynight{ background:rgba(255,214,120,.22); }

/* 야간 전용 별 트윙클 - 그림 상단(나무/하늘) 대역에만 은은하게, cover.css 트윙클 기법 재사용 */
.vg-star{
  position:absolute; width:3px; height:3px; border-radius:50%; background:#fff;
  opacity:0; z-index:6; pointer-events:none;
  /* #165 ④ 4칸 — 별이 가장 마지막이다 */
  transition:opacity var(--dn-dur, 2.4s) ease-in-out calc(var(--vg-dn-step, 0s) * 4);
}
/* 별도 --dn 을 따른다 — 0.6 부터 보이기 시작해 1.0 에서 최대. 반짝임 자체는 opacity 를
   건드리지 않고 filter 로만 준다(위 transition 과 애니메이션이 같은 속성을 다투면 깜빡인다). */
.vg-star{ opacity:calc(clamp(0, calc((var(--dn,0) - 0.6) / 0.25), 1) * .9); }
@media (prefers-reduced-motion: no-preference){
  .vg-star{ animation:vg-star-twinkle 2.6s ease-in-out infinite; }
}


@media (prefers-reduced-motion: reduce){
  /* #165 ④ — 시간을 줄일 때 **지연도 같이** 지운다. duration 만 지우면 [순서]가 남아
     0.2~0.8초 뒤에 층이 툭 튀어나오는, 줄인 것도 안 줄인 것도 아닌 화면이 된다. */
  .vg-art img, .vg-light, .vg-ambient, .vg-fog, .vg-star{ transition-duration:.01ms; transition-delay:0s; }
}
/* ★ opacity 를 애니메이션하지 않는다 — 위에서 --dn 으로 opacity 를 계산하는데
   애니메이션이 같은 속성을 잡으면 그 계산을 이겨 버려서 낮에도 별이 깜빡인다.
   반짝임은 filter:opacity() 로 준다(별개 속성이라 둘이 곱해진다). */
@keyframes vg-star-twinkle{ 0%,100%{ filter:opacity(.18); } 50%{ filter:opacity(1); } }

/* 문(출입구) 글로우 - 실제 그림 속 문 위치에 얹는 은은한 발광. 옛 scaleX 패널 기법은
   추상 도형 배경 전제였어서, 실사용 배경 위에서는 halo(box-shadow) 방식으로 교체 */
.door{
  position:absolute; width:10px; height:10px; margin:-5px 0 0 -5px; border-radius:50%;
  opacity:0; pointer-events:none; z-index:6; transition:opacity .4s ease-out;
}
.door.is-open{
  opacity:1; box-shadow:0 0 16px 7px #f6c453cc, 0 0 34px 16px #f6c45360;
  animation:vg-door-glow 1.2s ease-in-out;
}
.door-gate.is-open{ box-shadow:0 0 20px 9px #f6c453cc, 0 0 42px 20px #f6c45360; }
@keyframes vg-door-glow{ 0%,100%{ filter:brightness(1); } 50%{ filter:brightness(1.4); } }

/* 상점 히트박스 - 실제 건물 위 투명 영역(§10-2 좌표는 배경아트 실측 기준으로 재산정).
   그림에 이미 간판·조명이 그려져 있어 텍스트 오버레이 없이 hover/focus 발광만 준다. */
.shop{
  position:absolute; display:block; cursor:pointer; background:none; border:none;
  padding:0; border-radius:18%; z-index:5; -webkit-tap-highlight-color:transparent;
  pointer-events:auto;
  /* 2026-08-20 사람 직접 지시(전체화면 전환 후 버튼 위치 어긋남): index.html/playground.html의
     top/left/width/height %는 원화 실측 [좌상단 기준 박스]다(문 door 좌표가 각 박스 안에
     들어오는 것으로 검증). 중심 앵커 translate(-50%,-50%)를 두면 박스가 자기 크기의 절반씩
     좌상단으로 밀려 건물과 어긋난다 — 앵커 이동 금지. */
}
/* ★ 2026-09-03 #168 — 안드로이드 WebView 는 탭 뒤 :hover 가 남아 시트가 열린 동안 인셋 판이 비친다. hover 는
   [진짜 포인터] 있는 환경에서만(`@media (hover:hover)`) · focus-visible 은 :760 의 outline 이 맡는다. */
@media (hover:hover){
  .shop:hover{ box-shadow:0 0 0 3px rgba(246,196,83,.5), inset 0 0 30px rgba(246,196,83,.16); outline:none; }
}
.shop-badge{
  position:absolute; bottom:2%; left:50%; transform:translateX(-50%);
  font-size:.56rem; padding:1px 8px; border-radius:999px; color:#0c1a0f;
  background:#7fd88f; white-space:nowrap; pointer-events:none;
  animation:vg-badge-pulse 3s ease-in-out infinite;
}
/* [준비중] 배지 규칙은 지웠다 — 마을에 그 배지가 붙는 문이 0 이다(전체계획 #22 ② · V16).
   바로 위 .shop-badge 는 남겨 둔다: 다음 배지가 생기면 쓰는 껍데기다. */
@keyframes vg-badge-pulse{ 0%,100%{ opacity:.75; } 50%{ opacity:1; } }

/* 운명의 놀이터 - 배경아트에 전용 건물이 없어 조경 빈틈에 얹는 작은 표지판형 마커 */
.shop-playground{ display:flex; align-items:center; justify-content:center; }
.pg-marker{
  /* ★ 2026-08-31 (PHASE 1 · §21) — **감춘다.**
       하루 원형 프로필 옆에 보이던 [관람차 그림 + 파란 막대]가 이것이다.
       그래픽 버그가 아니라 🎡 **이모지 글리프**(막대는 관람차 받침대)인데,
       놀이터가 표지판 마커에서 [구역 전체 핫스팟]으로 바뀐 뒤(index.html:222 주석)
       같은 자리에 `.vg-sign`(간판 + 하루 얼굴 + 이름)이 따로 생겨 **뜻이 겹쳤다.**
       핫스팟 <a> 는 그대로 두고 눈에 보이는 이모지만 끈다 — 탭 자리는 안 바뀐다.
       되돌리려면 이 display 한 줄만 지운다. */
  display:none;
  font-size:1.5rem; filter:drop-shadow(0 2px 5px rgba(0,0,0,.55));
  animation:vg-badge-pulse 3s ease-in-out infinite;
}

/* 2026-08-27 사람 지시 §1 — 분수 완전 삭제.
   .vg-fountain / .vg-ripple / .vg-sparkle 과 그 키프레임 둘을 지웠다.
   ★ 미니미 반짝(.vg-tap-sparkle, 이 파일 아래쪽)은 값을 베낀 별개 클래스라 그대로 산다. */
/* (2026-09-04 #165 — 여기 홀로 남아 있던 여는 짝 없는 } 를 지웠다. 위 2026-08-27 분수 삭제가
   블록은 지우고 닫는 괄호만 남긴 자리다. 파서는 무시해 왔지만 [중괄호 수가 안 맞는 파일]은
   다음 사람이 자기 편집을 의심하게 만든다. 지운 뒤 마을빛검사 44/44 그대로다. */

/* 광장 체류 지점(§8-2 5번) - 배경아트에 이미 벤치·산책로가 그려져 있어 별도 아이콘 없이
   좌표 마커로만 존재(미니미 목적지 계산용, 화면엔 안 보임). */
.plaza-rest{ position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }

/* ---- 미니미(배경 주민) 레이어 - 실사용 스프라이트(04_자료\앱자료\미니미자료 크롭본).
   깊이감: village.js가 top%(그림 속 y좌표)에 비례해 z-index를 매겨 "아래=앞" 아이소
   메트릭 겹침 순서를 흉내낸다(§ "입체감 있게" 2026-08-19 사람 지시). ---- */
.village-npc-layer{ position:absolute; inset:0; z-index:7; pointer-events:none; }
/* 미니미도 --dn 을 따라 서서히 어두워진다 — is-night 로 한 번에 툭 바뀌면
   배경만 부드럽게 넘어가고 사람만 순간이동한 것처럼 보인다(2026-08-20 STEP4). */
/* 밤이 되면 미니미도 같이 어두워진다. ★ drop-shadow 는 여기 두지 않는다 —
   조각이 5장이라 그림자가 5겹으로 찍힌다. 그림자는 리그가 한 번만 만든다. */
.village-npc-layer img{
  /* 2026-09-13 [MAIN PREMIUM POLISH PHASE 2 — NPC POSITION-BASED AMBIENT LIGHT] —
     brightness() 안에 `+ var(--npc-amb,0) * (0.0171 + 0.0357*var(--dn,0))` 한 항만
     더했다(village-npc-ambient-light.js 머리 주석에 같은 두 상수의 산출 근거가 있다).
     --npc-amb(0~1, village.js 자리쓰기() 가 씀)는 NPC가 광원 zone에 얼마나 가까운가고,
     그 뒤 dn 배율(낮 2%~밤 5%)은 여기서 곱한다 — 그래서 시간대가 바뀌면 이미 있는
     `transition:filter var(--dn-dur)`(바로 아래 줄, 새로 안 만들었다)가 그 배율 변화까지
     같이 부드럽게 넘긴다. hue/sepia 는 안 썼다 — brightness 하나로 피부색·의상색이
     그대로 유지된다(§4). 새 filter 규칙이 아니라 기존 공식 한 줄에 항 하나를 더한
     것이므로 밤에 어두워지는 기존 동작과 항상 같은 값에서 같이 움직인다. */
  filter:brightness(calc(1 - 0.30 * var(--dn,0) + var(--npc-amb,0) * (0.0171 + 0.0357 * var(--dn,0)))) saturate(calc(1 - 0.20 * var(--dn,0)));
  transition:filter var(--dn-dur, 2.4s) ease-in-out;
}
.vg-npc-rig{ filter:drop-shadow(0 2px 2px rgba(0,0,0,calc(0.12 + 0.16 * var(--dn,0)))); }
/* ★ 2026-08-31 (PHASE 1 · §4 [NPC 접지감 개선])
     종전에는 실루엣 전체에 drop-shadow(0.25~0.55) 하나만 있었다. 그것은 [그림자]가 아니라
     **테두리**로 읽혀 인물이 배경 위에 얹힌 스티커처럼 보였다(사람 지적).
     그래서 ① 실루엣 그림자는 절반 아래로 낮추고 ② 발밑에 아주 옅은 타원을 깐다.
     ★ 지시 §4 금지: 진한 검정·RPG 형 강한 그림자·3D·네온 — 그래서 blur 로만 만들고
       불투명도는 낮(0.13)·밤(0.22) 둘 다 0.25 를 넘지 않는다. 색도 순검정이 아니라
       마을 배경의 흙색 계열(58,44,38)이라 파스텔 수채 위에서 뜨지 않는다. */
.vg-npc-rig::after{
  content:""; position:absolute; left:50%; bottom:-2px;
  width:56%; height:9%;
  transform:translateX(-50%);
  border-radius:50%;
  background:radial-gradient(ellipse at 50% 50%,
    rgba(58,44,38,calc(0.13 + 0.09 * var(--dn,0))) 0%,
    rgba(58,44,38,calc(0.07 + 0.05 * var(--dn,0))) 55%,
    rgba(58,44,38,0) 78%);
  pointer-events:none; z-index:-1;
}
.village-npc{
  /* ★ 2026-08-21 STEP10: 자리를 top/left 가 아니라 **transform** 으로 잡는다.
     top/left 를 매 프레임 쓰면 레이아웃이 30명 x 60fps 만큼 돈다 — 저사양 기기가 무너진다.
     transform 은 컴포지터에서만 돌아 레이아웃 비용이 0이다.
     ★ 2026-08-21 엔진 재작성: 이제 이동은 CSS transition 이 아니라 village.js 의
       매 프레임 시뮬레이션이 만든다(사람 사이 거리·길 밖 판정을 매 프레임 봐야 해서다).
       그래서 transform 에는 **transition 을 걸지 않는다** — 걸면 매 프레임 새 목표가
       들어와 애니메이션이 서로를 덮어써 움직임이 끈적해진다. opacity 만 부드럽게 둔다. */
  position:absolute; top:0; left:0;
  transition-property:opacity; transition-timing-function:ease;
  transition-duration:.45s; opacity:.95;
  will-change:transform;
  /* 2026-09-12 NIGHT MASTER FINAL POLISH §7 — "NPC가 밤에 묻혀 보인다 · DAY를 섞지도
     말고 강한 blue/night 필터도 금지 · 피부·의상 고유색 유지하며 8~12% 밝게".
     이미 있는 --dn 패턴(위 :600 발밑 그림자와 같은 식)을 그대로 쓴다 — 새 변수 없이
     dn 0.55~0.75 구간에서 brightness 1→1.10, saturate 1→1.04 로만 올린다. hue-rotate
     없음(색을 옮기지 않는다), 낮엔 두 값 다 1이라 화면엔 아무 변화가 없다. */
  filter:
    brightness(calc(1 + 0.10 * clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1)))
    saturate(calc(1 + 0.04 * clamp(0, calc((var(--dn,0) - 0.55) / 0.2), 1)));
}
/* #161 주민순환(2026-09-03 · 사람 지시 §10 화면 밖 입퇴장) — 정문 안쪽에서 옅어져 나가고, 같은 자리에서
   옅게 들어온다. 새 전환을 만들지 않는다 — 위 .village-npc 의 opacity .45s 를 그대로 탄다. */
.village-npc.village-npc-leaving,
.village-npc.village-npc-entering{ opacity:0; }
.village-npc-img{
  /* 2026-08-20 사람 직접 지시: "미니미 사이즈가 그대로/너무 작음" - 기존 30/36/42px 대비 2배로.
     실측 근거: village-day.jpg(1448x1086) 정문 아치 개구부 높이 대비 사람 키 비율 ~12~13%,
     .village-map 모바일 표시폭 420px(비율 1448/1086 -> 표시높이 ~315px) 기준 40px 안팎이 그 비율과
     맞았으나, 사람이 실제 화면에서 보고 "그대로/작다"고 재지적해 2배(60px)로 올린다 - 사람 눈이 정본. */
  /* ★ 2026-08-21 관절 걷기 이후: 자리잡기(translate)와 그림자는 **리그**(.vg-npc-rig)로
     옮겼다. 여기 남기면 조각 5장이 각자 -50%/-92% 로 밀려 서로 어긋나고,
     그림자도 5겹으로 겹쳐 발밑이 시커메진다. 여기는 크기만 정한다. */
  /* 2026-08-27 — 새 미니미 시트가 정한 크기는 [건물 문 높이의 50~60%] 이고
     시트 자체가 그것을 화면 70~80px 로 적어 둔다(시트 09절). 60 → 72px.
     ★ width 는 손대지 않는다(auto) — 정하는 순간 인물이 홀쭉해지거나 넓적해진다. */
  /* ★ 2026-08-31 (PHASE 1 · §3 [NPC 크기 규격 통일]) — 고정 px 을 걷어냈다.
       height 를 그림마다 박아 두면 [원본 PNG 가 큰 캐릭터가 화면에서도 크게] 보인다.
         · 기준값  --npc-h : 이 세트가 화면에서 가질 기본 신장 (BASE_NPC_SCALE 에 해당)
         · 개인값  --npc-s : 세트별 보정 배율. village.js MINI_ME_SETS 의 `배율` 이 얹는다.
       둘을 곱해 쓴다 — 원본 크기가 제각각이어도 화면 체감 크기를 맞출 수 있다.
     ★ width 는 여전히 auto 다 — 정하는 순간 인물이 홀쭉해지거나 넓적해진다(미니미 스킬 §4). */
  display:block; height:calc(var(--npc-h,72px) * var(--npc-s,1) * var(--npc-persp,1)); width:auto;
}
/* RW-082 갱신: 이제 캐릭터마다 실제 정면/후면/측면 원화 3장을 쓴다(village.js
   updateFacing). 측면 원화는 전부 "오른쪽을 보는" 기준으로 크롭돼 있어, 왼쪽으로
   이동할 때만 이 .mirror 클래스로 좌우반전한다 - 정면/후면은 반전하지 않음(원화 자체가
   그 방향이므로). "걷는 느낌"(자연스러운 팔·다리 움직임)은 진짜 걷기 프레임 대신 짧은
   주기 바운스+살짝 기울임으로 근사한다(구상 탭 §미니미 애니메이션 한계 참고). */
/* (좌우 반전은 .vg-npc-rig 가 한다 — 조각마다 뒤집으면 clip-path 와 어긋난다) */

/* M7-⑥(2026-08-20 사람 직접 지시 "뒤뚱뒤뚱이 아니라 실제처럼 천천히 걷기"):
   rotate ±3° 좌우 흔들림이 뒤뚱거림의 원인이라 제거. 걷기 느낌은
   [기본 속도 25px/s(village.js) + 아래 최소 수직 bob(1px, .6s) + 대각선 구간
   원화 프레임 교대(village.js startWalkFrames)] 조합으로 낸다. */
@keyframes vg-npc-walk-bob{
  0%,100%{ transform:translate(-50%,-92%) translateY(0); }
  50%{ transform:translate(-50%,-92%) translateY(-1px); }
}
@keyframes vg-npc-walk-bob-flip{
  0%,100%{ transform:translate(-50%,-92%) scaleX(-1) translateY(0); }
  50%{ transform:translate(-50%,-92%) scaleX(-1) translateY(-1px); }
}
/* ★ 옛 [통짜 상하 바운스]는 껐다 — 이제 관절이 걸음을 만든다(.vg-joint-*).
   둘을 같이 켜면 몸 전체가 통통 튀는 위에서 다리가 또 움직여 어지럽다.
   바운스 키프레임은 아래에 남겨 둔다: 관절을 못 쓰는 상황(모션 축소)에서 되살릴 자리다. */
/* 체류(분수 앞 등) — 살짝 흐려 [머무는 중]으로 읽히게. 자리는 JS 가 transform 으로 잡는다. */
.village-npc.resting{ transition-property:opacity; transition-duration:.6s; opacity:.8; }
/* §11-4: 벤치 착석 표현 - 살짝 낮고 작게 앉은 자세처럼 보이도록.
   ★ 2026-08-21 수정: 종전에는 이 값을 .village-npc-img(조각 5장)에 걸었다 —
     조각마다 -50%/-70% 가 또 걸려 리그의 -50%/-92% 와 이중으로 밀렸고, 팔다리는
     아래 .seated .vg-leg-* { transform:none } 이 이겨서 몸통만 어긋났다.
     자리잡기는 리그 한 곳에서만 한다(그래야 방향 rotateY 와도 안 싸운다). */
.village-npc.seated .vg-npc-rig{
  transform:translate(-50%,-78%) rotateY(var(--face,0deg)) scale(.88);
}
/* §11-4: 상점 재출입 - 문 안으로 들어가 있는 동안(재출현 전까지) 숨김 */
.village-npc.village-npc-hidden{ opacity:0; transition:opacity .3s ease; }
/* 2026-09-13 [MAIN PREMIUM POLISH PHASE 5 — NPC BUILDING ENTRY/EXIT NATURAL
   OCCLUSION] §12 — foreground 에 대부분 가려진 뒤에만 쓰는 아주 짧은 보조 opacity
   전환(80~150ms). 위 village-npc-hidden 의 기본 .3s 보다 훨씬 짧다 — village.js
   NPC건물왕복() 이 이 클래스가 걸려 있는 동안에만 opacity 목표(hidden 추가/제거)를
   바꾼다(그래야 이 짧은 트랜지션을 탄다 · 순서가 바뀌면 기본 .3s 로 새 버린다). */
.village-npc.vg-building-fade{ transition:opacity .12s ease-out; }

/* 미니미 말풍선 - village.js가 랜덤 주기로 짧은 대사를 띄운다(마을이 살아있는 느낌 보강)
   2026-08-20 사람 지시: 말풍선이 캐릭터를 가림 — 미니미 이미지는 앵커에서 translate(-92%)로
   위로 끌어올려져 있어, bottom:100%(앵커 상단)는 이미지 몸통 한가운데다. 이미지 실높이의
   92%만큼 더 위(머리 위)로 올린다. --npc-h는 아래 미디어쿼리에서 크기와 함께 갱신. */
/* ★ 2026-08-31 폴더블 지시 §2 — 신장은 **화면 폭이 아니라 카메라 배율**을 따른다.
   왜: 화면 폭으로 82→96px 을 주면 폴더블을 펼치는 순간 사람만 커져 [사람 ↔ 건물] 비율이
   깨진다(추가요구 §10 "NPC ↔ 담당 건물 관계가 바뀌면 실패"). 미니미는 월드 오브젝트이므로
   원화 안에서 늘 같은 키여야 한다 — 그 키가 139.5 원화px 다.
   근거: 접힌 실기기(411x903)의 카메라 배율 0.5879 에서 종전 값 82px 이었다 →
   82 / 0.5879 = 139.48. 그래서 아래 식은 접힌 화면에서 82.0px 로 **종전과 같은 값**을 낸다.
   --vg-cam 은 village-fullbleed.js 가 매 카메라 계산마다 <html> 에 쓴다(없으면 접힘값으로 폴백).
   그 앞 근거: ★ 2026-08-31 §3 — 72 → 82px (+13.9%). ★ 2026-08-27 #13 · 5차 §6 순서 1 —
   .village-npc-img 가 60→72 로 커진 뒤(:404)에도 60 이 남아 머리 위가 −5.1~−5.9 였다
   (03_디자인팀 5차 §5-② · #510 곁들이). 그림 높이와 같은 값으로 맞춘다. */
/* ★ 값을 calc() 로 쓰지 않는다 — custom property 는 getComputedStyle 이 **계산 전 문자열**로
   돌려준다. village.js:1402 표시높이() · :1838 이 parseFloat(--npc-h) 로 읽으므로
   calc(...) 를 넣으면 NaN 이 되어 조용히 60px 폴백으로 떨어진다(2026-08-31 헤드리스 실측 0px).
   그래서 village-fullbleed.js 가 **이미 계산된 px 문자열**을 --vg-npc-h 로 <html> 에 쓰고,
   여기서는 그것을 받기만 한다. 폴백 82px = 접힌 실기기(배율 0.5879) 종전 값. */
.village-npc{ --npc-h: var(--vg-npc-h, 82px); }
/* ★ 2026-08-21(#13 ⑤) left:50% → left:0.
   앵커 상자(.village-npc)의 폭은 리그 폭(34px)인데 리그는 translate(-50%) 로 앵커 원점에
   **중심을 맞춰** 그려진다. 그래서 left:50% 는 캐릭터 중심이 아니라 그 오른쪽 17.0px 이었다
   (실측: 캐릭터중심 202.0 · 말풍선중심 219.0). 꼬리도 같이 어긋나 있었다.
   left:0 + translate(-50%) 면 앵커 원점 = 캐릭터 중심에 맞는다.
   ★ 2026-08-27(#13 ⑥ · 대장 #436) --bubble-dx 를 transform 에서 **left** 로 옮겼다.
   transform 에 실으면 아래 transition(transform .3s)을 타고 옆에서 미끄러져 들어와, 뜨는 0.3s 동안
   화면 밖으로 최대 31.2px 나갔다(03_디자인팀 재채점2 §4-2 실측). left 는 전이 목록에 없어 즉시 자리에 선다.
   꼬리(::after)는 이미 calc(50% - dx) 로 반대로 밀려 있어 그대로 캐릭터를 가리킨다. 떠오름(-2px)·페이드는 그대로. */
.village-npc-bubble{
  position:absolute; left:var(--bubble-dx, 0px); bottom:calc(100% + var(--npc-h) * .92 + 4px);
  transform:translate(-50%, 4px);
  background:#fdf8ee; color:#3a2e22; font-size:.58rem; line-height:1.2;
  padding:3px 7px; border-radius:8px; white-space:nowrap;
  box-shadow:0 2px 6px rgba(0,0,0,.3); opacity:0; pointer-events:none;
  transition:opacity .3s ease, transform .3s ease; z-index:3;
}
/* 간-3(계약 §4-3) — 지도 밖으로 나가지 않게 village-bubble.js 가 --bubble-dx 로 민다.
   꼬리는 **반대로** 밀어 계속 캐릭터를 가리킨다(밀어도 누가 말하는지 안 흐려진다). */
.village-npc-bubble::after{
  content:""; position:absolute; left:calc(50% - var(--bubble-dx, 0px)); top:100%; transform:translateX(-50%);
  border:4px solid transparent; border-top-color:#fdf8ee;
}
/* 간-2(계약 §4-3) — 간판 발광을 파고들면 머리 위가 아니라 발밑으로 뒤집는다.
   발밑 = 앵커에서 npc-h × .08 아래(리그가 translate(-50%,-92%) 라서다).
   8px 은 .show 가 2px 끌어올린 뒤 계약 §3-2 의 실측 여백 6.0px 이 되게 잡은 값이다. */
.village-npc-bubble.below{ bottom:auto; top:calc(var(--npc-h) * .08 + 8px); }
/* 컷 세트는 앵커가 발끝(100%)이라 위 두 식(.92·.08)이 8%×키 만큼 어긋난다 —
   위 vg-npc-rig 100% 앵커와 짝을 맞춘다(2026-09-06 §5). */
.village-npc.vg-frames .village-npc-bubble{ bottom:calc(100% + var(--npc-h) + 4px); }
.village-npc.vg-frames .village-npc-bubble.below{ top:8px; }
.village-npc-bubble.below::after{
  top:auto; bottom:100%; border-top-color:transparent; border-bottom-color:#fdf8ee;
}
.village-npc-bubble.show{ opacity:1; transform:translate(-50%, -2px); }

/* RW-082: position:fixed라 body.has-banner-ad의 padding-bottom(정상 흐름 요소용)이
   안 먹는다 - 네이티브 배너 실측 높이(--banner-ad-height)만큼 직접 띄워야 가려지지 않는다. */
.village-toast{
  position:fixed; left:50%; bottom:calc(var(--banner-ad-height,0px) + 28px); transform:translateX(-50%);
  background:var(--text); color:var(--bg); padding:10px 18px; border-radius:999px;
  font-size:.82rem; box-shadow:var(--shadow); z-index:60; max-width:86vw; text-align:center;
}

.village-footer{ color:var(--muted); font-size:.76rem; text-align:center; max-width:320px; margin-top:8px; }

@media (prefers-reduced-motion: reduce){
  .door{ transition:none; }
  .door.is-open{ animation:none; }
  .village-npc{ transition:none; }
  .village-npc.village-npc-hidden{ transition:none; }
  .vg-star, .shop-badge, .pg-marker{ animation:none !important; }
  /* 터치 이펙트(#11 계약 §4-4) — 새 [효과 끔] 집합을 만들지 않고 이 집합에 넣는다.
     마디1(누름)은 **남긴다**: [지금 눌렀다]는 정보 자체는 접근성이지 장식이 아니다 —
     등장 시간만 없앤다. 마디2(퍼짐)와 미니미 반짝은 village-tap.js 가 아예 만들지 않는다. */
  .vg-tap{ animation-duration:.01ms !important; }
  .vg-tap-sparkle{ animation:none !important; display:none; }
  .village-npc.walking .village-npc-img{ animation:none !important; }
  .village-map::before{ transition:none; }
}

/* ---- 반응형 ---- (두번째씬_마을맵_기획.md §10-4)
   ★ 2026-08-31 폴더블 지시 — **화면 폭으로 지도와 미니미를 바꾸던 두 블록을 걷어냈다.**
     걷어낸 것과 그 이유:
       ① `.village-map{ width/height/border… }` (min-width 768 · 1024)
          → 지도 크기·자리는 이제 village-fullbleed.js 카메라 하나가 정한다(인라인 px).
            이 규칙들은 인라인에 밀려 **이미 아무 일도 하지 않았고**, 남겨 두면 다음 사람이
            [지도 크기를 여기서도 정한다]고 읽는다(CLAUDE.md 규칙 6 — 두 길을 만들지 않는다).
       ② `.village-npc{ --npc-h:82px / 96px }`
          → 화면 폭이 768/1024 를 넘는 순간 미니미만 커져 [사람 ↔ 건물] 비율이 깨졌다.
            폴더블을 펼치면 실제로 이 분기를 넘나든다. 신장은 이제 :477 에서 카메라 배율에
            비례한다 — 월드 안에서 늘 같은 키다(사람 지시 §2 · 추가요구 §10 · §11).
     지금 화면 크기로 갈리는 것은 **UI 뿐**이다: village.css:134(제목) ·
     village-ui.css:328(HUD 줄바꿈) · village-fullbleed.css(시트 폭). 월드는 갈리지 않는다. */

/* ---- 상점별 씬전환 애니메이션 - 샵 클릭 시 village.js가 클래스를 붙여 재생한다.
   6샵 각자의 포인트 컬러·성격(§10-2)에 맞춰 기법을 다르게 해 "같은 문 열림"이
   아니라 상점마다 다른 느낌으로 다음 화면에 들어가게 한다. 신규 이미지 애셋 없이
   CSS clip-path/transform만 사용(비용 0원, 기존 프로젝트 원칙과 동일). ---- */
.scene-transition{
  position:fixed; inset:0; z-index:200; pointer-events:none; opacity:0;
  display:flex; align-items:center; justify-content:center; overflow:hidden;
}
.scene-transition.active{ pointer-events:auto; opacity:1; }

.tt-icon{
  position:relative; z-index:1; font-size:3.4rem; opacity:0;
  filter:drop-shadow(0 2px 10px rgba(0,0,0,.35));
}
.scene-transition.active .tt-icon{
  animation:tt-icon-pop var(--tt-dur,500ms) ease-out forwards;
}
@keyframes tt-icon-pop{
  0%{ opacity:0; transform:scale(.4); }
  45%{ opacity:1; transform:scale(1.15); }
  100%{ opacity:0; transform:scale(1.5); }
}

/* 원형 확산(샵1 리아·샵3 설이·샵6 혜안당·놀이터 공용 기법) - 클릭한 샵 위치(--tx/--ty)에서
   퍼져나가며, 색·속도·이징만 샵마다 달라 같은 기법이어도 인상이 다르게 느껴진다. */
.scene-transition.tt-circle{
  background:var(--tt-color,#6d3fc0);
  clip-path:circle(0% at var(--tx,50%) var(--ty,50%));
  animation:tt-circle-in var(--tt-dur,500ms) var(--tt-ease,ease-out) forwards;
}
@keyframes tt-circle-in{ to{ clip-path:circle(140% at var(--tx,50%) var(--ty,50%)); } }
/* 놀이터 - 다채로운 색이 도는 무지개 원형(다른 5곳과 톤 차별, 가장 경쾌한 인상) */
.scene-transition.tt-circle.tt-rainbow{
  background:conic-gradient(from 0deg,#ff8fb3,#f6c453,#7fd88f,#7fc8e8,#a685e2,#ff8fb3);
}

/* 칸(샵2) - 냉철·비즈니스 톤: 통통 튀지 않는 정직한 대각선 슬라이드 */
.scene-transition.tt-wipe::before{
  content:""; position:absolute; inset:-10% -10%; background:var(--tt-color,#f6c453);
  transform:translate3d(110%,0,0) skewX(-8deg);
  animation:tt-wipe-in var(--tt-dur,520ms) cubic-bezier(.65,0,.35,1) forwards;
}
@keyframes tt-wipe-in{ to{ transform:translate3d(0,0,0) skewX(-8deg); } }

/* 묵(샵4) - 가장 느린 호흡의 훈장님답게 왼쪽에서 먹물이 번지듯 느리게, 끝단에 금박 라인 */
.scene-transition.tt-ink::before{
  content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, var(--tt-color1,#1c2a4a) 0%, var(--tt-color1,#1c2a4a) 96%, var(--tt-color2,#f6c453) 100%);
  clip-path:polygon(0 0,0 0,0 100%,0 100%);
  animation:tt-ink-in var(--tt-dur,700ms) ease-in forwards;
}
@keyframes tt-ink-in{ to{ clip-path:polygon(0 0,100% 0,100% 100%,0 100%); } }

@media (prefers-reduced-motion: reduce){
  .scene-transition, .scene-transition::before, .tt-icon{ animation:none !important; }
}

/* ── 개발자 시간 슬라이더 (?dev=1 일 때만 village-daynight.js 가 붙인다) ──────
   출시 빌드에도 코드는 들어가지만 쿼리 없이는 DOM 자체가 안 생긴다 —
   별도 빌드 분기를 만들면 [개발에서만 되는 것]이 생겨 나중에 어긋난다. */
.vg-devbar{
  /* ★ 2026-08-20 실측 — 아래(bottom)에 두면 안 보인다. 마을 그림이 세로로 길어(1024x1536)
     지도 하단이 화면 밖으로 나가기 때문이다. 사람이 [왜 낮/밤이 안 바뀌지] 라고 한 원인 중
     하나가 이것이었다(다른 하나는 자동 모드가 지금 시각을 따라 밤에 머무는 것 — 그건 정상 동작).
     그래서 지도 **위쪽**에 붙인다. 오른쪽 52px 는 낮/밤 토글 버튼 자리를 비워 둔 것이다. */
  position:absolute; left:8px; right:52px; top:8px; z-index:21;
  display:flex; align-items:center; gap:8px;
  padding:8px 10px; border-radius:10px;
  background:rgba(12,10,26,.72); border:1px solid rgba(255,255,255,.18);
  backdrop-filter:blur(4px);
}
.vg-devbar-out{
  flex:0 0 auto; min-width:118px; color:#ffe9b8;
  font:600 .74rem/1 ui-monospace, Consolas, monospace; letter-spacing:.02em;
}
.vg-devbar input[type="range"]{ flex:1 1 auto; min-width:0; accent-color:#ffca6e; }

/* 2026-08-21: 시간 슬라이더(00:00~24:00)는 없앴다 — 시간대가 벽시계가 아니라 게임시간
   (낮120→노을120→밤120)으로 바뀌었으므로 [몇 시]를 끄는 자리가 의미를 잃었다.
   대신 [그 시간대로 감기] 3버튼 + [한 시간대 길이] 선택으로 바꿨다.
   ★ 전역 button{min-height:44px}(style.css:75)를 여기서만 낮춘다 — 개발바는
     지도 위 8px 띠 안에 들어가야 해서 44px 버튼 3개가 들어갈 세로가 없다.
     테스트 전용이고 ?dev=1 없이는 DOM 자체가 안 생긴다. */
.vg-devbar-jump{ display:flex; gap:4px; flex:0 0 auto; }
.vg-devbar-jump button{
  min-height:26px; padding:0 8px; border-radius:7px;
  border:1px solid rgba(255,255,255,.22); background:rgba(255,255,255,.10);
  color:#ffe9b8; font:600 .72rem/1 var(--font-ui,"Malgun Gothic",sans-serif);
}
.vg-devbar-jump button:active{ background:rgba(255,202,110,.30); }
.vg-devbar-speed{
  flex:0 0 auto; min-height:26px; max-width:92px;
  border-radius:7px; border:1px solid rgba(255,255,255,.22);
  background:rgba(12,10,26,.9); color:#ffe9b8;
  font:600 .7rem/1 var(--font-ui,"Malgun Gothic",sans-serif); padding:2px 4px;
}

/* 개발자 테스트 버튼 (village-dev.js · 2026-08-21 사람 지시 "은화 만개 지급").
   ★ 자리: 낮/밤 토글(top:10 right:10 · 32px) **바로 아래**. 52 = 10 + 32 + 10(간격).
     위쪽 8~? 는 ?dev=1 시간 슬라이더(.vg-devbar)가 쓰므로 그 자리를 피한다.
   ★ 테스트용인 게 한눈에 보여야 오출시를 막는다 — 마을 팔레트(파스텔)와 일부러 다른
     경고색(호박/검정 대비)으로 둔다. 스토어 빌드에서는 스크립트째 빠진다. */
.vg-devcoin{
  position:absolute; top:52px; right:10px; z-index:21;
  border:1px dashed #ffca6e; border-radius:999px; padding:0 12px;
  background:rgba(12,10,26,.72); color:#ffe9b8;
  font:700 .72rem/1 ui-monospace, Consolas, monospace; letter-spacing:.02em;
  cursor:pointer; white-space:nowrap;
  /* 전역 button{min-height:44px}(style.css:75)를 그대로 받는다 — 터치 하한 44px 확보.
     .village-daynight 처럼 시각을 32px 로 줄이지 않는다(테스트 버튼은 크게 눌리는 편이 낫다). */
  display:inline-flex; align-items:center; justify-content:center;
}
.vg-devcoin:active{ background:rgba(255,202,110,.28); }

/* ============================================================
   STEP 7 — 상점 터치: 살짝 Zoom + Bottom Sheet (요청문 9절)
   ★ `.village-map` 자체를 키우지 않는다 — overflow 밖으로 나가 페이지가 밀린다.
     그림·광원·미니미·문 레이어만 같이 키운다(같이 안 키우면 문 글로우가 어긋난다).
   ============================================================ */
/* 카메라 — 움직이는 것 전부를 담는 래퍼 한 겹(village-shop.js 가 런타임에 만든다).
   ★ 레이어마다 transform 을 걸면 요소마다 새 스택 문맥이 생겨 `mix-blend-mode:screen`
     광원이 [그림] 이 아니라 [지도 바탕] 에 섞인다 — 줌 하는 순간 조명이 타 버린다.
     그래서 **한 겹만** 키운다. */
.vg-camera{
  position:absolute; inset:0;
  transform-origin: var(--zx,50%) var(--zy,50%);
  transition: transform .5s cubic-bezier(.22,.9,.3,1);
  will-change: transform;
}
.village-map.vg-zoomed .vg-camera{ transform: scale(1.22); }

/* 터치 가능하다는 아주 은은한 표시 (요청문 9절 마지막 줄)
   ★ 2026-09-03 #168 §39 art-director 채점 2 — 옛 판은 히트박스 상자(border-radius 18%)의 inset box-shadow 라
     [직각 발광판]으로 읽혔고(§34 RPG HUD), nth-of-type(2n/3n) 지연이 1·5 / 2·4 를 동시에 켰다.
     지금은 ::after 원형 radial 을 **문 위**(--door-x/--door-y · index.html 히트박스 style 에 문 좌표를 상자 % 로 환산)에
     두고, 한 바퀴 9s 에 다섯 집이 1.8s 씩 어긋나 **한 번에 한 집만** 0.54s 숨 쉰다. box-shadow → opacity 라 페인트 층도 가볍다
     (04_품질팀\도구\발열코드감사.js 페인트 2점 → 합성 0점). */
.shop[data-door-id]::after{
  content:""; position:absolute; left:var(--door-x,50%); top:var(--door-y,55%);
  width:min(56px,14vmin); aspect-ratio:1; transform:translate(-50%,-50%); border-radius:50%;
  background:radial-gradient(circle, rgba(255,246,214,.34) 0%, rgba(255,246,214,.12) 45%, rgba(255,246,214,0) 70%);
  opacity:0; pointer-events:none; will-change:opacity;
}
@media (prefers-reduced-motion: no-preference){
  .shop[data-door-id]::after{ animation: vg-shop-hint 9s ease-in-out infinite; }
  .shop[data-door-id="shop-3"]::after{ animation-delay: 1.8s; }
  .shop[data-door-id="shop-5"]::after{ animation-delay: 3.6s; }
  .shop[data-door-id="shop-worry"]::after{ animation-delay: 5.4s; }
  .shop[data-door-id="shop-playground"]::after{ animation-delay: 7.2s; }
}
@keyframes vg-shop-hint{
  0%, 88%, 100% { opacity:0; }
  94%           { opacity:1; }
}
.shop[data-door-id]:focus-visible{ outline:2px solid #ffd982; outline-offset:2px; }
.shop.is-picked::after{ opacity:1; animation:none; }

/* ── 바텀시트 ────────────────────────────────────────────── */
.vg-sheet-scrim{
  position:absolute; inset:0; z-index:28; background:rgba(8,6,18,.42);
  opacity:0; transition:opacity .24s ease; backdrop-filter:blur(1.5px);
}
.vg-sheet-scrim.show{ opacity:1; }

.vg-sheet{
  position:absolute; left:0; right:0; bottom:0; z-index:29;
  padding:10px 18px calc(18px + env(safe-area-inset-bottom,0px));
  border-radius:18px 18px 0 0;
  background:linear-gradient(180deg, rgba(26,20,44,.97), rgba(16,12,28,.99));
  border-top:1px solid rgba(255,255,255,.16);
  box-shadow:0 -10px 30px rgba(0,0,0,.45);
  transform:translateY(102%); transition:transform .26s cubic-bezier(.22,.9,.3,1);
}
.vg-sheet.show{ transform:translateY(0); }
.vg-sheet-grip{
  width:38px; height:4px; border-radius:999px; margin:2px auto 12px;
  background:rgba(255,255,255,.28);
}
.vg-sheet-head{ display:flex; align-items:center; gap:10px; }
.vg-sheet-icon{ font-size:1.5rem; line-height:1; }
.vg-sheet-title{
  margin:0; font-size:1.06rem; font-weight:700; color:#fff; letter-spacing:-.01em;
  padding:0; /* style.css 의 h2 규칙을 이 안에서만 되돌린다 */
}
.vg-sheet-sub{
  margin:6px 0 14px; font-size:.86rem; color:rgba(255,255,255,.72);
}
.vg-sheet-btns{ display:flex; gap:10px; }
.vg-sheet-go{
  flex:1 1 auto; min-height:48px; border-radius:12px; border:none; cursor:pointer;
  font-size:.95rem; font-weight:700; color:#12101c;
  background:var(--shop-color,#f6c453);
  box-shadow:0 4px 14px rgba(0,0,0,.35);
}
.vg-sheet-go:disabled{
  background:rgba(255,255,255,.16); color:rgba(255,255,255,.55); cursor:default; box-shadow:none;
}
.vg-sheet-close{
  flex:0 0 auto; min-height:48px; padding:0 18px; border-radius:12px; cursor:pointer;
  background:rgba(255,255,255,.10); color:#fff; border:1px solid rgba(255,255,255,.20);
  font-size:.9rem;
}
@media (prefers-reduced-motion: reduce){
  .vg-sheet, .vg-sheet-scrim, .vg-camera{ transition-duration:.01ms; }
  .shop[data-door-id]{ animation:none; }
}

/* ============================================================
   STEP 8 — 정문 개폐 (요청문 10절)
   ★ 원화의 철문을 자르지 않는다. 아치 개구부만큼만 덮는 판 두 짝을 좌우로 민다.
     좌표는 격자 실측: 개구부 x 40~62% · y 76~94% (bottom crop 2.5%p 격자).
   ★ 닫혀 있을 때는 거의 보이지 않아야 한다 — 원화의 금색 문양이 정본이다.
     열릴 때만 안쪽 어둠과 새어 나오는 등불빛이 드러난다.
   ============================================================ */
.vg-gate{
  /* ★★ 2026-09-19 [신규 941x1672 WORLD MASTER] 개구부 1%그리드 재실측(2배 확대 정밀 크롭):
     문기둥 안쪽 개구부 = x 41.98~57.92% · y 71.77~80.74%. 옛 값(x47.7~54.0/y74.5~80.5)은
     구원화 기준이라 폐기 — 이 원화에서 대문 중심은 화면 중앙(50%)이 아니라 52.60%다. */
  position:absolute; left:41.98%; top:71.77%; width:15.94%; height:8.97%;
  z-index:6; pointer-events:none; overflow:hidden;
}
/* 안쪽 — 문이 열리면 보이는 마을 밖 어둠 + 새어 나오는 빛 */
.vg-gate-inner{
  position:absolute; inset:0; border-radius:44% 44% 4% 4% / 30% 30% 4% 4%;
  background:
    radial-gradient(120% 60% at 50% 100%, rgba(255,214,140,.55) 0%, rgba(255,190,90,0) 62%),
    linear-gradient(180deg, rgba(6,4,14,.92), rgba(10,7,20,.80));
  opacity:0; transition:opacity .45s ease;
}
.vg-gate-leaf{
  position:absolute; top:0; bottom:0; width:50%;
  background:linear-gradient(180deg, rgba(30,22,10,.30), rgba(18,13,6,.42));
  border-radius:44% 8% 4% 4% / 30% 8% 4% 4%;
  opacity:0;                       /* 닫힘 = 원화 그대로 보이게 */
  transition:transform .5s cubic-bezier(.3,.85,.35,1), opacity .35s ease;
  transform-origin:center;
}
.vg-gate-l{ left:0; }
.vg-gate-r{ right:0; border-radius:8% 44% 4% 4% / 8% 30% 4% 4%; }

/* 열림 — village.js openDoor('gate') 가 .door-gate 에 .is-open 을 붙이는 1.6초 동안 */
.village-map:has(.door-gate.is-open) .vg-gate-inner{ opacity:1; }
.village-map:has(.door-gate.is-open) .vg-gate-leaf{ opacity:1; }
.village-map:has(.door-gate.is-open) .vg-gate-l{ transform:translateX(-88%) rotateY(28deg); }
.village-map:has(.door-gate.is-open) .vg-gate-r{ transform:translateX(88%) rotateY(-28deg); }

/* :has() 를 모르는 구형 WebView 대비 — village.js 가 지도에도 클래스를 붙인다(아래 JS). */
.village-map.gate-open .vg-gate-inner{ opacity:1; }
.village-map.gate-open .vg-gate-leaf{ opacity:1; }
.village-map.gate-open .vg-gate-l{ transform:translateX(-88%) rotateY(28deg); }
.village-map.gate-open .vg-gate-r{ transform:translateX(88%) rotateY(-28deg); }

@media (prefers-reduced-motion: reduce){
  .vg-gate-leaf, .vg-gate-inner{ transition-duration:.01ms; }
}

/* ============================================================
   STEP 9 — 배경 애니메이션 (요청문 11절) · cozy / living village
   ★ 전부 아주 미세하게. 눈에 "띄면" 과한 것이다.
   ★ 새 이미지 0장 — 전부 CSS. 원화 위에 얹는 결이라 원화를 해치지 않는다.
   ============================================================ */
/* 나뭇잎·꽃 흔들림 — 벚나무가 있는 좌우 가장자리와 상단 띠에만 아주 약하게 */
.vg-sway{ position:absolute; pointer-events:none; z-index:2; opacity:.5; }
@media (prefers-reduced-motion: no-preference){
  .vg-sway{ animation:vg-sway-move var(--d,7s) ease-in-out infinite; animation-delay:var(--delay,0s); }
}
@keyframes vg-sway-move{
  0%,100%{ transform:translate3d(0,0,0) rotate(0deg); }
  50%    { transform:translate3d(1.5px,-1px,0) rotate(.6deg); }
}
/* 꽃잎 — 아주 드물게 떨어진다(요청 "꽃 흔들림"의 cozy 판). 최대 6장. */
.vg-petal{
  position:absolute; width:6px; height:6px; border-radius:60% 20% 60% 20%;
  background:rgba(255,190,215,.75); pointer-events:none; z-index:6; opacity:0;
}
@media (prefers-reduced-motion: no-preference){
  .vg-petal{ animation:vg-petal-fall var(--d,11s) linear infinite; animation-delay:var(--delay,0s); }
}
@keyframes vg-petal-fall{
  0%   { transform:translate3d(0,-6px,0) rotate(0deg);     opacity:0; }
  12%  { opacity:.7; }
  88%  { opacity:.5; }
  100% { transform:translate3d(26px,190px,0) rotate(220deg); opacity:0; }
}
/* 무명암 주변의 신비로운 안개 (요청 "전체운세 건물 주변")
   2026-09-10 실측 — .vg-camera(960x960, world 0~100% 그대로)에서 재던 값이 top12% 라
   화면 하늘 쪽(스크린샷 y64/960)에 떠 있었다. 실제 도사집은 y39~50.5%(index.html shop-5,
   2026-09-10 재측정) — 그 주변을 감싸도록 다시 잰다. */
.vg-mist{
  position:absolute; left:51%; top:35%; width:20%; height:20%;
  pointer-events:none; z-index:3; opacity:.0;
  background:radial-gradient(60% 50% at 50% 70%, rgba(190,170,255,.30) 0%, rgba(150,130,255,0) 70%);
  mix-blend-mode:screen;
}
@media (prefers-reduced-motion: no-preference){
  .vg-mist{ animation:vg-mist-breathe 9s ease-in-out infinite; }
}
@keyframes vg-mist-breathe{
  0%,100%{ opacity:.18; transform:translate3d(0,0,0)   scale(1); }
  50%    { opacity:.42; transform:translate3d(0,-4px,0) scale(1.06); }
}
/* 상점 내부 불빛 변화 — 창문 광원이 아주 느리게 숨쉰다(밤에만 보인다) */
@media (prefers-reduced-motion: no-preference){
  .vg-light.is-window{ animation:vg-window-breathe 7.5s ease-in-out infinite; }
  .vg-light.is-window:nth-of-type(2n){ animation-delay:2.6s; }
}
@keyframes vg-window-breathe{ 0%,100%{ filter:brightness(.94); } 50%{ filter:brightness(1.10); } }

/*  ★ 2026-09-04 #165 ② — 아래 상태빛은 **이 유휴 호흡 바로 뒤**에 있어야 한다.
      둘 다 특이도 0,2,0 이라 나중에 읽힌 쪽이 이긴다. 상태빛을 위쪽(별 절)에 뒀더니
      위 `.vg-light.is-window{animation:...7.5s}` 이 덮어써 사주집 창문만 상태 호흡이
      **아예 안 걸렸다**(자 ⑩-e 실측 7.5s). 순서를 바꾸지 않는다.
      함께 바로잡은 것 하나 — 계약의 [유휴보다 커야 한다] 하한은 네온 ±6% 가 아니라
      **창문 자신의 유휴 ±8%**(위 .94↔1.10)가 기준이다: 0.55×0.08 = 0.044. 진폭 0.14 는 그 위다. */
/* ══ #165 ② 세계관형 상태빛 (2026-09-04 · 08_작업1팀) ═══════════════════════
   *"오늘 아직 안 갔구나"* 를 **글자가 아니라 빛으로** 말한다.
   계약 = 08_작업1팀\산출물u_상태빛_2026-09-04\계약.json (원화 실측에서 뽑았다)

   ★ 새로 만드는 것이 없다 — 여기 있는 것은 **이미 켜지는 불에 얹는 호흡** 하나뿐이다.
     · 새 광원 0   : 사주집 창(index.html:159,160)·도사집 등롱(:150)은 원래 있던 그 불이다
     · 새 입자 0   : 타로집 별빛 3개는 기존 별 20 중 3개를 지붕 위로 **자리 고정**한 것이다
                    (village-daynight.js 별자리잡기 — 상한 45 는 village-ambient.js:5~6 에서 이미 만석)
     · 새 색 0     : 색은 그 불이 가진 --c 그대로다(밤 원화에서 구운 값)
   ★ 왜 낮에는 없나 — 원화 실측(산출물u_상태빛_2026-09-04\원화실측.json)에서 **낮**의 그 자리들은
     둘레보다 12.1~16.8 밖에 안 밝다(밤은 24.2~110.5). 낮 배경 휘도가 89.5~157 이라 크림빛은
     [빛]으로 안 읽힌다. 그래서 문턱을 등롱과 같은 dn 0.32 로 둔다 — 낮의 창구는
     이미 있는 상태 라벨(하루 1회 · village-react.js 상태라벨)이다.
   ★ 진폭 0.14 는 어디서 왔나 — 밤 원화에서 **가장 약하게 칠해진 불**(사주집 큰창 · 둘레보다 24.2)
     ÷ (광원색 휘도 233 − 밤 배경 61.9) = 0.141. 즉 [원화의 불 한 개]만큼만 더 밝아진다.
     아래끝은 기존 유휴 흔들림(밝기 ±6% · 위 vg-neon-breathe)보다 커야 [유휴]가 아니라
     [상태]로 읽힌다 → 0.55×0.06 = 0.033. 0.14 는 그 위다.
   ★ 주기 3.4s 는 별 트윙클 2.6s 와 네온 호흡 4.2s **사이**다 — 어느 쪽과도 안 겹쳐야
     새 리듬으로 들린다.                                                                */
/*  --vg-state-base 는 [그 불의 평상시 세기] 를 그대로 다시 쓴 것이다(위 :241·:243 과 같은 식).
    새로 고른 숫자가 아니다 — 다르게 적으면 상태일 때 오히려 더 **어두운** 순간이 생긴다.
    --vg-state-on 은 [상태빛을 켤 때인가](dn 0.32 문턱)이고, 더할 양에도 곱해 둔다.
    안 곱하면 낮(dn .08)에도 호흡의 마루가 0.14 로 떠 **낮에 없어야 할 불**이 생긴다. */
/*  ★ 2026-09-04 art-director 조건 1 — 상태 가산 문턱을 0.32 → **0.60** 으로 올렸다.
    잡아 준 것 둘 :
      ① 문턱이 하나가 아니었다. 타로집 별빛은 `.vg-star` 의 실효 문턱 0.60(위 별 절)을 타는데
         창문·등불만 0.32 라, **노을 내내 사주집·도사집은 말하고 타로집만 침묵**했다.
      ② 노을 원화에는 창 불이 아예 안 칠해져 있다 — 원화실측.json 노을 튐 : 타로집 창 −0.6·−6.0 ·
         사주집 창 2.1·−7.3 (자가 세운 [진짜 불] 선은 25 다). 그런데 상태빛이 노을에 사주집 창을
         0.44 → 0.714 로 켰다. **원화가 끄고 그린 창을 켜는 것**이라 의도.md [안 하는 것] ② 와 부딪친다.
    0.60 은 새 숫자가 아니다 — 위 `.vg-star` 가 이미 쓰는 그 램프 시작점이다. 이제 셋이 같은 순간에 켜진다.
    ★ 아래 --vg-state-base 의 0.30 / 0.32 는 **안 올린다.** 그것은 [그 불의 평상시 세기]를 그대로 다시
      쓴 식이라(:253·:255 와 같은 값) 여기를 0.60 으로 바꾸면 노을에 base 가 0 이 되어 상태가 켜진 창이
      오히려 **꺼진다**. 조건이 막으려던 것은 [가산]이고, 가산은 --vg-state-on 하나가 잠근다. */
.vg-light.is-state{ --vg-state-on:clamp(0, calc((var(--dn,0) - 0.60) / 0.25), 1);
                    animation:vg-state-breathe 3.4s ease-in-out infinite; }
/*  진폭 --vg-state-amp — 값은 아래 [진폭 실측] 주석이 정한다. 변수로 뽑아 둔 이유는
    자(진폭쓸기.js)가 이 한 값만 갈아 끼우며 화면을 찍어 고를 수 있게 하기 위해서다. */
/*  ★ 진폭 실측 (2026-09-04 · 08_작업1팀\산출물u_상태빛_2026-09-04\진폭쓸기.js · 쓸기\)
      처음에 0.14 로 뒀다 — [원화에서 **가장 약하게** 칠해진 불 한 개]에서 나온 값이다.
      자(마을빛검사 44/44)는 통과했는데 **눈으로는 안 보였다**(같은 사이클 크롭 대조 · 사람의눈
      [왕복 원칙]: 기계 통과 + 눈 실패 = 수정해야 한다). 그래서 진폭만 쓸어 실측했다 —
      상태를 켠 밤 화면에서 호흡의 [골]과 [마루]를 찍어 그 자리 화소차를 잰 값:
        진폭   0.14   0.22   0.30   0.38   0.46   0.55
        사주집 23.3   37.7   51.0   65.3   77.7   77.7   ← 0.46 에서 천장
        도사집 18.7   29.7   40.7   48.0   48.0   48.0   ← 0.38 에서 천장
      **0.38 을 쓴다.**
      ★ 2026-09-04 art-director 가 [왜 천장인가]를 바로잡았다 — 요소의 한계가 아니라 **opacity 가 1.0 에서
        잘리는 산술**이다: 등불 base .65 + 가산 → 진폭 0.35 에서 1.00 · 창문 base .55 → 0.45 에서 1.00.
        그래서 도사집은 0.38·0.46·0.55 가 전부 48.0 이고(0.35 초과분은 버려진다) 사주집은 0.46 부터 77.7 이다.
        0.38 은 [둘이 함께 천장에 닿는 값]이 아니라 **도사집이 잘리기 시작하는 값**이다. 값 자체는 그대로 쓴다 —
        도사집이 0.35 를 다 쓰고 사주집이 0.38 을 다 쓰는 자리라 어느 쪽도 손해가 없다.
        [골을 내려 클립을 피하는] 길은 쓰지 않는다 — 할 일이 있는 집이 골 순간에 없는 집보다 어두워진다.
      비교 기준 : 원화가 칠해 둔 불들의 튐 24.2~110.5(원화실측.json) — 65.3·48.0 은 그 안쪽이다. */
:root{ --vg-state-amp:0.38; }
/*  2026-09-11 STEP2 — 이 두 줄에도 위(:282 부근) --vg-zone-boost 를 똑같이 더한다.
    안 더하면 [상태 호흡] 중인 불(is-state 붙은 것 — 지금 어떤 상점이 대화 진행 중이라
    이미 숨쉬는 중)은 건물반응() 이 걸려도 값이 하나도 안 바뀌었다(실측: --vg-zone-boost
    는 .07 로 정상 계산되는데 opacity 는 그대로 — 이 두 줄이 별도 변수(--vg-state-base)로
    opacity 를 **완전히 새로 계산**해서 위의 덧셈을 가린 것이 원인). 같은 방식(곱하지
    않고 더하기)을 그대로 쓴다 — 진폭 계산(.38·클립 분석)은 한 글자도 안 건드린다. */
.vg-light.is-window.is-state { --vg-state-base:calc(clamp(0, calc((var(--dn,0) - 0.30) / 0.25), 1) * .55 + var(--vg-zone-boost, 0)); }
.vg-light.is-lantern.is-state{ --vg-state-base:calc(clamp(0, calc((var(--dn,0) - 0.32) / 0.25), 1) * .65 + var(--vg-zone-boost, 0) * .9); }
@keyframes vg-state-breathe{
  0%,100%{ opacity:var(--vg-state-base, 0); }
  50%    { opacity:calc(var(--vg-state-base, 0) + var(--vg-state-on, 0) * var(--vg-state-amp, .14)); }
}
/* 타로집 별빛 — 별 셋이 지붕 위에 붙박이고, 상태일 때만 조금 더 크게·오래 숨쉰다.
   자리는 village-daynight.js 가 계약값(계약.json ②.타로집_별빛.자리)으로 넣는다. */
/*  타로집 별빛 — 3px 점을 4px 로 키우고 밝기만 흔든 첫 판은 **하늘의 다른 별 17개와 구별이 안 됐다**
    (같은 사이클 눈 대조). 화소차만 132.3 으로 크고 **넓이가 없어서** 안 읽힌 것이다.
    그래서 크기가 아니라 [빛나는 넓이]로 말한다 — 별 자신의 흰빛으로 번짐 한 겹.
    번짐 크기의 근거 : 원화 광원 하나가 r 2.63%(지도 915px 기준 지름 ≈ 24px)이고 별은 셋이므로
    셋이 합쳐 그만해지려면 하나에 ≈ 8px 지름이다 → 마루 blur 6px + spread 1px (3px 점 위에서 ≈ 8~9px).
    새 요소도 새 색도 없다 — box-shadow 한 줄이고 색은 별의 흰빛 그대로다. */
.vg-star.is-state{ width:4px; height:4px; animation:vg-state-star 3.4s ease-in-out infinite; }
@keyframes vg-state-star{
  0%,100%{ transform:scale(.85); box-shadow:0 0 2px 0 rgba(255,255,255,.45); }
  50%    { transform:scale(1.15); box-shadow:0 0 6px 1px rgba(255,255,255,.95); }
}
/* 움직임을 줄인 사람에게는 [호흡] 대신 [조금 더 밝은 채로 켜져 있음]으로 말한다 — 상태는 남는다. */
@media (prefers-reduced-motion: reduce){
  .vg-light.is-state{ animation:none; opacity:calc(var(--vg-state-base, 0) + var(--vg-state-on, 0) * var(--vg-state-amp, .14) * .5); }
  .vg-star.is-state { animation:none; box-shadow:0 0 4px 1px rgba(255,255,255,.7); }
}
.village-map.vg-paused .vg-light.is-state,
.village-map.vg-paused .vg-star.is-state{ animation-play-state:paused; }

.village-map.vg-lite .vg-petal,
.village-map.vg-lite .vg-mist,
.village-map.vg-lite .vg-sway{ display:none; }
.village-map.vg-paused .vg-petal,
.village-map.vg-paused .vg-mist{ animation-play-state:paused; }

/* ============================================================
   미니미 관절 걷기 (2026-08-21 사람 지시 "손과 다리가 움직이게")
   ------------------------------------------------------------
   ★ 팔다리 원화가 따로 없다 — 스프라이트는 통짜 PNG 한 장이다.
     그래서 **같은 그림을 다섯 겹 깔고 clip-path 로 부위를 오려** 각자 흔든다.
     겹치는 부분이 없어야 팔이 두 개로 보이지 않는다 — 그래서 몸통은
     [어깨 아래 팔 자리]를 polygon 으로 파낸 모양이다.

   ★★ 2026-08-21 전체계획 #12 — 자르는 자리는 이제 **village-rig.js 가 캐릭터마다 계산한다.**
     03_디자인팀 계약(`2026-08-21_전체계획12_미니미걷기\판정과_계약.md`)이 실측으로 보인 것:
       · 한 값으로 30명을 자를 수 없다 — 어깨 37.4~60.0% · 그림자 위끝 70.4~98.3%
       · 상수 44/70 으로 자르면 30명 전원이 걸음마다 실루엣의 5.1~8.2% 를 잃는다(예외 0명)
       · 원인은 각도가 아니라 **자르는 방식**이다 — 다섯 조각이 서로 안 겹쳐서,
         조각이 돌아간 만큼 그 자리에 배경이 드러난다
     그래서 몸통에서 파내는 구멍을 [그 조각을 -각도~+각도 로 돌린 것들의 **교집합**]으로
     바꿨다(조문1). 그러면 조각이 어디로 돌든 제 구멍을 스스로 덮는다.
     구멍 4개는 서로 겹치고 테두리에 안 닿아서 clip-path:polygon() 으로는 못 그린다 —
     village-rig.js 가 **SVG 마스크**로 씌운다. 아래 .vg-body 의 polygon 은 그 파일이
     안 실렸을 때를 위한 **기본값**이다(종전 모습 그대로 걷는다).

   ★ 팔은 같은 쪽 다리와 **반대로** 흔든다(실제 걸음). 각도는 팔 7° · 다리 **20°**(계약 §4-4).
     ⚠ 다리 20° 와 `--gait`(village-rig.js 걸음ms) 는 **한 짝**이다. 한쪽만 바꾸면
       보폭과 실제 이동거리가 어긋나 발이 미끄러진다 — 계약 §4-4 가 그 식을 적어 뒀다.
   ============================================================ */
/* ★ 2026-08-21 사람 직접 지시 "방향을 바꿀 때 순간적으로 회전하지 않고 자연스럽게 회전한다".
   종전에는 .mirror 클래스가 scaleX(-1) 로 **한 프레임에** 뒤집었다 — 그게 [순간 회전]이다.
   이제 village.js 가 --face 를 0deg / 180deg 로 바꾸면 rotateY 가 --face-dur 동안 돌아
   [옆모습을 지나 반대쪽을 보는] 실제 회전이 된다. 180deg 는 scaleX(-1) 과 같은 그림이라
   끝 자세는 종전과 완전히 동일하다 — 가는 길만 생겼다.
   ★ 0.30s 는 걸음 주기(--gait 기본 .62s)의 절반 — 한 걸음 안에 몸이 다 돈다.
     이보다 길면 게걸음처럼 보이고, 짧으면 다시 [툭] 뒤집힌 것으로 읽힌다. */
.vg-npc-rig{
  position:relative; display:inline-block;
  /* 종전 .village-npc-img 가 하던 자리잡기 + 방향 */
  transform:translate(-50%,-92%) rotateY(var(--face,0deg));
  transform-origin:50% 92%;
  transition:transform var(--face-dur,.30s) ease-in-out;
}
@media (prefers-reduced-motion: reduce){
  .vg-npc-rig{ transition:none; }
}

/* 조각은 첫 장(몸통) 위에 정확히 포개진다. 첫 장만 흐름에 남아 리그 크기를 만든다. */
.vg-part{ display:block; }
.vg-part:not(.vg-body){ position:absolute; left:0; top:0; }

/* 몸통 = 머리(0~44% 전폭) + 가슴(44~70% 가운데만). 팔 자리를 파내 겹침을 없앤다. */
.vg-body{
  clip-path:polygon(0% 0%, 100% 0%, 100% 44%, 74% 44%, 74% 70%, 26% 70%, 26% 44%, 0% 44%);
}
.vg-arm-l{ clip-path:inset(44% 74% 30% 0);  transform-origin:20% 46%; }
.vg-arm-r{ clip-path:inset(44% 0 30% 74%);  transform-origin:80% 46%; }
.vg-leg-l{ clip-path:inset(70% 50% 0 0);    transform-origin:42% 70%; }
.vg-leg-r{ clip-path:inset(70% 0 0 50%);    transform-origin:58% 70%; }

/* 걷는 동안에만 흔든다. 서 있을 때는 완전히 정지 — 제자리에서 팔다리가 움직이면
   걷는 게 아니라 떠는 것처럼 보인다. */
@media (prefers-reduced-motion: no-preference){
  .village-npc.walking .vg-leg-l{ animation:vg-joint-leg-a var(--gait,.62s) ease-in-out infinite; }
  .village-npc.walking .vg-leg-r{ animation:vg-joint-leg-b var(--gait,.62s) ease-in-out infinite; }
  .village-npc.walking .vg-arm-l{ animation:vg-joint-arm-b var(--gait,.62s) ease-in-out infinite; }
  .village-npc.walking .vg-arm-r{ animation:vg-joint-arm-a var(--gait,.62s) ease-in-out infinite; }
  /* 걸음마다 한 번씩 몸이 눌린다(양다리 주기의 절반).
     ★★ 2026-08-21 조문5 — 이 -1px 은 **리그 전체**가 한다. 종전에는 .vg-body 만 올려서
       반 걸음마다 몸통이 팔다리에서 1px 떠올랐고, 그 틈만으로 실루엣의 5.2% 가 드러났다
       (계약 §4-5 · §5 — 현행 6.8% 중 대부분이 이것이었다).
     ★ transform 이 아니라 **translate 속성**으로 올린다. 리그의 transform 은
       [자리잡기 + 방향 rotateY] 가 쓰고 있고 거기 transition 이 걸려 있다 —
       같은 자리에 애니메이션을 걸면 방향 전환이 [툭] 끊긴다(그게 종전 결함이었다). */
  .village-npc.walking .vg-npc-rig{ animation:vg-joint-bob calc(var(--gait,.62s) / 2) ease-in-out infinite; }
}
/* ⚠ 다리 20° 는 village-rig.js 의 걸음ms(gait = 4·r·sin20 ÷ 속도) 와 한 짝이다(계약 §4-4). */
@keyframes vg-joint-leg-a{ 0%,100%{ transform:rotate(20deg); } 50%{ transform:rotate(-20deg); } }
@keyframes vg-joint-leg-b{ 0%,100%{ transform:rotate(-20deg); } 50%{ transform:rotate(20deg); } }
@keyframes vg-joint-arm-a{ 0%,100%{ transform:rotate(7deg); }  50%{ transform:rotate(-7deg); } }
@keyframes vg-joint-arm-b{ 0%,100%{ transform:rotate(-7deg); } 50%{ transform:rotate(7deg); } }
@keyframes vg-joint-bob{  0%,100%{ translate:0 0; }            50%{ translate:0 -1px; } }

/* 2026-09-11 STEP1 [NPC IDLE 다양화 §2] — 대기 중에도 완전히 굳어 있지 않게, 아주 옅은
   숨쉬기 하나만 얹는다. 위 vg-joint-bob 과 같은 이유로 translate 속성을 쓴다(리그의
   transform 은 이미 [자리잡기+방향 rotateY] 가 쓰고 있다 — 같은 자리에 걸면 방향 전환이
   끊긴다). 앉은 사람은 뺀다(벤치에 파묻힌 것처럼 들썩여 보인다 · .seated 는 이미
   팔다리 animation 을 꺼 둔 것과 같은 이유). 진폭 1.2px · 4.4s — 걷기 bob(0.3s·1px)보다
   한참 느리고 옅어야 [숨쉬는 정지]로 읽히지 [떠는 정지]로 안 읽힌다. */
@media (prefers-reduced-motion: no-preference){
  .village-npc:not(.walking):not(.seated) .vg-npc-rig{
    animation:vg-npc-idle-breathe 4.4s ease-in-out infinite;
  }
}
@keyframes vg-npc-idle-breathe{ 0%,100%{ translate:0 0; } 50%{ translate:0 -1.2px; } }

/* 2026-09-11 STEP1 [NPC↔NPC 생활 상호작용 §1] — village.js 의 인사제스처() 가 마주보기
   시작 때 `.is-greeting` 을 짧게 붙였다 뗀다(700ms). 목례(가볍게 숙였다 드는 인사)처럼
   보이도록 **rotate** 만 쓴다 — translate(위 숨쉬기·걷기 bob)와도, transform(자리잡기+
   방향)과도 서로 다른 CSS 속성이라 셋이 동시에 걸려도 안 끊긴다. `.vg-frames` 세트(마을
   주민 전원)는 transform-origin 이 발끝(50%/100%, 아래 §203 E 절)이라 rotate 가 정확히
   허리를 숙이는 모양으로 나온다. */
@media (prefers-reduced-motion: no-preference){
  .village-npc.is-greeting .vg-npc-rig{ animation:vg-npc-greet .7s ease-in-out 1; }
}
@keyframes vg-npc-greet{ 0%,100%{ rotate:0deg; } 40%{ rotate:-7deg; } 70%{ rotate:2deg; } }

/* ★ 2026-09-03 전체계획 #203 E — 걷기 컷이 있는 세트(.vg-frames · village.js 걷기프레임준비).
   몸통 한 장만 보이고 village.js 가 그 src 를 컷마다 바꾼다. 조각 4장은 숨긴다 — 흔들 팔다리가
   컷 안에 이미 그려져 있다. 리그의 자리잡기·rotateY·그림자·bob 은 그대로 쓴다.
   `!important` 인 이유: 몸통의 clip-path 기본값(위 .vg-body polygon)이 머리 아래 팔 자리를 파낸다. */
.village-npc.vg-frames .vg-part:not(.vg-body){ display:none; }
.village-npc.vg-frames .vg-body{
  clip-path:none !important; -webkit-mask-image:none !important; mask-image:none !important;
  animation:none !important;
}
/* 2026-09-06 지시 §5 [발 위치를 이동 좌표 기준점으로] — 걷기프레임반입.py 로 뽑은 sd/nw
   세트는 알파 bbox 를 **발끝까지 딱 맞춰** 자른다(여백 0 — 인물가르기() 가 foreground 의
   y1 을 그대로 캔버스 바닥에 붙인다). 그런데 위 공용 `.vg-npc-rig` 는 -92%(앵커에서 8%
   아래가 이미지 바닥)라 — 옛 팔다리-리그 세트(마진이 있던 원화)에 맞춘 값이다. 컷 세트에
   그대로 쓰면 앵커(=이동 좌표·그림자 자리)가 실제 발바닥보다 8%×키 위에 떠, 원근 배율이
   커질수록(§3) 뜨는 픽셀 수도 늘어 [가까이 올수록 더 떠 보이는] 것으로 읽혔다.
   컷 세트만 100%/100% 로 다시 앵커링한다 — 옛 리그 세트(현재 전부 미니미제외 로 안 씀)는
   그대로 둔다. */
.village-npc.vg-frames .vg-npc-rig{
  /* 2026-09-13 [미니미 이동 시스템 통합 P0] STEP1 — village.js setPartsSrc() 가
     --npc-foot-fix 를 그 세트의 back(뒷모습) 실측 여백(%)만큼 건다(front·side 는 0).
     -100% 대신 calc(-100% + fix%) 로 그만큼 "덜 위로"(=아래로) 옮겨 실제 발끝을
     다시 world 앵커(도로 표면)에 맞춘다 — % 값이라 렌더 높이(scale·perspective)에
     자동 비례해서 scale 자체는 한 글자도 안 바꾼다(§1-5). */
  transform:translate(-50%, calc(-100% + var(--npc-foot-fix, 0) * 1%)) rotateY(var(--face,0deg));
  transform-origin:50% 100%;
}

/* 앉아 있을 때는 관절을 접어 둔다(다리를 흔들면 벤치를 뚫는다) */
.village-npc.seated .vg-leg-l,
.village-npc.seated .vg-leg-r,
.village-npc.seated .vg-arm-l,
.village-npc.seated .vg-arm-r{ animation:none; transform:none; }

/* 조문5 이후 바운스는 리그가 하므로, 멈춤·모션축소도 리그를 같이 다뤄야 한다 */
.village-map.vg-paused .vg-part,
.village-map.vg-paused .vg-npc-rig{ animation-play-state:paused; }
@media (prefers-reduced-motion: reduce){
  .vg-part{ animation:none !important; }
  /* 2026-09-11 STEP1 — rotate 추가(위 vg-npc-greet 목례가 새로 쓰는 속성). 기존 두 줄은 그대로. */
  .vg-npc-rig{ animation:none !important; translate:none !important; rotate:none !important; }
}

/* ════════════════════════════════════════════════════════════════════
   터치 이펙트 — 전체계획 #11 · 03_디자인팀 계약
   `03_디자인팀\산출물\2026-08-21_전체계획11_터치이펙트_계약.md` §3~§4
   구현은 village-tap.js. 여기 있는 숫자는 **전부 그 계약의 값**이다 —
   감으로 고르지 않았고, 이 파일에서 바꾸지도 않는다(바꿀 일이 생기면 계약을 먼저 고친다).
   ──────────────────────────────────────────────────────────────────
   계약이 정한 값
     · 링 안쪽 겹  rgba(255,255,255,.8) 2px  (= .vg-ripple 분수 물결과 같은 값)
     · 링 바깥 겹  box-shadow 0 0 8px <그 상점 강조색>  (#3 계약 §4-4)
     · 납작 비율   2.03 : 1  (분수 물결 실측 130 ÷ 64) — 지도 위 대상만. UI 부품은 1:1
     · 마디1 등장  100ms · 지름 44px (RW-074 탭 하한)
     · 마디2 지속  상점·문 500ms cubic-bezier(.22,.9,.3,1) (= .vg-camera 줌과 같은 시간·곡선)
                   그 밖 180ms ease-out (= 분수 물결과 같은 곡선)
     · 취소        80ms (100 × 0.772 = 앱 퇴장/입장 비율 실측)
   ────────────────────────────────────────────────────────────────── */

/* 이펙트는 **대상이 아니라 별도 노드**에 그린다(계약 §4-1).
   .shop 자신에게 box-shadow 를 주면 vg-shop-hint 애니메이션이 그것을 이겨 화면에 안 나온다
   — 결함 ⓒ 가 정확히 그 자리에서 났다. 노드를 분리하면 그 다툼 자체가 없다. */
.vg-tap-layer{ position:absolute; inset:0; z-index:8; pointer-events:none; }
/* 지도 밖 UI(뒤로가기·nav·낮/밤·시트 버튼)용. 카메라 줌을 따라가면 안 되는 것들이라
   화면 고정 좌표계에 그린다 — 지도 위 대상과 host 를 가르는 이유가 이것뿐이다. */
.vg-tap-layer-ui{ position:fixed; inset:0; z-index:70; pointer-events:none; }

.vg-tap{
  position:absolute; border:2px solid rgba(255,255,255,.8); border-radius:50%;
  /* ★ 크기를 애니메이션하지 않는다 — transform/opacity 만 쓴다(계약 §4-3).
     기준 크기를 [마디1 지름 44px] 로 두고 scale 로 키우므로, 손가락이 닿아 있는
     동안(가장 오래 보이는 상태)의 테두리가 계약값 2px 과 정확히 같다. */
  width:44px; opacity:1; transform:scale(0);
  will-change:transform, opacity;
}
/* 지도 위 대상 — 아이소메트릭 원근의 납작한 링. 44 ÷ 2.03 = 21.67 */
.vg-tap-map{ height:21.67px; margin:-10.835px 0 0 -22px; }
/* UI 부품 — 땅 위에 퍼지는 물결이 아니므로 정원(1:1) */
.vg-tap-ui{ height:44px; margin:-22px 0 0 -22px; }
/* 바깥 겹 = [어느 상점인가]. 빈 땅·UI 부품은 정체성이 없어 이 겹을 안 붙인다(계약 §3-1) */
.vg-tap-accent{ box-shadow:0 0 8px var(--vg-tap-accent, transparent); }

.vg-tap-in{ animation:vg-tap-in 100ms ease-out forwards; }
.vg-tap-out{ animation:vg-tap-out var(--vg-tap-dur,180ms) var(--vg-tap-ease, ease-out) forwards; }
.vg-tap-cancel{ animation:vg-tap-cancel 80ms ease-out forwards; }

@keyframes vg-tap-in{ from{ transform:scale(0); } to{ transform:scale(1); } }
/* --vg-tap-s2 = 그 대상 히트박스 가로 ÷ 44 (village-tap.js 가 실측해서 넣는다) */
@keyframes vg-tap-out{ from{ transform:scale(1); opacity:1; } to{ transform:scale(var(--vg-tap-s2,2)); opacity:0; } }
@keyframes vg-tap-cancel{ from{ opacity:1; } to{ opacity:0; } }

/* 미니미 반짝 — .vg-sparkle(분수)의 모양·움직임을 그대로 승계한다. 새 도형 0개(계약 §3-7) */
.vg-tap-sparkle{
  position:absolute; width:5px; height:5px; margin:-2.5px 0 0 -2.5px; border-radius:50%;
  background:#fff; box-shadow:0 0 6px 2px rgba(223,243,255,.85);
  transform:translateY(-50%) scale(.4); opacity:0;
  animation:vg-tap-twinkle 180ms ease-out forwards;
}
@keyframes vg-tap-twinkle{
  0%{ opacity:0; transform:translateY(-50%) scale(.4); }
  45%{ opacity:1; }
  100%{ opacity:0; transform:translateY(-85%) scale(1); }
}

/* 2026-09-11 STEP3 [숨은 터치 반응 §9 — 벚꽃 터치] — village-tap.js 벚꽃반응() 이 2~3장
   만든다. 이 프로젝트에 [떨어지는 꽃잎] 그림 자체가 없어(원화는 정지된 나무일 뿐이다)
   유일하게 새 도형을 쓰는 칸이다 — transform·opacity 만 쓴다(§18). --pf-dx·--pf-r 은
   JS 가 장마다 살짝 다르게 넣어(§9 "2~3장이 살짝 흩날리고") 한 지점에서 여러 장이 나와도
   전부 같은 궤적으로 겹쳐 보이지 않게 한다. */
.vg-tap-petal{
  position:absolute; width:7px; height:6px; margin:-3px 0 0 -3.5px;
  border-radius:65% 35% 65% 35%; background:#ffc9dc; opacity:0; pointer-events:none;
  animation:vg-petal-fall 1.1s ease-out forwards;
}
@keyframes vg-petal-fall{
  0%   { opacity:0;  transform:translate(0,0) rotate(0deg) scale(.6); }
  18%  { opacity:.9; }
  100% { opacity:0;  transform:translate(var(--pf-dx,14px), 30px) rotate(var(--pf-r,140deg)) scale(.85); }
}
@media (prefers-reduced-motion: reduce){
  .vg-tap-petal{ animation:none !important; display:none; }
}

/* 2026-09-11 STEP5 [DAILY MICRO EVENT §11-E — 놀이터 풍선] — village-daily-event.js 가
   하루 한 번, 이길 수도 있는 다섯 후보 중 하나로 띄운다. 이 배경엔 [떠오르는 풍선] 그림이
   없어(원화의 풍선다발은 정지된 그림) 유일하게 새 도형을 쓰는 칸이다(위 꽃잎과 같은
   이유) — transform·opacity 만 쓴다(§18). 색은 JS 가 3개를 서로 다르게 넣는다. */
.vg-daily-balloon{
  position:absolute; width:14px; height:18px; margin:-9px 0 0 -7px;
  border-radius:50% 50% 46% 46% / 58% 58% 42% 42%;
  opacity:0; pointer-events:none;
  animation:vg-balloon-float 3.6s ease-in-out forwards;
}
@keyframes vg-balloon-float{
  0%   { opacity:0;   transform:translateY(0) scale(.85); }
  12%  { opacity:.92; }
  50%  { transform:translateY(-26px) scale(1); }
  85%  { opacity:.92; }
  100% { opacity:0;   transform:translateY(-6px) scale(.92); }
}
@media (prefers-reduced-motion: reduce){
  .vg-daily-balloon{ animation:none !important; display:none; }
}

/* 문 7곳이 자기 탭 영역을 갖는다 — 결함 ⓐ(문 8곳 전부 무반응)를 닫는 자리.
   ★ 히트박스를 늘려서 풀지 않는다(계약 §3-5). 상점 히트박스 하변을 내리면 전체계획 #14 의
     [히트박스가 전부 건물 안]이 깨진다. 대신 이 앱에 이미 있는 기법(.village-daynight::after)을
     그대로 복제해 **문에만** 44x44 를 준다: .door 는 10x10 이고 테두리가 없으므로
     padding-box 기준 10 + 17x2 = 44. (테두리가 있으면 이 산식이 틀린다 — village.css 위쪽 주석 참고)
   ★ 정문(.door-gate)은 제외한다 — 갈 곳이 코드에 없다(village-shop.js 상점 표에 gate 0건).
     그 자리는 빈 땅 파문이 받는다(계약 §3-6). 목적지는 07_기획팀 #2 몫. */
.door[data-door]:not(.door-gate){ pointer-events:auto; cursor:pointer; }
.door[data-door]:not(.door-gate)::after{ content:""; position:absolute; inset:-17px; }

/* 결함 ⓒ — [눌림] 표시가 기본 설정에서 화면에 안 나왔다(계약 §2-4 실측:
   is-picked 가 붙어도 getComputedStyle 이 rgba(0,0,0,0) 0 0 0 0 을 냈다).
   CSS 애니메이션은 같은 속성의 일반 선언을 무조건 이기므로 vg-shop-hint 가 이겼다.
   **남기는 쪽을 골랐다** — 이 표시는 [지금 눌렀다](이펙트가 말한다)가 아니라
   [지금 열려 있는 상점]이라는 상태라, 사라지는 링이 대신할 수 없다(계약 §4-2 는 둘 중
   하나를 고르고 이유를 코드에 남기라고 했다). 힌트 애니메이션을 그 동안만 끈다. */
/* ★ 2026-09-03 #168 §39 재채점 [권장] — [지금 열려 있는 상점] 상태는 이제 문 위 원(.shop.is-picked::after · 위 힌트 절)이
   말한다. 인셋 box-shadow 는 18% 모서리 직각 판이라 원과 겹쳐 떠(우체통 시트 · 돌아온표시 1.4s) 걷었다. 상태 자체는 남는다. */
.shop[data-door-id].is-picked{ animation:none; }

/* 2026-08-27 — 지도가 낮/밤 2장이 됐다(전체계획 #58 확정본 · 08-26).
   3장 교차용 계산(노을 포함)은 뜻이 없어졌다 — 그대로 두면 0.45~0.55 구간에
   낮도 밤도 0 이 돼 지도가 통째로 사라진다.
   낮을 바탕으로 항상 깔고, 밤이 그 위에서 --dn 0.40~0.60 구간에 0→1 로 덮는다.

   ★ 2026-08-29 — **걷어냈다.** 이 덮어쓰기의 전제(노을 그림이 없다)가 사라졌다.
     의사결정 #25 가 ⓐ(3구간 유지)로, #36 이 재발주로 판정돼 낮·노을·밤 세 장을
     같은 배치로 다시 그려 반입했다(assets/village/village-sunset-0829.jpg 신규).
     이제 위 :182 의 종 모양 계산이 원래 뜻대로 돈다 — 0.45~0.55 는 노을이 채운다.
     되돌리려면 아래 두 줄의 주석을 풀고 index.html 의 .vg-art-sunset 을 지운다.

.vg-art img.vg-art-day{ opacity:1 !important; }
.vg-art img.vg-art-night{ opacity:clamp(0, calc((var(--dn,0) - 0.40) / 0.2), 1) !important; }
*/

/* ════════════════════════════════════════════════════════════════
   2026-08-27 사람 지시서 — Less UI, More Village
   ① 상단 타이틀·안내문은 첫 진입에만(village-lean.js 가 클래스를 준다)
   ② 은화 개발버튼은 [개발자 모드로 작게](사람 직접 지시)
   ════════════════════════════════════════════════════════════════ */
/* 첫 진입에 잠깐 보이는 상태 — 지도를 가리지 않게 배경을 깔지 않는다 */
.vg-lean-intro{ transition:opacity .6s ease-in-out; }
.vg-lean-fade{ opacity:0; }
/* 사라진 뒤 — display:none 이라야 자리(높이)까지 비어 지도가 그만큼 올라온다.
   visibility:hidden 로 두면 빈 줄이 남아 [UI 를 덜어낸] 효과가 안 난다. */
.vg-lean-gone{ display:none !important; }

/* (은화 개발버튼 규칙은 village-ui.css 로 모았다 — 같은 선택자를 두 파일에 두지 않는다) */

/* ════════════════════════════════════════════════════════════════
   2026-09-12 BUILDING ENTRY CINEMATIC — 사람 직접 지시.
   "버튼을 눌러 페이지 이동" 이 아니라 "마을에서 건물 안으로 들어간다"는 느낌.
   village-building-entry.js 가 탭 순간 .village-map 에 클래스 하나(§2 카메라
   push-in)와 --vg-entry-origin(건물별 transform-origin)을 건다 — village-fullbleed.js
   의 카메라(width/height/left/top 만 쓴다 · 규칙 6)와 **속성이 겹치지 않는다**
   (여긴 transform 하나만 새로 쓴다). 기존 상점 전환 오버레이(#scene-transition ·
   village.js SHOP_TRANSITIONS)는 그대로 살아서 같은 순간 위에 얹힌다 — "하나의
   Building Entry Engine" 요구를 [새 오버레이를 또 만들지 않는다]로 지킨다.
   ★ 배율은 1.03(요청 범위 1.025~1.04 중간) — "확대된다"고 의식되면 과한 것이라는
   지시 원문대로 아주 작게 잡았다. ═════════════════════════════════════════ */
.village-map.vg-entry-pushin{
  transform-origin: var(--vg-entry-origin, 50% 50%);
  animation: vg-entry-push .6s cubic-bezier(.22,.7,.3,1) forwards;
}
@keyframes vg-entry-push{
  0%{ transform:scale(1); }
  100%{ transform:scale(1.03); }
}
/* §4 주변부 정리 — "새까맣게 만들지 않는다 · 진입 후반부에만 · black overlay 로
   화면 전체를 덮지 않는다". 아주 옅은 radial vignette 하나, 후반부(60%~)에만 나타난다.
   선택 건물이 있는 중앙은 100% 그대로 읽힌다(투명 55%까지). */
.village-map.vg-entry-pushin::after{
  content:""; position:absolute; inset:0; z-index:8; pointer-events:none;
  background:radial-gradient(ellipse 68% 62% at var(--vg-entry-origin, 50% 50%),
    transparent 55%, rgba(12,8,22,.30) 100%);
  opacity:0; animation:vg-entry-vignette .6s ease-out forwards;
}
@keyframes vg-entry-vignette{ 0%,55%{ opacity:0; } 100%{ opacity:1; } }
@media (prefers-reduced-motion: reduce){
  .village-map.vg-entry-pushin{ animation:none; }
  .village-map.vg-entry-pushin::after{ animation:none; opacity:0; }
}
/* §13 뒤로가기 복귀 — "과하게 만들 필요 없다 · 짧은 soft reveal 정도". 매 로드(첫 진입
   포함)에 아주 짧게 한 번만 걸렸다 풀린다 — 상담 화면에서 막 돌아온 것과 처음 여는 것을
   구분하지 않는다(구분하려면 referrer 판정을 새로 만들어야 해서 규칙 6에 걸린다 — 대신
   [항상 부드럽게 뜬다]로 둘 다 해가 되지 않게 한다). */
.village-map.vg-entry-reveal{ opacity:.88; transition:opacity .22s ease-out; }
