/* 달려라 제주 감귤 — 시작 화면
   태블릿 가로 전용. 데스크톱·모바일폰 대응 없음. */

:root {
  --paper: #F2E7D0;   /* 크림 (배경) */
  --brick: #8E4A3C;   /* 제목 */
  --zest:  #E07B1E;   /* 제목의 '제주' — 잘 익은 감귤색 */
  --wood:      #CC846C;   /* 나무 상자 — 트럭 안 감귤 상자와 같은 색 */
  --wood-line: #8E4A4A;   /* 나무 상자 테두리 */
  --ink:   #46362A;

  /* 인트로 시간표 — 여기 숫자만 바꾸면 전체가 따라간다
       0s ─ 들어옴 ─ 4s ─ 서 있음 ─ 7s ─ 다시 출발 ─ 8.6s ─ 카드 넘김 ─ 9.35s */
  --drive: 4s;                                /* 트럭이 들어오는 시간 */
  --hold:  3s;                                /* 중앙에 서 있는 시간 */
  --exit:  1.6s;                              /* 다시 출발해 화면 밖으로 */
  --swipe: .75s;                              /* 게임 화면으로 넘어가는 시간 */
  --t-exit:  calc(var(--drive) + var(--hold));            /* 출발 시각 */
  --t-swipe: calc(var(--drive) + var(--hold) + var(--exit)); /* 넘김 시각 */
  --ease-exit: cubic-bezier(.45, 0, .75, .35);  /* 출발 가속 곡선 — 감속의 반대 */
  --ease:  cubic-bezier(.34, .52, .30, 1);    /* 감속 곡선 — 바퀴도 같이 쓴다.
      앞이 너무 가파르면 순식간에 들어와 버리고 끝에서만 오래 기어간다.
      최고속도를 평균의 2.2배로 낮춰 들어오는 내내 보이도록 했다 */
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0; height: 100%; overflow: hidden;
  background: var(--paper); color: var(--ink);
  font-family: "Gowun Dodum", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  touch-action: manipulation;
  user-select: none;
}

/* ⚠ 홈 화면 앱에서는 높이를 **vh 로 잡는다** (2026-09-03).
   아이패드에서 홈 화면 앱으로 열면 iOS 가 화면(954)보다 짧은 뷰포트(922)를 준다.
   그래서 height:100% 로 이어 놓으면 화면 아래 32px 이 body 배경색만 남은 띠가 된다.
   재 보니 단위마다 보는 값이 갈렸다:

       100%   922      100vh   954  ← 화면 전체
       100dvh 922      100lvh  954  ← 화면 전체
                       100svh  954

   vh 가 화면 전부를 본다. html 만 vh 로 바꾸면 그 아래(body → #deck → 화면들)는
   전부 % 로 이어져 있으므로 따라온다.

   ⚠ 사파리에서는 쓰면 안 된다. 거기서는 vh 가 주소창을 무시한 높이(954)라
     실제로 보이는 822 보다 132px 크다 — 아래가 잘린다. 사파리는 100% 가 맞다
     (재 보니 사파리에서는 남는 띠가 0 이었다).

   ⚠ height=device-height 를 메타에 넣는 길도 재 봤는데 소용없었다 —
     innerHeight 는 922 그대로였고 svh 까지 922 로 떨어졌다. 넣지 말 것.

   두 겹으로 건다. display-mode 를 iOS 가 못 알아들을 때를 대비해
   index.html 이 navigator.standalone 을 보고 .appmode 도 붙인다 */
@media (display-mode: standalone), (display-mode: fullscreen) {
  html { height: 100vh; }
}
html.appmode { height: 100vh; }

/* ---------- 카드 두 장 ---------- */

#deck {
  display: flex;
  width: 200%; height: 100%;   /* 화면 두 개가 옆으로 붙어 있다 */
  animation: deal var(--swipe) cubic-bezier(.62, .02, .34, 1) var(--t-swipe) both;
}
/* 띠를 절반(=화면 하나)만큼 왼쪽으로 민다.
   시작 화면이 왼쪽으로 빠지면서 게임 화면이 오른쪽에서 따라 들어온다 */
@keyframes deal {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* 확인용 : 주소에 #game 을 붙이면 인트로를 건너뛴다.
   게임 화면 손볼 때 11초씩 기다리지 않아도 된다 */
body.skip-intro #deck {
  animation: none;
  transform: translateX(-50%);
}

#start, #game { width: 50%; flex: none; height: 100%; }

#game {
  position: relative;          /* HUD 를 여기에 붙인다 */
  background: var(--paper);
}

/* ---------- 지도 ----------
   #map-plane 을 rotateX 로 눕힌다. 지금은 도 도형만 있고 장식은 없다 */
/* ⚠ --map-shift : 지도를 오른쪽으로 미는 값 (2026-09-06).
   왼쪽에 배터리와 단추 셋이 들어서면서 지도가 그 밑에 깔렸다.
   ⚠ **여기 한 곳에만 적는다.** map.js 가 이 값을 읽어 투영 원점을 같이 옮긴다 —
     CSS 만 밀면 조형물과 손가락 좌표가 지도와 어긋난다 (지도는 밀리는데 점은 안 밀린다) */
#map-stage {
  --map-shift: 84px;
  position: absolute; inset: 0;
  overflow: hidden;
  perspective: 1400px;
  touch-action: none;          /* 브라우저 기본 확대·스크롤을 끄고 직접 처리한다 */
  background: var(--paper);
}
#map-plane {
  position: absolute; left: calc(50% + var(--map-shift, 0px)); top: 46%;
  width: 809px; height: 1297px;        /* korea-provinces.svg 의 viewBox 와 같은 크기.
        빈 바다를 잘라 지도에 딱 맞췄다 — 회전 중심이 지도 한가운데가 된다.
        ⚠ 확대하면 map.js 가 이 셋(width·height·margin)과 위 perspective 를 함께 키운다.
           CSS scale 로만 늘리면 브라우저가 구워 둔 그림을 늘려 선과 점이 뭉개진다 */
  margin: -648.5px 0 0 -404.5px;
  transform-style: preserve-3d;
  transform-origin: 50% 50%;
}
/* ⚠ 날아가는 동안에만 레이어로 올린다 (map.js 의 flyTo 가 붙였다 뗀다).
   배율이 매 프레임 바뀌는데 평면 크기는 그대로라, 이것이 없으면 브라우저가 프레임마다
   거대한 SVG(제주를 볼 때 3236x5188)를 다시 굽는다.
   ⚠ 늘 걸어 두지 않는다 — 그 큰 것을 내내 레이어로 들고 있으면 메모리를 먹는다 */
#map-plane.flying { will-change: transform; }
#map-land { position: absolute; inset: 0; }
#map-land svg { display: block; width: 100%; height: 100%; }

/* 땅 = 도. 전부 같은 노란색으로 둔다.
   상태에 따라 색이 바뀌면 눌리지도 않았는데 불이 들어온 것처럼 보인다 */
#map-land .prov {
  fill: #FCCC6C;
  /* --stroke-k 는 map.js 가 배율에 맞춰 넣는다. 4배까지는 1 이라 굵기가 그대로고,
     그보다 깊이 확대하면 화면에서의 굵기가 더 늘지 않도록 얇아진다 */
  stroke: #8E4A4A; stroke-width: calc(4px * var(--stroke-k, 1));
  stroke-linejoin: round;
  cursor: pointer;
}

/* 울릉도·독도 : 작아서 같은 굵기로 그리면 선에 파묻힌다 */
#map-land .prov.isle { stroke-width: calc(2.4px * var(--stroke-k, 1)); }

/* 트럭이 도는 순서 — 1장에서 18장까지 이어지는 길.
   점선으로 둬야 "이미 난 길" 이 아니라 "앞으로 갈 길" 로 읽힌다.
   굵기·점 간격 모두 배율을 따라간다 (--stroke-k) */
#map-land #prov-route {
  fill: none;
  stroke: #8E4A4A;
  stroke-width: calc(3px * var(--stroke-k, 1));
  stroke-dasharray: calc(9px * var(--stroke-k, 1)) calc(7px * var(--stroke-k, 1));
  stroke-linecap: round;
  opacity: 0.55;
  pointer-events: none;        /* 땅이 곧 버튼이다 — 길이 터치를 가로채면 안 된다 */
}

/* ---------- 랜드마크 : 지도 위에 서 있는 것 ----------
   ⚠ 여기에는 3D 가 하나도 없다. 일부러 그렇게 두었다.
      평면 안에 넣고 기울기·회전을 되돌려 세우는 방법을 두 번 시도했지만
      (CSS 변수+calc, 그다음 JS 가 각도를 직접 쓰기) 아이패드 사파리에서
      번번이 지도에 누웠다. 그래서 3D 를 아예 버렸다.
      map.js 의 screenAt() 이 평면 위 좌표를 화면 좌표로 손수 투영하고,
      placeProps() 가 매 프레임 자리와 크기를 얹는다. 원근 배율(k)도 거기서 나온다.
      늘 화면을 정면으로 보므로 회전할 일이 없다 — 회전이 없으니 찌그러질 일도 없다 */
#map-props {
  position: absolute; inset: 0;
  pointer-events: none;        /* 땅이 곧 버튼이다 */
}

/* 그림이 꽂힐 자리. 크기가 없는 점 하나라 그림은 이 점을 발로 삼는다.
   자리는 map.js 가 transform 으로 넣는다 (화면 픽셀) */
#map-props .prop {
  position: absolute; left: 0; top: 0;
  width: 0; height: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/* 랜드마크 한 장. 찢어진 종이 테두리는 그림 안에 들어 있다 (docs/image-prompts.md).
   높이는 map.js 가 --h 로, 가로세로는 --ar 로 넣는다.
   반듯하게 세우면 인쇄물처럼 보인다 — 밑동을 축으로 조금 기울인다(--lean) */
#map-props .prop .card {
  position: absolute; left: 0; top: 0;
  /* 크기는 한 번만 잡고 그대로 둔다 — 실제 크기는 map.js 가 .prop 의 scale 로 정한다.
     매 프레임 이 값을 고치면 그때마다 배치가 새로 계산돼 버벅인다 */
  height: var(--prop-base, 400px);
  width: calc(var(--prop-base, 400px) * var(--ar, 1.2));
  transform-origin: 50% 100%;
  transform: translate(-50%, -100%) rotate(var(--lean, 0deg));
}

/* 그림을 그대로 채운다.
   contain 이라 비율이 어긋나도 늘어나지 않고, bottom 이라 남는 여백은 위로 간다 —
   밑동이 지도에 닿아 있어야 서 있는 것으로 보인다 */
/* 눌린 조형물 — 한 번 눌렸다 튄다. 마라도든 아니든 **모든** 조형물이 똑같이 반응한다.
   눌러도 아무 일이 없는 곳이라도 "내가 이것을 눌렀다" 는 것은 손끝에 읽혀야 한다.
   기준점이 발(50% 100%)이라 밑동은 땅에 붙은 채 몸만 눌렸다 편다.
   ⚠ translate 와 rotate 를 키프레임마다 그대로 다시 적는다 —
     transform 은 통째로 갈아 끼워지므로 scale 만 적으면 그림이 제자리를 잃는다 */
#map-props .prop .card.pop { animation: prop-pop 460ms both; }
@keyframes prop-pop {
  /* 눌린다 — 아주 조금만. 1.045/.955 도 종이가 튕기는 것처럼 세서 다시 낮췄다 (2026-08-31)
     세기를 줄인 만큼 시간을 늘렸다 — 짧고 작으면 눌린 줄도 모르고 지나간다 */
  0%   { transform: translate(-50%, -100%) rotate(var(--lean, 0deg)) scale(1, 1);
         animation-timing-function: cubic-bezier(.4, .1, .35, 1); }
  32%  { transform: translate(-50%, -100%) rotate(var(--lean, 0deg)) scale(1.022, .978);
         animation-timing-function: cubic-bezier(.4, 0, .3, 1); }
  /* 되펴며 아주 조금만 넘긴다. 이 한 칸이 있어야 "튕겼다" 가 아니라 "숨을 쉬었다" 로 읽힌다 */
  66%  { transform: translate(-50%, -100%) rotate(var(--lean, 0deg)) scale(.995, 1.006);
         animation-timing-function: cubic-bezier(.33, 0, .3, 1); }
  100% { transform: translate(-50%, -100%) rotate(var(--lean, 0deg)) scale(1, 1); }
}

#map-props .prop .art {
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
  object-position: bottom;
}

/* 이름표 — 어디인지 알려 주는 표지다.
   ⚠ 조형물과 달리 지도 배율을 따라가지 않는다. 확대해도 글자 크기가 그대로여야
      읽히고, 축소했을 때 좁쌀만 해지지도 않는다. 그래서 .prop 밖에 따로 선다.
   자리는 map.js 가 매 프레임 넣는다 (조형물 머리 꼭대기).
   ⚠ 글자가 허용되는 곳은 여기와 HUD 숫자·손님 말풍선뿐이다 —
      플레이 화면에는 글자를 넣지 않는다 (claude.md) */
#map-props .tag {
  position: absolute; left: 0; top: 0;
  transform-origin: 0 0;
  will-change: transform;
  /* 조형물보다 늘 앞에 선다. 이름표를 먼저 붙이고 조형물을 뒤에 붙이므로(map.js)
     그냥 두면 화면 가장자리에 붙들린 이름표가 제 그림 뒤로 들어가 가려진다 */
  z-index: 1;

  display: block;
  margin-top: -11px;            /* 머리에서 살짝 띄운다 — 꼭지가 그 사이를 잇는다 */
  padding: 3px;                 /* 이 여백이 곧 나무 테의 두께다 */
  background: var(--wood);      /* 나무 테 */
  border: 3px solid var(--wood-line);
  border-radius: 15px;
  box-shadow: 0 3px 0 rgba(142, 74, 74, .20);
}
/* 안쪽 크림 판 — 나무 상자에 끼운 이름표처럼 보인다 */
#map-props .tag b {
  display: block;
  padding: 2px 13px 4px;
  background: #FBF3DE;
  border-radius: 10px;
  font-family: "Jua", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  font-weight: 400;             /* Jua 는 단일 굵기 */
  font-style: normal;           /* <i> 로 만들었으므로 기울기를 되돌린다 */
  font-size: 16px;
  line-height: 1.3;
  letter-spacing: -.3px;
  white-space: nowrap;
  color: var(--brick);
}
/* 조형물에 꽂힌 말뚝 — 이게 있어야 이름표가 떠 있지 않고 박혀 있는 것으로 읽힌다 */
#map-props .tag::after {
  content: "";
  position: absolute; left: 50%; top: 100%;
  width: 4px; height: 11px;
  margin-left: -2px;
  background: var(--wood-line);
  border-radius: 0 0 2px 2px;
}

/* 발밑 그림자 — 이게 있어야 "떠 있는" 게 아니라 "서 있는" 것으로 보인다.
   이쪽은 되돌리지 않는다. 지도에 납작하게 붙어 함께 눕는다 */
#map-land #spot-shadows ellipse {
  fill: #8E4A4A;
  opacity: 0.22;
  pointer-events: none;
}

/* 명소 핀 — 도를 골랐을 때만 그 도 안에 나타난다.
   도 점과 반대로 칠해서(속은 밝게, 테두리는 진하게) 주종이 헷갈리지 않게 한다.
   글자는 쓰지 않는다 — 성격은 색으로만 가른다 */
#map-land #spot-pins circle {
  fill: #FCFCE4;
  stroke: #8E4A4A; stroke-width: calc(2px * var(--stroke-k, 1));
  pointer-events: none;
}
#map-land #spot-pins circle[data-kind="가공"] { fill: #E07B1E; }   /* 갈거나 끓인다 */
#map-land #spot-pins circle[data-kind="유통"] { fill: #8FA87C; }   /* 규격품 위주 */
#map-land #spot-pins circle[data-kind="선물"] { fill: #8E4A4A; }   /* 규격품만 */

/* ---------- 도 한가운데 귤 꼭지 : 스테이지가 앉을 자리 ----------
   그림은 받은 그림의 윤곽을 그대로 딴 두 장이다 (tools/trace-calyx.py) —
   바깥 별꼴과 가운데 덩이. 색은 그림에 없고 여기서 칠한다 (mask).
   받은 그림은 #3C9B19 · #1F7800 이었는데 채도가 화면에서 혼자 튀어 낮췄다 —
   지도의 팔레트(#8FA87C · #6F8A5C)로 끌어온 값이다.

   ⚠ 진짜 3D 다. 별꼴은 땅에 눕고 가운데 덩이는 그 위로 솟는다 (얇은 판 네 겹).
      지도와 함께 눕고 돌므로 되세우지 않는다 — 랜드마크가 평면 밖으로 밀려난 함정
      (사파리가 3D 를 접는 것)에 걸리지 않는 까닭이다.
   ⚠ 이 층의 조상 어디에도 overflow·opacity·filter 를 두지 말 것 */
#map-plane #prov-calyx {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  pointer-events: none;          /* 땅이 곧 버튼이다 */

  --lit:   #8FA870;              /* 빛을 받는 윗면 */
  --shade: #6E8A57;              /* 그늘 — 별꼴의 두께와 꼭지 옆면 */
  --cut:   #55703E;              /* 꼭지가 잘린 단면. 받은 그림에서도 여기가 가장 어둡다 */
}
#prov-calyx .calyx {
  position: absolute;
  transform-style: preserve-3d;
  /* ⚠ 눌리는 것을 되받지 않는다. 지도가 누우면 꼭지도 같이 눌리는 것이 맞다 — 땅에 놓인 물건이므로.
     되받아 늘리면 화면에서는 늘 반듯해 보이지만, 그 대신 꼭지가 땅이 아니라 화면을 따라다니는
     것처럼 보인다. 세 가지를 다 겪어 보고(양만 따라가기 · 축까지 따라가기 · 쉬는 자세에 못박기)
     되받기 자체를 걷어냈다 (2026-08-31). 두 손가락으로 쓸어올리면 꼭지 기둥의 옆구리가 드러난다 */
  /* --r 은 map.js 가 도마다 넣고, --m 은 평면 배율이다.
     평면이 커지면(setDetail) 꼭지도 같은 비율로 커져야 땅과 어긋나지 않는다 */
  --s:  calc(var(--r) * var(--m, 1) * 1px);
  --pw: calc(var(--s) * 2);                              /* 별 판 가로 */
  --ph: calc(var(--pw) / 1.1453);   /* 1.1453 = 그림의 가로/세로 (tools/trace-calyx.py 가 찍어 준다) */
  /* 꼭지가 솟는 높이. 눌리는 것을 되받지 않으므로, 지도를 눕혔을 때 이 기둥의 옆구리가
     "여기 뭔가 서 있다" 를 말하는 유일한 단서다 — 0.13 으로 뒀더니 눕혀도 납작한 무늬였다 */
  --sh: calc(var(--pw) * 0.30);
}
#prov-calyx i {
  position: absolute; left: 0; top: 0;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: 100% 100%;   mask-size: 100% 100%;
}

/* 꽃받침 — 땅에 눕는다. 두 겹이라 두께가 보인다 */
#prov-calyx .star {
  width: var(--pw); height: var(--ph);
  -webkit-mask-image: url("../../assets/calyx-star.svg?v=4");
          mask-image: url("../../assets/calyx-star.svg?v=4");
  background: var(--lit);
  transform: translate(-50%, -50%);
}
/* 아래에 깔리는 한 겹 — 땅에 닿는 면이라 그늘이다. 겹쳐 있지 않고 실제로 조금 내려가 있어,
   가장자리에서 원근 때문에 삐져나오는 만큼이 곧 두께로 보인다 */
#prov-calyx .star.deep {
  background: var(--shade);
  transform: translate(-50%, -50%) translateZ(calc(var(--pw) * -0.05));
}

/* 꼭지 — 얇은 판을 네 겹 띄워 세운 기둥. 가로 30.4% · 세로 34.9% 는 그림에서 잰 값이다
   (tools/trace-calyx.py 가 찍어 준다). 어긋나면 꼭지가 별꼴에서 삐져나온다 */
#prov-calyx .stem {
  width: calc(var(--pw) * 0.304); height: calc(var(--ph) * 0.349);
  -webkit-mask-image: url("../../assets/calyx-stem.svg?v=4");
          mask-image: url("../../assets/calyx-stem.svg?v=4");
  background: var(--shade);
  transform: translate(-50%, -50%) translateZ(calc(var(--sh) * var(--z, 1)));
}
#prov-calyx .stem.cut { background: var(--cut); }   /* 맨 위 한 겹만 단면 색이다 */

/* ---------- 보유 자원 : 나무 상자 두 칸 ----------
   로그인 버튼과 세로 중심을 맞춘다 (버튼 88, 상자 72 → 위를 8px 내린다) */
/* ⚠ 아래 #purse·.slot·#shop 은 지금 화면에 **없다** (2026-09-03).
   귤·연료·상점 마크업을 index.html 에서 걷어냈다 — 셀 것도 살 것도 아직 없어서
   0 이라는 숫자와 눌리지 않는 진열창만 남아 있었기 때문이다.
   규칙은 그대로 둔다. 게임 로직이 붙을 때 마크업만 되살리면 다시 선다
   (_bak/index_bak_hud_trim_*.html 에 있다) */

#purse {
  position: absolute;
  top: 18px; left: 34px;       /* 로그인 버튼과 세로 중심을 맞춘다 (둘 다 70px) */
  display: flex; gap: 14px;
  z-index: 5;
}
/* 판자 원본이 720x360 = 2:1 이라 화면에서도 그 비율을 지킨다.
   안쪽 크림 판은 가로 10% 세로 20% 여백에 80% x 60% 크기다 */
.slot {
  position: relative;
  width: 208px; height: 104px;
  background: url("../../assets/slot.svg?v=20") center / 100% 100% no-repeat;
}

/* 귤 칸 : 숫자가 주인공이고 아이콘은 표식으로 물러난다.
   귤은 "세는 것" 이다 — 127개인지 128개인지가 판단을 바꾸므로 숫자가 커야 한다.
   (연료는 "재는 것" 이라 유면만으로 읽힌다. 그래서 두 칸의 속이 다르다)

   ⚠ 아이콘과 숫자를 둘 다 절대배치하면 네 자리(1000 이상)에서 겹친다.
     가로로 세워 두면 겹칠 수가 없다 — 자리는 배치가 잡는다.
     안쪽 여백은 크림 판(가로 10% 여백에 80%)에 맞춘 값이다 */
.slot[data-slot="gyul"] {
  display: flex; align-items: center; gap: 10px;
  padding: 0 30px 0 28px;
}
.gyul-icon {
  flex: none;
  width: 52px; height: 52px;      /* 원본 560x560 정사각. 72 → 52 로 물러났다 */
}

/* 숫자 — 남는 자리를 다 차지하고 오른쪽으로 붙는다.
   ⚠ 오른쪽 정렬이 핵심이다. Jua 는 본문용 한글 폰트라 고정폭 숫자가 없을 수 있어,
     128 → 99 로 줄 때 왼쪽에서 글자가 밀린다. 오른쪽에 붙여 두면
     서체가 무엇이든 오른쪽 끝이 고정돼 출렁이지 않는다.
     tabular-nums 는 있으면 쓰고 없으면 그만이다 */
.slot .num {
  flex: 1; min-width: 0;
  font-family: "Jua", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  font-weight: 400;               /* Jua 는 단일 굵기 */
  font-size: 44px; line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
  pointer-events: none;           /* 명패는 누르는 것이 아니다 */
}
/* 네 자리(1000 이상)가 되면 44px 로는 9px 넘친다.
   흔한 한~세 자리를 크게 두는 편이 낫고, 넘칠 때만 한 단계 낮춘다.
   게임 로직이 자릿수를 보고 data-wide 를 붙였다 떼면 된다 —
   글자 수는 CSS 가 셀 수 없다 */
.slot[data-slot="gyul"][data-wide] .num { font-size: 34px; }

/* 적재량 띠 — 나무 테두리를 감귤색이 감아 올라간다.
   왜 이것이 필요한가: 숫자만 있으면 "128" 이 많은 건지 적은 건지 알 수 없다.
   최대치를 외우게 하거나 128/300 처럼 두 숫자를 쓰는 대신,
   테두리가 얼마나 감겼는지로 "더 실을 수 있나" 를 한눈에 답한다.

   pathLength=100 이라 길이를 재지 않아도 --fill(0~1)이 그대로 비율이 된다.
   실제 둘레(약 1830단위)를 계산할 필요가 없다 — 도형을 고쳐도 안 깨진다 */
.slot .load {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}
.slot .load path {
  fill: none;
  stroke: var(--zest);
  stroke-width: 26;               /* 나무 띠(52단위)의 절반. 양옆으로 나무가 남는다 */
  stroke-linecap: butt;
  stroke-dasharray: 100;
  stroke-dashoffset: calc(100 - 100 * var(--fill, 0));
  transition: stroke-dashoffset .32s cubic-bezier(.34,.52,.30,1);
}
/* 가득 차면 띠가 닫힌다 — 그것 자체가 신호라 따로 경고하지 않는다.
   다만 이음매가 보이면 안 닫힌 것처럼 보이므로 끝을 둥글게 맞물린다 */
.slot[data-full] .load path { stroke-linecap: round; }

/* 늘거나 줄 때 아이콘이 한 번만 톡 —
   사람은 변화를 봐야 안다. 그 이상 흔들면 시끄럽다.
   js 에서 data-bump 를 붙였다 떼면 된다 */
@keyframes gyul-pop {
  50% { transform: scale(1.12); }   /* 가로 배치라 translateY 는 필요 없다 */
}
.slot[data-slot="gyul"][data-bump] .gyul-icon {
  animation: gyul-pop .22s ease-out;
}

/* 연료 칸 : 기름통을 판 위에 얹는다. 위아래로 조금 넘겨 놓인 느낌을 낸다 */
.can {
  position: absolute;
  left: 32px; top: 13px;
  width: 63px; height: 78px;      /* 통 원본 420x520 과 같은 비율 */
  transform: rotate(-5deg);       /* 아무렇게나 놓아둔 것처럼 */
  transform-origin: 50% 88%;      /* 바닥을 축으로 기울어야 놓인 것처럼 보인다.
      기울면 모서리가 밖으로 나가므로, 회전 후 네 꼭짓점이 나무틀
      (x 5.8~202.2 / y 5.8~98.2) 안에 들어오도록 크기와 위치를 잡았다 */
}
.can-body, .can-fill {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
}
/* 기름 5장은 통과 같은 좌표계(420x520)에 그려져 있고 자기 안에 클립을 갖고 있다.
   그래서 통 위에 그대로 겹치기만 하면 창 안에 정확히 담긴다 */
.can-fill { display: none; }
.slot[data-slot="fuel"][data-fuel="1"] .can-fill[data-fill="1"],
.slot[data-slot="fuel"][data-fuel="2"] .can-fill[data-fill="2"],
.slot[data-slot="fuel"][data-fuel="3"] .can-fill[data-fill="3"],
.slot[data-slot="fuel"][data-fuel="4"] .can-fill[data-fill="4"],
.slot[data-slot="fuel"][data-fuel="5"] .can-fill[data-fill="5"] { display: block; }

/* ---------- 옛 상점 단추 — **걷어냈다** (2026-09-07 밤) ----------
   2026-09-03 에 HUD 상점 단추를 마크업에서 뺐고, 그 자리는 #shop-btn 이 맡았다.
   그런데 CSS 만 #shop 이라는 이름으로 남아 있어서, 2026-09-07 에 상점 **판**을
   같은 이름으로 만들자 판이 88x88 로 눌렸다 — 화면 왼쪽 위에 손톱만 하게 박혔다.
   ⚠ 죽은 규칙은 남겨 두면 언젠가 산 것을 문다. 옛 판은 _bak/main_bak_bug_fix_* 에 있다.
   ⚠ #shop 은 이제 **상점 판**이다 (이 파일 맨 아래). 단추는 #shop-btn 이다 */
/* ---------- 로그인 : 감귤 버튼 ----------
   #deck 에 transform 이 걸려 있어 position: fixed 는 뷰포트가 아니라 #deck 기준이 된다.
   그래서 absolute 로 붙인다 — 카드가 넘어올 때 화면과 같이 들어온다 */
#login {
  position: absolute;
  top: 26px; right: 34px;
  width: 88px; height: 88px;     /* 터치 타겟 최소 44px 을 넉넉히 넘긴다 */
  padding: 0; border: 0; background: none;
  cursor: pointer; z-index: 5;
  transition: transform .12s ease-out;
}
#login svg { display: block; width: 100%; height: 100%; }
#login:active, #login.pressed { transform: scale(.93); }        /* 태블릿이라 hover 는 쓰지 않는다 */
#login:focus-visible {
  outline: 3px solid var(--zest); outline-offset: 4px;
  border-radius: 50%;
}

/* ---------- 나침반 : 지도가 틀어졌을 때만 나타난다 ----------
   회전·기울이기를 넣으면 실수로 돌렸을 때 돌아올 길이 없다. 그 탈출구다 */
#compass {
  position: absolute;
  top: 128px; right: 41px;
  width: 74px; height: 74px;      /* 터치 타겟 44px 을 넘긴다 */
  padding: 0; border: 0; background: none;
  cursor: pointer; z-index: 5;
  /* 늘 같은 자리에 서 있는다.
     예전에는 지도가 틀어졌을 때만 나타났는데, 그러면 있다 없다 하는 버튼이 되어
     "저기 뭐가 있었는데" 를 찾게 된다. 지도 조작 버튼은 늘 같은 자리에 있어야
     손이 기억한다 — 나타나고 사라지는 것 자체가 방해다.
     되돌릴 것이 없을 때 눌러도 아무 일이 없을 뿐, 해가 되지 않는다 */
  transition: transform .12s ease-out;
}
#compass svg { display: block; width: 100%; height: 100%; }
#compass:active, #compass.pressed { transform: scale(.9); }
#compass:focus-visible { outline: 3px solid var(--zest); outline-offset: 4px; border-radius: 50%; }

/* ================= 지도에서 명소를 눌렀을 때 =================

   2026-08-31 — 여기 있던 "지도 → 가게" 진입 연출을 통째로 걷어냈다.
   흐름을 다시 잡기로 해서다 (누르면 확대되며 편지가 도착하는 쪽으로).
   지운 것은 _bak/ 의 before_remove_dive_ 로 시작하는 폴더에 통째로 있다.

   ⚠ 다시 지을 때 잊지 말 것 — 처음 판이 패드에서 심하게 끊겼고, 재 보니 원인이 이랬다:
     · 확대하던 것이 #game 이었다. 그 안의 #map-plane 이 제주를 볼 때 3236 x 5188px
       = 1680만 화소였고(최대 12배면 1억 5천만), preserve-3d 라 한 장으로 굽지도 못한다.
       그것을 3.4배로 키우니 그려야 할 넓이가 11.6배였다.
     · will-change 가 없어 레이어로 올라가 있지 않았다.
     · transform-origin 을 넣고 강제로 다시 재는 데 첫 프레임에서 4.8ms 가 들었다.
   그래서 큰 것을 크게 키우지 말고, 덮는 쪽(납작한 전면 레이어)이 자라게 하고,
   자리와 레이어 승격은 한 프레임 먼저 앉혀야 한다.

   지금 map.js 는 문이 있는 명소를 누르면 신호만 보낸다:
     document.addEventListener("gamgyul:spotopen", e => e.detail.spot)          */

#start {
  position: relative;          /* 종이 결(.grain)을 여기에 붙인다 — 아래 "종이" 묶음 참고 */
  height: 100%;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 0 8vw;
}

/* ---------- 트럭 ---------- */

/* 1층 : 화면 밖에서 중앙까지 들어온다 */
#lane {
  animation:
    drive-in  var(--drive) var(--ease)      both,
    drive-out var(--exit)  var(--ease-exit) var(--t-exit) forwards;
}
@keyframes drive-in {
  from { transform: translateX(118vw); }   /* 오른쪽 화면 밖에서 출발 */
  to   { transform: translateX(0); }
}
/* 서 있다가 왼쪽으로 빠져나간다. 지연 구간에서는 forwards 라 값을 적용하지 않으므로
   그 동안은 drive-in 의 도착 상태가 그대로 유지된다 */
@keyframes drive-out {
  from { transform: translateX(0); }
  to   { transform: translateX(-125vw); }
}

/* 2층 : 엔진 진동 — 두두둗 */
#rattle {
  animation: rattle .11s steps(2, end) infinite;
}
@keyframes rattle {
  0%   { transform: translate(0, 0)      rotate(0deg); }
  50%  { transform: translate(.5px, -2px) rotate(-.28deg); }
  100% { transform: translate(0, 0)      rotate(0deg); }
}

#truck-wrap { position: relative; }

#truck-wrap > svg {
  display: block;
  position: relative; z-index: 2;   /* 연기·잔상보다 위 */
  width: 620px; height: auto;        /* viewBox 를 트럭에 맞춰 잘랐으므로 이 값이 실제 폭이다 */
  max-width: 100%;                   /* 좁은 태블릿에서 넘치지 않게 */
}

/* 3층 : 바퀴. 부모 g 가 이미 휠 중심으로 옮겨놨으므로 원점(0,0)에서 회전한다.
   트럭과 같은 감속 곡선을 써서 함께 멈춘다 */
.wheel {
  transform-box: fill-box;      /* 기준을 SVG 전체가 아니라 바퀴 자신의 박스로 */
  transform-origin: 50% 50%;    /* 그 박스의 중심 = 바퀴 축 */
  animation:
    roll     var(--drive) var(--ease)      both,
    roll-out var(--exit)  var(--ease-exit) var(--t-exit) forwards;
}
@keyframes roll {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-1435deg); }   /* 왼쪽으로 굴러가므로 반시계.
      3.98바퀴 = 이동거리(118vw ≒ 1392px) ÷ 바퀴둘레(≒349px). 헛돌지 않도록 맞춘 값 */
}
/* 나갈 때도 이어서 굴린다. 도착 각도에서 출발해야 튀지 않는다.
   4.75바퀴 = 나가는 거리(125vw ≒ 1475px) ÷ 바퀴둘레(≒310px) */
@keyframes roll-out {
  from { transform: rotate(-1435deg); }
  to   { transform: rotate(-2955deg); }   /* 이어서 4.22바퀴 (125vw ÷ 349px) */
}

/* ---------- 배기구 연기 ----------
   트럭은 왼쪽을 향해 달린다. 따라서 뒤쪽은 오른쪽이다.
   연기 그림은 왼쪽 끝이 뾰족하므로, 뒤집지 않고 그대로 두면
   뾰족한 쪽이 트럭에 붙고 뭉실한 쪽이 뒤로 흘러간다. */
.puff {
  position: absolute;
  right: -180px; bottom: -6px;     /* 뒷범퍼 높이에서 꽁무니에 붙인다 */
  width: 186px; height: auto;
  opacity: 0; pointer-events: none; z-index: 1;
  transform-origin: left center;   /* 뾰족한 끝은 붙어 있고 뭉실한 쪽만 부푼다 */
  animation: puff 2.1s ease-out infinite;
}
.puff1 { animation-delay: 0s; }
.puff2 { animation-delay: .7s; }
.puff3 { animation-delay: 1.4s; }

@keyframes puff {
  0%   { opacity: 0;   transform: translate(0, 0)         scale(.35); }
  12%  { opacity: .85; }
  100% { opacity: 0;   transform: translate(100px, -44px) scale(1.2); }
}

/* ---------- 지나간 자리에 남는 선 ----------
   트럭 꽁무니(오른쪽)에서 나와 뒤로 밀리며 사라진다.
   지연·길이를 --drive 비율로 두어, 속도를 바꿔도 알아서 따라간다. */
.trail {
  position: absolute;
  right: 5px;                      /* 트럭 뒷면 위치. 여기서 흘러나온다 */
  height: 6px; border-radius: 999px;
  background: #CC846C;
  opacity: 0; pointer-events: none; z-index: 1;
  /* 들어올 때 한 번(--in), 나갈 때 또 한 번(--out).
     두 번째는 forwards 라 지연 중에는 값을 적용하지 않으므로 첫 번째와 다투지 않는다 */
  animation:
    trail calc(var(--drive) * .23) ease-out var(--in)  both,
    trail calc(var(--exit)  * .5)  ease-out var(--out) forwards;
}
.trail1 { bottom: 141px; width: 149px; --in: calc(var(--drive) * .06); --out: calc(var(--t-exit) + var(--exit) * .05); }
.trail2 { bottom: 89px; width: 213px; --in: calc(var(--drive) * .19); --out: calc(var(--t-exit) + var(--exit) * .22); }
.trail3 { bottom: 34px; width: 175px; --in: calc(var(--drive) * .13); --out: calc(var(--t-exit) + var(--exit) * .12); }
.trail4 { bottom: 176px; width: 120px; --in: calc(var(--drive) * .33); --out: calc(var(--t-exit) + var(--exit) * .32); }
.trail5 { bottom: 70px; width: 188px; --in: calc(var(--drive) * .48); --out: calc(var(--t-exit) + var(--exit) * .18); }

@keyframes trail {
  0%   { opacity: 0;   transform: translateX(-30px) scaleX(.4); }
  25%  { opacity: .45; transform: translateX(0)     scaleX(1); }
  100% { opacity: 0;   transform: translateX(200px) scaleX(.6); }
}

/* ---------- 글자 : 트럭이 선 뒤에 들어온다 ---------- */
h1 {
  font-family: "Jua", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  font-size: 76px; margin: 0; font-weight: 400;   /* Jua 는 단일 굵기 */
  letter-spacing: -.5px; color: var(--brick);
  text-wrap: balance;
  animation: rise .7s ease-out calc(var(--drive) - .25s) both;
}

#credit {
  margin: 10px 0 0; font-size: 17px;
  letter-spacing: .3px;
  animation: rise .7s ease-out calc(var(--drive) + .05s) both;
}
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: .5; transform: none; }
}

/* 제목에서 '제주'만 감귤 껍질색으로 */
h1 .jeju { color: var(--zest); }
h1 { animation-name: rise-solid; }
@keyframes rise-solid {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- 종이 ----------
   화면은 "크림색 판" 이 아니라 "한 장의 종이" 여야 한다. 그러려면 눈금이 다른
   세 가지가 겹쳐야 한다 — 어느 하나만으로는 종이가 되지 않는다:

     ① 고운 결   assets/paper-fiber.png    128px 타일, 1:1
                 손톱만 한 섬유 얼룩. 가까이서 보이는 것
     ② 큰 얼룩   assets/paper-mottle.png   256px 타일을 900px 로 늘려 깐다
                 손바닥만 한 두께 차이·물듦. 멀리서 보이는 것.
                 이것이 있어야 "한 장의 종이" 로 읽힌다 — ① 만 있으면
                 아무리 얹어도 평평한 판에 먼지가 낀 것으로 보인다
     ③ 가장자리  radial-gradient. 귀퉁이로 갈수록 아주 옅게 어두워진다.
                 알아채면 과한 것이다 — 화면 절반 안쪽은 아예 0 이고,
                 옆구리에서 4.5%, 가장 짙은 네 귀퉁이에서 13% 다

   ⚠ 결 타일을 SVG 필터(feTurbulence)로 만들지 말 것. 예전에 화면 전체에 걸었다가
     지도가 움직일 때마다 뷰포트 전체를 다시 계산해서 프레임이 무너졌다.
     미리 구운 타일은 한 번 그려지고 끝난다.
   ⚠ 타일을 손으로 고치지 말 것 — tools/bake-paper.py 로 구운 결과물이다.
     세기(알파)까지 구워 넣었으므로 여기에 opacity 를 걸지 않는다.

   왜 두 장을 나눠 두는가 (한 장으로 크게 구우면 되지 않나)
     ①은 1:1 로 깔아야 알갱이가 알갱이로 보이고, ②는 늘려 깔아야 흐릿해져 얼룩이 된다.
     배율이 다르므로 한 판에 담을 수 없다. 대신 둘 다 작아서 합쳐 20KB 다.

   ⚠ 왜 화면 전체(fixed)가 아니라 화면마다 한 장씩인가 — 2026-09-01
     예전에는 body 밑에 #grain 한 장을 fixed 로 깔고 z-index 를 9 로 뒀다.
     그런데 HUD 가 5 라, 결이 **버튼과 숫자 위**에 얹혀 UI 가 뿌옇게 흐려 있었다.
     결을 HUD 아래로 내리려면 z-index 만 낮춰서는 안 된다 — #grain 은 #deck 의
     형제이고 #deck 에는 transform 이 걸려 있어(카드 넘김) 그 자체가 쌓임 맥락이다.
     바깥에서 z-index 를 뭘로 주든 #deck 통째와만 견주게 되므로, 그 안의 HUD 밑으로는
     결코 내려가지 못한다. 그래서 화면 **안으로** 옮겼다 —
     #start 에 한 장, #game 에 한 장. 이제 결은 지도에만 얹히고 HUD 는 또렷하다 */
.grain {
  position: absolute; inset: 0;
  pointer-events: none;
  z-index: 4;                  /* 지도(쌓임 없음) 위, HUD(5) 아래 */

  /* 앞에 적은 것이 위에 깔린다 — 가장자리 · 큰 얼룩 · 고운 결 차례.
     farthest-corner 라 화면 비율이 달라져도 그늘이 정확히 네 귀퉁이에서 가장 짙다.
     중심을 44% 로 조금 올린 것은 지도가 화면 가운데보다 위에 앉기 때문이다(#map-plane top:46%) */
  background:
    radial-gradient(farthest-corner at 50% 44%,
                    rgba(90, 70, 55, 0) 46%,
                    rgba(90, 70, 55, .045) 76%,
                    rgba(90, 70, 55, .13) 100%),
    url("../../assets/paper-mottle.png?v=1") repeat,
    url("../../assets/paper-fiber.png?v=1") repeat;
  background-size: 100% 100%, 900px 900px, 128px 128px;
}

/* ---------- 세로 방향 안내 ---------- */
#rotate { display: none; }
@media (orientation: portrait) {
  #rotate {
    display: grid; place-items: center;
    position: fixed; inset: 0; z-index: 99;
    background: var(--paper); font-size: 26px;
  }
  #start, .grain { display: none; }
}

/* ---------- 움직임을 줄이도록 설정한 기기 ---------- */
@media (prefers-reduced-motion: reduce) {
  #lane, #rattle, .wheel, .puff, .trail, h1, #credit { animation: none; }
  .puff, .trail { opacity: 0; }
  #credit { opacity: .5; }
  /* 화면 전환 자체는 남긴다. 미끄러지지 않고 정해진 시각에 한 번에 바뀐다 */
  #deck { animation: deal 1ms steps(1, end) var(--t-exit) both; }
  #login, #shop, #compass { transition: none; }
  .slot[data-slot="gyul"][data-bump] .gyul-icon { animation: none; }
  /* 띠는 남긴다 — 움직임이 아니라 상태다. 다만 미끄러지지 않고 한 번에 바뀐다 */
  .slot .load path { transition: none; }
  #map-props .prop .card.pop { animation: none; }

}

/* ---------- 초청장 : 문이 열린 명소를 누르면 봉투가 도착한다 ----------
   자리·크기·시간은 전부 여기 숫자로만 정한다. 값을 왜 그렇게 잡았는지는
   tools/preview/_envelope-test.html 에 길게 적어 두었다 — 그 시험판에서 맞춘 것을 옮겼다.
   ⚠ 편지지 위 글자 자리는 여기 없다. src/data/letters.js 의 SHEET 가 유일한 출처이고
      letter.js 가 그것을 읽어 얹는다 (그림이 정한 값이라 두 군데 적으면 어긋난다).
   ⚠ 이 묶음의 이름은 전부 post- · sheet- 로 시작한다. 위쪽에 이미 rise 라는 이름이
      쓰이고 있어(조형물이 솟는 것) 그대로 두면 서로 덮어쓴다 */
#post {
  position: absolute; inset: 0;
  z-index: 12;                 /* HUD(5) 위를 덮는다 — 편지를 보는 동안에는 지도를 만지지 않는다 */
  background: rgba(70, 54, 42, .34);
  opacity: 0;
  transition: opacity .26s ease-out;
  touch-action: manipulation;

  --env:   .92;                /* 봉투 배율 */
  --let:   .85;                /* 편지지 끝 배율 — letter.js 의 LET 과 같아야 한다 */
  --hinge: 202px;              /* 뚜껑 경첩 (봉투 판 기준 y) */
  --drop:  105px;              /* 봉투를 한가운데보다 이만큼 내린다 — 아가리 위에 편지지가 빠져나올 자리 */
  --open:  -132deg;            /* 뚜껑이 다 젖혀진 각. 90도에서 가장 납작하고 거기서 멀어질수록 다시 선다 */
  --small: .47;                /* 봉투에 끼어 있을 때의 편지지 크기 — 봉투 안폭에 거의 꽉 차야 한다 */
  --in:    183px;              /* 끼어 있는 자리 */
  --up:    -150px;             /* 슥 올라온 자리 */

  --t-press: 90ms;  --t-flap: 600ms; --d-flap: 90ms;
  --d-rise: 480ms;  --t-rise: 1250ms;
  --d-away: 1030ms; --t-away: 700ms;
}
#post[hidden] { display: none; }
#post.arrive { opacity: 1; }
#post.leave  { opacity: 0; }
/* 다 읽고 치울 때 — 편지가 살짝 물러나며 사라지고 그 명소의 배경만 남는다.
   어둡게 깔아 둔 것(background)도 함께 걷히므로 배경이 제 밝기로 드러난다 */
#post.away {
  opacity: 0;
  transform: scale(.965);
  transition: opacity .32s ease-in, transform .32s ease-in;
}

/* 층 차례 : 뒷판(1) < 편지지(2) < 앞주머니(3) < 뚜껑(4 → 0).
   뚜껑은 닫혔을 때 맨 앞이고, 수직을 지나면서 맨 뒤로 넘어간다.
   ⚠ 껍데기가 셋이다 — 바깥(.layer)이 물러남, 가운데(.breathe)가 숨쉬기, 안(#flap)이 젖힘.
      한 요소에 두 움직임을 걸면 서로 덮어쓴다 */
#post .layer {
  position: absolute; left: 50%; top: 50%;
  width:  calc(960px * var(--env));
  height: calc(640px * var(--env));
  margin-left: calc(-480px * var(--env));
  margin-top:  calc(-320px * var(--env) + var(--drop));
}
#post .breathe { position: absolute; inset: 0; }
#post .layer img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#env-back { z-index: 1; } #env-front { z-index: 3; } #env-flap { z-index: 4; }

/* 원근은 #flap 바로 위에 걸어야 먹는다. 소실점도 경첩에 둔다 —
   기본값(상자 한가운데)은 경첩보다 아래라 뚜껑이 뒤로 눕는 대신 위로 솟구쳐 보였다 */
#env-flap .breathe {
  perspective: 1100px;
  perspective-origin: 50% calc(var(--hinge) * var(--env));
}
#flap {
  position: absolute; inset: 0;
  transform-origin: 50% calc(var(--hinge) * var(--env));
  transform-style: preserve-3d;
}
/* 양면 — backface-visibility 에 기대지 않는다. 실제로 안 먹어서 90도를 넘었는데도
   바깥면(봉랍)이 그대로 보였다. 수직을 지나는 그 순간(45%) 손으로 갈아 끼운다 */
#flap .face { position: absolute; inset: 0; width: 100%; height: 100%; }
#flap .back { opacity: 0; }

#sheet {
  position: absolute; left: 50%; top: 50%; z-index: 2;
  width:  calc(1170px * var(--let));
  height: calc(900px * var(--let));
  margin-left: calc(-585px * var(--let));
  margin-top:  calc(-450px * var(--let));
  transform: translateY(var(--in)) scale(var(--small));
}
#sheet img { width: 100%; height: 100%; display: block;
             filter: drop-shadow(0 14px 24px rgba(70, 54, 42, .26)); }
/* 편지지에 얹는 글 — #sheet 가 커질 때 그 배율을 그대로 타므로 글도 함께 커진다.
   자리는 letter.js 가 SHEET 를 읽어 넣는다 */
#sheet-title { position: absolute; line-height: 1; transform: translateY(-50%);
               color: #8E4A3C; white-space: nowrap; }
#post .lline { position: absolute; line-height: 0; white-space: nowrap; color: #46362A; }

/* 대기 : 아주 조금 숨쉰다 — "눌러 보라" 를 글자 없이 말하는 유일한 방법이다 */
#post .breathe { animation: post-breathe 3.6s ease-in-out infinite; }
@keyframes post-breathe { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-5px) } }
/* 열기 시작하면 멈춘다 — 안 멈추면 물러남과 서로 잡아당겨 툭 튄다.
   지금 자리를 그대로 두려면 none 이 아니라 paused 여야 한다 */
#post.go .breathe { animation-play-state: paused; }

/* (1) 눌림 */
#post.go .layer { animation: post-press var(--t-press) ease-out both,
                             post-away var(--t-away) cubic-bezier(.32,.5,.4,1) var(--d-away) both; }
@keyframes post-press { to { transform: scale(.985) } }

/* (2) 뚜껑 : 뻣뻣하게 들리다가 넘어가 눕는다.
   45% 를 수직(-90도)으로 못박아 둔다 — 뒤로 넘어가는 시점을 눈대중으로 재지 않기 위해서다 */
#post.go #flap { animation: post-flap var(--t-flap) var(--d-flap) both; }
@keyframes post-flap {
  0%   { transform: rotateX(0deg);   animation-timing-function: cubic-bezier(.45,.02,.6,.45) }
  45%  { transform: rotateX(-90deg); animation-timing-function: cubic-bezier(.22,.62,.3,1) }
  100% { transform: rotateX(var(--open)) }
}
#post.go #flap .front { animation: post-faceout var(--t-flap) step-end var(--d-flap) both; }
#post.go #flap .back  { animation: post-facein  var(--t-flap) step-end var(--d-flap) both; }
@keyframes post-faceout { 0%, 45% { opacity: 1 } 46%, 100% { opacity: 0 } }
@keyframes post-facein  { 0%, 45% { opacity: 0 } 46%, 100% { opacity: 1 } }

/* 수직을 지나는 그 순간 몸통 뒤로 넘어간다 */
#post.go #env-flap { animation: post-press var(--t-press) ease-out both,
                                post-away var(--t-away) cubic-bezier(.32,.5,.4,1) var(--d-away) both,
                                post-zflip var(--t-flap) step-end var(--d-flap) both; }
@keyframes post-zflip { 0%, 45% { z-index: 4 } 46%, 100% { z-index: 0 } }

/* (3) 편지지 : 슥 올라온다 → 멈칫 → 커진다.
   나오는 동안에는 앞주머니 뒤(2)에 있어야 "빠져나온다" 로 읽힌다. 다 올라온 그 순간(44%)
   봉투 앞(5)으로 올라온다 — 안 그러면 커지는 동안 봉투가 편지 한복판을 덮는다 */
#post.go #sheet { animation: sheet-rise var(--t-rise) var(--d-rise) both,
                             sheet-lift var(--t-rise) step-end var(--d-rise) both; }
@keyframes sheet-lift { 0%, 44% { z-index: 2 } 45%, 100% { z-index: 5 } }
@keyframes sheet-rise {
  0%   { transform: translateY(var(--in)) scale(var(--small));
         animation-timing-function: cubic-bezier(.2,.8,.3,1) }
  44%  { transform: translateY(var(--up)) scale(var(--small)) }
  51%  { transform: translateY(var(--up)) scale(var(--small));
         animation-timing-function: cubic-bezier(.3,.7,.26,1) }
  100% { transform: translateY(0) scale(1) }
}

/* (4) 봉투가 물러난다. 편지지가 다 빠져나올 때까지 가만히 있는다 —
   둘이 동시에 움직이면 무엇이 나오는 중인지 눈이 못 따라간다 */
@keyframes post-away {
  0%   { transform: scale(.985); opacity: 1 }
  55%  { opacity: .72 }
  100% { transform: translateY(150px) scale(.62); opacity: 0 }
}

/* 도는 동안만 층으로 올린다 — 끝나면 letter.js 가 뗀다 */
#post.go .layer, #post.go #sheet, #post.go #flap { will-change: transform, opacity; }

@media (prefers-reduced-motion: reduce) {
  #post .breathe { animation: none }
  #post.go .layer, #post.go #env-flap, #post.go #flap, #post.go #sheet {
    animation-duration: 1ms; animation-delay: 0ms;
  }
}


/* ---------- 명소 안으로 : 셔터가 닫히고, 장 이름이 뜨고, 다시 열리면 그곳이다 ----------
   ⚠ 지도를 확대해서 들어가지 않는다. 그렇게 만들었다가 걷어냈다 (2026-08-31) —
      #map-plane 이 제주를 볼 때 3236x5188px 이고 preserve-3d 라 한 장으로 못 굽는다.
      3.4배로 키웠더니 그려야 할 넓이가 11.6배가 되어 패드에서 심하게 끊겼다.
      셔터는 그 문제가 아예 없다 — 움직이는 것은 네 장의 단색 판이고, 배경은 가려진 동안
      한 번에 앉는다. 자라는 것도 커지는 것도 없다.
   ⚠ 여기서 움직이는 것은 transform 과 opacity 뿐이다 */

/* 배경 — 셔터가 닫힌 동안 소리 없이 앉는다 */
#scene {
  position: absolute; inset: 0;
  z-index: 10;                   /* HUD(5) 위, 초청장(12) 아래 */
  overflow: hidden;
  background: var(--sky);
}
#scene[hidden] { display: none; }

/* 색조 세 벌. 그림이 아직 없는 명소는 이 띠만으로 선다 —
   바다(sea) · 들(field) · 거리(town). scene.js 가 data-tone 으로 고른다 */
#scene { --sky: #E8E2CE; --mid: #9C9CB4; --near: #8FA87C; }

/* 창 구멍 — 트럭이 배경을 얼마나 잘라내는가. 단위를 뗀 수가 참값이다.
   ⚠ #cabin 이 이 셋에서 방을 통째로 셈하고(벽 폭·원근 깊이·모서리), 입장 연출이
     같은 셋에서 "잘리는 만큼" 을 셈한다. 그래서 **한 곳에만** 둔다 —
     #cabin 에 두면 배경 쪽에서 못 보고, 두 곳에 적으면 반드시 어긋난다 */
#scene { --winx: 7; --wint: 14; --winb: 20; }

/* ══════════ 입장 연출 — 셔터를 올린 뒤 트럭이 지어진다 (2026-09-06) ══════════
 *
 *   셔터가 다 올라간다 (gamgyul:shutterup)
 *     → 배경만 보인다. 0.25초 그대로 둔다 — 그곳이 어디인지 볼 시간이다
 *     → 트럭이 잘라낼 만큼 딱 그만큼 확대된다
 *     → 다시 축소되면서 상·하·좌·우에서 천장 · 상판 · 벽 둘이 밀려 들어온다
 *     → 마지막에 차양막이 위에서 내려온다
 *     → gamgyul:entered — 그때부터 배경을 눌러 나갈 수 있다
 *
 * ⚠ 시간표가 **여기 한 곳에** 있다. src/js/enter.js 는 class 하나를 붙였다 떼기만 한다 —
 *   JS 에 setTimeout 을 늘어놓으면 시간이 두 곳에 적혀 반드시 어긋난다.
 *
 * ⚠ 조각마다 animation 이 제 transform 을 **통째로** 덮어쓴다. 그래서 keyframes 안에
 *   그 조각의 본래 회전(rotateX·rotateY)을 같이 적어야 한다 — 빠뜨리면 눕혀 둔 면이
 *   벌떡 일어서서 방이 납작한 상자가 된다.
 *
 * ⚠ fill-mode 는 both 다. 늦게 오는 조각(차양막은 1.7초)이 그때까지 화면에 남아
 *   있으면 안 되므로, 기다리는 동안 from 자리(화면 밖)에 붙들어 둔다.
 */

#scene {
  /* 트럭이 잘라내는 만큼 = 확대할 배율.
     ⚠ 눈대중이 아니다. 창은 가로로 (100-2*winx)% · 세로로 (100-wint-winb)% 만 남으므로
       그 남는 몫의 역수가 "잘린 만큼" 이다. 가로 1/0.86 = 1.163 · 세로 1/0.66 = 1.515.
       둘 중 **큰 쪽**을 쓴다 — 작은 쪽을 쓰면 확대해도 위아래가 여전히 잘려,
       "잘리는 만큼 확대했다" 가 성립하지 않는다.
     ⚠ 창 구멍을 고치면 이 값도 다시 셈할 것 */
  --enter-zoom: 1.515;
  /* 확대의 중심은 화면 한가운데가 **아니다** — 창 구멍의 한가운데다.
     창이 위로 치우쳐 있으므로(위 14% · 아래 20%) 화면 중심으로 확대하면
     확대했다 줄어드는 동안 배경이 위아래로 미끄러진다 */
  --enter-eye: calc((var(--wint) + (100 - var(--wint) - var(--winb)) / 2) * 1%);
}

/* ⚠ **셔터가 올라가기 전부터** 조각들이 화면 밖에 나가 있어야 한다 (2026-09-06).
   연출을 shutterup 에서 걸었더니, 셔터가 올라가는 0.42초 동안 트럭이 제자리에 있는
   채로 드러났다가 셔터가 다 올라간 순간 조각들이 화면 밖으로 튕겨 나갔다 —
   "떴다가 갑자기 사라지고 다시 생긴다" 가 이것이다.
   그래서 배경을 앉히는 때(gamgyul:scenedressed)에 연출을 걸어 두되 **멈춰 둔다**.
   멈춰 있는 동안 fill-mode(both)가 조각들을 from 자리(화면 밖)에 붙들어 준다.
   셔터가 다 올라가면 .hold 만 떼고, 그때부터 0 초에서 시작한다.
   ⚠ 정적 transform 을 따로 적어 두는 길도 있지만 그러면 같은 자리값이 두 곳에 적힌다 —
     멈춰 세우는 쪽이 from 을 한 곳에만 두게 한다 */
#scene.entering.hold #scene-art,
#scene.entering.hold .band,
#scene.entering.hold .roof,
#scene.entering.hold .counter,
#scene.entering.hold .post.l,
#scene.entering.hold .post.r,
#scene.entering.hold .awn { animation-play-state: paused; }

#scene.entering #scene-art,
#scene.entering .band {
  transform-origin: 50% var(--enter-eye);
  animation: enter-push 1.75s cubic-bezier(.33,0,.2,1) both;
}
/* ★ 2026-09-06 앞머리(그냥 보는 시간)를 두 번 줄였다 — 2.0초 → 0.5초 → **0.25초** (3.5초 → 2초 → **1.75초**).
   들어설 때마다 되풀이되는 연출이라, 처음 한 번은 좋았던 뜸이 두 번째부터 기다림이 된다.
   ⚠ 줄인 것은 **그냥 보는 앞머리뿐**이다. 확대(0.6초)와 물러남(0.9초)은 그대로 두었다 —
     그 둘까지 줄이면 "잘릴 만큼 밀고 들어갔다 물러난다" 가 안 읽히고 그냥 튄다.
   ⚠ 백분율을 눈대중으로 적지 말 것 — 한 바퀴가 1.75초이므로 0.25/1.75 = 14.29% ·
     (0.25+0.6)/1.75 = 48.57% 다. 앞머리를 다시 고치면 이 셀을 다시 할 것.
   ⚠ src/js/enter.js 의 TOTAL 도 같이 고칠 것 (지금 2150).
   ⚠ 아래 조각들의 시작 시각도 같은 만큼 앞당겼다. 하나만 고치면 트럭이 아직
     확대돼 있는데 옆에서 조각이 들어오거나, 다 물러난 빈 화면을 기다리게 된다 */
@keyframes enter-push {
  0%, 14.29% { transform: scale(1); }                 /* 0 ~ 0.25초 — 그냥 본다 */
  48.57%     { transform: scale(var(--enter-zoom)); } /* ~0.85초 — 잘릴 만큼 밀고 들어간다 */
  100%       { transform: scale(1); }                 /* ~1.75초 — 물러나며 트럭이 선다 */
}

/* 상·하·좌·우에서 밀려 들어오는 넷. 밀려나는 거리는 제 몫보다 넉넉히 잡는다 —
   딱 맞게 잡으면 원근에 눌린 만큼 끝이 화면에 걸린 채로 남는다 */
#scene.entering .roof    { animation: enter-roof    .62s cubic-bezier(.22,.61,.36,1) .80s both; }
#scene.entering .counter { animation: enter-counter .62s cubic-bezier(.22,.61,.36,1) .87s both; }
#scene.entering .post.l  { animation: enter-post-l  .56s cubic-bezier(.22,.61,.36,1) 1.03s both; }
#scene.entering .post.r  { animation: enter-post-r  .56s cubic-bezier(.22,.61,.36,1) 1.03s both; }
/* 차양막은 맨 나중이다. 살짝 지나쳤다 되돌아오게 두어 천이 한 번 출렁이게 한다 */
#scene.entering .awn     { animation: enter-awn     .58s cubic-bezier(.34,1.28,.64,1) 1.45s both; }

@keyframes enter-roof {
  from { transform: translateY(-30vh) rotateX(-90deg); }
  to   { transform: translateY(0)     rotateX(-90deg); }
}
@keyframes enter-counter {
  from { transform: translateY(34vh) rotateX(90deg); }
  to   { transform: translateY(0)    rotateX(90deg); }
}
@keyframes enter-post-l {
  from { transform: translateX(-22vw) rotateY(90deg); }
  to   { transform: translateX(0)     rotateY(90deg); }
}
@keyframes enter-post-r {
  from { transform: translateX(22vw) rotateY(-90deg); }
  to   { transform: translateX(0)    rotateY(-90deg); }
}
/* ⚠ 차양막만은 **화면 단위(vh)** 로 밀어낸다. -118% 로 뒀다가 물렸다 —
   translateY 의 %는 제 높이 기준이라 90px 짜리 차양막은 90px 밖에 안 올라가는데,
   차양막은 화면 위 14% 자리(창 윗변)에 있으므로 그것으로는 화면을 못 벗어난다.
   기다리는 3.2초 내내 천장 위에 얹혀 보인다 (실제로 그랬다) */
@keyframes enter-awn {
  from { transform: translateY(-26vh); }
  to   { transform: translateY(0); }
}

/* ⚠ 움직임을 줄여 달라는 기기에서는 연출을 걷는다. 그래도 **자리는 제자리여야** 하므로
   animation 만 끄면 된다 (fill-mode 로 붙들려 있던 조각들이 본래 자리로 돌아온다) */
@media (prefers-reduced-motion: reduce) {
  #scene.entering #scene-art, #scene.entering .band,
  #scene.entering .roof, #scene.entering .counter,
  #scene.entering .post.l, #scene.entering .post.r,
  #scene.entering .awn { animation: none; }
}
#scene[data-tone="field"] { --sky: #EDE6D2; --mid: #CFCBA8; --near: #8FA87C; }
#scene[data-tone="town"]  { --sky: #E9E2D0; --mid: #CC846C; --near: #C9B48E; }

/* 먼 띠와 가까운 띠. 가운데는 비워 둔다 — 편지가 그 위에 뜨기 때문이다 */
#scene .band {
  position: absolute; left: -2%; right: -2%;
  border-top: 3px solid #8E4A4A;
}
#scene .band.far  { top: 46%; height: 22%; background: var(--mid);  opacity: .55; }
#scene .band.near { top: 66%; height: 40%; background: var(--near); opacity: .8; }

/* 그림이 있으면 이 한 장이 띠를 덮는다 */
#scene-art { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
#scene-art[hidden] { display: none; }

/* ---------- 포스터 선반 ----------
   왼쪽 아래 단추를 누르면 모은 포스터가 비스듬히 서서 옆으로 늘어선다.
   한 장을 누르면 가운데로 나와 크게 선다 (src/js/gallery.js).

   ⚠ 비스듬함은 **한 장씩 다르게** 준다 (--lean, ±3.5도). 다 같은 각도로 세우면
     벽에 기대 놓은 것이 아니라 인쇄된 무늬처럼 보인다. 랜드마크의 --lean 과 같은 생각이다.

   ⚠ 가로 스크롤은 손가락으로만 한다 — 화살표 단추를 두지 않는다.
     태블릿에서는 미는 것이 자연스럽고, 화면에 단추를 늘리지 않는다는 규칙에도 맞는다.
     걸리는 것 없이 그냥 흘러간다 — scroll-snap 은 걷어냈다 (아래 .rail 의 쌓지) */
#shelf-btn {
  position: absolute; left: 26px; bottom: 266px;   /* 줄의 맨 위 — 상점(146) 위 */
  width: 104px; height: 104px;          /* 터치 타겟 44px 을 넉넉히 넘긴다 */
  padding: 0; border: none; background: none;
  cursor: pointer; z-index: 5;
  transition: transform .16s ease-out;
}
#shelf-btn svg { width: 100%; height: 100%; display: block; }
#shelf-btn:active, #shelf-btn.pressed { transform: scale(.92) rotate(-2deg); }
#shelf-btn:focus-visible { outline: 4px solid var(--zest); outline-offset: 4px; border-radius: 14px; }

#shelf {
  position: absolute; inset: 0;
  z-index: 13;                        /* 초청장(12) 위 — 지도를 덮고 선다 */
  background: rgba(70, 54, 42, .40);
  display: flex; align-items: center;
  opacity: 0;
  transition: opacity .24s ease-out;
}
#shelf[hidden] { display: none; }
#shelf.on { opacity: 1; }

/* 늘어선 줄 — 손가락으로 민다 */
#shelf .rail {
  display: flex; align-items: flex-end; gap: 30px;
  width: 100%;
  /* ⚠ 위아래 여유가 **꼭 있어야 한다** (2026-09-06 에 고쳤다). 종이는 기울어져 있고
     (--lean 최대 3.5도) 그림자가 아래로 26px 번지는데, overflow-y: hidden 이라
     그 몫이 없으면 종이의 윗귀와 그림자가 **잘려 나간다.** 잘린 자리가 곧은 선이라
     "위가 잘렸다" 로 보인다 */
  padding: 5vh 12vw 5vh;
  overflow-x: auto; overflow-y: hidden;
  /* ⚠ **미는 일은 gallery.js 가 통째로 맡는다** (dragScroll). 그래서 브라우저의 손짓을
     아예 끈다 — pan-x 로 두면 브라우저가 먼저 밀기 시작하면서 pointermove 를 끊어
     (pointercancel) 우리 쪽 끌기가 중간에 죽는다. 둘이 같은 일을 하려 들면 늘 진다.
     ⚠ 휠·트랙패드는 그대로 굴러간다 — touch-action 은 손가락에만 걸린다 */
  touch-action: none;
  /* ⚠ **scroll-snap 을 걸지 않는다** (2026-09-06 에 걷어냈다). 전에는
     scroll-snap-type: x proximity 였는데, 미는 일을 gallery.js 가 scrollLeft 로 직접
     하므로 둘이 서로를 당겼다 — 우리가 한 프레임 밀면 브라우저는 그것을 "밀기가
     끝난 것" 으로 보고 가까운 종이로 당기기 시작한다. 그 당김이 다음 프레임의
     우리 값과 부딪혀 미는 내내 딸꾹질한다. 튕길 때는 더 심해서, 미끄러지는
     동안 줄이 갔다 섰다 한다. 둘이 같은 일을 하려 들면 늘 진다 —
     touch-action 을 끈 것과 같은 까닭이다.
     ⚠ 되살리고 싶으면 미는 일을 브라우저에 통째로 돌려주어야 한다
       (touch-action: pan-x + dragScroll 제거). 반쪽씩은 안 된다 */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
#shelf .rail::-webkit-scrollbar { display: none; }

/* 포스터 한 장 — 5:7 세로. 높이로 크기를 잡아야 화면이 낮아도 안 잘린다 */
#shelf .sheet {
  flex: none;
  height: 46vh; aspect-ratio: 5 / 7;   /* 위아래 여유(5vh씩)를 준 만큼 줄였다 */
  padding: 0; border: none; cursor: pointer;
  border-radius: 4px;
  background: var(--paper);
  box-shadow: 0 12px 26px rgba(70, 54, 42, .34);
  transform: rotate(var(--lean)) translateY(0);
  transition: transform .18s ease-out;
  position: relative; overflow: hidden;
  display: grid; place-items: center;
}
/* 누르면 한 번 들린다. ⚠ **미는 동안에는 들지 않는다** — 넘기려고 손을 대는 순간
   종이가 튀어 오르면 그 손짓이 "누른 것" 처럼 보여 어수선하다 (gallery.js 가 .dragging 을 붙인다) */
#shelf .rail:not(.dragging) .sheet:active, #shelf .rail:not(.dragging) .sheet.pressed { transform: rotate(var(--lean)) translateY(-10px) scale(1.02); }
#shelf .rail.dragging .sheet { transition: none; }
#shelf .sheet:focus-visible { outline: 4px solid var(--zest); outline-offset: 5px; }

/* 안쪽 키라인 — 화방 프롬프트가 시키는 그 선이다 */
#shelf .sheet::after {
  content: ""; position: absolute; inset: 12px;
  border: 2.5px solid var(--line); border-radius: 3px;
  opacity: .55; pointer-events: none;
}
#shelf .sheet img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* 그림을 끌 수 없게 — 끌면 브라우저가 그림 끌기로 가로채 선반이 안 밀린다 */
  -webkit-user-drag: none; user-select: none; pointer-events: none;
}

/* 아직 못 받은 포스터 — 빈 종이만 걸어 둔다 (src/js/progress.js).
   ⚠ 감추지 않는다. 감추면 선반이 텅 비어 고장난 것으로 보이고,
     무엇을 모으는 중인지도 알 수 없다. 빈 자리가 있어야 모을 뜻이 생긴다.
   ⚠ 이름을 흐리게라도 적지 않는다 — 적으면 "받았는데 잘못 그린 것" 으로 보인다 */
#shelf .sheet.locked {
  /* ⚠ opacity 를 걸지 말 것. 걸었더니 종이가 비쳐서 뒤의 지도가 그대로 보였다 —
     빈 종이가 아니라 유리가 된다 (2026-09-04 고쳤다).
     물러나 보이게 하는 것은 색과 그림자로 한다 */
  background: #E4DAC3;                            /* 종이보다 조금 어둡고 탁하다 */
  box-shadow: 0 6px 14px rgba(70, 54, 42, .16);   /* 걸린 것보다 얕게 */
  cursor: default;
}
#shelf .sheet.locked::after { border-style: dashed; opacity: .35; }
#shelf .sheet.locked:active, #shelf .sheet.locked.pressed { transform: rotate(var(--lean)) translateY(0); }


/* 그림이 아직 없을 때 — 종이에 이름만. 종이는 그 도의 지배색으로 옅게 물들인다 */
#shelf .sheet b {
  font-family: "Jua", sans-serif;
  font-size: clamp(20px, 3.4vh, 38px);
  color: var(--line); text-align: center;
  padding: 0 10%; line-height: 1.3;
}

/* 크게 본 한 장 */
/* 크게 보기 — ⚠ 스르르 떠야 한다. 그냥 나타나면 화면이 **툭 바뀐다.**
   종이가 제자리에서 커져 나오는 것처럼 보이게 조금 작게 시작해 제 크기로 앉는다 */
#shelf .up {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: rgba(70, 54, 42, .52);
  opacity: 0; transition: opacity .2s ease-out;
}
#shelf .up.on { opacity: 1; }
#shelf .up .sheet { transform: scale(.94); transition: transform .22s cubic-bezier(.34,1.16,.64,1); }
#shelf .up.on .sheet { transform: scale(1); }
#shelf .up[hidden] { display: none; }
#shelf .up .sheet {
  height: 86vh; cursor: zoom-out;
  box-shadow: 0 22px 48px rgba(70, 54, 42, .42);
}
#shelf .up.on .sheet:active, #shelf .up.on .sheet.pressed { transform: scale(.97); }

@media (prefers-reduced-motion: reduce) {
  #shelf, #shelf .sheet, #shelf-btn { transition: none; }
}

/* ---------- 가게 셔터 ----------
   명소에 닿으면 가게는 아직 닫혀 있다. 편지를 읽고 나서 손잡이를 잡고
   위로 끌어올리면 그 명소가 드러난다 (src/js/shutter.js).

   ⚠ 바로 아래의 #iris(카메라 셔터)와 **다른 물건이다.** 이름이 겹치니 조심할 것 —
     #iris 는 장이 바뀔 때 지나가는 전환이고, 이것은 손으로 여는 문이다.

   ⚠ 움직이는 것은 transform 하나뿐이다. 높이를 줄이는 길도 있었으나 그러면
     매 프레임 배치를 다시 셈한다. 통째로 위로 밀면 그리기 단계에서 끝난다 —
     실제 셔터도 위로 말려 올라가므로 보기에도 그쪽이 맞다.

   ⚠ 골(.slats)을 늘리지 말 것. 세로로 **이어 깐다**(repeat-y).
     구운 그림이 1280x864 인데 화면은 그보다 길 수 있다 — 늘리면 골이 눌린다.
     864 는 골 주기(27px)의 32배라 이어 붙는 자리가 골과 맞는다.

   ⚠ 손잡이 막대는 위아래 8px 이 비어 있다 (테두리와 흔들림이 번질 자리).
     그래서 아래로 그만큼 내려 앉힌다 — 안 그러면 막대 밑으로 골이 비어져 나온다.
     8px 은 판(1280) 기준이므로 화면에서는 0.625vw 다 */
#shutter {
  position: absolute; inset: 0;
  z-index: 11;                 /* 배경(10) 위, 초청장(12) 아래 — 편지는 셔터 위에 뜬다 */
  --up: 0;                     /* 0 내려짐 … 1 다 올라감. shutter.js 가 굴린다 */
  transform: translateY(calc(var(--up) * -100%));
  touch-action: none;          /* 끌어올리는 동안 화면이 같이 스크롤되면 안 된다 */
  cursor: grab;
}
#shutter[hidden] { display: none; }
#shutter.ease { transition: transform .42s cubic-bezier(.22,.61,.36,1); }
#shutter.grabbed { cursor: grabbing; }

/* ⚠ 손잡이(맨 아래 막대)를 잡아야만 올라간다 (2026-09-06, shutter.js 의 onHandle).
   그래서 커서도 거기서만 손 모양이 된다 — 셔터 몸통은 그냥 화살표다.
   커서가 갈리는 것이 "여기를 잡아라" 를 말없이 알려 준다 (글자를 쓰지 않는다는 강칙) */
#shutter { cursor: default; }
#shutter .rail { cursor: grab; }
#shutter.grabbed .rail { cursor: grabbing; }

/* 엉뚱한 데를 눌렀다 — 손잡이가 한 번 크게 들썩여 어디를 잡아야 하는지 알려 준다.
   ⚠ 들썩임(shutter-nudge)보다 크고 빠르다. 같은 크기로 두면 늘 하던 짓과 구별이
     안 되어 "내가 잘못 눌렀구나" 가 전해지지 않는다 */
#shutter.hint .rail { animation: shutter-knock .62s cubic-bezier(.34,1.4,.64,1); }
@keyframes shutter-knock {
  0%   { transform: translateY(0) }
  26%  { transform: translateY(-22px) }
  52%  { transform: translateY(0) }
  70%  { transform: translateY(-9px) }
  100% { transform: translateY(0) }
}

#shutter .slats {
  position: absolute; inset: 0;
  background: url("../../assets/shutter-body.png?v=2") repeat-y top center;
  background-size: 100% auto;
}
#shutter .rail {
  position: absolute; left: 0; bottom: -0.625vw;
  width: 100%; height: auto; display: block;
}

/* 손잡이가 조금 들썩인다 — "잡아당겨 보라" 를 글자 없이 말하는 유일한 방법이다.
   편지를 치운 뒤에만 붙는다. 손을 대면 곧바로 멎는다 */
#shutter.ready:not(.grabbed) .rail { animation: shutter-nudge 2.4s ease-in-out infinite; }
@keyframes shutter-nudge {
  0%, 62%, 100% { transform: translateY(0) }
  72%           { transform: translateY(-9px) }
  84%           { transform: translateY(-3px) }
}

/* ═══════════════════════════════════════════════════════════════════
   나 — 오른쪽 위 감귤 단추를 누르면 열리는 판 (2026-09-06, src/js/login.js)

   단추(#login)와 그 CSS 는 2026-08-28 부터 있었는데 JS 가 없어 눌러도 아무 일이
   없었다. 그 자리를 채운 것이다.

   ⚠ **로그인은 선택이다.** 이 판은 로그인 없이도 늘 열린다 — 이름과 진행도가
     여기 있기 때문이다. 로그인 단추는 판 맨 아래의 조용한 줄이지 관문이 아니다.
   ⚠ 선반(#shelf)과 같은 층(13)이다. 둘이 같이 열릴 일은 없다.
   ⚠ 종이 위에 놓인 쪽지처럼 보이게 한다 — 이 게임에는 유리도 그림자놀이도 없다.
   ═══════════════════════════════════════════════════════════════════ */
#me {
  position: absolute; inset: 0;
  z-index: 13;                        /* 초청장(12) 위 · 선반과 같은 층 */
  background: rgba(70, 54, 42, .40);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity .24s ease-out;
}
#me[hidden] { display: none; }
#me.on { opacity: 1; }

#me .card {
  position: relative;
  /* ⚠ 560 → 840 → 1000 으로 두 번 키웠다 (2026-09-06). 태블릿 1180 폭에서
     560 은 화면의 47% 라 쪽지처럼 작았고, 840 도 여전히 작았다. 지금은 85% 다 —
     이 판이 이 화면에서 하는 일(이름·진행도·이어 하기)이 곁다리가 아니기 때문이다 */
  width: min(1000px, 92vw);
  padding: 60px 64px 52px;
  /* ⚠ 숫자판이 펼쳐지면 판이 화면보다 길어진다 (2026-09-06 에 그렇게 나왔다).
     넘치면 잘리는 것이 아니라 안에서 밀리게 둔다 — 잘리면 "잇기" 가 사라진다 */
  max-height: 92vh; overflow-y: auto;
  background: var(--paper);
  border: 5px solid var(--wood-line);
  border-radius: 22px;
  box-shadow: 0 14px 0 rgba(142, 74, 74, .18);
  transform: translateY(14px) scale(.97);
  transition: transform .24s cubic-bezier(.2, .9, .3, 1);
}
#me.on .card { transform: none; }

/* 닫기 — 글자가 아니라 그림이다. ×는 폰트 조각에 없는 글자다 */
#me .x {
  position: absolute; top: 10px; right: 10px;
  width: 52px; height: 52px;          /* 터치 타겟 44px 을 넘긴다 */
  padding: 12px; border: 0; background: none; cursor: pointer;
}
#me .x svg { display: block; width: 100%; height: 100%; }
#me .x:active, #me .x.pressed { transform: scale(.9); }

/* ---------- 누구인가 ---------- */
#me .who { display: flex; align-items: center; gap: 30px; }
#me .face { width: 132px; height: 132px; flex: none; }
#me .tag { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
#me .tag b {
  font-family: "Jua", sans-serif; font-weight: 400;
  font-size: 48px; color: var(--brick); line-height: 1.1;
}
#me #me-roll {
  font-family: "Gowun Dodum", sans-serif; font-size: 17px;
  color: var(--wood-line); background: none;
  border: 2px solid rgba(142, 74, 74, .35); border-radius: 999px;
  padding: 9px 20px; cursor: pointer;
  transition: transform .12s ease-out;
}
#me #me-roll:active, #me #me-roll.pressed { transform: scale(.94); }

/* ---------- 지금까지 ----------
   ⚠ 숫자는 JS 가 갈아 끼운다. 글자는 index.html 에 있다 (폰트 조각 때문에) */
#me .stat {
  display: flex; gap: 20px; margin: 36px 0 26px;
}
#me .stat > div {
  flex: 1; padding: 22px 26px;
  background: rgba(204, 132, 108, .16);
  border-radius: 14px;
}
#me .stat span {
  display: block; font-family: "Gowun Dodum", sans-serif;
  font-size: 16px; color: var(--wood-line); opacity: .8;
}
#me .stat b {
  display: block; margin-top: 4px;
  font-family: "Jua", sans-serif; font-weight: 400;
  font-size: 36px; color: var(--brick);
}
#me .stat i { font-style: normal; }

#me .where {
  margin: 0; font-family: "Gowun Dodum", sans-serif;
  font-size: 16px; color: var(--ink); opacity: .62;
}

/* ---------- 로그인 자리 ----------
   ⚠ 아직 제공자를 안 정해서 단추가 disabled 다. 감추지 않는 까닭 —
     감춰 두면 이 자리가 있다는 것을 다음 사람이 모른다. 붙일 때는 이 단추의
     click 에 그 흐름을 걸고 disabled 만 떼면 된다 (src/js/login.js 의 쪽지) */
#me .signin {
  margin-top: 26px; padding-top: 24px;
  border-top: 2px solid rgba(142, 74, 74, .22);
}
#me .signin .lead {
  margin: 0 0 16px; font-family: "Gowun Dodum", sans-serif;
  font-size: 16px; color: var(--ink); opacity: .78;
}
#me .signin .note {
  margin: 14px 0 0; font-family: "Gowun Dodum", sans-serif;
  font-size: 14px; color: var(--ink); opacity: .55;
}

/* 둘을 나란히 — 코드를 받는 쪽과 넣는 쪽 */
#me .ways {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
#me .way {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 72px;               /* 터치 타겟 44px 을 넉넉히 넘긴다 */
  padding: 0 20px; border: 0; border-radius: 16px;
  font-family: "Jua", sans-serif; font-size: 22px;
  background: var(--wood); color: var(--paper);
  cursor: pointer; transition: transform .12s ease-out, filter .12s ease-out;
}
/* ⚠ 둘은 대등하다. 한쪽만 감귤색으로 두면 "이걸 눌러라" 로 읽히는데,
   어느 쪽을 누를지는 그 기기가 주는 쪽인가 받는 쪽인가에 달렸다 */
#me .way svg { width: 30px; height: 30px; flex: none; display: block; color: currentColor; }
#me .way:active, #me .way.pressed { transform: scale(.97); }
#me .way:focus-visible { outline: 3px solid var(--zest); outline-offset: 3px; }
#me .way[disabled] { cursor: default; transform: none; filter: saturate(.5) opacity(.55); }
#me .way.on { filter: none; box-shadow: inset 0 0 0 3px var(--wood-line); }

/* 코드를 주고받는 동안에는 위쪽을 접는다 (2026-09-06).
   ⚠ 안 접으면 숫자판이 화면 아래로 밀려 잘린다 — 820px 짜리 판에서 이름·통계·
     안내를 다 두고 숫자판까지 넣으면 860px 이 넘는다. 안에서 스크롤하게 두면
     "잇기" 단추가 접힌 자리 아래에 숨어 아무도 못 찾는다.
   ⚠ 이름과 통계는 이때 필요 없는 것들이다. 코드를 넣는 동안에는 코드만 보면 된다 */
#me.step .who, #me.step .stat, #me.step .where { display: none; }
#me.step .signin { margin-top: 0; padding-top: 0; border-top: 0; }

/* ---------- 코드를 보여 주거나 넣는 자리 ----------
   ⚠ 자판을 띄우지 않는다. 태블릿 전용이라 화면 자판이 뜨면 판이 절반쯤 가려지고,
     여섯 자리 숫자 하나 넣자고 그럴 일이 아니다. 숫자판을 그려서 쓴다 */
#me .code { margin-top: 18px; text-align: center; }
#me .code[hidden] { display: none; }
#me .code .say {
  margin: 0 0 10px; font-family: "Gowun Dodum", sans-serif;
  font-size: 15px; color: var(--ink); opacity: .7;
}
#me .code .say.small { font-size: 13.5px; opacity: .5; margin: 10px 0 0; }
#me .code b {
  display: block;
  font-family: "Jua", sans-serif; font-weight: 400;
  font-size: 68px; letter-spacing: .2em; color: var(--brick);
  font-variant-numeric: tabular-nums;
}
/* 넣는 쪽은 빈 칸이 보여야 몇 자리인지 안다 */
#me .code .typed {
  min-height: 62px;
  border-bottom: 3px solid rgba(142, 74, 74, .3);
  margin: 0 auto 14px; width: 8ch;
}
#me .pad {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  max-width: 460px; margin: 0 auto;
}
#me .pad button {
  min-height: 58px; border: 0; border-radius: 12px;   /* 터치 타겟 44px 을 넘긴다 */
  background: rgba(204, 132, 108, .2); color: var(--brick);
  font-family: "Jua", sans-serif; font-size: 26px;
  cursor: pointer; transition: transform .1s ease-out;
}
#me .pad button:active, #me .pad button.pressed { transform: scale(.94); }
#me .pad button.wide { font-size: 17px; }
#me .pad button.go { background: var(--zest); color: var(--paper); font-size: 19px; }
#me .pad button[disabled] { opacity: .4; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  #me, #me .card { transition: none; }
}

/* ---------- 셔터 ----------
   날 넷이 사방에서 조여든다. 각 장이 절반을 넉넉히 넘게 덮으므로 다 닫히면 틈이 없다.

   ⚠ 곧게 미는 것에서 **비스듬히 도는 것**으로 바꿨다 (2026-08-31).
      곧게 밀면 커튼이 닫히는 것이지 셔터가 아니다. 날마다 조금씩 기울여 두고
      위 → 오른쪽 → 아래 → 왼쪽 차례로 한 박자씩 늦게 들이면, 가운데를 한 바퀴 훑으며
      조여드는 것으로 읽힌다. 실제 렌즈 셔터의 날도 곧게 오지 않고 돌아 들어온다.
   ⚠ 다 닫히면 날의 테두리를 지운다. 안 지우면 노란 화면 한가운데에 십자로 선이 남는다 —
      닫힌 뒤에는 판 넷이 아니라 한 장이어야 한다. 열리기 시작하면 도로 살아난다 */
#iris {
  position: absolute; inset: 0;
  z-index: 14;                   /* 초청장(12)보다 위 — 나갈 때도 덮어야 한다 */
  overflow: hidden;
  pointer-events: none;
}
#iris[hidden] { display: none; }
#iris.busy { pointer-events: auto; }   /* 도는 동안에는 지도를 만지지 못하게 */

#iris .blade {
  position: absolute;
  /* ⚠ 땅(#FCCC6C)보다 한 톤 깊은 노랑이다. 똑같이 두었더니 날과 땅이 구별되지 않아
     모서리 선 하나로만 날을 알아볼 수 있었고, 그 선이 곧 눈에 거슬리는 것이 되었다.
     색이 갈리면 날은 덩어리로 읽히고, 선은 거들 뿐이라 일찍 지워도 된다 */
  background: #F5C05C;
  border-color: #8E4A4A;
  border-style: solid;
  border-width: 0;
  /* 느리게. 0.44초로 뒀더니 눈이 따라가기 전에 끝나 버렸다 (2026-08-31).
     ⚠ 눈에 보이는 것은 앞쪽 6할뿐이다 — 날이 절반을 조금 넘게 덮으므로 구멍은 0.5초쯤에 이미
       닫히고, 나머지는 날이 제자리에 앉는 시간이다. 그래서 체감보다 길게 잡아야 한다 */
  transition: transform .8s cubic-bezier(.3, .04, .22, 1), border-color .14s linear;
}
/* 넷이 **한꺼번에** 와서 화면 한가운데에서 만난다.
   ⚠ 날을 절반보다 조금만 크게(54%) 잡는 것이 요점이다. 예전에는 62% 여서 위아래 둘만으로
      화면이 다 덮였고, 그러면 구멍이 점이 아니라 **가로줄**로 닫힌다 — 커튼이지 셔터가 아니다.
      54% 면 넷이 다 있어야 덮이므로 구멍이 가로세로 함께 좁아져 가운데 한 점으로 모인다.
   ⚠ 겹치는 만큼(8%)은 기울기가 만드는 틈보다 넉넉해야 한다.
      2.5도로 1180px 을 가로지르면 51px 이 어긋나는데, 8% 는 65px 이라 덮고도 남는다.
      기울기를 더 주려면 날도 같이 키울 것 — 안 그러면 귀퉁이가 뚫린다 */
#iris .blade.t { left: -14%; right: -14%; top: 0;    height: 54%; border-bottom-width: 4px;
                 transform: translateY(-110%) rotate(var(--r)); --r: -2.5deg; }
#iris .blade.b { left: -14%; right: -14%; bottom: 0; height: 54%; border-top-width: 4px;
                 transform: translateY(110%) rotate(var(--r));  --r: -2.5deg; }
#iris .blade.l { top: -14%; bottom: -14%; left: 0;   width: 54%;  border-right-width: 4px;
                 transform: translateX(-110%) rotate(var(--r)); --r: -2.5deg; }
#iris .blade.r { top: -14%; bottom: -14%; right: 0;  width: 54%;  border-left-width: 4px;
                 transform: translateX(110%) rotate(var(--r));  --r: -2.5deg; }

/* ---------- 밭으로 드나드는 셔터는 **초록**이다 (2026-09-07) ----------
   ⚠ 색을 아무 초록으로 고른 것이 아니다. 위의 노란 날이 지도 땅(#FCCC6C)보다
     한 톤 깊은 값인 것과 **똑같은 걸음**이다 :
        지도 땅 #FCCC6C → 날 #F5C05C   (-7, -12, -16)
        밭  흙 #8FA87C → 날 #889C6C   (-7, -12, -16)
     같은 까닭에서 나온다 — 셔터가 열리는 순간 뒤에 있는 것과 날이 같은 색이면
     모서리 선 하나로만 날을 알아보게 되고, 그 선이 곧 눈에 거슬리는 것이 된다.
     밭 흙을 고치면 이 값도 같은 걸음으로 따라와야 한다.
   ⚠ 팔레트 밖의 진한 초록을 쓰지 말 것. 이 세계의 초록은 muted sage 한 벌뿐이다.
   ⚠ 들고 날 때 **둘 다** 초록이다 (farm.js). 나올 때 노랑으로 되돌리면 아직 보이는
     것이 초록 밭인데 노란 날이 덮여 두 색이 부딪힌다 */
#iris[data-tone="farm"] .blade { background: #889C6C; }

/* 닫힘 — 넷이 같이 온다. 늦게 오는 날을 두면 구멍이 그만큼 가운데를 벗어난다 */
#iris.shut .blade { transform: rotate(var(--r)); border-color: transparent; }
/* 테두리는 **날끼리 가까워지기 전에** 지운다 (0.30초에 시작, 0.44초에 사라진다).
   ⚠ 마주 본 두 날은 서로를 지나쳐 겹치도록 앉는다. 그때까지 모서리 선이 남아 있으면
      한 날의 선이 다른 날의 노란 면 위에 얹혀, 닫힌 화면을 가로지르는 줄이 된다 —
      기울여 두었으므로 그 줄은 비스듬하고, 가운데를 지나지도 않는다.
      날을 알아보게 하는 것은 이제 색이 맡으므로 선은 일찍 거둔다 */
#iris.shut .blade { transition-delay: 0s, .30s; }

/* 열림 — 들어온 그대로 되돌아간다. 테두리는 곧바로 되살아난다 */
#iris.open .blade { transition-duration: .72s, .1s; transition-delay: 0s; border-color: #8E4A4A; }

/* 장 이름 — 셔터가 다 닫힌 동안에만 있다.
   ⚠ 플레이 화면에 글자를 넣지 않는 것이 원칙이지만, 여기는 화면과 화면 사이의 막간이다.
      GPGP 도 장이 바뀔 때만 글자를 보여 준다 */
#iris-title {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px;
  opacity: 0; transform: translateY(8px);
  transition: opacity .24s ease-out, transform .24s ease-out;
  font-family: "Jua", sans-serif; color: #8E4A3C; text-align: center;
}
#iris.tell #iris-title { opacity: 1; transform: none; }
#iris-title b { font-size: 44px; font-weight: 400; letter-spacing: .04em; }
#iris-title span { font-size: 30px; color: #9C5454; }

@media (prefers-reduced-motion: reduce) {
  #iris .blade, #iris.shut .blade, #iris.open .blade {
    transition-duration: 1ms; transition-delay: 0s;
  }
  #iris-title { transition-duration: 1ms; }
}

/* ═══════════════════════════════════════════════════════════════════
   트럭 안 — 서빙 해치 너머로 명소를 내다본다 (2026-09-05)

   앞의 카운터(2026-09-04)는 풍경 위에 물건을 일렬로 세운 것이었고, 그래서
   "트럭 안" 이 아니라 "풍경 앞" 으로 읽혔다. 안이라는 느낌은 물건이 아니라
   **테두리**가 만든다 — 밖을 내다볼 창이 있어야 안에 있는 것이 된다.
   그래서 이 층에는 물건이 하나도 없다. 창틀과 상판, 그리고 빛뿐이다.

   깊이를 만드는 것은 넷이다. 하나라도 빠지면 다시 납작해진다 :
     ① 창    — 풍경을 화면 끝까지 채우지 않고 구멍 안에 가둔다
     ② 원근  — 상판을 rotateX 로 실제로 눕힌다. 정면 판은 벽이지 상판이 아니다
     ③ 명암  — 안은 지붕 밑이라 그늘, 밖은 햇빛. 이 대비가 안과 밖을 가른다
     ④ 창턱  — 상판이 창과 만나는 선. 이것이 없으면 상판이 어디서 시작하는지 모른다

   ⚠ **테두리(#8E4A4A)를 반드시 두른다.** 처음에 테두리 없이 그라데이션만으로
     지었더니 명소 배경과 붙여 놓았을 때 다른 그림 두 장을 겹친 것처럼 보였다 —
     이 게임은 어디나 손그림 자주빛 윤곽이 있고, 캐빈만 매끈하면 혼자 논다.

   ⚠ **그늘을 너무 먹이지 말 것.** 트럭 몸통(#F5B251)을 어둡게만 눌렀더니
     주황이 아니라 나무색으로 읽혔다 — 나무를 없애려고 시작한 일인데 도로 나무가
     된 것이다. 채도를 지키고 명도만 낮춘다.

   ⚠ 그림을 쓰지 않는다. 도색 철판도 스테인리스도 색과 결이지 형태가 아니라서
     구울 까닭이 없다 — 창 크기를 고칠 때마다 여덟 장을 다시 뽑는 일이 사라진다.
     그림이 필요한 것은 물건(감귤 상자·저울·선과기)뿐이고, 그것은 이 위에 얹는다.

   ⚠ 나무를 쓰지 않는다. 나무는 감귤 상자에만 남긴다 — 거기는 실제로 나무가 맞다.

   ⚠ 창 크기를 고치려면 --win 셋만 만지면 된다. 여섯 조각이 그 값을 같이 읽는다.
   ═══════════════════════════════════════════════════════════════════ */

#cabin {
  position: absolute; inset: 0;
  z-index: 3;                    /* 배경 그림 위 */
  pointer-events: none;          /* 풍경이다. 만지는 것이 아니다 */

  /* 창 구멍 — 이 셋이 나머지를 다 정한다.
     ⚠ 단위를 뗀 수(--winx·--wint·--winb)가 참값이고 %는 거기서 만든다 (2026-09-06).
       좌우 벽이 이 값으로 원근을 셈하는데, CSS 는 %로 나눌 수 없기 때문이다.
       고칠 때는 **위의 수만** 고치면 %도 벽도 같이 따라온다 */
  /* ⚠ --winx 를 10 → 7 로 줄였다 (2026-09-06). 좌우 벽이 화면의 20%(236px)를 먹어
     방보다 틀이 먼저 눈에 들어왔다. 지금은 14%(165px)이고 창이 80% → 86% 로 넓어졌다.
     ⚠ 이 값 하나가 방 전체를 정한다 — 벽 폭 · 원근 깊이(--s → --depth) · 천장과 상판이
       기울어 보이는 정도가 다 여기서 나온다. 그래서 고치면 여섯 모서리가 저절로 따라온다.
     ⚠ 차양의 물결 밑단도 여기서 나온다 — 가리비 깊이가 창 폭의 1/32 이다 */
  /* ⚠ --winx · --wint · --winb 는 이제 **#scene 에 있다** (2026-09-06).
     입장 연출이 "트럭이 잘라내는 만큼" 을 셈하려면 배경 쪽에서도 같은 값을 봐야 하는데,
     여기 두면 두 곳에 같은 수를 적게 된다. #cabin 은 #scene 의 자식이라 그대로 물려받는다 */
  --win-x: calc(var(--winx) * 1%);   /* 좌우 벽이 창과 만나는 자리 */
  /* ⚠ 2026-09-05 에 18% / 26% 에서 줄였다 — 틀이 화면의 48.5% 를 먹어 명소가 눌렸다.
     지금은 38.5% 다. 더 줄여도(11/16) 보기는 시원한데 상판이 선반 턱이 되고,
     상판 그림(5.5:1)이 9:1 판에 늘어나 사다리꼴 각이 누워 원근이 죽는다.
     ⚠ --win-b 를 고치면 상판 그림이 늘어나는 비율이 함께 바뀐다. 많이 줄일 거면
       deck.png 를 그 비율로 다시 뽑을 것 — 늘려 쓰는 그림이라 각이 그대로 눕는다 */
  --win-t: calc(var(--wint) * 1%);   /* 위 차양 아래면 */
  --win-b: calc(var(--winb) * 1%);   /* 아래 상판 */

  /* ---------- 원근 — 이 층 전체가 하나의 방이다 (2026-09-06) ----------
     상판만 눕어 있고 좌우가 정면 판이면 깊이가 상판에서 끊긴다. 좌우도 같이 누워야
     천장 · 좌우 · 상판이 한 소실점으로 모여 "안에서 내다본다" 가 성립한다.
     ⚠ --persp 는 상판(.counter)과 **같은 값**이라야 한다. 다르면 방이 둘이 된다.
     ⚠ --vp(소실점 세로)는 고른 값이 아니라 **잰 값**이다 — 지금 상판의 옆선을 위로
       늘리면 화면 y 413(=50.4%)에서 만난다. 상판 각을 고치면 이 값도 다시 재야 한다.
     ⚠ --s 는 창(먼 면)이 화면(가까운 면)보다 얼마나 작아지는가다. 좌우 벽 폭이
       화면 반폭의 10% 이므로 0.8 이다. 벽의 자리·높이·깊이가 전부 여기서 나온다 */
  --persp: 520px;
  --vp: 50.4;
  --s: calc(1 - var(--winx) / 50);
  --depth: calc(var(--persp) * (1 / var(--s) - 1));   /* 나에게서 창까지 = 벽의 두께 */

  /* 천장·상판이 소실점에서 얼마나 떨어져 있나(화면 높이의 %). 창 구멍에서 거꾸로 푼 값이다.
     그리고 그 면이 나를 지나쳐 화면 끝까지 오려면 앞으로 얼마나 더 뻗어야 하는가(px).
     ⚠ 이 넷이 벽과 같은 셈을 쓰므로, 여섯 모서리가 창 네 귀퉁이에서 정확히 만난다 */
  --y-ceil: calc((var(--vp) - var(--wint)) / var(--s));
  --y-deck: calc((100 - var(--winb) - var(--vp)) / var(--s));
  --ceil-front: calc(var(--persp) * (1 - var(--y-ceil) / var(--vp)));
  --deck-front: calc(var(--persp) * (1 - var(--y-deck) / (100 - var(--vp))));
  perspective: var(--persp);
  perspective-origin: 50% calc(var(--vp) * 1%);


  /* ⚠ **트럭 공통 팔레트 밖으로 나가지 않는다** (2026-09-05 에 크게 물렸다).
     처음에 이 자리를 "도색 철판과 스테인리스" 로 여기고 갈색·회갈색을 새로 지어냈다.
     그랬더니 화면이 고급스러워지고 이 게임의 포근한 결이 통째로 사라졌다 —
     팔레트 밖 색이 열셋이었고, 공통 스타일이 못박은 세 가지를 정면으로 어겼다 :
       "밝고 산뜻하게, 평균 밝기 80%, 채도 20%"
       "평평한 빛과 부드러운 음영. 큰 면에 그라데이션을 두지 말 것"
       "어두운 색 금지"
     트럭 안은 철공소가 아니라 **크림색 칠을 한 포근한 가게 안**이다.
     아래 다섯 색은 전부 공통 팔레트에 있는 것이고, 새로 지어낸 것이 하나도 없다. */
  --line:  #8E4A4A;              /* 윤곽 — 어디나 이 색이다 */
  --cream: #FCFCE4;              /* 기둥 몸통 · 차양 밝은 줄 · 앞턱 */
  --zest:  #FCCC6C;              /* 차양 진한 줄 · 창턱 · 창틀 안쪽 두께 */
  --clay:  #CC846C;              /* 천장 · 상판 옆 구석 */
  --ash:   #CCCCCC;              /* 상판 */
  --blush: #FCE4E4;              /* 아주 옅은 반사광 */
}

/* ---------- 좌우 벽 — 눕은 면 (2026-09-06) ----------
   앞서는 화면 좌우에 세운 **정면 판**이었다(납작한 기둥 두 장). 창은 생겼지만
   깊이가 없었다 — 상판만 rotateX 로 눕어 있고 좌우가 정면이면, 보는 사람은
   "창틀을 그린 그림" 으로 읽는다. 벽도 같이 누워야 방이 된다.

   ⚠ 사다리꼴로 **흉내내지 않는다.** clip-path 로 잘라도 겉모양은 나오지만 결이
     안 눕는다 — 벽지의 얼룩과 붓결이 창 쪽으로 촘촘해지는 그것이 깊이의 진짜다.
     그래서 rotateY 로 실제로 세운다. 상판이 rotateX 로 하는 것과 같은 방식이다.

   ⚠ 90도다. 어중간하게 70도쯤 돌리면 벽이 **비스듬히 열린 문**이 된다.
     트럭 옆벽은 보는 방향과 나란한 면이므로 정확히 90도라야 한다. 그렇게 두어도
     화면 가운데에서 벗어나 있으므로 선으로 눌리지 않고 쐐기꼴로 펼쳐진다.

   ⚠ 자리를 눈으로 맞추지 말 것. 넷이 한 소실점을 봐야 하므로 전부 셈에서 나온다 :
       가까운 끝(화면 가장자리) 위 = --vp + (--wint - --vp) / --s   →  4.9% (y 40)
       높이                        = (100 - --wint - --winb) / --s  → 82.5% (676px)
       깊이(폭)                    = --persp * (1/--s - 1)          →  130px
     먼 끝이 창 구멍(위 14% · 아래 20% · 좌우 10%)에 정확히 물리도록 거꾸로 푼 값이다.
     창 크기를 고치면 세 줄이 저절로 따라온다 — 손으로 다시 잴 일이 없다.

   ⚠ 가까운 끝이 화면 가장자리에서 위아래로 넘친다(y 40 ~ 717). 넘치는 것이 맞다 —
     천장 띠와 상판이 그 위를 덮어 자른다. 그래서 이 층이 **맨 나중**이어야 하고
     (z-index 4), 그 덮이는 선이 곧 벽·천장·상판이 만나는 모서리가 된다.
     여기를 --win-t / --win-b 에 맞춰 잘라 두면 벽이 다시 직사각형이 되어 도로 납작해진다.

   ⚠ 리벳을 박지 않는다. 철판이라는 티를 내려고 넣었던 것인데, 공통 스타일이
     "평평한 빛" 을 못박아 두었고 동그란 금속 알갱이는 그 결과 혼자 튄다 */
#cabin .post {
  position: absolute;
  z-index: 4;                    /* 그림 층(.art, 3)보다 위 — 천장·차양의 잘린 끝을 덮는다 */
  top:    calc((var(--vp) + (var(--wint) - var(--vp)) / var(--s)) * 1%);
  height: calc((100 - var(--wint) - var(--winb)) / var(--s) * 1%);
  width:  var(--depth);          /* 판의 가로가 곧 깊이다 — 눕혀 세우므로 */
  background: var(--cream);

  /* 위·아래 모서리 — 벽이 천장과, 상판과 만나는 선 (2026-09-06).
     ⚠ 이 선이 없으면 두 색이 그냥 맞닿아 "면이 꺾였다" 가 아니라 "칠이 갈렸다" 로 읽힌다.
       이 게임은 어디나 자주빛 손그림 윤곽이 있고, 여기만 없으면 그 자리만 접힌 종이처럼 보인다.
     ⚠ 이 선은 눕은 판 위에 있으므로 창 쪽으로 갈수록 저절로 가늘어진다(0.8배). 그것이 맞다.
     ⚠ box-sizing 을 border-box 로 두는 것이 핵심이다. 기본값(content-box)이면 선 굵기만큼
       판이 커져 먼 끝이 창 구멍 밖으로 3px 삐져나가고, 그만큼 차양 위를 덮는다 */
  box-sizing: border-box;
  border-top:    0.30vw solid var(--line);
  border-bottom: 0.30vw solid var(--line);
}
/* ⚠ 모서리 그늘(.post::before · .deck::after · 천장 그림 앞의 두 겹)은 **걷어냈다**
   (2026-09-06 밤). 넣어 봤더니 화면이 어색해졌다 — 이 게임의 공통 스타일이 못박은
   "평평한 빛과 부드러운 음영 · **큰 면에 그라데이션을 두지 말 것** · 어두운 색 금지"
   를 정면으로 어긴 것이었다. 2026-09-05 에 트럭 안을 철판·스테인리스로 칠했다가
   물린 것과 **같은 실수**다. 이 화풍에서 면을 가르는 것은 그늘이 아니라 **선과 색**이다.
   지금 입체감을 지고 있는 것 : 벽·천장·상판의 윤곽선(0.30vw --line) · 벽지 그림
   자체에 들어 있는 밝기 차(창 쪽 244 · 깊은 쪽 201) · 원근으로 모이는 여섯 모서리.
   되살릴 일이 생기면 _bak/main_bak_drop_shadows_*.css 에 그대로 있다 */

/* ⚠ 축은 **가까운 끝**이다. 먼 끝(창 쪽)을 축으로 잡으면 벽이 화면 밖으로 뻗어
     나가 좌우가 통째로 사라진다 — 방향만 뒤집힌 것이라 눈치채기 어렵다 */
#cabin .post.l {
  left: 0;
  transform-origin: 0 50%; transform: rotateY(90deg);
  border-right: 0.34vw solid var(--line);
  background-image: linear-gradient(to right, #E3B8A4 0%, #F5E7D6 38%, var(--cream) 72%);
}
#cabin .post.r {
  right: 0;
  transform-origin: 100% 50%; transform: rotateY(-90deg);
  border-left: 0.34vw solid var(--line);
  background-image: linear-gradient(to left, #E3B8A4 0%, #F5E7D6 38%, var(--cream) 72%);
}
/* 창틀 안쪽 두께 — 빛이 밖에서 들어오므로 창을 마주 본 면이 밝다.
   이 선 하나가 벽에 두께를 준다.
   ⚠ 벽이 누운 뒤로는 이것이 벽의 **먼 끝**이다. 폭(0.62vw)은 화면이 아니라 깊이로
     들어가므로, 화면에서는 원근에 눌려 그보다 좁게 보인다. 그것이 맞다 */
#cabin .post.l::after,
#cabin .post.r::after {
  content: ""; position: absolute; top: 0; bottom: 0;
  width: 0.62vw; background: var(--zest);
}
#cabin .post.l::after { right: 0; }
#cabin .post.r::after { left: 0;  }

/* ---------- 지붕 — 트럭 안 천장과, 창 너머로 보이는 차양 ----------
   ⚠ 차양을 화면 폭 전체에 두르지 말 것. 그렇게 했더니 줄무늬가 기둥 위까지 덮어
     **천장이 사라졌다** — 기둥이 위에서 잘린 것처럼 보이고, 안이 아니라 무대 앞이 된다.
     안에서 보면 차양은 창 **밖**에 달린 물건이므로 창 폭만큼만 보인다.
   ⚠ 어두운 갈색 띠로 두지 말 것. 한때 #6E4220 으로 뒀는데 화면 위 18% 가
     시커먼 덩어리가 되어, 포근한 그림 위에 무거운 뚜껑을 덮은 꼴이었다 */
#cabin .roof {
  position: absolute; left: 0; right: 0;
  /* 눕은 면이다 (2026-09-06). 앞서는 화면 위에 붙인 정면 띠였는데, 좌우 벽이 누운
     뒤로는 그 띠만 화면과 나란해서 천장이 "벽에 붙인 색종이" 로 읽혔다.
     ⚠ 축(z=0)은 벽의 윗선과 같은 자리다. 위쪽 --ceil-front 만큼은 나를 **지나쳐**
       앞으로 나온다 — 그래야 화면 맨 위까지 천장이 이어진다. 안 그러면 위가 빈다.
     ⚠ rotateX 는 **-90도**다. +90 이면 면이 위를 보게 뒤집혀 뒷면을 보게 되고,
       그림이 좌우로 뒤집힌다. 천장은 밑에서 올려다보는 면이다 */
  top: calc((var(--vp) - var(--y-ceil)) * 1% - var(--ceil-front));
  height: calc(var(--ceil-front) + var(--depth));
  transform-origin: 50% var(--ceil-front);
  transform: rotateX(-90deg);
  background: linear-gradient(to bottom, #D89680 0%, var(--clay) 72%, #C07C64 100%);
}
/* ⚠ 차양 밑그림 둘(.roof::before 줄무늬 · .roof::after 가리비)이 여기 있었다.
   2026-09-06 에 차양을 창 안으로 옮기면서 걷어냈다 — 아래 "차양" 묶음이 대신한다.
   왜 옮겼나 : 저 둘은 천장 띠(0~14%) 안에 그려져 있었는데, 안에서 보면 그 자리는
   트럭 **천장**이다. 차양은 창 밖에 달린 물건이라 거기 있을 수가 없다.
   원본은 _bak/main_bak_awning_3d_*.css 에 있다 */

/* ---------- 창 안쪽 그늘 ----------
   구멍 가장자리가 안으로 들어가며 어두워진다. 창틀과 풍경이 맞닿는 선을
   칼로 자른 듯 두면 풍경이 벽에 붙인 사진처럼 보인다 — 이 한 겹이 그것을 푼다.
   ⚠ 아주 옅게. 진하게 걸면 "어두운 색 금지" 를 어기고 창가가 눅눅해진다 */
#cabin .reveal {
  position: absolute; z-index: 2;   /* 창턱(::after)이 상판 위에 얹혀야 한다 */
  left: var(--win-x); right: var(--win-x);
  top: var(--win-t); bottom: var(--win-b);
  box-shadow:
    inset 0 0 2.6vw rgba(156, 84, 84, .16),
    inset 0 1.2vw 2.2vw rgba(156, 84, 84, .14);   /* 위가 조금 더 깊다 — 지붕이 드리운다 */
}
/* 창턱 — 상판이 창과 만나는 선. 손님이 돈을 올려놓는 그 자리다.
   ⚠ 이것이 없으면 상판이 어디서 시작하는지 읽히지 않아, 눕힌 보람이 사라진다 */
#cabin .reveal::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%;
  height: 1.2vw;
  background: linear-gradient(to bottom, var(--cream) 0%, var(--zest) 62%);
  border-top: 0.30vw solid var(--line);
  box-shadow: 0 0.3vw 0.6vw rgba(156, 84, 84, .18);
}

/* ---------- 상판 ----------
   ⚠ 여기가 이 층의 핵심이다. 앞의 것은 1180x200 정면 판이었고, 정면 판은
     아무리 잘 칠해도 벽으로 읽힌다. 내 앞에 놓인 상판이면 위에서 비스듬히
     내려다보는 면이므로 뒤가 좁은 사다리꼴이어야 한다.
   ⚠ CSS 로 사다리꼴을 흉내내지 않고 rotateX 로 **실제로 눕힌다.** 그래야 결도
     같이 눕는다 — 뒤로 갈수록 촘촘해지는 그 결이 깊이를 만드는 진짜다.
   ⚠ perspective 를 멀리 두지 말 것. 760px 로 뒀더니 좌우가 거의 안 좁아져서
     눕힌 것이 눈에 안 보였다. 520px 이면 사다리꼴이 또렷하다.
   ⚠ 판을 화면보다 넓게(-13%) 잡지 말 것. 좁아지는 좌우 끝이 화면 밖으로 밀려나서
     가운데의 평행한 부분만 남고, 결국 회색 벽 한 장으로 보였다.
   ⚠ 스테인리스가 아니다. 한때 브러시드 스테인리스로 칠했다가 물렸다 —
     차가운 회갈색이 이 게임에서 혼자 고급스럽게 떠올랐다.
     칠한 법랑 상판이라고 보면 된다 : 옅은 회색(--ash)에 부드러운 음영뿐 */
#cabin .counter {
  position: absolute; left: 0; right: 0;
  /* 눕은 면이다 (2026-09-06). 앞서는 화면 아래 20% 짜리 상자 안에서 판 하나를
     rotateX 로 눕히고 넘치는 뒤쪽을 잘라 쓰는 방식이었다. 그러면 상판의 먼 변이
     창보다 좁아서(236~944 대 118~1062) 상판 옆에 테라코타 쐐기가 남았고,
     좌우 벽의 아랫선과 상판의 옆선이 **다른 선 둘**이 되어 구석이 어수선했다.
     이제는 벽과 같은 셈으로 눕히므로 그 둘이 **한 선**이다 — 쐐기가 사라진다.
     ⚠ 축(z=0)은 벽의 아랫선과 같은 자리다. 아래쪽 --deck-front 만큼은 나를
       지나쳐 앞으로 나와 화면 맨 아래에서 끝난다.
     ⚠ rotateX 는 **+90도**다. -90 이면 면이 아래를 보게 뒤집혀 뒷면을 보게 되고,
       그림이 좌우로 뒤집힌다. 상판은 위에서 내려다보는 면이다.
     ⚠ overflow 를 걸지 말 것 — 자를 것이 없다. 걸면 3D 가 통째로 납작해진다 */
  top: calc((var(--vp) + var(--y-deck)) * 1% - var(--depth));
  height: calc(var(--depth) + var(--deck-front));
  transform-origin: 50% var(--depth);
  transform: rotateX(90deg);
  /* ⚠ 상판 위에 물건을 세우려면 이것이 있어야 한다 (2026-09-06).
     없으면 자식이 판에 **납작하게 눌려** 같이 누워 버린다 — 물건이 상판에 그려진
     그림이 된다. preserve-3d 라야 자식이 제 자리에서 일어설 수 있다.
     ⚠ overflow 는 여전히 걸지 말 것 — 걸면 이것까지 무효가 된다 */
  transform-style: preserve-3d;
  background: var(--clay);

  /* 창턱 선 — 풍경이 상판과 맞닿는 자리 (2026-09-06).
     ⚠ 여기 **띠를 두지 마라.** 2026-09-05 까지는 sill.png 로 크림→주황 띠를 깔았는데,
       창 아래에 띠를 하나 더 두는 것 자체가 화면을 어수선하게 만든 원인이었다
       (화방 F3 : "sill.png · lip.png 는 없앴다"). 필요한 것은 경계가 있다는 사실뿐이고,
       그것은 선 하나로 족하다 — 좌우 벽이 쓰는 것과 같은 굵기·같은 색이라야
       세 선이 창 아래 모서리에서 한 선으로 이어진다.
     ⚠ 이 선은 눕은 판의 **먼 변**에 있으므로 원근에 눌려 저절로 가늘어진다. 그것이 맞다.
     ⚠ box-sizing 은 border-box 라야 한다. 기본값이면 선 굵기만큼 판이 길어져
       앞쪽이 화면 밖으로 밀린다 */
  box-sizing: border-box;
  border-top: 0.30vw solid var(--line);
}
/* 상판의 살갗 — 자리도 크기도 상판이 정한다.
   ⚠ 그림의 **위**가 창 쪽(먼 곳)이다.
   ⚠ 여기 원근을 그리지 말 것. 판이 이미 누워 있으므로 그린 원근은 두 겹이 된다 —
     닦은 결은 나란한 가로줄로 그리면 화면에서 저절로 뒤로 갈수록 촘촘해진다.
   ⚠ 옛 deck.png 은 사다리꼴과 테라코타 구석을 그림에 구워 넣은 것이라 여기 못 쓴다.
     그래서 index.html 에서 뺐다 — 화방에 평평한 판으로 다시 시켜 두었다 */
#cabin .deck {
  position: absolute; inset: 0;
  background-image:
    /* 그림 — 평평한 테라코타 판. **늘려서** 채운다 (화방 F4 : "NOT tiled, stretched to fit").
       ⚠ 이어 깔지 않는다. 상판은 창 폭 전체를 한 장으로 덮는 면이라 이음매가 있으면
         그 자리가 판을 가로지르는 줄이 된다 */
    url("../../assets/cabin/deck.png?v=3"),
    /* 닦은 결 — 나란한 가로줄이다. 판이 누워 있으므로 뒤로 갈수록 촘촘해진다 */
    /* ⚠ 아주 옅게, 아주 성기게. 판이 누우면 앞쪽이 확대되므로 여기서 또렷한 줄은
       화면 아래에서 골판지처럼 굵어진다 (2026-09-06 에 한 번 그렇게 나왔다) */
    repeating-linear-gradient(to bottom,
      rgba(255,255,255,.22) 0 1px, transparent 1px 22px),
    /* 창에서 들어온 빛 — 창 쪽(위)이 밝다.
       ⚠ 회색이 아니라 **테라코타**다 (2026-09-06). 옅은 회색은 크림빛 벽과 명도가
         거의 같아 방이 하나의 밝은 덩어리로 보였다. 상판을 가장 진한 면으로 두어야
         천장 > 벽 > 상판 차례가 서고, 그 위에 놓일 감귤(주황)도 잘 보인다 */
    linear-gradient(to bottom, #D9967E 0%, #D28D75 46%, var(--clay) 100%);
  background-size: 100% 100%, auto, auto;
  background-repeat: no-repeat, repeat, no-repeat;
}

/* 물건만 담는 판 — 상판과 **똑같은 자리·똑같은 기울기**의 한 겹 더 (2026-09-06).
   ⚠ 왜 한 겹 더 두나 — 좌우 벽이 z-index 4 로 상판보다 앞이다(방의 모서리를 만들려면
     그래야 한다). 그런데 물건은 벽 **앞에 놓인 물건**이라 벽을 덮어야 맞는데,
     상판 안에 두면 상판이 벽 뒤라 물건까지 같이 뒤로 밀려 세로로 잘린다.
     상판(.counter)에 z-index 를 주어 앞으로 빼면 벽의 아랫자락을 덮어 모서리가 무너진다 —
     그래서 **판을 하나 더** 두고 거기에만 물건을 담는다. 셈이 같으니 자리는 정확히 겹친다.
   ⚠ 바탕을 지운다. 안 지우면 테라코타 판이 한 겹 더 얹혀 창을 덮는다 */
#cabin .counter.wares { z-index: 5; background: none; }
#cabin .counter.wares .deck { display: none; }

/* ── 상판 위 물건 (2026-09-06) ─────────────────────────────────────────
 *
 *   상판이 **진짜 눕은 면**이므로, 물건은 그 면 위에 세우기만 하면 된다 —
 *   rotateX(-90deg) 로 판이 누운 만큼 되세우면 발이 판에 닿은 채 곧게 선다.
 *   그러면 원근이 **저절로** 따라온다 : 뒤에 놓을수록 작아지고 위로 올라간다.
 *   ⚠ 자리값을 손으로 셈하지 마라. 2026-09-05 판은 납작한 상자 안에서 --k(축소율)와
 *     --b(밑동)를 물건마다 손으로 풀어 박았는데, 창 크기를 고칠 때마다 여덟 개를
 *     다시 재야 했다. 지금은 판이 실제로 누워 있으니 그 셈이 통째로 필요 없다.
 *
 * ⚠ 크기를 물건마다 다르게 주지 마라. 그림 여덟(열둘)이 **같은 자에 맞춰**
 *   그려져 있다 — 캔버스 560x560, 규격품 귤 지름 56px, 캔버스를 채우지 않는다.
 *   그러니 상자 크기를 하나로 두어야 서로의 크기가 맞는다. 크기를 개별로 주는 순간
 *   그 약속이 깨진다 (2026-09-05 에 굴러 나온 귤이 상자 속 귤보다 커진 그 사고다).
 *
 * ⚠ 몇 개를 올리느냐가 크기보다 중요하다. 여덟을 한꺼번에 올렸다가 어수선해서
 *   통째로 걷어냈다 — 틀린 것은 크기가 아니라 **개수**였다. 지금은 셋이다.
 *
 *   --x  판 위에서 좌우 자리 (%)
 *   --d  판의 앞 끝(나에게 가장 가까운 변)에서 얼마나 뒤인가 (px). 클수록 창에 가깝다 */
#cabin .counter .w {
  position: absolute;
  left: var(--x); bottom: var(--d);
  width: var(--wares); height: var(--wares);
  margin-left: calc(var(--wares) / -2);   /* --x 가 물건의 한가운데가 되게 */
  transform-origin: 50% 100%;             /* 발을 축으로 세운다 */
  transform: rotateX(-90deg);
  background-repeat: no-repeat;
  background-position: bottom center;
  background-size: contain;               /* 그림 안의 제 크기를 그대로 지킨다 */
  pointer-events: none;
}
#cabin { --wares: 232px; }                /* 상판에 놓는 물건의 상자 한 변 */

/* 자리 — **오른쪽에 모은다** (2026-09-06). 왼쪽과 가운데를 비워 두는 까닭은
   명소 배경 80장이 거기를 지나기 때문이다 — 주인공을 셋째 지점에 세우라고
   배경 프롬프트가 못박아 두었고, 그 자리를 물건으로 덮으면 그 품이 사라진다.
   ⚠ 고르게 두지 마라. 고른 간격으로 홀로 놓이면 풍경이 아니라 **툴바**로 읽힌다 —
     좌우도 깊이도 셋이 다 다르게 둔다.
   ⚠ **키가 큰 것을 뒤에 두지 마라.** 뒤(창 쪽)에 둘수록 발이 높이 올라가므로,
     높은 물건은 뒤에 두면 그 키가 통째로 창 안으로 들어가 명소를 가린다 —
     휴지통을 --d 96 에 두었더니 절벽을 덮었다. 높은 것일수록 앞이다.
   ⚠ 낮은 것은 뒤에 두어도 된다. 그래야 깊이가 갈려 한 줄로 서 보이지 않는다 */
#cabin .counter .w.napkin  { --x: 57%; --d: 46px; background-image: url("../../assets/truck/napkin-box.png?v=1"); }
#cabin .counter .w.pick    { --x: 68%; --d: 88px; background-image: url("../../assets/truck/toothpick-cup.png?v=1"); }

/* 휴지통 — 가장 앞에 선다. 상자는 나머지와 **같은 --wares** 다.
   ⚠ 한때 330px 로 키웠다가 되돌렸다. 셋이 같은 자로 그려져 있어(캔버스 560x560 ·
     규격품 귤 56px) 상자를 하나만 키우면 그 약속이 깨진다 — 2026-09-05 에 그것이 깨져
     굴러 나온 귤이 상자 속 귤보다 커진 사고가 있었다 */
#cabin .counter .w.bin {
  --x: 79%; --d: 34px;
  background-image: url("../../assets/truck/bin-skewers.png?v=1");
}

/* 앞 모서리 — 상판이 끝나는 자리의 테. 두께가 보여야 상판이 판때기가 아니라 물건이 된다 */
#cabin .lip {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 1.25vw;
  background: linear-gradient(to bottom, var(--cream) 0%, #E4E4DC 52%, var(--ash) 100%);
  border-top: 0.24vw solid var(--line);
  box-shadow: 0 -0.2vw 0.5vw rgba(156, 84, 84, .14);
}

/* ---------- 상판 위 물건 — 걷어냈다 (2026-09-05) ----------
   여덟 장(#cabin .w)을 올렸다가 같은 날 걷어냈다. 어수선했다 —
   상판이 물건으로 꽉 차서 **무엇이 이 가게의 일인지** 안 읽혔고,
   왼쪽 감귤 상자가 기둥에 물려 잘렸고, 앞의 것들이 화면 아래로 흘러내렸다.

   ⚠ 크기가 틀려서가 아니다. 여덟 장은 공통 단위(560x560 · 규격품 귤 56px)를
     지켜 들어왔고 화면에서도 맞았다. 틀린 것은 **몇 개를 놓느냐**였다.
     다시 올릴 때 자리값을 다시 재지 말 것 — 그대로 쓰고 개수만 줄이면 된다.

   되살리려면 _bak/main_bak_wares_remove_* 의 이 자리(--unit·#cabin .w·여덟 줄·
   .show-wares)와 _bak/index_bak_wares_remove_* 의 <i class="w ..."> 여덟 줄을
   되돌린다. 그림은 assets/truck/ 에 그대로 있다.

   ⚠ 되살릴 때는 **주문에 쓰이는 것만** 올린다. 장식으로 채우지 않는다 —
     규격 분기가 이 게임의 조작이므로, 상판에 놓인 것은 전부 만질 것이어야 한다. */

/* ═══════════════════════════════════════════════════════════════════
   창틀 그림 — 조각 여섯 (assets/cabin/, 2026-09-05)

   위의 CSS 판은 **밑그림이자 대비책**이다. 그림이 들어오면 이 층이 그 위를 덮는다.
   왜 그림으로 가나 — CSS 로 그린 창틀은 화면에서 가장 컴퓨터가 그린 티가 나는 자리였다.
   차양 가리비가 완벽한 반원이고, 줄무늬 경계가 자로 그은 듯하고, 리벳이 정원이다.
   나머지는 전부 흔들리는 손그림인데 거기만 매끈해서 혼자 논다.

   ⚠ 왜 한 장이 아니라 여섯인가 — 패드는 기종마다 화면 비율이 다르다(1.33~1.52).
     한 장으로 늘리면 가리비가 찌그러진다. 조각마다 **늘어나는 방향을 하나로 묶어** 두면
     어느 비율에서도 제 모양을 지킨다 :
       가로로만 이어 깔기 : 천장 · 차양 · 창턱 · 앞턱
       세로로만 이어 깔기 : 기둥 둘
       늘리기            : 상판 (평평한 면이라 늘어나도 티가 안 난다)

   ⚠ 차례가 곧 층이다. 기둥이 **맨 나중**이라야 차양·창턱·상판의 잘린 끝을 덮는다.
     순서를 바꾸면 조각 경계가 드러난다.

   ⚠ 이어 깔리는 조각은 눈금을 **%로** 준다(25% 따위). px 로 주면 화면이 넓어질 때
     마지막 한 칸이 잘려 가리비가 반쪽으로 끝난다.

   ⚠ 그림이 없으면 아무 일도 안 일어난다 — background-image 는 파일이 없어도 조용하다.
   ═══════════════════════════════════════════════════════════════════ */

#cabin .art {
  position: absolute;
  pointer-events: none;
  background-repeat: no-repeat;
  /* ⚠ 그림 층은 CSS 밑그림보다 **위**다. 대개는 DOM 에서 나중에 오는 것으로 충분한데
     창 안쪽 그늘(.reveal)만은 z-index 2 를 갖고 있어서 차례로는 못 이긴다.
     그래서 창턱 밑그림(.reveal::after)의 진한 감귤색 띠가 sill.png 를 뚫고 올라와,
     창 아래가 **크림 띠와 주황 띠 두 줄**로 보였다 (2026-09-05 에 고쳤다). */
  z-index: 3;
}
/* ⚠ 상판 그림(.art.deck-art)은 걷어냈다 (2026-09-06). 옛 deck.png 은 사다리꼴
   원근을 그림에 구워 넣은 것인데, 이제 판이 실제로 누우므로 원근이 두 겹이 된다.
   화방에 **평평한 판**으로 다시 시켜 두었다 — 들어오면 #cabin .deck 의
   background-image 맨 앞에 한 줄 얹으면 된다 */

/* 천장의 살갗 — 천장(.roof) **안**에 든다. 자리도 크기도 천장이 정한다.
   ⚠ 그림의 **아래**가 창 쪽(먼 곳)이다. 눕히면 그렇게 된다 — 그림을 다시 그릴 때
     위아래를 여기 맞출 것 */
#cabin .art.ceiling {
  inset: 0;
  /* ⚠ 25% 가 아니라 auto 다. 25% 로 두면 폭은 화면 폭을, 높이는 화면 높이를 따라가서
     비율이 다른 패드마다 칸이 8%까지 눌린다(리벳이 타원이 된다). auto 는 높이에
     폭을 맞춰 주므로 비율이 고정된다 — 오른쪽 끝에 반 칸이 남지만, 이어지는 무늬라
     잘려도 티가 안 나고 그나마도 벽이 덮는다 */
  /* ⚠ 천장이 누운 뒤로는 제 크기로 맞출 까닭이 없다 (2026-09-06). 판의 세로가 곧
     깊이라, 그림 속 가로 이음매는 "얼마나 먼 데 있는 이음매인가" 가 된다.
     벽의 모서리와 얕은 각으로 엇갈리던 문제도 같이 풀렸다 — 이제 둘이 32도로
     또렷하게 만나고, 이음매가 벽에서 끝나는 것이 실제 천장의 모습이다 */
  /* ⚠ 그늘이 그림보다 **앞**에 온다 (맨 앞 층이 맨 위다).
     천장이 좌우 벽과 만나는 구석과, 창에서 먼 쪽(판의 위 = 내 머리 위)이 어둡다.
     빛은 창에서 들어오므로 창 쪽(아래)이 밝고 안쪽으로 갈수록 잦아든다.
     ⚠ 12% 는 눈대중이 아니다 — 벽이 창과 만나는 자리(--winx = 10%)보다 조금 넓게
       잡아야, 눕은 천장이 원근에 눌리면서 그늘의 안쪽 끝이 벽선과 맞물린다 */
  background-image: url("../../assets/cabin/ceiling.png?v=4");
  background-size: auto 100%;
  background-repeat: repeat-x;
  /* 지붕 앞 끝 — 트럭 안 천장이 끝나고 창 밖 차양이 시작하는 선 (2026-09-06).
     ⚠ 이 선을 차양 쪽에 두면 안 된다. 차양은 눕은 판이라 선이 깊이 방향으로 들어가
       화면에서는 1px 도 안 남는다. 창틀은 정면이므로 여기가 제자리다.
     ⚠ border-box 로 두어야 띠 높이(--win-t)가 그대로 유지되고 그림이 선 위에 앉는다 */
  box-sizing: border-box;
  border-bottom: 0.30vw solid var(--line);
}
/* 차양막 — 원통형. 벽에서 나와 앞으로 볼록하게 휘어 내려온다 (2026-09-06).
   ⚠ 그림 한 장이다. 이어 깔지 않는다 — 줄 하나하나가 직사각형이 아니라 **사다리꼴**이고
     기운 각이 자리마다 달라서, 같은 그림을 되풀이하는 타일로는 이 모양이 안 나온다.
     타일로 하루를 썼다 (명암·주름·휜 면을 다 얹어 봤다). 평평한 띠는 칠을 아무리
     손봐도 굽어 보이지 않는다 — 굽은 것은 칠이 아니라 **모양**이기 때문이다.
   ⚠ **높이를 폭의 9.10% 로 잡을 것.** 그림의 가로세로비(1000:91)와 어긋나면 물결이
     타원으로 눌린다. 창 폭이 (100 - 2*winx)vw 이므로 그것의 0.089 다.
     tools/bake-awning.mjs 를 다시 돌리면 맞춰야 할 %를 찍어 준다.
   ⚠ 손으로 고치지 말 것 — tools/bake-awning.mjs 로 그린 결과물이다.
     벌어지는 정도(--spread) · 줄 수(--slats) · 덮개 높이(--hc)를 그 도구에서 고친다 */
#cabin .awn {
  position: absolute;
  left: var(--win-x); right: var(--win-x);
  top: var(--win-t);
  height: calc((100 - 2 * var(--winx)) * 0.091 * 1vw);
  /* ⚠ 코드가 그리던 SVG 에서 **손그림 PNG 로 갈았다** (2026-09-06).
     tools/bake-awning.mjs 가 그린 것은 셈으로는 정확했지만 선이 자로 잰 듯 곧아
     이 화풍에서 혼자 놀았다. 그 도구와 SVG 는 남겨 둔다 — 비율을 다시 잴 때 쓴다.
     ⚠ 가로세로비는 그대로 2000:182 = 폭의 9.10% 다. 아래 height 를 건드리지 말 것 */
  background-image: url("../../assets/cabin/awning-curved.png?v=1");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* ⚠ 옛 차양(타일 두 겹 · #cabin .awn .hem)은 걷어냈다 (2026-09-06).
   그림도 지우지 않았다 — assets/cabin/awning.png · valance.png 는 그대로 있고
   화면에서만 뺐다. index.html 의 <i class="awn"> 한 줄과 여기 규칙 둘을 되돌리면
   그대로 다시 선다.
   되살릴 판은 _bak/main_bak_awning_remove_*.css 에 있다. 그 안에 하루치 시행착오가
   주석으로 다 들어 있으니 다시 만들기 전에 먼저 읽을 것 — 눕은 판(원근) · 앞 가로대 ·
   롤러 · 접이 지지대 · 밑면 그늘 · 세로 주름 · 휜 면까지 다 해 보고 물린 것들이다.
   특히 잊지 말 것 : 칸이 짝수라 **반 칸(--awn-half)을 밀지 않으면** 화면 정중앙에
   이음매가 걸려 좌우 색이 뒤집힌다 (좌우 어긋남이 1.3 에서 54.6 으로 뛴다) */


/* ⚠ 2026-09-05 에 기둥을 걷어낸 동안, 좌우 끝에 남는 갈색 귀를 메우려고 그늘막을
   화면 폭 전체로 두르고 가리비를 3.125vw 로 키운 묶음이 여기 있었다.
   2026-09-06 에 좌우 벽이 돌아오면서 통째로 지웠다 — 원래 규칙(창 폭만큼만 두른다)이
   그대로 맞다. 안에서 보면 차양은 창 **밖**에 달린 물건이기 때문이다.
   되살릴 일이 생기면 _bak/main_bak_cabin_walls_20260906_011323.css 의 이 자리에 있다 */

/* ⚠ 창턱 그림(.art.sill)은 **걷어냈다** (2026-09-06). 화방이 sill.png · lip.png 두
   조각을 규격에서 아예 지웠기 때문이다 — "창 아래에 띠를 하나 더 두는 것 자체가
   화면을 어수선하게 만든 원인 가운데 하나였다" (화방 F3 아래 쪽지).
   지금 그 자리를 맡는 것은 #cabin .counter 의 border-top **선 하나**다.
   좌우 벽의 border-bottom 과 같은 굵기·같은 색이라 셋이 창 아래에서 한 선으로 이어진다.
   ⚠ 되살리려면 index.html 의 <i class="art sill"> 한 줄과 아래 규칙을 되돌리고,
     .counter 의 border-top 을 빼야 한다 — 안 빼면 띠와 선이 겹쳐 다시 3단이 된다.
     옛 규칙은 _bak/main_bak_cabin_art_5_*.css 의 이 자리에 그대로 있다.
     같은 까닭으로 앞턱(.art.lip-art)도 화면에 걸지 않는다 — 규칙만 아래에 남겨 둔다 */
/* 좌우 벽의 살갗 — 벽(.post) **안에** 든다. 자리도 크기도 벽이 정하므로 inset 0 이다.
   ⚠ 벽이 누운 뒤로 이 그림의 가로는 화면이 아니라 **깊이**로 들어간다. 그림 왼쪽의
     테라코타(창에서 먼 쪽)가 트럭 안쪽 깊은 곳이 되고, 오른쪽의 감귤색 실선이 창턱이
     된다 — 빛이 창에서 들어오는 그 방향과 그대로 맞는다. 손댈 것이 없다.
   ⚠ 세로로만 이어 깐다(repeat-y). 가로로도 깔면 창까지 가는 길에 같은 벽이
     여러 번 되풀이돼 깊이가 지워진다 */
#cabin .art.post-l, #cabin .art.post-r {
  inset: 0;
  /* ⚠ post.png 가 아니라 **wall.png** 다 (2026-09-06). post.png 는 벽이 정면 판이던
     시절의 그림이라, 판이 실제로 누운 지금은 그려 넣은 원근이 두 겹이 된다.
     wall.png 는 펼쳐서 평평하게 그린 260x520 이고 눕히는 일은 코드가 한다.
     post.png 는 지우지 않고 둔다 — 되돌릴 자리다 */
  background-image: url("../../assets/cabin/wall.png?v=3");
  background-size: 100% auto; background-repeat: repeat-y;
}
/* 오른쪽은 같은 그림을 뒤집어 쓴다 — 벽 안에서 뒤집으므로 밝은 끝이 양쪽 다 창을 향한다 */
#cabin .art.post-r { transform: scaleX(-1); }
/* 앞턱 트림 — 화면 폭 전체, 맨 앞 */
#cabin .art.lip-art {
  left: 0; right: 0; bottom: 0; height: 1.6vw;
  background-image: url("../../assets/cabin/lip.png?v=1");
  background-size: 25% 100%; background-repeat: repeat-x;
}

/* ================= 지도 위 오브젝트 넷 (2026-09-06) =================

   랭킹 게시판 · 랭킹 단추 · 포스터 단추 · 포스터 선반.
   그림은 assets/ui/ 이고 화방은 docs/지도오브젝트-프롬프트.html 이다.

   ⚠ 넷 다 그림에 **스티커 테**(크림 테 + 갈색 선)가 구워져 있다. 그 테는 화면에서
     흰 테 6px · 갈색 선 3px 로 보이도록 캔버스마다 되나눈 값이다 — 그러므로
     **화면 크기를 화방의 표대로 잡아야 한다.** 게시판 308x360 · 단추 88 · 선반 폭 100%.
     다르게 잡으면 넷의 테 굵기가 갈려 한 벌로 안 읽힌다.
   ⚠ 그래서 여기 크기가 vw 가 아니라 px 다. 나침반(74)·감귤(88)과 같은 방식이다.
   ⚠ 그림자·테두리를 CSS 로 더 두르지 말 것. 테는 이미 그림 안에 있다 —
     한 겹 더 두르면 두 번째 링이 되어 물건이 도장으로 보인다. */

/* ---------- 단추 둘 : 왼쪽 아래에 세로로 선다 ---------- */
#shelf-btn img, #rank-btn img { width: 100%; height: 100%; display: block; }

#rank-btn {
  position: absolute; left: 26px; bottom: 26px;    /* 줄의 맨 아래 — 첫째 칸 */
  width: 104px; height: 104px;          /* 터치 타겟 44px 을 넉넉히 넘긴다 */
  padding: 0; border: none; background: none;
  cursor: pointer; z-index: 5;
  transition: transform .16s ease-out;
}
#rank-btn:active, #rank-btn.pressed { transform: scale(.92) rotate(-2deg); }
#rank-btn:focus-visible { outline: 4px solid var(--zest); outline-offset: 4px; border-radius: 14px; }

/* ---------- 랭킹 게시판 ---------- */
#ranks {
  position: absolute; inset: 0;
  z-index: 13;                        /* 선반·나와 같은 층 */
  background: rgba(70, 54, 42, .40);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity .24s ease-out;
}
#ranks[hidden] { display: none; }
#ranks.on { opacity: 1; }

/* 나무판. 크기는 화방의 표가 정한다 (1400x840 을 1000x600 으로).
   ★ 2026-09-06 저녁 — 세로 판 308x360 → 가로 판 → **스크롤 판**으로 두 번 고쳤다.
     308 은 나(#me) 판이 1000 인 화면에서 쪽지처럼 작았고, 세 줄로는 백 명을 못 담는다.
   ⚠ 그림은 화방의 R1 이고 `node tools/bake-ui.mjs` 로 굽는다. 크림 우물이 텅 빈 까닭은
     줄이 스크롤되기 때문이다 — 괘선을 그림에 그으면 선만 제자리에 남는다 */
#ranks .board {
  position: relative;
  /* ★ 2026-10-07 : 1000 → 1120 → **1160px** 로 키웠다 (팀장 지시 — 작아 보였다). 상점판과 한 벌로 같이 움직인다.
     ⚠ 더 키울 자리가 거의 없다 — 1180 폭 화면에서 좌우 10px 씩 남는다 */
  width: min(1160px, 98vw);
  aspect-ratio: 1400 / 840;           /* 그림 비율 그대로 — 늘리지 않는다 */
  /* ★ 2026-10-07 : 랭킹판과 상점판을 **한 벌로** 맞췄다 — 기준은 **랭킹판**이다 (팀장 지시).
     상점(#shop .board)이 이 그림 · 크기 · 여는 몸짓 · 가운데 제목을 그대로 따른다.
     다른 것은 우물 안 내용물뿐이다. ⚠ 한쪽만 고치지 말 것.
     (한때 반대로 상점판 shop-board.png 에 맞췄다가 같은 날 물렸다) */
  background: url("../../assets/ui/ranking-board.png?v=6") center/100% 100% no-repeat;
  transform: translateY(14px) scale(.96);
  transition: transform .24s ease-out;
}
#ranks.on .board { transform: none; }

/* 글자 자리는 전부 **그림 좌표(1400x840)의 백분율**이다. 눈대중으로 주지 말 것 —
   머리 띠 y 60~190 · 크림 우물 x 90~1310 y 220~750 (2026-10-07 에 780 → 750).
   ⚠ 괘선은 그림에 없다. 줄이 스크롤되므로 줄 사이 선은 여기서 줄마다 긋는다 */
/* 제목은 머리 띠 한가운데. 상점도 같은 자리 · 같은 크기다 */
#ranks .head {
  position: absolute; left: 0; right: 0;
  top: 7.143%; height: 15.476%;                    /* 60/840 · 130/840 */
  display: grid; place-items: center;
  font-family: "Jua", sans-serif; font-size: 34px;
  color: var(--wood-line);
}

/* 우물 안에서 줄이 흐른다. 다섯 줄이 우물에 딱 맞는다 (li 가 20%).
   ★ 2026-10-07 : 판의 아래 테를 좌우 두께로 맞추며 우물 밑동이 y 780 → **750** 이 되었다
     (tools/fix-board-bottom.py). 우물이 220~750 이므로 높이 530/840 = 63.095% 다 */
#ranks .rows {
  position: absolute;
  left: 6.4286%; width: 87.143%;                   /* 90/1400 · 1220/1400 */
  top: 26.190%; height: 63.095%;                   /* 220/840 · 530/840 */
  margin: 0; padding: 0; list-style: none;
  overflow-y: auto; overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;       /* 끝까지 밀어도 뒤의 지도가 따라 움직이지 않는다 */
  scrollbar-width: none;
  /* 위아래 끝을 흐린다 — 글자가 우물 가장자리에서 잘리는 대신 사라진다.
     ⚠ 이것이 "더 있다" 는 유일한 신호다. 화살표 단추는 두지 않는다 (선반과 같다) */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 18px, #000 calc(100% - 18px), transparent 100%);
}
#ranks .rows::-webkit-scrollbar { display: none; }

#ranks .rows li {
  height: 20%;                        /* 우물 높이의 1/5 — 다섯 줄이 꼭 맞는다 */
  display: grid; grid-template-columns: 62px 1fr auto auto;
  align-items: center; gap: 0 28px;
  padding: 0 40px;
  font-family: "Gowun Dodum", sans-serif; font-size: 26px;
  color: var(--ink);
  border-bottom: 1.5px solid rgba(142, 74, 74, .16);   /* 그림이 아니라 줄이 데리고 다닌다 */
}
#ranks .rows li:last-child { border-bottom: none; }
#ranks .rows .n { font-family: "Jua", sans-serif; font-size: 32px; color: var(--zest); }
/* 세 자리(100위)까지 자리를 잡아 둔다 — 자릿수가 늘어도 이름이 밀리지 않는다 */
#ranks .rows .n { font-variant-numeric: tabular-nums; }
/* 이름은 **우리 글씨체(Jua)** 로 쓴다 (2026-09-06). 본문체로 두었더니 이름만 문서처럼
   읽혀서, 숫자·머리글과 따로 놀았다 — 남이 지은 이름표는 이 게임의 글씨여야 한다.
   ⚠ 낱말은 names.js 에서만 오므로 폰트 조각에 이미 다 들어 있다 (bake-fonts 의 SOURCES) */
#ranks .rows .who {
  font-family: "Jua", sans-serif; font-weight: normal; font-size: 28px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 숫자 둘은 폭을 붙들어 둔다 — 62곳과 5곳이 섞여도 단위가 세로로 줄을 맞춘다 */
#ranks .rows .v {
  font-variant-numeric: tabular-nums; color: var(--ink); opacity: .72;
  min-width: 126px; text-align: right;
}
#ranks .rows .v b { font-family: "Jua", sans-serif; font-size: 30px; color: var(--wood-line); margin-right: 4px; }

/* 나 — 옅은 감귤빛 한 겹. 글자색을 바꾸지 않는다(읽기가 나빠진다).
   ⚠ 줄이 백 개까지 흐르므로 이것이 **나를 찾는 유일한 표시**다 */
#ranks .rows li.me { background: rgba(252, 204, 108, .42); }

/* ---------- 포스터 선반의 널판 ---------- */
/* 줄과 널판을 한 덩어리로 세운다 — 포스터가 허공이 아니라 판 위에 기대 선다 */
#shelf .stand { flex: 1; min-width: 0; display: flex; flex-direction: column; }
#shelf .plank {
  display: block; width: 100%;
  aspect-ratio: 2000 / 300;
  /* ⚠ 가로로만 늘리지 않는다. aspect-ratio 로 비율을 붙들어 두므로 폭에 맞춰
     통째로 줄어든다 — 늘리면 좌우 끝의 테만 두꺼워져 스티커가 아니라 늘어난 사진이 된다 */
  background: url("../../assets/ui/shelf-rail.png?v=2") center/100% 100% no-repeat;
  /* 그림 위쪽 25px(판 높이의 8.3%)은 스티커 테와 판의 윗면이다. 그만큼 끌어올려
     포스터의 밑동이 그 윗면에 닿게 한다 — 나란히 두면 종이가 판 위에 떠 보인다 */
  margin-top: -1.3vw;
  pointer-events: none;               /* 판을 눌러도 닫히는 것은 gallery.js 가 맡는다 */
}

@media (prefers-reduced-motion: reduce) {
  #rank-btn { transition: none; }
  #ranks, #ranks .board { transition: none; }
}

/* ---------- 상점 단추 : 왼쪽 아래 세로 줄의 가운데 ----------
   ★ 2026-09-06 저녁 순서를 바꿨다 — **위에서 아래로 포스터(266) · 상점(146) ·
     랭킹(26)**. 104 + 16 씩 내려온다.
   ⚠ 눈으로 읽는 차례가 위에서 아래다. 그래서 이 줄도 그렇게 읽는다 — 포스터가 맨 위,
     랭킹이 맨 아래다. index.html 의 단추 차례(포스터 · 상점 · 랭킹)와도 같으므로
     탭으로 옮겨 다니는 순서와 보이는 순서가 어긋나지 않는다.
   ⚠ 세 단추의 bottom 은 한 벌로 움직인다. 하나만 고치면 둘이 겹쳐 선다 */
#shop-btn {
  position: absolute; left: 26px; bottom: 146px;
  width: 104px; height: 104px;
  padding: 0; border: none; background: none;
  cursor: pointer; z-index: 5;
  transition: transform .16s ease-out;
}
#shop-btn img { width: 100%; height: 100%; display: block; }
#shop-btn:active, #shop-btn.pressed { transform: scale(.92) rotate(-2deg); }
#shop-btn:focus-visible { outline: 4px solid var(--zest); outline-offset: 4px; border-radius: 14px; }

/* ---------- 감귤나무 단추 : 오른쪽 아래 (2026-09-07) ----------
   누르면 감귤 밭으로 간다. 화방의 T1 이다.

   ⚠ **왼쪽 아래 셋과 한 줄로 세우지 말 것.** 저 셋은 열었다 닫는 판이고
     (보고 나면 지도가 그대로 있다) 이것은 화면을 통째로 바꾼다. 넷째로 붙이면
     몸짓이 같아 보여 "또 하나의 정보 판" 으로 읽힌다 — 자리가 곧 그 물건이 무엇인지다.
   ⚠ 오른쪽 위(감귤 88 · 나침반 74)와 세로로 마주 본다. 그쪽이 나에 관한 것이라면
     이쪽은 트럭에 관한 것이다.
   ⚠ 크기는 104px 로 저 셋과 같다 — 스티커 테 굵기가 그 크기를 전제로 구워져 있다
     (화방 03의 표). 여기만 다르게 잡으면 넷의 테가 화면에서 갈린다.
   ⚠ left 가 아니라 right 26px 이다. 왼쪽 줄과 같은 여백이라 둘이 대칭으로 읽힌다 */
#farm-btn {
  position: absolute; right: 26px; bottom: 26px;
  /* ★ 2026-09-07 밤에 104 → **156px** 로 키웠다 (왼쪽 셋의 1.5배).
     ⚠ 그래서 **스티커 테가 저 셋보다 굵어 보인다.** 테는 그림에 16px 로 구워져 있고
       화면에서 보이는 굵기는 (구운 값 ÷ 배율)이라, 104px 이던 때는 6.9px 이었지만
       156px 에서는 **10.4px** 이 된다. 화방 03절이 "한 조각만 다르게 주면 넷이 한 벌로
       안 읽힌다" 고 한 그 자리를 일부러 벗어난 것이다 — 이것 하나만 크게 두기로 했으므로.
     ⚠ 굵기를 맞추고 싶으면 **그림을 다시 굽는다** : 156px 에서 6px 로 보이려면
       판에서 테 9 · 선 5 다 (화방 T1 의 CANVAS 문단과 03절 표를 같이 고칠 것).
       CSS 로는 못 고친다 — 테가 그림 안에 들어 있다 */
  width: 156px; height: 156px;
  padding: 0; border: none; background: none;
  cursor: pointer; z-index: 5;
  transition: transform .16s ease-out;
}
#farm-btn img { width: 100%; height: 100%; display: block; }
#farm-btn:active, #farm-btn.pressed { transform: scale(.92) rotate(2deg); }   /* 저 셋과 반대로 기운다 */
#farm-btn:focus-visible { outline: 4px solid var(--zest); outline-offset: 4px; border-radius: 14px; }

/* ---------- 배터리 : 왼쪽 위 ----------
   오른쪽 위 감귤 단추(top 26)와 마주 본다. 누르는 것이 아니라 읽는 것이라
   손가락이 닿을 일이 없고, 그래서 지도를 끄는 데 방해가 되면 안 된다 */
#battery {
  position: absolute; left: 26px; top: 26px;
  width: 200px; height: 120px;        /* 그림 400x240 을 반으로 — 화방의 규격 표 */
  z-index: 5;
  pointer-events: none;               /* 지도를 여기서부터 끌어도 잡히지 않는다 */
}
/* 껍데기와 칸 다섯이 **같은 판**이다 (둘 다 400x240). 그래서 자리값이 여기 없다 —
   겹쳐 놓기만 하면 칸이 우물 안에 정확히 앉는다.
   ⚠ 칸을 CSS 로 그리지 않는 까닭은 화방 04절에 적어 두었다 */
#battery i {
  position: absolute; inset: 0;
  background-position: center; background-size: 100% 100%; background-repeat: no-repeat;
}
#battery .shell { background-image: url("../../assets/ui/battery.png?v=1"); }
/* 차는 칸은 불투명도가 천천히 오른다. 그 한 겹이 "지금 차는 중" 이다 —
   ⚠ 여기에 애니메이션을 걸지 말 것. 값은 battery.js 가 1초에 한 번 적는다.
     transition 은 그 계단을 부드럽게 이어 줄 뿐이다 */
#battery .cell { opacity: 0; transition: opacity .9s linear; }
#battery .cell[data-cell="1"] { background-image: url("../../assets/ui/battery-cell-1.png?v=2"); }
#battery .cell[data-cell="2"] { background-image: url("../../assets/ui/battery-cell-2.png?v=2"); }
#battery .cell[data-cell="3"] { background-image: url("../../assets/ui/battery-cell-3.png?v=2"); }
#battery .cell[data-cell="4"] { background-image: url("../../assets/ui/battery-cell-4.png?v=2"); }
#battery .cell[data-cell="5"] { background-image: url("../../assets/ui/battery-cell-5.png?v=2"); }

@media (prefers-reduced-motion: reduce) {
  #shop-btn, #farm-btn { transition: none; }
  #battery .cell { transition: none; }
}

/* 칸이 꺼질 때 — 세 번 깜빡인 뒤에 사라진다 (src/js/battery.js 의 spend).
   ⚠ 차는 것과 몸짓이 달라야 한다. 차는 것은 내내 이어지는 일이라 조용히 밝아지고,
     쓰는 것은 한 번 일어나는 일이라 눈에 걸려야 한다 — 소리 없이 사라지면
     무엇이 줄었는지 못 보고 지나간다.
   ⚠ 시간(0.64초)이 battery.js 의 BLINK_MS 와 같아야 한다. 여기가 길면 다 깜빡이기 전에
     칸이 지워지고, 짧으면 꺼진 뒤에도 한 번 더 번쩍인다 */
@keyframes battery-out {
  0%   { opacity: 1; }
  16%  { opacity: .12; }
  33%  { opacity: 1; }
  49%  { opacity: .12; }
  66%  { opacity: 1; }
  82%  { opacity: .12; }
  100% { opacity: 0; }
}
#battery .cell.out {
  /* ⚠ transition 을 이겨야 한다. .cell 에 걸린 0.9초짜리가 남아 있으면
     깜빡임이 뭉개져 한 번 흐려졌다 마는 것으로 보인다 */
  transition: none;
  animation: battery-out .64s steps(1, end) 1 forwards;
}
@media (prefers-reduced-motion: reduce) {
  /* 깜빡임은 알림이라 없애지 않는다 — 다만 번쩍이지 않게 한 번만 흐려졌다 꺼진다 */
  #battery .cell.out { animation: none; opacity: 0; transition: opacity .5s linear; }
}

/* ---------- 셔터에 붙은 포스터 (2026-09-06, src/js/wall.js) ----------
   발을 들인 도마다 한 장씩 쌓인다. 자리·크기·기울기는 JS 가 이름을 씨앗으로 정한다 —
   여기서는 "종이가 어떻게 생겼나" 만 정하고 어디에 붙는지는 정하지 않는다.
   ⚠ 손잡이(.rail)보다 **뒤**에 온다. DOM 차례가 그것을 정한다 (wall → rail) */
#shutter .wall { position: absolute; inset: 0; pointer-events: none; }
#shutter .wall[hidden] { display: none; }

#shutter .poster {
  position: absolute;
  background-size: 100% 100%; background-repeat: no-repeat;
  transform: rotate(var(--lean, 0deg));
  /* 벽에서 살짝 뜬 종이 — 그림자가 없으면 셔터에 인쇄된 무늬로 보인다.
     ⚠ 짙게 주지 말 것. 종이 한 장이 뜬 높이는 몇 밀리미터다 */
  filter: drop-shadow(0 0.35vw 0.55vw rgba(70, 54, 42, .28));
}

/* 테이프 한 조각. 그림에는 없다 — 붙이는 자리는 코드가 정한다는 규칙 그대로다.
   ⚠ 종이보다 밝고 반투명해야 한다. 불투명하면 종이 위에 얹힌 또 하나의 종이가 된다 */
#shutter .poster b {
  position: absolute; top: -1.1vw;
  width: 22%; height: 2.4vw;
  background: rgba(252, 252, 228, .62);
  border: 0.12vw solid rgba(142, 74, 74, .22);
  transform: rotate(var(--tape, 0deg));
}

/* ================= 감귤 밭 (2026-09-07) =================

   오른쪽 아래 나무 단추를 누르면 초록 셔터가 닫혔다 열리며 여기가 드러난다 (src/js/farm.js).
   **2행 3열 여섯 칸**에 나무를 심고, 아래 메뉴바에서 도구를 끌어다 놓는다.

   ⚠ #scene 과 **같은 층(z-index 10)** 이다. 덱의 세 번째 카드가 아니다 —
     덱은 width 200% 에 화면 둘이 붙어 있고 인트로가 그것을 -50% 민다(deal).
     셋으로 늘리면 그 셈이 통째로 틀어지고, HUD·지도·명소가 전부 #game 안에 매여 있다.
   ⚠ 셔터(#iris, 14)가 이 위를 덮는다 — 그래야 전환이 밭을 가린다.

   ⚠⚠ **자리를 여기서 잡지 않는다.** 여섯 칸의 자리·크기는 src/data/farm.js 의 표가 정하고
     JS 가 %로 환산해 인라인으로 적는다. 여기 있는 것은 그 자리에 무엇이 어떻게 보이느냐다.
     자리를 CSS 로 옮기면 배경 그림의 두둑과 어긋나는데, 그 어긋남은 그림을 다시 뽑을
     때까지 못 고친다.

   ★ 그림이 아직 없다. 배경(#farm-art)·나무(.crop)·도구가 다 들어오기 전까지는
     아래 CSS 밑그림이 그대로 보인다 (트럭 창틀과 같은 대비책). */

#farm {
  position: absolute; inset: 0;
  z-index: 10;                    /* #scene 과 같다. 둘이 동시에 뜨는 일은 없다 */
  overflow: hidden;
  background: #EDE6D2;
  /* 메뉴바가 덮는 띠 — 표(BAR)의 152/820 이다. 여기 한 곳에만 적는다.
     ★ 2026-09-07 밤에 15.85%(130) → 18.54%(152) 로 키웠다. 도구가 이 높이의 몫으로
       커지므로 띠를 키우는 것이 곧 도구를 키우는 것이다 */
  --bar: 18.54%;
  touch-action: none;             /* 도구를 끄는 동안 화면이 같이 밀리면 안 된다 */
}
#farm[hidden] { display: none; }

/* ---------- 그림이 없을 때의 밑그림 ----------
   #scene 의 tone="field" 와 같은 색이다. 같은 색을 쓰므로 밭과 명소가 한 세계로 읽힌다.

   ★ 2026-09-07 에 고쳤다 — 하늘·땅 두 색 띠만 깔았더니 **밭으로 안 보였다.**
     칸이 어디인지도 알 수 없어 화면이 그냥 이상했다. 이제 두둑 여섯을 같이 깐다.
   ⚠ 두둑을 **표(PLOTS)와 같은 자리**에 놓는다. 눈대중으로 두면 진짜 배경이 들어왔을 때
     "밑그림에서는 맞았는데 그림에서는 어긋난다" 가 되어, 무엇이 틀렸는지 알 수 없어진다.
     지름은 화방이 못박은 150x52 · 210x72 을 판(1180x820)으로 나눈 값이다 (반지름).
   ⚠ 이것은 **배경 그림이 들어오면 통째로 덮인다** (#farm-art 가 불투명하고 z-index 1).
     그래서 두 벌로 보일 걱정이 없다 — 예전 쪽지가 그것을 잘못 걱정했다.
   ⚠ 여기 있는 것은 밑그림일 뿐이라 손으로 다듬지 말 것. 고칠 것은 늘 프롬프트 쪽이다 */
#farm::before {
  content: ""; position: absolute; inset: 0;
  /* ⚠ 두둑은 **여기 없다** (2026-09-07 저녁). 칸마다 .bed 한 장으로 따로 놓는다 —
     그래야 물 준 밭이 젖는 것을 보여 줄 수 있고, 자리를 고칠 때 배경을 다시 안 뽑는다.
     여기 두면 진짜 배경이 들어왔을 때도 두둑이 두 벌이 된다 */
  background:
    /* 땅 — 오름보다 **앞**에 깔아 지평선 아래를 덮는다.
       ⚠ 이 순서가 요점이다. 오름을 땅 위에 얹었더니 타원의 아래 절반이 잔디로 내려와
         뒷줄 두둑을 덮었고, 먼 산이 아니라 호수로 보였다 (2026-09-07 에 물렸다).
         땅을 앞에 두면 지평선 위로 솟은 만큼만 남는다 — 그것이 곧 먼 산이다 */
    linear-gradient(transparent 0 46%, #8FA87C 46% 100%),
    /* 먼 오름 둘. 지평선(46%)에 걸쳐 두고 위로 솟은 부분만 보인다 */
    radial-gradient(ellipse 19% 5.2% at 63% 48%, #9C9CB4 0 88%, transparent 90%),
    radial-gradient(ellipse 13% 3.6% at 28% 48%, #9C9CB4 0 88%, transparent 90%),
    /* 하늘 */
    linear-gradient(#EDE6D2 0 100%);
}

/* 배경 한 장. 그림이 없으면 스스로 숨고(onerror) 위의 밑그림이 드러난다.
   ⚠⚠ **cover 가 아니라 fill 이다** (2026-09-07). 여섯 칸의 자리는 이 그림 위의
     백분율인데, cover 로 두면 화면 비율이 그림(1180:820 = 1.44)과 다를 때 그림이
     **잘려서** 두둑이 그 백분율 자리에서 밀려난다. 패드는 기종마다 1.33~1.52 이라
     반드시 어긋난다 — 그것도 실기기에서만 드러난다.
     fill 이면 늘어나는 대신 백분율이 늘 같은 점을 가리킨다. 최대 8% 늘어남인데
     손으로 그린 들판에서는 안 보인다 (가리비 물결처럼 모양이 정해진 것과 다르다) */
#farm-art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: fill; z-index: 1;
}
#farm-art[hidden] { display: none; }

/* ---------- 해와 달 ----------
   기기의 실제 시각을 따라 하늘을 지나간다. 자리와 진하기는 JS 가 매겨 주고
   (src/js/farm.js 의 paintSky), 여기서는 크기와 **이어지는 방식**만 정한다.

   ⚠ 지금은 **꺼져 있다.** 배경에 해가 그려져 있어 그대로 켜면 둘이 된다 —
     assets/farm/sun.png 이 들어오면 #farm[data-art-sky] 가 붙어 켜진다.
   ⚠ 5초마다 다시 앉히는데 한 번에 0.01% 도 안 움직인다. 눈에 안 보이는 것이 맞다 —
     실제 해도 그렇다. 부드럽게 잇는 것은 아래 transition 이 한다. 그래서 **길게** 준다.
   ⚠ transition 에 opacity 를 같이 걸어야 뜨고 지는 것이 이어진다 */
#farm-sky { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
#farm-sky > i {
  position: absolute;
  width: 13%;                       /* 표(SKY.size)와 같은 값 */
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: center / contain no-repeat;
  opacity: 0;
  transition: left 6s linear, top 6s linear, opacity 6s linear;
}
#farm-sky .sun  { background-image: url("../../assets/farm/sun.png?v=1"); }
#farm-sky .moon { background-image: url("../../assets/farm/moon.png?v=1"); }
/* 그림이 없으면 하늘을 통째로 감춘다 — 배경에 그려진 해가 그대로 산다 */
#farm:not([data-art-sky]) #farm-sky,
#farm:not([data-art-sky]) #farm-dusk { display: none; }

/* 땅거미. ⚠ **어둡게 만들지 말 것** — 이 세계의 팔레트가 어두운 색을 금한다.
   밤은 어두운 것이 아니라 차분한 것이다. 진하기는 JS 가 매기고 최댓값은 표에 있다(0.22) */
#farm-dusk {
  position: absolute; inset: 0; z-index: 2;
  background: #9C9CB4;
  mix-blend-mode: multiply;         /* 덧칠이 아니라 그늘로 앉는다 */
  opacity: 0; pointer-events: none;
  transition: opacity 6s linear;
}

/* ---------- 밭 다섯 칸 ----------
   자리는 JS 가 적는다 (left/top/--k). 여기는 크기와 보이는 것만 정한다.
   ⚠ left/top 은 **두둑 한가운데**다. 가로는 가운데를 그 점에 맞추고,
     세로는 밑동이 그 점에 오도록 아래쪽을 잡는다 — 나무는 위로 자란다 */
#farm-field { position: absolute; inset: 0; z-index: 2; }

#farm .plot {
  --k: 1;                      /* 표가 준다 — 앞줄 1, 뒷줄이 작다 */
  --tree: 1;                   /* 표(TREE)가 준다 — 나무만 두둑보다 크게 */
  --fill: 0;                   /* 이 단계가 얼마나 찼나 0~1 */
  /* ⚠ --tree 를 .crop 쪽에 선언하면 **안 먹는다.** 제 몸에 선언한 값이 물려받은 값을
     이기므로, JS 가 칸에 적어 준 값이 무시되고 늘 1 이 된다 (실제로 밟았다).
     --k 와 같은 자리에 두는 것이 맞다 */
  position: absolute;
  /* ⚠ **px 가 아니라 %다.** px 로 두면 화면이 커질 때 자리(left/top, %)만 따라가고
     크기는 그대로여서 배경의 두둑과 어긋난다. 210/1180 · 230/820 을 %로 옮긴 값이다 —
     배경 그림의 판이 바뀌면 이 둘도 같이 고칠 것 */
  width: calc(17.80% * var(--k));
  height: calc(28.05% * var(--k));
  transform: translate(-50%, -76%);
  padding: 0; border: 0; background: none;
  cursor: pointer;
  /* ⚠ 터치 타겟 44px 을 크게 넘긴다 — 가장 작은 뒷줄도 151x166 이다 */
}

/* ---------- 두둑 ----------
   ★ 2026-09-07 저녁에 배경에서 빼내 여기로 옮겼다 (data/farm.js 머리말에 까닭 셋).
     그림 한 장을 칸마다 놓고, 칸이 --k 로 줄어드니 두둑도 같이 줄어든다.

   ⚠ 크기·자리를 눈대중으로 주지 마라. 표에서 셈해 나온 값이다 —
     두둑 알맹이는 앞줄에서 210x72 (ART 1180x820 기준)이고, 그림 판은 그보다 넉넉한
     480x192 라 알맹이가 판의 420x144 를 차지한다. 판을 ART 픽셀로 환산하면
       가로 210 x 480/420 = 240  →  240/1180 = 20.34% (밭 폭)
       세로  72 x 192/144 =  96  →   96/ 820 = 11.71% (밭 높이)
     칸 자체가 밭의 17.80% x 28.05% 이므로 칸 기준으로는
       가로 20.34/17.80 = 114.3%   ·   세로 11.71/28.05 = 41.7%
   ⚠ bottom 은 두둑 **한가운데**가 칸의 24% 자리(= 표의 y, 나무 밑동)에 오게 잡은 값이다 :
     24 - 41.7/2 = 3.15%. .crop 의 밑동과 정확히 같은 높이라야 나무가 흙에 선다 */
#farm .plot .bed {
  position: absolute; left: 50%; bottom: 3.15%;
  width: 114.3%; height: 41.7%;
  transform: translateX(-50%);
  /* ⚠ contain 이 아니라 **늘려 깐다**(100% 100%). 배경이 object-fit: fill 로 늘어나므로
     그 위에 **누워 있는** 것은 같이 늘어나야 흙이 땅에서 뜨지 않는다.
     서 있는 것(.crop 의 나무)은 반대로 제 비율을 지킨다 — 늘어난 나무는 곧 이상해 보인다.
     누운 것은 늘리고 선 것은 지킨다, 가 이 화면의 규칙이다 */
  background: center / 100% 100% no-repeat;
  background-image: url("../../assets/farm/bed.png?v=1");
}

/* ---------- 거름 준 흙 — **표시를 걷어냈다** (2026-09-07 밤) ----------
   비료를 준 나무에 아무 표도 붙이지 않는다. data-fert 는 farm.js 가 그대로 적지만
   그것을 받는 규칙이 여기 없다 — 다시 표시할 일이 생기면 그 자리에 걸면 된다.

   ⚠ 걷어낸 것 둘, 둘 다 화면에서 재 보고 물린 것이다 :
     ① **흙 물들이기** — 1.10/.975 로는 #CA846D → #CA7F67 로 2~3% 밖에 안 움직여
        아예 안 보였고, 짙게 올리니 이번엔 심은 흙 단계의 "젖은 흙" 과 헷갈렸다.
     ② **노란 외곽선** — drop-shadow 넷을 겹쳐 나무 밖에 테를 둘렀다.
        보이기는 잘 보였지만 손그림 세계에서 그 테만 또렷한 벡터 선이라 겉돌았다.
   ⚠ 그래서 비료를 알리는 것은 이제 **메뉴바의 개수(.cnt)와 쪽지의 남은 시간**뿐이다 —
     비료를 주면 그 숫자가 그 자리에서 두 시간 줄어든다. 그것이 곧 영수증이다.
   ⚠ 다시 넣고 싶어지거든 **나무에 덧그리지 말 것.** 나무 그림은 네 단계가 한 자로
     그려진 한 벌이라, 거기에 코드로 무엇을 얹는 순간 그 자만 깨진다 */

/* 그림이 아직 없을 때의 두둑 밑그림.
   ⚠ 그림이 실렸는지는 farm.js 의 probe() 가 재서 #farm[data-art-bed] 를 붙인다.
     background-image 는 onerror 가 없어 CSS 만으로는 알 수 없고, 그냥 두면 밑그림이
     진짜 그림 **위에** 남아 "그림을 넣었는데 화면은 그대로" 가 된다 */
#farm .plot .bed::before {
  content: ""; position: absolute; inset: 12% 6%;
  background: #CC846C;
  border: 0.22vw solid #8E4A4A;
  border-radius: 50%;
}
#farm[data-art-bed] .plot .bed::before { display: none; }

/* 심긴 것. 그림이 들어오면 여기에 붙는다 —
   ⚠ background 라 파일이 없으면 조용히 안 보이고, 놓는 순간 제자리에 나타난다 (img 와 다르다).
   ⚠ 이 단계가 차 오르는 것은 **크기 하나로만** 말한다. 막대도 숫자도 시계도 두지 않는다 —
     화면에 글자를 넣지 않는다는 규칙이고, 여섯 칸에 진행 막대가 여섯이면
     밭이 아니라 계기판이 된다 */
#farm .plot .crop {
  position: absolute; left: 50%; bottom: 24%;
  /* ⚠ **두둑이 아니라 나무만 커진다.** 칸(= 두둑)은 그대로 두고 이 판만 --tree 배로
     키운다. 칸까지 키우면 밭이 통째로 커져 다섯이 화면에 안 들어간다.
     아랫변이 bottom: 24% 에 붙어 있으므로 커지는 것은 **위로**다 — 밑동은 안 움직인다 */
  width:  calc(100% * var(--tree));
  height: calc(100% * var(--tree));
  background: center bottom / contain no-repeat;
  transform: translateX(-50%) scale(calc(.86 + .14 * var(--fill)));
  transform-origin: 50% 100%;
  transition: transform .3s ease-out;
  /* ⚠⚠ **손끝을 가로채지 않는다.** 나무가 칸보다 크므로 잎이 칸 밖으로 넘치는데,
     그대로 두면 앞줄 나무의 잎이 뒷줄 두둑 위를 덮어 **뒤엣것을 못 누른다.**
     누르는 것은 나무가 아니라 **두둑**이다 — 칸 자체가 그 자리다 */
  pointer-events: none;
}
#farm .plot[data-stage="sown"]    .crop { background-image: url("../../assets/farm/stage-sown.png?v=1"); }
#farm .plot[data-stage="sprout"]  .crop { background-image: url("../../assets/farm/stage-sprout.png?v=1"); }
#farm .plot[data-stage="sapling"] .crop { background-image: url("../../assets/farm/stage-sapling.png?v=1"); }
#farm .plot[data-stage="fruit"]   .crop { background-image: url("../../assets/farm/stage-fruit.png?v=1"); }

/* 밑그림 — 그림이 없어도 무엇이 심겼는지는 알아야 한다.
   ⚠⚠ **.crop 이 이 위를 덮지 못한다.** .grow 가 DOM 에서 뒤라 위에 그려지기 때문이다.
     그래서 farm.js 의 probe() 가 자람 그림이 실렸는지 재서 #farm[data-art-stage] 를
     붙이고, 그때 이 밑그림을 걷는다. 예전 쪽지가 "덮는다" 고 적었던 것은 틀렸다 */
/* ⚠ bottom 은 .crop 과 **같은 24%** 여야 한다. 26% 로 두었더니 밑그림이 두둑 한가운데보다
   살짝 위에 떠서, 진짜 그림이 들어오면 자리가 미묘하게 달라진다 */
#farm .plot .grow {
  /* 밑그림도 같은 배로 — 그림이 없을 때만 보이지만 자리는 같아야 한다 */
  transform-origin: 50% 100%;
  position: absolute; left: 50%; bottom: 24%;
  pointer-events: none;
  transform: translateX(-50%);
  background: #8FA87C;
  border: 3px solid #8E4A4A;
  border-radius: 46% 54% 44% 56% / 52% 56% 44% 48%;
  width: 0; height: 0; opacity: 0;
  transition: width .3s ease-out, height .3s ease-out, opacity .2s;
}
#farm .plot[data-stage="sown"]    .grow {
  width: 30%; height: 9%; opacity: 1;
  background: #CC846C; border-radius: 50%;    /* 갓 덮은 흙 한 무더기 */
}
#farm .plot[data-stage="sprout"]  .grow { width: 22%; height: 16%; opacity: 1; }
#farm .plot[data-stage="sapling"] .grow { width: 52%; height: 40%; opacity: 1; }
#farm .plot[data-stage="fruit"]   .grow { width: 78%; height: 60%; opacity: 1; }
#farm[data-art-stage] .plot .grow { display: none; }

/* ---------- 물을 기다리는 칸 ----------
   ★ 2026-09-07 밤 : 물이 시계를 켜는 것으로 바뀌었다. 심기만 하고 물을 안 주면
     흙만 덮인 채 가만히 있는데, 가만히 있는 것과 **자라는 중인 것이 똑같이 보이면**
     플레이어는 밭이 고장난 줄 안다.
   ⚠ 그래서 아주 얕게 숨 쉰다. 다 자란 것(.ripe)과 **다른 몸짓**이라야 한다 —
     저쪽은 크기가 오르내리고 이쪽은 흐려졌다 짙어진다. 둘 다 크기로 하면 구별이 안 된다.
   ⚠ 세게 하지 말 것. 다섯 칸이 한꺼번에 깜박이면 밭이 시끄러워진다
   ★★ 2026-10-07 : **흐려졌다 짙어지는 것을 걷어냈다.** 0.72 까지 흐려지는 흙무더기는
     "물을 기다린다" 가 아니라 **반쯤 투명한 유령**으로 읽혔다 — 심긴 건지 안 심긴 건지,
     고장인지 연출인지 헷갈렸다. 투명은 뜻이 하나로 안 읽히는 신호다.
     이제 둘로 말한다 —
       ① 흙은 **늘 불투명**이다. 물 주기 전엔 **마른 흙**(옅고 바랜 색), 물을 주면 그림 그대로의
          **젖은 흙**(짙은 #9A5353)이 된다. 물을 주는 순간 색이 짙어지는 것이 곧 "줬다" 의 대답이다
       ② 그 위에 **물조리개가 통통 뛴다** (도구 그림 tool-water.png 그대로). 아이콘이 있으면
          할 일이 있다는 뜻이다 — 쪽지(#farm-tip)와 같은 규칙이고, 끌어다 놓을 그 도구 그림이라
          무엇을 하라는지도 정확하다
   ⚠ 다 자란 것(.ripe)은 나무가 숨 쉬고 이쪽은 **아이콘이** 뛴다 — 몸짓이 겹치지 않는다
   ⚠ 마른 흙을 필터로 만든다(그림을 새로 뽑지 않는다). ⚠⚠ **밝게 하지 말 것.** 밝기를 올려 봤더니
     두둑(#CA846D)과 가까워져 흙무더기가 **다시 비쳐 보였다** — 두둑보다 옅은 흙은 투명한 것으로
     읽힌다. 짙기는 그대로 두고 **채도만** 빼서 "바랜 흙" 으로 가른다 */
#farm .plot.waiting .crop { filter: saturate(.5) brightness(.97); }
#farm .plot .crop { transition: transform .3s ease-out, filter .5s ease-out; }
#farm .plot.waiting::after {
  content: ""; position: absolute; left: 50%; bottom: 42%;
  width: 52%; aspect-ratio: 1;
  background: url("../../assets/farm/tool-water.png?v=1") center / contain no-repeat;
  transform: translateX(-50%);
  animation: farm-thirsty 1.4s ease-in-out infinite;
  pointer-events: none;
  z-index: 2;
}
@keyframes farm-thirsty {
  0%, 100% { transform: translate(-50%, 0)   rotate(-6deg); }
  50%      { transform: translate(-50%, -18%) rotate(6deg); }
}

/* ---------- 도구에 붙는 개수 ----------
   ★ 2026-09-07 밤 : 씨앗과 비료가 **셀 수 있는 물건**이 되었다. 몇 개 남았는지
     도구 위에 숫자로 붙는다 (물은 공짜라 안 붙는다 — 표의 kind 가 그것을 가른다).

   ⚠ **숫자만 쓴다.** 이 게임은 화면에 글자를 넣지 않으므로 "3개" 가 아니라 "3" 이다.
   ⚠ 0 이면 도구가 **흐려지되 사라지지 않는다.** 감추면 밭에 그런 도구가 있다는
     사실 자체가 사라진다 — 상점에서 사 올 생각도 못 하게 된다.
   ⚠ 뱃지를 도구 **밖으로** 살짝 내민다. 안에 넣으면 그림 위에 숫자가 얹혀
     무엇을 그린 도구인지가 흐려진다 */
#farm .tool { position: relative; overflow: visible; }
#farm .tool .cnt {
  position: absolute; right: -8%; bottom: -6%;
  min-width: 34%; padding: 0 4%;
  background: #FCFCE4;
  border: 0.16vw solid #8E4A4A;
  border-radius: 999px;
  font-family: "Jua", sans-serif; font-size: 1.5vw; line-height: 1.5;
  color: #8E4A3C; text-align: center;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}
/* 다 썼다 — 도구도 숫자도 같이 흐려진다. 숫자만 남으면 "0 인데 왜 밝지" 가 된다 */
#farm .tool.out { opacity: .38; }

/* ---------- 딴 순간 ----------
   ★ 2026-09-07 밤 : 다 자란 나무를 누르면 딴다. 그 순간 나무가 한 번 흔들린다.
   ⚠ 손끝에 읽혀야 한다 — 숫자만 뜨고 나무가 가만히 있으면 "눌러서 얻었다" 가 아니라
     "어디선가 숫자가 나왔다" 가 된다. 이 저장소가 조형물·셔터 손잡이에서 지켜 온 규칙이다.
   ⚠ 나무는 곧바로 어린 나무로 바뀌므로 흔들림은 **짧아야** 한다. 길면 바뀌는 것과 겹쳐
     무엇이 일어났는지 흐려진다 */
#farm .plot.picked .crop { animation: farm-picked .34s cubic-bezier(.34,1.4,.64,1); }
@keyframes farm-picked {
  0%   { transform: translateX(-50%) scale(1); }
  35%  { transform: translateX(-50%) scale(.93) rotate(-1.5deg); }
  100% { transform: translateX(-50%) scale(1); }
}

/* 끌어온 도구가 지금 이 칸 위에 있다 */
#farm .plot.over {
  outline: 4px dashed rgba(142, 74, 74, .55);
  outline-offset: -6px; border-radius: 18px;
}

/* 다 자랐다 — 아주 얕게 숨 쉰다.
   ⚠ 여섯이 한꺼번에 뛰면 밭이 시끄러워지므로 크기만 1% 오르내린다.
     눈에 걸리되 방해가 되지 않는 세기다 */
#farm .plot.ripe .crop { animation: farm-ripe 2.6s ease-in-out infinite; }
@keyframes farm-ripe {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50%      { transform: translateX(-50%) scale(1.012); }
}

/* 안 되는 것을 놓았다 — 한 번 흔든다.
   ⚠ 아무 반응이 없으면 "게임이 멈췄다" 로 읽힌다 (셔터 손잡이 .hint 와 같은 생각) */
#farm .plot.nope { animation: farm-nope .3s ease-out; }
@keyframes farm-nope {
  0%, 100% { transform: translate(-50%, -76%) rotate(0deg); }
  30%      { transform: translate(-53%, -76%) rotate(-2.5deg); }
  70%      { transform: translate(-47%, -76%) rotate(2.5deg); }
}

/* ---------- 아래 메뉴바 ----------
   ⚠ 높이는 --bar 하나로 정한다. 배경 그림이 그만큼 덮이는 것을 알고 그려져 있으므로
     (표의 BAR = y 690~820) 여기를 고치면 배경도 다시 뽑아야 한다 */
#farm-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--bar);
  display: flex; align-items: center; justify-content: center;
  gap: 5vw;                       /* 도구가 커진 만큼 사이도 벌린다 */
  z-index: 3;
  /* 그림이 없을 때의 밑그림 — 밋밋한 테라코타 띠에 윤곽선 한 줄 */
  background: #CC846C;
  border-top: 0.34vw solid #8E4A4A;
}
/* ⚠ 그림은 **늘려 깐다**(100% 100%). 판이 2000x220 (9.09:1)이고 띠는 화면 폭 x --bar
   (1180x130 = 9.08:1)라 비율이 이미 맞는다 — contain 으로 두면 좌우에 빈 자리가 남는다.
   ⚠ 그림에 제 윤곽선이 들어 있으므로 CSS 테두리는 걷는다. 안 걷으면 선이 두 줄이 된다 */
#farm[data-art-bar] #farm-bar {
  background: center / 100% 100% no-repeat url("../../assets/farm/bar.png?v=1");
  border-top: none;
}

#farm .tool {
  /* ⚠ **높이로 잡는다.** 폭을 calc(var(--bar) * .78) 로 줬다가 물렸다 (2026-09-07) —
     --bar 는 백분율이고 width 의 %는 **띠의 높이가 아니라 부모의 폭**을 따른다.
     그래서 도구가 화면 폭의 12% 짜리로 부풀어 띠 밖으로 넘쳐 나왔다.
     높이의 %는 띠의 높이를 따르므로, 높이를 잡고 aspect-ratio 로 폭을 따라오게 한다.
     ★ 2026-09-07 밤에 78% → 86% 로 키웠다. 띠(--bar)도 같이 커졌으므로 도구는
       화면에서 101 → 131 art-px 이 된다.
     ⚠ 90% 를 넘기지 말 것 — 띠 위아래에 여백이 없으면 도구가 널에 낀 것으로 보인다 */
  height: 86%;
  aspect-ratio: 1;
  padding: 0; background: none;
  cursor: grab; touch-action: none;
  transition: transform .12s ease-out;
  /* 그림이 아직 없을 때의 밑그림 — 모양만 서로 다르게 해 둔다.
     그림이 들어오면 그 위를 img 가 덮는다 */
  background: #FCFCE4;
  border: 0.3vw solid #8E4A4A;
}
/* ⚠ [hidden] 을 **다시 재워야** 한다. display:block 이 브라우저 기본값의
   hidden{display:none} 을 이기므로, 그림이 없어 onerror 로 숨긴 img 가 그대로 남아
   깨진 그림 아이콘이 보인다 (실제로 그렇게 나왔다) */
#farm .tool img { width: 100%; height: 100%; display: block; }
#farm .tool img[hidden] { display: none; }
#farm .tool[data-tool="seed"]  { border-radius: 8% 8% 12% 12%; }
#farm .tool[data-tool="water"] { border-radius: 46% 20% 20% 20%; background: #9C9CB4; }
#farm .tool[data-tool="fert"]  { border-radius: 12%; background: #CC846C; }
/* ⚠ 그림이 오면 밑그림을 **반드시** 걷는다. 도구 그림은 바깥이 투명이라, 안 걷으면
   크림 상자와 테두리가 그림 뒤로 비쳐 도구마다 네모 판을 깔고 있는 꼴이 된다 */
#farm[data-art-tool] .tool {
  background: none; border: 0; border-radius: 0;
}
#farm .tool:active, #farm .tool.pressed { transform: scale(.94); }
#farm .tool.held  { transform: scale(.88); opacity: .45; }   /* 집어 든 동안 자리를 지킨다 */
#farm .tool:focus-visible { outline: 3px solid var(--zest); outline-offset: 4px; }

/* 끌고 다니는 유령.
   ⚠ 도구 자체를 옮기지 않는 까닭 — 옮기면 메뉴바에 빈 구멍이 생겨
     무엇을 집었는지 되레 흐려진다 */
#farm-ghost {
  position: absolute; width: 96px; height: 96px;
  margin: -48px 0 0 -48px;          /* 손끝이 가운데에 온다 */
  pointer-events: none; z-index: 6;
  background: center / contain no-repeat;
  opacity: .92;
}
#farm-ghost[hidden] { display: none; }
#farm-ghost[data-tool="seed"]  { background-image: url("../../assets/farm/tool-seed.png?v=1"); }
#farm-ghost[data-tool="water"] { background-image: url("../../assets/farm/tool-water.png?v=1"); }
#farm-ghost[data-tool="fert"]  { background-image: url("../../assets/farm/tool-fert.png?v=1"); }

/* ---------- 돌아가기 ----------
   ★ 2026-10-07 : 오른쪽 → **왼쪽 아래**로 옮겼다 (팀장 지시). 지도의 단추 줄이 왼쪽에 서므로
     "나가는 단추는 왼쪽" 으로 한 벌이 된다. 뒤로 가기 화살표(‹)가 가리키는 쪽과도 맞는다.
     ⚠ 옛 쪽지의 "나무 단추와 같은 오른쪽" 은 물렸다
   ⚠ 메뉴바 위로 올려 앉힌다 — 아래에 두면 띠에 가린다 */
#farm-back {
  position: absolute; left: 34px; bottom: calc(var(--bar) + 26px);
  width: 88px; height: 88px;
  padding: 0; border: 0; background: none;
  cursor: pointer; z-index: 4;
  transition: transform .12s ease-out;
}
#farm-back svg { display: block; width: 100%; height: 100%; }
#farm-back:active, #farm-back.pressed { transform: scale(.92); }
#farm-back:focus-visible { outline: 3px solid var(--zest); outline-offset: 4px; border-radius: 50%; }

@media (prefers-reduced-motion: reduce) {
  #farm-back, #farm .tool, #farm .plot .crop, #farm .plot .grow { transition: none; }
  #farm .plot.ripe .crop, #farm .plot.nope, #farm .plot.waiting::after { animation: none; }
}

/* ---------- 남은 시간 쪽지 (2026-09-07 밤) ----------
   칸을 누르면 그 두둑 앞에 잠깐 뜬다 : **다음에 할 일 하나 + 남은 시간**.

   ★★ 2026-09-07 밤 : 생김새를 **지도의 이름표와 같게** 맞췄다 (#map-props .tag).
     나무 테에 크림 판을 끼우고 아래로 말뚝이 꽂힌 그 짜임 그대로다.
     ⚠ 알약꼴로 따로 만들었던 것을 물렸다 — 같은 게임 안에서 "잠깐 뜨는 쪽지" 가
       화면마다 다른 모양이면, 그것이 같은 종류의 것이라는 사실이 안 읽힌다.
     ⚠ 값(색·테 굵기·모서리·말뚝)을 여기서 새로 지어내지 말 것. 지도 쪽을 고치면
       이쪽도 같이 고쳐야 한다 — 두 곳에 있는 값이다.
     ⚠ 다만 크기는 밭에 맞춰 vw 로 준다. 지도 이름표는 px 인데(지도가 확대·축소되므로
       화면 기준으로 붙들어야 한다) 밭은 통째로 늘어나는 화면이라 vw 가 맞다.

   ⚠⚠ **두둑 앞자락에 앉는다 — 위가 아니다.** 나무는 두둑에서 위로 자라므로 위에 띄우면
     반드시 나무를 가린다. 자리는 JS 가 두둑을 재서 **백분율**로 적는다 —
     px 로 박으면 패드를 돌렸을 때 쪽지만 옛 자리에 남는다.
   ⚠ **늘 띄우지 않는다.** 다섯 칸에 막대가 다섯이면 밭이 아니라 계기판이 된다.
   ⚠ 글자를 쓰지 않는다 — 숫자와 아이콘뿐이다. */
#farm-tip {
  position: absolute; z-index: 4;
  /* 지도 이름표와 같은 나무 테 */
  padding: 0.28vw;                       /* 이 여백이 곧 나무 테의 두께다 */
  background: var(--wood);
  border: 0.28vw solid var(--wood-line);
  border-radius: 1.4vw;
  box-shadow: 0 0.28vw 0 rgba(142, 74, 74, .20);
  pointer-events: none;                  /* 다음 손끝을 막지 않는다 */
  opacity: 0;
  transform: translate(-50%, -0.5vw);
  transition: opacity .2s ease-out, transform .2s ease-out;
}
#farm-tip[hidden] { display: none; }
/* 살짝 내려앉으며 나타난다 — 두둑에서 피어오르는 것이 아니라 두둑에 놓이는 것이다 */
#farm-tip.on { opacity: 1; transform: translate(-50%, 0); }

/* 안쪽 크림 판 — 나무 상자에 끼운 이름표처럼 보인다 (지도 쪽과 같다) */
#farm-tip .plate {
  display: flex; align-items: center; gap: 0.42vw;
  padding: 0.18vw 0.9vw 0.3vw 0.5vw;
  background: #FBF3DE;
  border-radius: 0.95vw;
  font-family: "Jua", sans-serif;
  font-size: 1.5vw; line-height: 1.3; white-space: nowrap;
  font-variant-numeric: tabular-nums;
  color: #E07B1E;                        /* 숫자는 감귤빛 — 눈이 먼저 간다 */
}
#farm-tip b { font-weight: 400; }
#farm-tip .t[hidden] { display: none; }

/* ⚠ **말뚝(::after)은 걷어냈다** (2026-09-07 밤). 지도 이름표를 그대로 옮겨 오면서
     같이 왔는데, 여기서는 두 가지가 어긋났다 —
     ① 이 쪽지는 두둑 **앞자락**에 앉는다. 땅에 꽂힌 것이 아니라 두둑에 기대 놓인 것이라
        말뚝이 꽂힐 흙이 그 아래에 없다. 지도 이름표는 땅 위에 서 있어 말뚝이 맞다.
     ② 그래서 획 하나가 허공으로 삐져나와 **숫자 1 처럼** 읽혔고, 앞줄 칸에서는
        메뉴바의 노란 띠를 넘어갔다.
   ⚠ 지도 쪽(#map-props .tag)의 말뚝은 그대로 둔다 — 거기서는 맞는 물건이다.
     이 둘은 이제 나무 테와 크림 판까지만 같다 */

/* 아이콘. 도구 그림을 그대로 줄여 쓴다 — 메뉴바에서 집어 올 그것과 같은 그림이라야
   "저것을 끌어다 놓아라" 가 설명 없이 읽힌다 */
#farm-tip .act {
  width: 2vw; height: 2vw; flex: none;
  background: center / contain no-repeat;
}
#farm-tip[data-act="water"] .act { background-image: url("../../assets/farm/tool-water.png?v=1"); }
#farm-tip[data-act="fert"]  .act { background-image: url("../../assets/farm/tool-fert.png?v=1"); }
#farm-tip[data-act="pick"]  .act { background-image: url("../../assets/gyul.svg?v=20"); }
/* 할 일이 없으면 아이콘 자리를 아예 없앤다 — 빈 칸이 남으면 쪽지가 한쪽으로 쏠린다 */
#farm-tip.bare .act { display: none; }
#farm-tip.bare .plate { padding-left: 0.9vw; }

/* 방금 땄다 — 숫자가 개수다. 한 번 톡 튀어 "얻었다" 로 읽히게 한다 */
#farm-tip.got { animation: farm-got .4s cubic-bezier(.34,1.5,.64,1); }
@keyframes farm-got {
  0%   { transform: translate(-50%, 0) scale(.7); }
  55%  { transform: translate(-50%, -0.5vw) scale(1.12); }
  100% { transform: translate(-50%, 0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  #farm-tip { transition: none; }
  #farm-tip.got { animation: none; }
}


/* ================= 날씨 (2026-09-07 밤) =================
   오늘 제주에 진짜로 비가 오면 밭에도 비가 온다. 그리는 것은 src/js/sky.js 가
   두 화면(밭 · 지도)에 붙이는 층 하나뿐이고, 여기가 그 생김새다.

   ⚠⚠ **어둡게 만들지 않는다.** 공통 스타일이 "밝기 80% · 채도 20% · 어두운 색 금지" 다.
     비 오는 날을 진짜로 어둡게 칠하면 그 화면만 다른 게임이 된다 —
     여기서 비는 어두운 것이 아니라 **차분한 것**이다 (밤을 그렇게 다룬 것과 같다).
     번개·먹구름·검은 하늘을 넣지 말 것. 큰비(storm)도 그냥 비가 더 많은 것이다.
   ⚠ 색은 팔레트 안에서만 — 회보라 #9C9CB4(구름·비) · 크림 #FCFCE4(안개·눈).
   ⚠ 움직이는 것은 **transform 뿐이다.** top·left 를 애니메이션하면 매 프레임 배치를
     다시 셈한다 — 화면을 통째로 덮는 층이라 그 값이 크다 (이 저장소의 규칙)
   ⚠ 손을 막지 않는다(pointer-events: none). 비가 오는 날 밭을 못 만지면 안 된다 */
.wx {
  position: absolute; inset: 0;
  overflow: hidden;                    /* 방울이 화면 밖으로 새지 않게 */
  pointer-events: none;
}

/* 한 겹 덮는 색 — 진하기는 sky.js 의 표(LOOK)가 준다. 색은 늘 크림 하나다.
   ⚠⚠ **곱하기(multiply)를 쓰지 않는다** (2026-09-07 밤에 물렸다). 처음에 비·흐림을
     회보라 #9C9CB4 곱하기로 덮었는데, 곱하기는 **반드시 밝기를 깎는다** —
     크림 하늘(252,252,228)이 236,236,217 로 6% 어두워졌고 큰비는 10% 였다.
     그것이 그대로 **저녁**으로 읽혔다. 게다가 배경 그림에 해가 그려져 있어
     "해는 떴는데 어둑하다" 가 되어 더 이상했다.
   ⚠ 밝기를 깎지 않고 날씨를 말하는 길은 **뿌옇게 하는 것**이다. 크림을 덧칠하면
     밝기가 오히려 조금 오르고 색만 빠진다 — 비 오는 날의 공기가 실제로 그렇다.
     이 세계에서 비는 어두운 것이 아니라 **차분한 것**이라는 규칙과도 이제 맞는다.
   ⚠ 그래서 섞는 법이 **하나뿐이다.** 전에는 둘이었고(크림은 덧칠 · 회보라는 그늘)
     data-mix 로 갈랐는데, 그 갈래가 곧 밤티의 원인이었다. 되살리지 말 것 */
.wx::before {
  content: "";
  position: absolute; inset: 0;
  background: #FCFCE4;
  opacity: var(--wx-veil, 0);
  transition: opacity .8s ease-out;    /* 날씨가 툭 바뀌지 않는다 — 하늘이 그렇듯 서서히 */
}

/* 떨어지는 것 하나. 자리(--x) · 크기(--s) · 빠르기(--t) · 지연(--d)은 JS 가 흩는다.
   ⚠ **음수 지연**이라 이미 떨어지던 중간에서 시작한다. 0 부터면 서른네 방울이
     한 줄로 나란히 떨어져 비가 아니라 발이 된다 */
.wx .d {
  position: absolute;
  left: var(--x); top: -8vh;
  animation: wx-rain var(--t) linear var(--d) infinite;
}

/* 비 — 회보라 획. ⚠ 가늘고 옅게 둘 것. 굵으면 손그림 세계에서 그 획만 또렷한
   벡터 선이 되어 겉돈다 (비료 노란 테두리를 그래서 걷어냈다) */
.wx[data-sky="rain"] .d,
.wx[data-sky="storm"] .d {
  width: 0.22vw;
  height: calc(2.6vw * var(--s));
  border-radius: 1vw;
  background: #9C9CB4;
  opacity: .5;
}
.wx[data-sky="storm"] .d { opacity: .6; }

/* 눈 — 크림 알갱이. 느리고, 옆으로 흔들린다.
   ⚠⚠ **테두리가 있어야 한다.** 처음엔 크림 동그라미만 두었더니 하늘 위에서 통째로
     사라졌다 — 하늘도 크림(#FCFCE4)이라 같은 색끼리 겹친 것이다. 땅(초록) 위에서만
     보여서 "눈이 밭에만 내린다" 가 됐다 (화면에서 확인했다).
     테두리는 이 세계가 원래 하는 일이라 덧붙인 것이 아니다 — 여기서는 모든 것이
     윤곽선을 두른다. 색도 그 윤곽색(#8E4A4A)을 옅게 쓴다.
   ⚠ 진하게 두지 말 것. 알갱이가 손톱만 하므로 조금만 진해도 검은 점이 흩뿌려진 것으로
     보인다 — 팔레트의 "어두운 색 금지" 를 화면에서 어기는 셈이 된다 */
.wx[data-sky="snow"] .d {
  width: calc(0.7vw * var(--s));
  aspect-ratio: 1;
  border-radius: 50%;
  background: #FCFCE4;
  box-shadow: 0 0 0 0.09vw rgba(142, 74, 74, .34);
  opacity: .92;
  animation-name: wx-snow;
  animation-timing-function: ease-in-out;
}

/* 비는 조금 기울어 곧게 떨어진다. 기울기를 두 키프레임에 **다 적는다** —
   한쪽에만 적으면 떨어지는 내내 획이 제자리로 돌아가며 돈다 */
@keyframes wx-rain {
  from { transform: translate3d(0, 0, 0) rotate(11deg); }
  to   { transform: translate3d(-4vh, 118vh, 0) rotate(11deg); }
}
/* 눈은 좌우로 흔들리며 내린다 — 곧게 떨어지면 눈이 아니라 흰 비다 */
@keyframes wx-snow {
  from { transform: translate3d(0, 0, 0); }
  33%  { transform: translate3d(2.2vw, 39vh, 0); }
  66%  { transform: translate3d(-1.8vw, 78vh, 0); }
  to   { transform: translate3d(1vw, 118vh, 0); }
}

/* 층이 어디에 끼는가 — 화면마다 다르다. 차례가 곧 층이다.
   ⚠ 밭 : 두둑·나무보다 앞이되 **메뉴바(3)보다는 뒤**다. 띠는 "내 손 앞" 이라 젖지 않는다
   ⚠ 지도 : 지도·조형물보다 앞이되 **종이 결(4)보다는 뒤**다. 날씨는 그려진 세계의 것이고
     종이 결은 그것이 인쇄된 종이이며 HUD(5)는 그 위에 붙인 스티커다 — 그 차례를 지킨다 */
#farm > .wx { z-index: 2; }
#game > .wx { z-index: 3; }

/* ⚠ 움직임을 줄이는 설정에서는 **떨어지는 것만** 멈춘다. 덮는 색은 남긴다 —
   그것까지 지우면 비 오는 날과 맑은 날이 똑같아져 날씨가 아예 없는 것이 된다 */
@media (prefers-reduced-motion: reduce) {
  .wx .d { display: none; }
  .wx::before { transition: none; }
}

/* ================= 상점 (2026-09-07 밤) =================

   왼쪽 아래 가게 단추를 누르면 열린다 (src/js/shop.js). 창이 둘 —
   **자원**(코인으로 감귤·에너지·비료) · **광고**(보고 코인 받기).

   ⚠ 선반(#shelf)·랭킹판(#ranks)·나(#me)와 **같은 층**(z-index 13)이고 같은 몸짓이다.
   ⚠⚠ 판이 **랭킹 게시판과 같은 크기**다 (그림 1400x840 → 화면 1000x600). 둘 다 어둑한 막
     위에 서는 판이라, 크기나 스티커 테가 다르면 서로 다른 게임에서 온 것으로 보인다.
     ⚠ 그래서 크기를 **px 로 못박는다.** 테가 그림 안에 구워져 있어 화면 크기가 곧
       테 굵기다 — %로 두면 기종마다 테가 달라진다 (지도 오브젝트 화방 03절).
   ⚠ 글자 자리는 **그림 좌표(1400x840)의 백분율**로 잡는다 :
     머리 띠 y 60~190 · 크림 우물 x 90~1310 · y 220~750 (2026-10-07 에 780 → 750).
     판을 다시 뽑으면 이 셋을 같이 맞출 것.
   ⚠ 살 수 없는 것은 **감추지 않고 흐리게** 둔다. 감추면 무엇을 파는 곳인지가 사라진다
     (밭의 비료 도구와 같은 생각). */

#shop {
  position: absolute; inset: 0;
  z-index: 13;
  background: rgba(70, 54, 42, .40);
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
  transition: opacity .24s ease-out;
}
#shop[hidden] { display: none; }
#shop.on { opacity: 1; }

/* 판 — 그림 한 장. 랭킹 게시판과 같은 크기다.
   ★ 2026-10-07 : 1000x600 → **1120x672** 로 키웠다 (팀장 지시). 랭킹판과 같은 식으로 바꿨다 —
     ⚠ 둘 중 하나만 고치지 말 것. 테가 그림에 구워져 있어 크기가 다르면 테 굵기가 갈린다 */
#shop .board {
  position: relative;
  width: min(1160px, 98vw); aspect-ratio: 1400 / 840;
  /* ★ 2026-10-07 : **랭킹판과 같은 그림**을 쓴다 (팀장 지시). 그림·여는 몸짓·가운데 제목이
     #ranks .board 와 같다. shop-board.png 는 지우지 않았고 안 쓴다 */
  background: url("../../assets/ui/ranking-board.png?v=6") center/100% 100% no-repeat;
  font-family: "Jua", sans-serif; color: #8E4A3C;
  transform: translateY(14px) scale(.96);
  transition: transform .24s ease-out;
}
#shop.on .board { transform: none; }

/* ---------- 머리 띠 ---------- (그림의 y 60~190 = 7.14%~22.6%) */
#shop .head {
  /* 오른쪽을 비워 둔다 — 닫기 단추(.x-close)가 판의 귀퉁이에 따로 선다 */
  position: absolute; left: 6.4%; right: 12.5%; top: 7.14%; height: 15.5%;
  display: flex; align-items: center; gap: 1.4%;
}
/* 제목은 랭킹판처럼 머리 띠 **한가운데**다. 코인은 오른쪽(닫기 단추 앞)에 남는다 */
/* ⚠ left 는 50% 가 아니다 — 머리 띠(.head)가 판의 6.4%~87.5% 라서, **판의** 한가운데는
   띠 안에서 (50 - 6.4) / 81.1 = 53.76% 다. 띠를 고치면 이 값도 다시 셈할 것 */
#shop .head .ttl {
  position: absolute; left: 53.76%; top: 50%; transform: translate(-50%, -50%);
  font-size: 34px; color: var(--wood-line);
}
#shop-coins { margin-left: auto; }
#shop-coins { display: flex; align-items: center; gap: 8px; font-size: 34px; color: #8E4A3C;
              font-variant-numeric: tabular-nums; }
.coin {
  width: 38px; height: 38px; flex: none;
  background: center / contain no-repeat url("../../assets/shop/shop-coin.png?v=1");
}
/* ---------- 닫기 (상점 · 랭킹 공용) ----------
   ★ 2026-10-07 : 둘을 **한 단추**로 맞췄다. 상점은 머리 띠 안에 코인과 붙어 있었고 랭킹판은
     아예 없었다. 이제 둘 다 판의 **오른쪽 위 귀퉁이**, 머리 띠 한가운데 높이에 같은 모양으로 선다.
   ⚠ 자리는 그림 좌표다 — 머리 띠 y 60~190 의 한가운데(15.48%)에 단추 한가운데를 맞춘다.
     translate 로 맞추지 않는다 — 눌림(scale)과 transform 이 부딪혀 눌릴 때 자리가 튄다
   ⚠ X 획은 가늘게(3px), 테는 단추보다 굵지 않게. 굵으면 판의 테와 다투어 지저분해 보였다
   ⚠ 닫기에도 눌림을 준다. 여기만 반응이 없으면 "닫히나?" 하고 두 번 누르게 된다 */
.x-close {
  position: absolute; right: 6.4%; top: calc(15.48% - 27px);
  width: 54px; height: 54px; padding: 0; cursor: pointer; z-index: 2;
  background: #FCFCE4; border: 3px solid #8E4A4A; border-radius: 50%;
  box-shadow: 0 2px 0 rgba(142, 74, 74, .25);
  transition: transform .12s ease-out;
}
.x-close::before, .x-close::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 22px; height: 3px; margin: -1.5px 0 0 -11px;
  background: #8E4A4A; border-radius: 2px;
}
.x-close::before { transform: rotate(45deg); }
.x-close::after  { transform: rotate(-45deg); }
.x-close:active, .x-close.pressed { transform: scale(.9); }
.x-close:focus-visible { outline: 3px solid var(--zest); outline-offset: 3px; }

/* ---------- 창 둘 ----------
   ★ 2026-10-07 : **크림 우물 안으로 들였다.** 전에는 우물 윗가장자리에 걸터앉아(top 24.5%)
     테라코타 테와 크림 우물에 반씩 걸쳤고, 그래서 판 위에 겹쳐 붙은 것처럼 보였다.
     우물은 y 220~750 (26.19%~89.29%) · x 90~1310 이다 — 그 안쪽으로 넉넉히 들인다 */
#shop .tabs { position: absolute; left: 9%; top: 30%; display: flex; gap: 12px; }
#shop .tab {
  font-family: "Jua", sans-serif; font-size: 26px; color: #8E4A3C;
  padding: 6px 26px; cursor: pointer;
  background: #EDE2C8; border: 4px solid #8E4A4A; border-radius: 999px;
  transition: background .16s;
}
/* ⚠ 고른 창은 **색이 찬다.** 밑줄이나 테두리 굵기로 가르면 태블릿에서 안 읽힌다 */
#shop .tab.on { background: #FCCC6C; }

/* ---------- 우물 ---------- (그림의 x 90~1310 · y 220~750) */
#shop .pane {
  /* 탭 아래부터 우물 밑까지. 탭이 우물 안으로 내려온 만큼 같이 내렸다 */
  /* ⚠ 우물 밑동이 y 750(= 아래에서 10.71%)이다 (2026-10-07, fix-board-bottom.py) */
  position: absolute; left: 8%; right: 8%; top: 41%; bottom: 13%;
  display: none;
}
#shop[data-tab="goods"] #shop-goods { display: flex; }
#shop[data-tab="ads"]   #shop-ad    { display: flex; }

/* ---------- 자원 ---------- */
#shop-goods { gap: 22px; align-items: stretch; }
#shop .good {
  flex: 1; padding: 18px 10px 14px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  background: none; border: 0;
  font-family: "Jua", sans-serif; color: #8E4A3C;
  transition: transform .14s ease-out, opacity .16s;
}
#shop .good:active, #shop .good.pressed { transform: scale(.96); }
/* 살 수 없다 — 흐리게. 감추지 않는다 */
#shop .good.off { opacity: .42; }
#shop .good .pic { width: 120px; height: 120px; background: center / contain no-repeat; }
/* ⚠ 씨앗과 비료는 **밭 메뉴바의 그것과 같은 그림**이다 (assets/farm/). 상점에서 산 것을
   밭에서 끌어다 놓으므로 같은 그림이라야 같은 물건으로 읽힌다 */
#shop .good[data-good="seed"]   .pic { background-image: url("../../assets/farm/tool-seed.png?v=1"); }
#shop .good[data-good="gyul"]   .pic { background-image: url("../../assets/shop/shop-gyul.png?v=1"); }
#shop .good[data-good="energy"] .pic { background-image: url("../../assets/shop/shop-energy.png?v=1"); }
#shop .good[data-good="fert"]   .pic { background-image: url("../../assets/farm/tool-fert.png?v=1"); }
#shop .good .give { font-size: 34px; font-variant-numeric: tabular-nums; }
#shop .good .cost {
  display: flex; align-items: center; gap: 7px;
  font-size: 28px; font-variant-numeric: tabular-nums;
}
#shop .good .cost .coin { width: 30px; height: 30px; }
/* 샀다 — 한 번 톡 튄다 */
#shop .good.got { animation: shop-got .38s cubic-bezier(.34,1.5,.64,1); }
@keyframes shop-got { 0% { transform: scale(1); } 45% { transform: scale(1.08); } 100% { transform: scale(1); } }
/* 못 산다 — 한 번 흔든다 (밭의 두둑과 같은 생각) */
#shop .good.nope { animation: shop-nope .3s ease-out; }
@keyframes shop-nope {
  0%, 100% { transform: translateX(0); }
  30% { transform: translateX(-7px); }
  70% { transform: translateX(7px); }
}

/* ---------- 광고 ---------- */
#shop-ad { flex-direction: column; align-items: center; justify-content: center; gap: 16px; }
#ad-go {
  display: flex; align-items: center; gap: 20px; cursor: pointer;
  padding: 20px 46px;
  background: #FCCC6C; border: 5px solid #8E4A4A; border-radius: 22px;
  font-family: "Jua", sans-serif; color: #8E4A3C;
  transition: transform .14s ease-out, opacity .16s;
}
#ad-go:active, #ad-go.pressed { transform: scale(.97); }
#ad-go:disabled { opacity: .42; cursor: default; }
/* 재생 표 — 세모 하나. 글자를 쓰지 않는다 */
#ad-go .play {
  width: 0; height: 0; flex: none;
  border-left: 28px solid #8E4A4A;
  border-top: 17px solid transparent;
  border-bottom: 17px solid transparent;
}
#ad-go .give { display: flex; align-items: center; gap: 9px; font-size: 34px;
               font-variant-numeric: tabular-nums; }
/* 보는 중 — 세모가 천천히 숨 쉰다. 시늉이지만 기다리는 것은 진짜다 */
#shop-ad.watching #ad-go .play { animation: ad-wait 1s ease-in-out infinite; }
@keyframes ad-wait { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
#shop-ad.got #ad-go .give { animation: shop-got .4s cubic-bezier(.34,1.5,.64,1); }

#ad-left { font-size: 24px; margin: 0; font-variant-numeric: tabular-nums; }
#shop-ad.done #ad-left { opacity: .5; }
/* ⚠ 이 줄을 지우지 말 것. 시늉을 시늉이라 하지 않으면 심사하는 사람도 팀원도
   진짜 광고가 붙은 줄 안다 — 그것이 나중에 더 큰 문제가 된다 */
#shop-ad .mock { font-size: 19px; margin: 0; opacity: .6; }

@media (prefers-reduced-motion: reduce) {
  #shop, #shop .board, #shop .good, #ad-go { transition: none; }
  #shop .good.got, #shop .good.nope, #shop-ad.watching #ad-go .play { animation: none; }
}

/* ══════════════════ 장사 — 손님 · 주문 · 제작대 (2026-10-07) ══════════════════
 *
 * src/js/stall.js 가 class 만 붙였다 뗀다. 움직임과 생김새는 전부 여기 있다.
 *
 * ⚠ 화면 넘김(창 → 제작대)은 **translate 속성**으로 한다 — transform 이 아니다.
 *   입장 연출이 #scene-art·.band·#cabin 조각들의 transform 을 animation 으로 쥐고 있어서
 *   (fill-mode both), transform 으로 밀면 그쪽에 덮여 아무 일도 안 일어난다.
 *   translate 는 transform 과 따로 놀고 그 위에 얹힌다.
 * ⚠ 색은 트럭 공통 팔레트뿐이다 (#cabin 쪽지). 3D 의 명암은 **새 색이 아니라**
 *   윤곽색(#8E4A4A)을 면마다 옅게 덮어서 만든다 — 윗면 0 · 앞면 .07 · 옆면 .16.
 */

/* ---------- 손님 ---------- */
/* ⚠ 키와 자리 (2026-10-07 에 다시 잡았다) — 창의 **왼쪽 끝**에 선다. 오른쪽은 말풍선이 쓴다.
     판(480x800)의 아랫변이 화면 아래로 빠지고, 상판(창 아랫선 80%)이 허리 아래를 가린다.
   ⚠ **머리가 차양에 걸리지 않게** 판을 내렸다. 차양의 물결 밑단이 화면 25% 언저리라,
     머리 꼭대기를 그보다 넉넉히 아래(37% 안팎)에 둔다. 모자·꼬리머리까지 들어가야 한다.
     어른 : 판 위 28% → 머리 37%.  나이대마다 판에서 머리가 시작하는 자리가 달라 따로 맞춘다 :
       노인 머리 판의 15% · 청소년 22.5% · 어린이 37.5% (화방의 키 720·680·620·500)
   ⚠ 빈손은 이제 쓰지 않는다 — 물건은 매대에 놓이고 손이 아래에서 올라와 집어 간다 (#reach) */
#guest {
  position: absolute; z-index: 2;            /* 배경 위 · 트럭 안(#cabin, 3) 아래 */
  left: 1%; bottom: -20%;
  height: 92%; aspect-ratio: 480 / 800;
  transform: translateX(-75vw);
  transition: transform .52s cubic-bezier(.3,.7,.4,1), translate .55s cubic-bezier(.6,0,.3,1);
  pointer-events: none;
}
#guest[hidden] { display: none; }
#guest[data-age="elder"] { bottom: -17%; }
#guest[data-age="teen"]  { bottom: -13%; }
#guest[data-age="child"] { bottom: -4%; }
#guest.in    { transform: none; }
/* 떠날 때는 **아래로** 내려간다 (2026-10-08) — 창 아랫선(상판) 뒤로 쑥 빠진다. 옆으로 걸어 나가면 다음 손님과 엇갈려 보였다 */
#guest.leave { transform: translateY(110%); transition-duration: .55s; transition-timing-function: cubic-bezier(.5,0,.8,.4); }
#guest .fig { position: absolute; inset: 0; background: center bottom / contain no-repeat; transform-origin: 50% 100%; }
#guest .fig.stub svg { width: 100%; height: 100%; display: block; }
#guest.happy .fig { animation: guest-hop .5s ease-out 2; }
#guest.sad   .fig { animation: guest-shake .4s ease-out; }
@keyframes guest-hop   { 0%,100% { transform: none; } 40% { transform: translateY(-3%); } }
@keyframes guest-shake { 0%,100% { transform: none; } 30% { transform: rotate(-3deg); } 70% { transform: rotate(3deg); } }

/* ---------- 주문 말풍선 · 나가기 ---------- */
#stall { position: absolute; inset: 0; z-index: 6; pointer-events: none;
         transition: translate .55s cubic-bezier(.6,0,.3,1); }
#stall > * { pointer-events: auto; }
#stall[hidden] { display: none; }
/* 말풍선 — **말**이다 (2026-10-07. 그전엔 상품 그림이었다). 손님 머리 오른쪽에서
   창 오른쪽 끝(벽 7% 안쪽)까지 **길게 뻗는다.** 꼬리는 왼쪽, 손님 머리 쪽을 가리킨다.
   ⚠ 대사는 src/data/orders.js — 한 줄, 길어야 두 줄 */
/* ⚠ top 은 stall.js 의 placeBubble() 이 손님 머리 높이로 적는다. 여기 27% 는 그 전에 잠깐 쓰는 값이다.
   translate -50% 로 **한가운데**를 그 높이에 둔다 — 그래서 줄이 늘면 위아래로 고르게 커진다.
   ⚠ transform 이 아니라 translate 다. 튀어나오는 animation(order-pop)이 transform 을 쓴다 */
#order {
  position: absolute; left: 31%; right: 9%; top: 27%;
  translate: 0 -50%;
  display: flex; align-items: center; gap: 22px;
  padding: 20px 20px 20px 34px;
  background: #FCFCE4; border: 5px solid #8E4A4A; border-radius: 30px;
  box-shadow: 0 6px 0 rgba(142,74,74,.18);
  animation: order-pop .3s cubic-bezier(.34,1.5,.64,1);
}
#order[hidden] { display: none; }
#order::after {                               /* 꼬리 — 왼쪽, 손님 머리 쪽을 가리킨다 */
  content: ""; position: absolute; left: -20px; top: 50%; margin-top: -16px;
  width: 30px; height: 30px; background: #FCFCE4;
  border: 5px solid #8E4A4A; border-top: 0; border-right: 0;
  transform: rotate(45deg);
}
@keyframes order-pop { from { transform: translateX(-24px) scale(.9); opacity: 0; } }
#order .say {
  flex: 1; margin: 0;
  font-family: "Jua", sans-serif; font-size: 2.6vw; line-height: 1.35;
  color: #8E4A3C; word-break: keep-all;
}
/* 대사 — 타자 치듯 한 글자씩 드러난다 (stall.js 의 typeSay). 글자는 처음부터 자리를 잡고 있다 */
#order .say i { font-style: normal; opacity: 0; }
#order .say i.on { opacity: 1; animation: say-in .16s ease-out; }
@keyframes say-in { from { opacity: 0; translate: 0 4px; } }
/* 확인 단추 — 감귤빛 둥근 판에 꺾쇠 하나 (2026-10-08 다시 그렸다).
   그림자 띠로 두께를 주고, 대사를 다 쓰기 전에는 접혀 있다가(.typed) 톡 튀어나온다 */
#order-ok {
  width: 84px; height: 84px; flex: none; position: relative; cursor: pointer; padding: 0;
  background: radial-gradient(circle at 38% 32%, #FDE3A6 0 22%, #FCCC6C 52%);
  border: 5px solid #8E4A4A; border-radius: 50%;
  box-shadow: 0 6px 0 #C9906A;
  scale: 0; opacity: 0; pointer-events: none;
  transition: transform .12s ease-out, box-shadow .12s ease-out;
}
#order-ok svg { width: 100%; height: 100%; display: block; }
#order.typed #order-ok { scale: 1; opacity: 1; pointer-events: auto; animation: ok-pop .34s cubic-bezier(.34,1.7,.64,1); }
@keyframes ok-pop { from { scale: 0; opacity: 0; } }
#order-ok:active, #order-ok.pressed { transform: translateY(5px); box-shadow: 0 1px 0 #C9906A; }
#stall-exit {
  position: absolute; left: 26px; top: 26px; width: 88px; height: 88px;
  padding: 0; border: 0; background: none; cursor: pointer;
  transition: transform .12s ease-out;
}
#stall-exit[hidden] { display: none; }
#stall-exit svg { width: 100%; height: 100%; display: block; }
#stall-exit:active, #stall-exit.pressed { transform: scale(.92); }
/* 남은 손님 — 동그라미 줄 (2026-10-08). 나가기 단추가 있던 자리(왼쪽 위)에 선다.
   맞게 건넬 때마다 하나씩 감귤빛으로 찬다. 다 차면 사라지고 그 자리에 나가기 단추가 나온다 */
#stall-goal {
  position: absolute; left: 26px; top: 34px;
  display: flex; gap: 10px; padding: 10px 14px;
  background: #FCFCE4; border: 3px solid #8E4A4A; border-radius: 40px;
  pointer-events: none;
}
#stall-goal[hidden] { display: none; }
#stall-goal i {
  width: 26px; height: 26px; border-radius: 50%;
  background: #FCE4E4; box-shadow: inset 0 0 0 3px #8E4A4A;
  transition: background .25s;
}
#stall-goal i.on { background: #FCCC6C; animation: goal-on .4s cubic-bezier(.34,1.6,.64,1); }
/* 놓친 손님 — 같은 줄에서 그 차례가 노랑 대신 붉게 찬다 (2026-10-09. ✕ 줄 #stall-miss 는 걷었다) */
#stall-goal i.miss { background: #D8706A; animation: goal-on .4s cubic-bezier(.34,1.6,.64,1); }
@keyframes goal-on { from { scale: .5; } to { scale: 1; } }
/* 명소를 끝냈다 — 나가기 단추가 톡 튀어나오고 한동안 숨 쉰다 */
#stall-exit.ready { animation: exit-in .5s cubic-bezier(.34,1.6,.64,1), exit-breathe 1.6s ease-in-out .5s infinite; }
@keyframes exit-in { from { scale: 0; } to { scale: 1; } }
@keyframes exit-breathe { 0%, 100% { scale: 1; } 50% { scale: 1.08; } }

/* 받은 코인 — 숫자만 (HUD 숫자와 같은 허용) */
#stall .coin-pop {
  position: absolute; left: 36%; top: 60%;   /* 판매대(#cabin .sell) 오른쪽 위 */
  display: flex; align-items: center; gap: 8px;
  font-family: "Jua", sans-serif; font-size: 44px; color: #8E4A3C;
  text-shadow: 0 3px 0 #FCFCE4, 0 -3px 0 #FCFCE4, 3px 0 0 #FCFCE4, -3px 0 0 #FCFCE4;
  pointer-events: none;
  animation: coin-pop 1.4s ease-out forwards;
}
#stall .coin-pop .coin { width: 46px; height: 46px; }
@keyframes coin-pop { 0% { transform: translateY(10px) scale(.6); opacity: 0; }
                      20% { transform: none; opacity: 1; } 100% { transform: translateY(-70px); opacity: 0; } }

/* ---------- 판매대 — 상판 위의 진짜 3D (2026-10-07 밤) ----------
   그전에는 창 구멍에 겹친 **2D 판**(#reach)이었다. 물건이 상판 위에 놓인 것처럼 보이게 하려고
   창 아랫선에서 잘라 썼는데, 상판은 눕은 면이고 물건은 납작한 그림이라 둘이 따로 놀았다.
   이제 상판과 **같은 판**(#cabin .counter.wares) 위에 선다 — 원근·크기가 상판의 셈을 그대로 탄다.

   좌표 — 이 판은 rotateX(90deg) 로 누운 상판이다. 판 안에서
     top 0       = 상판 **먼 끝** (창 아랫선, 손님 쪽)
     아래로 갈수록 나에게 가깝다
     translateZ + = 상판에서 **위로** 뜬다
   서는 것(물건·손)은 rotateX(-90deg) 로 판이 누운 만큼 되세운다 — 상판 위 물건 셋과 같은 방식이다.

   ⚠ 손은 상판 먼 끝에 **세운 틀**(.slot) 안에서만 보인다. 틀의 아랫변이 곧 상판 먼 끝이라
     그 밑에서 올라오는 손은 상판 너머(창 밖)에서 올라오는 것으로 읽힌다. overflow 로 자르므로
     틀 안은 납작해지는데, 손은 원래 납작한 그림이라 상관없다.
   ⚠ 시간은 stall.js 의 serve() 와 짝이다 : 놓임 .38초 뒤 손이 올라옴(.42초) → .52초 뒤 쥐고 내려감(.46초).
     한쪽만 고치면 손이 다 오기 전에 물건이 사라지거나, 빈손으로 올라왔다 간다.
   ⚠ 자리는 창 **왼쪽**이다(--sx 30%). 손님이 창 왼쪽 끝에 서므로 손이 그 앞에서 올라와야 한다.
     오른쪽(57~79%)은 상판 위 물건 셋 자리라 비워 둔다 */
#cabin .sell {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  pointer-events: none;
  --sx: 30%;                     /* 판매대 한가운데의 좌우 자리 */
  --sz: 58px;                    /* 물건이 상판 먼 끝에서 얼마나 앞에 놓이나 (쟁반이 나갔을 때) */
  --run: 118px;                  /* 쟁반이 내 앞에서 기다리는 자리 — 손님 앞 자리보다 이만큼 가깝다 */
}
/* ★ 2026-10-08 : 쟁반·그늘·물건이 한 수레다. 늘 **내 앞**에 있다가(--run) 물건이 놓이면 손님 앞으로
   밀려 나가고(.go), 손님이 집어 가면 빈 채로 돌아온다(class 를 다 떼면).
   ⚠ translateY 다 — 누운 판 안이라 Y 가 곧 깊이다. top 을 움직이지 말 것 (배치를 매 프레임 다시 셈한다) */
#cabin .sell .cart {
  position: absolute; inset: 0;
  transform-style: preserve-3d;
  transform: translateY(var(--run));
  transition: transform .6s cubic-bezier(.45,.05,.3,1);
}
#cabin .sell.go .cart { transform: translateY(0); }
/* 쟁반 — 판매대의 자리. 늘 깔려 있다 (물건이 없을 때도 "여기가 건네는 곳" 이 보인다).
   상판에서 10px 떠 있고, 앞 테(.rim)가 그 두께를 세워 보인다 — 납작한 칠이 아니라 판이다 */
#cabin .sell .tray {
  position: absolute; left: var(--sx); top: calc(var(--sz) - 34px);
  width: 17vw; height: 100px; margin-left: -8.5vw;
  box-sizing: border-box;
  background:
    repeating-linear-gradient(to right, rgba(142,74,74,.10) 0 3px, transparent 3px 26px),
    var(--cream);
  border: 0.28vw solid var(--line); border-radius: 14px;
  box-shadow: inset 0 0 0 0.7vw var(--zest);
  transform: translateZ(10px);
  transform-style: preserve-3d;
}
#cabin .sell .tray .rim {        /* 앞 테 — 쟁반의 두께. 앞 변에 매달려 상판까지 내려온다 */
  position: absolute; left: 6px; right: 6px; top: 100%; height: 10px;
  background: var(--zest); border: 0.22vw solid var(--line); border-top: 0;
  border-radius: 0 0 10px 10px;
  transform-origin: 50% 0; transform: rotateX(-90deg);
}
/* 물건 그늘 — 상판에 눕는다. 물건이 내려앉을 때 같이 짙어진다 */
#cabin .sell .shade {
  position: absolute; left: var(--sx); top: var(--sz);
  width: 8vw; height: 26px; margin: -13px 0 0 -4vw;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(142,74,74,.32), rgba(142,74,74,0));
  transform: translateZ(11px);
  opacity: 0; transition: opacity .2s;
}
/* 완성품 — 서는 그림 한 장. 위에서 내려와 쟁반에 앉는다 */
#cabin .sell .item {
  position: absolute; left: var(--sx); top: var(--sz);
  width: 10.4vw; height: 12.48vw;
  margin: -12.48vw 0 0 -5.2vw;              /* 발(아랫변 가운데)이 --sx·--sz 에 오게.
                                               ⚠ translate % 로 하지 말 것 — 누운 판 위라 깊이로 밀린다 */
  transform-origin: 50% 100%;
  transform: translateZ(11px) rotateX(-90deg) translateY(-9vw);
  opacity: 0;
  transition: transform .3s cubic-bezier(.34,1.4,.64,1), opacity .15s;
}
/* 손님의 손 — 상판 먼 끝에 세운 틀 안에서 올라온다.
   ⚠ 이름이 .reach 다. .slot 으로 두었더니 지도의 나무 명판 규칙(.slot)이 배경을 칠해 빈 상자가 떠 있었다 */
#cabin .sell .reach {
  position: absolute; left: calc(var(--sx) + 1.4vw); top: 0;
  width: 9vw; height: 16vw; margin: -16vw 0 0 -4.5vw;
  transform-origin: 50% 100%;
  transform: rotateX(-90deg);
  overflow: hidden;
}
#cabin .sell .hand {
  position: absolute; left: 0; right: 0; bottom: 0;
  aspect-ratio: 120 / 220;
  transform: translateY(102%);
  transition: transform .42s cubic-bezier(.3,.8,.4,1);
}
#cabin .sell .hand svg { width: 100%; height: 100%; display: block; }

#cabin .sell.put .item  { transform: translateZ(11px) rotateX(-90deg); opacity: 1; }
#cabin .sell.put .shade { opacity: 1; }
#cabin .sell.grab .hand { transform: translateY(46%); }    /* 손가락이 물건 뒤로 올라선다 */
/* 쥐고 내려간다 — 물건은 손을 따라 **먼 끝 쪽으로** 들려 가며 사라진다 */
#cabin .sell.take .hand { transform: translateY(102%); transition: transform .46s cubic-bezier(.6,0,.8,.5); }
/* ★ 2026-10-08 밤 : 손을 걷었다 (팀장 : 손 없이 스르륵). 물건은 제자리에서 옅어지기만 한다.
   손(.reach)은 index.html 에 남겨 두고 감춘다 — 되살리려면 이 두 규칙과 stall.js serve() 의 grab 걸음을 되돌린다 */
#cabin .sell .reach { display: none; }
#cabin .sell.take .item { transform: translateZ(11px) rotateX(-90deg); opacity: 0; transition: opacity .6s ease; }
#cabin .sell.take .shade { opacity: 0; }

/* 상품 그림 (stall.js 의 인라인 SVG) */
.ico { display: block; }
.ico svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* ---------- 카메라가 뒤로 돈다 ----------
   창 쪽 풍경이 왼쪽으로 밀려나고(조금만 — 시차) 제작대가 오른쪽에서 들어온다.
   ⚠ 0.55초. 길면 손님을 기다리게 하는 것 같고, 짧으면 "어디로 갔지" 가 된다 */
#scene .band, #scene-art, #cabin { transition: translate .55s cubic-bezier(.6,0,.3,1); }
#scene.cooking .band, #scene.cooking #scene-art, #scene.cooking #cabin,
#scene.cooking #guest, #scene.cooking #stall { translate: -38% 0; }

/* ══════════ 제작대 — 진짜 3D (2026-10-07 · 같은 날 밤에 다시 지었다) ══════════
 *
 * 작업대·뒷벽·창턱·도구는 CSS 3D 상자다. 면 넷(윗면·앞면·옆면 둘)을 실제로 세우고 눕힌다 —
 * 그림으로 원근을 흉내내지 않는다 (트럭 안에서 이미 배운 것이다 : 그린 원근은 두 겹이 된다).
 *
 * ★ 다시 지은 까닭 (2026-10-07 밤) — 첫 판은 같은 크기의 상자 일곱이 한 줄로 서 있었고
 *   작업대 앞면이 화면 아래 절반을 빈 크림 판으로 덮었다. 무엇이 도구인지보다 "상자가 있다" 가 먼저 읽혔다.
 *   지금은 :
 *     ① 카메라를 더 숙였다(-24 → -34도). 작업대 윗면이 화면의 주인공이고 앞면은 수납장 띠다
 *     ★ 같은 밤 한 번 더 — **옆이 떠 보였다.** 작업대와 뒷벽이 화면보다 좁아서 양옆에 작업대 옆면이
 *       비스듬히 드러났고, 그 바깥이 빈 크림 삼각형이라 작업대가 허공에 뜬 섬처럼 읽혔다.
 *       고친 것 셋 : 작업대·뒷벽을 화면 밖까지 넓혔다(1100 → 1620 · 1180 → 1700, 왼쪽으로 260 씩) ·
 *       카메라를 조금 세웠다(-34 → -30도) · 원근을 멀리 두었다(1300 → 1800px. 옆선이 덜 모인다).
 *       ⚠ 다시 좁히지 말 것 — 옆면이 화면 안에 들어오는 순간 같은 문제가 돌아온다.
 *       ⚠ 넓히면서 뒷벽 안의 창·선반, 작업대 윗면의 매트 left 를 260 씩 밀었다 (도구는 월드 좌표라 그대로)
 *     ② 도구마다 생김새가 있다 — **낮은 3D 받침 + 그 위에 서는 그림 한 장**(.bb, 빌보드).
 *        받침이 자리·그늘·깊이를 맡고, 그림이 "무엇인가" 를 맡는다. 상자처럼 생긴 것
 *        (냉동고·오븐·건조기·귤 상자)은 상자 자체가 몸이다
 *     ③ 뒷벽은 크림 칠 한 장 · 차양 달린 내보내는 창 · 튀어나온 창턱뿐이다
 *        (타일·선반·병·컵을 넣었다가 같은 밤에 걷어냈다 — 어색했고 하는 일이 없었다)
 *
 * 좌표 — 작업대 윗면이 y = 0 이고 아래가 +y, 나에게 가까울수록 +z 다. 단위는 px(월드).
 *   상자 하나 = --x · --z (뒤쪽 왼쪽 모서리) · --w(폭) · --d(깊이) · --h(높이) · --y(바닥 높이).
 *   도구는 작업대 위에 서므로 --y 0, 작업대는 바닥에서 솟으므로 --y = 제 높이(300).
 *   도구의 자리·크기는 stall.js 의 LAYOUT 표 하나에 있다.
 * 카메라 — .k-cam 이 rotateX 로 내려다본다. 그래서 윗면이 보인다.
 *   ⚠ 각을 키우면 윗면이 넓어지고 앞면이 얇아진다. 28~38도 사이에 둘 것 —
 *     작으면 정면 사진이 되어 3D 가 죽고, 크면 위에서 찍은 평면도가 되어 그림(.bb)이 납작해진다.
 * ⚠ 이 층의 조상에 overflow · opacity · filter 를 두지 말 것 — preserve-3d 가 납작해진다
 *   (#kitchen 자신의 overflow: hidden 은 perspective 를 가진 바깥 틀이라 괜찮다).
 * ⚠ 손가락은 화면에 투영된 면을 누른다 — elementFromPoint 가 3D 면과 서 있는 그림을 그대로 집는다.
 * ⚠ 색은 트럭 공통 팔레트뿐이다. 명암은 새 색이 아니라 윤곽색을 면마다 옅게 덮어서 만든다.
 */
#kitchen {
  position: absolute; inset: 0; z-index: 7;
  overflow: hidden;
  perspective: 1800px;
  perspective-origin: 50% 0%;
  translate: 100% 0;
  transition: translate .55s cubic-bezier(.6,0,.3,1);
  touch-action: none;
  --k: 1;
  --cam: 46deg;      /* 카메라가 내려다보는 각 — 도구 그림(.bb.sp)이 정면으로 돌아서는 각과 같다 */
}
#kitchen[hidden] { display: none; }
#scene.cooking #kitchen { translate: 0 0; }
/* 뒤 배경 — 화면 가장자리에 비는 곳이 생겨도 같은 벽으로 읽히게 뒷벽과 같은 크림 */
#kitchen .k-wall { position: absolute; inset: 0; background: #FCFCE4; }
/* ★ 2026-10-08 : 시야를 다시 잡았다 — 벽이 화면 위 절반을 먹고 있었다.
   카메라를 더 숙이고(-30 → -46도) 작업대를 깊게(400 → 470) 해서 작업대 윗면이 화면의 주인공이 된다.
   벽은 위 끝에 띠로만 남고 그 자리에 기다리는 손님 얼굴판(.k-guest)이 선다 — 빈 벽이 아니라 할 일이 있는 자리다.
   ⚠ 카메라 각(--cam)·자리(top 27%)·작업대 깊이(index.html 의 .k-bench --d)는 한 벌이다 */
#kitchen .k-cam {
  position: absolute; left: 50%; top: 27%; width: 0; height: 0;
  transform-style: preserve-3d;
  transform: scale(var(--k)) rotateX(calc(var(--cam) * -1));
}
#kitchen .k-world {
  position: absolute; left: 0; top: 0;
  transform-style: preserve-3d;
  transform: translate3d(-551px, 0px, -40px);   /* 도구 줄(x 20~1082)의 가운데가 화면 가운데 */
}

/* 상자 하나 */
.k-box {
  position: absolute; left: 0; top: 0;
  transform-style: preserve-3d;
  transform: translate3d(var(--x), var(--y, 0px), var(--z));
}
.k-box .f {
  position: absolute; left: 0; top: 0;
  box-sizing: border-box;
  transform-origin: 0 0;
  border: 3px solid #8E4A4A;
  background: #FCFCE4;
}
.k-box .f.top    { width: var(--w); height: var(--d);
                   transform: translate3d(0, calc(var(--h) * -1), 0) rotateX(90deg);
                   transform-style: preserve-3d; }
.k-box .f.front  { width: var(--w); height: var(--h);
                   transform: translate3d(0, calc(var(--h) * -1), var(--d));
                   box-shadow: inset 0 0 0 999px rgba(142,74,74,.07); }
.k-box .f.side   { width: var(--d); height: var(--h);
                   box-shadow: inset 0 0 0 999px rgba(142,74,74,.16); }
.k-box .f.side.l { transform: translate3d(0, calc(var(--h) * -1), 0) rotateY(-90deg); }
.k-box .f.side.r { transform: translate3d(var(--w), calc(var(--h) * -1), 0) rotateY(-90deg); }

/* 서는 그림 — 윗면(.f.top) 안에 놓고 판이 누운 만큼 되세운다.
   --bx · --bz : 윗면 위 발의 자리(px, 윗면의 왼쪽 위가 0) · --bw · --bh : 그림 크기 */
.k-box .bb {
  position: absolute; left: var(--bx); top: var(--bz);
  width: var(--bw); height: var(--bh);
  margin: calc(var(--bh) * -1) 0 0 calc(var(--bw) / -2);
  transform-origin: 50% 100%;
  transform: rotateX(-90deg);
}
.k-box .bb svg { width: 100%; height: 100%; display: block; overflow: visible; }
/* 그림 발밑의 그늘 — 윗면에 눕는다 */
.k-box .bb-shade {
  position: absolute; left: var(--bx); top: var(--bz);
  width: calc(var(--bw) * .9); height: 30px;
  margin: -18px 0 0 calc(var(--bw) * -.45);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(142,74,74,.28), rgba(142,74,74,0));
}

/* ---------- 작업대 — 윗면은 테라코타 나무판, 앞면은 수납장 문 넷 ---------- */
.k-bench { --y: 600px; }   /* 바닥에서 솟는다 — 앞판이 화면 아래 끝까지 내려가야 작업대가 뜨지 않는다 */
.k-bench .f.top {
  background:
    repeating-linear-gradient(to bottom, transparent 0 78px, rgba(142,74,74,.16) 78px 80px),
    repeating-linear-gradient(to bottom, rgba(252,252,228,.14) 0 2px, transparent 2px 26px),
    #CC846C;
}
.k-bench .f.front {
  background:
    /* 손잡이 넷 — 문마다 가운데 위 */
    radial-gradient(circle at 12.5% 72px, #FCCC6C 0 9px, #8E4A4A 9px 12px, transparent 12px),
    radial-gradient(circle at 37.5% 72px, #FCCC6C 0 9px, #8E4A4A 9px 12px, transparent 12px),
    radial-gradient(circle at 62.5% 72px, #FCCC6C 0 9px, #8E4A4A 9px 12px, transparent 12px),
    radial-gradient(circle at 87.5% 72px, #FCCC6C 0 9px, #8E4A4A 9px 12px, transparent 12px),
    /* 문 사이 틈 */
    repeating-linear-gradient(to right, transparent 0 calc(25% - 2px), #8E4A4A calc(25% - 2px) 25%),
    /* 윗단 띠 */
    linear-gradient(to bottom, #FCCC6C 0 30px, #8E4A4A 30px 33px, #FCFCE4 33px);
}
.k-bench .f.side { background: #CC846C; }
/* 그림이 들어오면 (assets/kitchen/bench-top.png · bench-front.png, 화방 docs/제작대-프롬프트.md)
   stall.js 의 probe 가 #kitchen 에 .bench-art 를 붙이고 위의 CSS 밑그림을 덮는다.
   ⚠ 그때 CSS 테두리를 뗀다 — 그림에 제 손그림 윤곽이 있어 두 줄이 되고, 그 곧은 선이 "각진" 느낌의 주범이었다.
   ⚠ 늘려 깐다(100% 100%). 그림이 면과 같은 비율(윗면 1620x470 = 3.45:1 · 앞면 1620x600 = 2.7:1)로 와야 안 눌린다 */
#kitchen.bench-art .k-bench .f.top   { background: url("../../assets/kitchen/bench-top.png?v=1") 0 0 / 100% 100% no-repeat; border: 0; }
#kitchen.bench-art .k-bench .f.front { background: url("../../assets/kitchen/bench-front.png?v=1") 0 0 / 100% 100% no-repeat; border: 0; box-shadow: none; }
#kitchen.bench-art .k-bench .f.side  { border: 0; }

/* 매트 — 작업대 윗면 앞쪽에 깔린다. 다 만든 것을 내려놓는 자리.
   ⚠ 그 위의 물건은 **일으켜 세운다**(rotateX -90). 안 세우면 매트에 그려진 그림이 된다 */
#kitchen .k-mat {
  position: absolute; left: 600px; top: 262px; width: 420px; height: 170px;
  box-sizing: border-box;
  background:
    repeating-linear-gradient(to right, rgba(142,74,74,.10) 0 2px, transparent 2px 40px),
    repeating-linear-gradient(to bottom, rgba(142,74,74,.10) 0 2px, transparent 2px 40px),
    #FCE4E4;
  border: 4px solid #8E4A4A; border-radius: 22px;
  box-shadow: inset 0 0 0 10px #FCFCE4;
  transform-style: preserve-3d;
  /* ⚠ 윗면에서 2px 띄운다. 같은 평면에 겹쳐 두면 손가락 판정(elementFromPoint)이 매트 대신
     작업대 윗면을 집는다 — 귤을 매트에 놓아도 아무 일이 없다 (각도를 바꾼 날 실제로 밟았다) */
  transform: translateZ(2px);
}
#kitchen .k-mat .ico {
  position: absolute; left: 50%; top: 62%;
  width: 124px; height: 148px; margin: -148px 0 0 -62px;
  transform-origin: 50% 100%;
  transform: rotateX(calc(-90deg + var(--cam)));   /* 카메라를 정면으로 본다 — 도구 그림과 같은 각 */
}
#kitchen .k-mat.lift .ico { opacity: .25; }
#kitchen .k-mat.made .ico { animation: mat-made .36s cubic-bezier(.34,1.5,.64,1); }
@keyframes mat-made { from { transform: rotateX(calc(-90deg + var(--cam))) scale(.4); } to { transform: rotateX(calc(-90deg + var(--cam))) scale(1); } }

/* ---------- 뒷벽 · 내보내는 창 ---------- */
/* ★ 2026-10-08 : 벽을 작업대 뒤끝에서 **300px 더 뒤로** 물렸다. 도구 그림은 카메라를 보려고 뒤로 눕는데
   (--cam), 키 큰 냉동고는 그 꼭대기가 벽면을 뚫고 들어가 **위가 잘렸다.** 벽은 무늬 없는 크림이라 물려도 티가 안 난다.
   ⚠ 아래로 길게(1100px) 늘려 작업대 뒤끝 아래까지 덮는다 — 안 그러면 벽과 작업대 사이에 틈이 보인다 */
#kitchen .k-back {
  position: absolute; left: -900px; top: -900px; width: 2900px; height: 1100px;
  transform: translateZ(-300px);
  box-sizing: border-box;
  background: #FCFCE4; border: 0;
}
/* ---------- 도구 일곱 — 받침(3D) + 서는 그림(.bb) ---------- */
.k-box.st { cursor: pointer; }
/* ① 귤 상자 — 나무 상자 자체가 몸이다. 위로 귤이 소복이 솟는다(.bb). 비면 귤이 사라진다 */
.st-crate .f { background: #CC846C; }
.st-crate .f.front, .st-crate .f.side {
  background-image: repeating-linear-gradient(to bottom, transparent 0 18px, #8E4A4A 18px 21px); }
.st-crate .f.top {
  background:
    radial-gradient(circle at 18% 30%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    radial-gradient(circle at 46% 26%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    radial-gradient(circle at 78% 32%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    radial-gradient(circle at 30% 70%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    radial-gradient(circle at 62% 72%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    radial-gradient(circle at 88% 74%, #FCCC6C 0 17px, #8E4A4A 17px 20px, transparent 20px),
    #9C5454;
  box-shadow: inset 0 0 0 9px #CC846C, inset 0 0 0 12px #8E4A4A;
}
.st-crate.empty .f.top { background: #9C5454; box-shadow: inset 0 0 0 9px #CC846C, inset 0 0 0 12px #8E4A4A; }
.st-crate.empty .bb, .st-crate.empty .bb-shade { display: none; }

/* ② 착즙기 — 낮은 받침 위에 착즙기 그림. 손잡이(.lever)가 --fill 만큼 내려가고 컵(.juice)이 찬다.
   ⚠ --fill 은 stall.js 가 상자(.st-juicer)에 적는다 — 그림 안까지 물려 내려온다 */
.st-juicer .f { background: #FCE4E4; }
.st-juicer .lever { transform-box: view-box; transform-origin: 98px 26px;
                    transform: rotate(calc(var(--fill, 0) * -20deg)); }
.st-juicer .load  { opacity: 0; }
.st-juicer.loaded .load { opacity: 1; }
.st-juicer .juice { transform-box: fill-box; transform-origin: 50% 100%;
                    transform: scaleY(var(--fill, 0)); }
.st-juicer.loaded .lever { animation: lever-hint 1.2s ease-in-out infinite; }   /* 눌러 보라는 손짓 — 글자 대신 */
.st-juicer.busy .lever   { animation: none; }
@keyframes lever-hint { 0%, 70%, 100% { transform: rotate(0); } 80% { transform: rotate(-8deg); } 90% { transform: rotate(0); } }

/* ③ 도마 — 얇은 판. 칼은 판 위에 눕는다(.flat) */
.k-box .flat { position: absolute; inset: 10px; }
.k-box .flat svg { width: 100%; height: 100%; display: block; }
/* ④ 냄비 — 화구(받침) 위에 냄비 그림 */
.st-pot .f { background: #9C9CB4; }
.st-pot .f.top { background: #CCCCCC; }
/* ⑤ 냉동고 · ⑥ 오븐 · ⑦ 건조기 — 상자가 몸이다 */
.st-freeze .f { background: #FCFCE4; }
.st-freeze .f.top { background: #CCCCCC; }
.st-freeze .f.front { background:
  linear-gradient(#8E4A4A 0 0) 14px 52% / 6px 46px no-repeat,
  linear-gradient(#8E4A4A 0 0) 14px 16% / 6px 26px no-repeat,
  linear-gradient(to bottom, #FCFCE4 0 34%, #8E4A4A 34% calc(34% + 3px), #FCFCE4 calc(34% + 3px)); }
.st-oven .f { background: #CC846C; }
.st-oven .f.front { background:
  radial-gradient(circle at 25% 22px, #FCFCE4 0 8px, #8E4A4A 8px 11px, transparent 11px),
  radial-gradient(circle at 50% 22px, #FCFCE4 0 8px, #8E4A4A 8px 11px, transparent 11px),
  radial-gradient(circle at 75% 22px, #FCFCE4 0 8px, #8E4A4A 8px 11px, transparent 11px),
  #CC846C; }
.st-oven .f.front::after { content: ""; position: absolute; inset: 46px 14px 14px; border: 3px solid #8E4A4A; border-radius: 10px;
  background: linear-gradient(to bottom, #FCE4E4, #FCCC6C); }
.st-dryer .f { background: #FCE4E4; }
.st-dryer .f.front { background:
  repeating-linear-gradient(to bottom, transparent 0 24px, #8E4A4A 24px 27px) 0 18px / 100% calc(100% - 30px) no-repeat,
  #FCE4E4; }

/* 안 열린 칸 — 천을 덮어 둔다. 무엇이 올지 모양(높이)만 보이고, 위에 자물쇠 그림이 선다.
   ⚠ 감추지 않는다 — 다음에 무엇이 열리는지가 보여야 길이 보인다 */
.k-box.st.locked .f { background: #8FA87C !important; box-shadow: inset 0 0 0 999px rgba(142,74,74,.10); }
.k-box.st.locked .f::before, .k-box.st.locked .f::after { display: none; }
.k-box.st.locked .f.front { border-radius: 0 0 22px 22px; box-shadow: inset 0 -14px 0 rgba(142,74,74,.12); }
.k-box.st.locked .bb, .k-box.st.locked .flat, .k-box.st.locked .bb-shade { display: none; }
.k-box.st .lock { display: none; }
.k-box.st.locked .lock { display: block; }
.k-box.st.locked { cursor: default; }


/* ---------- 도구 그림 (2026-10-08) — 상자 대신 손그림 한 장이 몸이다 ----------
   stall.js 가 그림이 실린 도구에 .art 를 붙이고 상자를 높이 4px 짜리 투명한 발판으로 줄인다.
   그림(600x600, 월드 1px = 그림 2px)은 발판 앞 가장자리 가운데에 서서 **카메라를 정면으로 본다** —
   앞면과 윗면이 그림 안에 이미 그려져 있으므로 비스듬히 세우면 원근이 두 겹이 된다.
   ⚠ 정면으로 돌리는 각은 카메라의 기울기(--cam)와 같아야 한다. 하나만 고치지 말 것 */
.k-box.st.art .f { background: none !important; border: 0; box-shadow: none !important; }
.k-box.st.art .f::before, .k-box.st.art .f::after { display: none; }
.k-box.st.art .bb.sp { transform: rotateX(calc(-90deg + var(--cam))); }
.k-box.st.art .bb.sp img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
/* 손을 받는 것은 그림이 실제로 그려진 상자(.hit)뿐이다 — 투명한 판이 옆 도구를 덮지 않게 (stall.js 의 HIT) */
.k-box .bb.sp { pointer-events: none; }   /* 판(<i>) 자체도 300x300 이라 막아 둔다 */
.k-box .bb.sp .hit { position: absolute; display: block; pointer-events: auto; }
.k-box.st.art .bb-shade.sp { height: 40px; margin-top: -26px; }
/* 귤 상자 — 비면 빈 상자 그림으로 갈아 낀다 */
.st-crate.art .emptied { opacity: 0; }
.st-crate.art.empty .emptied { opacity: 1; }
.st-crate.art.empty img:not(.emptied):not(.padlock) { opacity: 0; }
.st-crate.art.empty .bb, .st-crate.art.empty .bb-shade { display: block; }
/* 착즙기 네 겹 — 손잡이는 경첩(420,178)을 축으로 내려가고, 주스는 컵 안(y 440~560)을 아래에서 채운다
   ⚠ 2026-10-09 : 밑이 잘리던 그림을 12px 올려 다시 구웠다 — 이 묶음의 세로 % 가 전부 2% 씩 올라갔다 */
/* ★ 2026-10-08 : 손잡이는 **--press**(반쪽 하나를 짜는 동안 0→1)를 따른다. 컵(--fill)은 두 번에 걸쳐 차므로 둘이 따로다 */
.st-juicer.art .lever { transform-origin: 70% 29.67%; transform: rotate(calc(var(--press, 0) * -22deg)); }
.st-juicer.art.loaded .lever { animation: lever-hint 1.2s ease-in-out infinite; }
.st-juicer.art.busy .lever { animation: none; }
.st-juicer.art .juice { clip-path: inset(calc(73.33% + (1 - var(--fill, 0)) * 20%) 0 0 0);
                        transform: none; }   /* ⚠ 밑그림용 scaleY(--fill) 를 끈다 — 그림에도 걸려 반 잔이 1/4 로 눌려 보였다 (2026-10-08 밤) */
/* 통귤 판(juicer-4)은 안 쓴다 — 이제 반쪽을 올린다 (.pieces).
   ⚠ ".bb.sp img" 규칙(display: block)보다 세야 한다 — 자물쇠 때와 같은 함정이다 */
.st-juicer.art .bb.sp img.load { display: none; }
/* 안 열린 도구 — 그림을 바래게 두고 자물쇠를 건다. ⚠ 감추지 않는다 (다음에 무엇이 열리는지가 보여야 한다) */
.k-box.st.art.locked .bb, .k-box.st.art.locked .bb-shade { display: block; }
.k-box.st.art.locked .bb img:not(.padlock) { filter: grayscale(.7) opacity(.45); }
/* ⚠ 위의 ".bb.sp img" 규칙(display·inset·크기)보다 **세야** 한다 — 같은 세기로 두면 자물쇠가 모든 도구에 판만 하게 뜬다 */
.k-box.st.art .bb.sp img.padlock { display: none; inset: auto; left: 50%; bottom: 14%; width: 26%; height: auto; translate: -50% 0; }
.k-box.st.art.locked .bb.sp img.padlock { display: block; }
/* 끌어 놓을 자리 — 상자가 투명하므로 그림 둘레에 점선을 두른다 */
/* (끌어 놓을 자리 점선 .over 는 2026-10-09 에 걷었다 — 부자연스러웠다. 놓으면 될 뿐이다) */

/* ---------- 손으로 올린 것 — 도마 위 귤·반쪽, 착즙기 위 반쪽 (2026-10-08) ----------
   도구 그림(.bb.sp, 600 판 = 300px) 안의 백분율 자리다. 판의 좌표는 docs/제작대-프롬프트.md 의 "도구 그림" 절.
   ⚠ 도마는 빈 도마 그림(board-empty)이어야 한다 — 옛 그림에는 반쪽이 그려져 있어 진짜와 헷갈린다 */
.k-box .pieces { position: absolute; inset: 0; pointer-events: none; }
.k-box .pieces .pc { position: absolute; display: block; }
.k-box .pieces .pc svg, .k-box .pieces .pc img { width: 100%; height: 100%; display: block; object-fit: contain; }
/* ---------- 가운데 도마 (.k-mat, 2026-10-08) ----------
   ★ 옛 분홍 매트가 **진짜 도마**가 되었다. 귤을 올려 칼로 자르고, 다 만든 것을 내려놓는다.
   그림(cutboard.png 1260x510)이 오면 그것을, 없으면 CSS 나무판을 깐다.
   위에 오르는 것(통귤·반쪽·완성품)은 매트 때처럼 **일으켜 세워** 카메라를 본다 */
#kitchen .k-mat {
  background:
    repeating-linear-gradient(to bottom, rgba(142,74,74,.10) 0 2px, transparent 2px 18px),
    #FCCC6C;
  border: 4px solid #8E4A4A; border-radius: 40px;
  box-shadow: inset 0 0 0 12px #F0B85C;
}
#kitchen.cut-art .k-mat { background: url("../../assets/kitchen/cutboard.png?v=1") 0 0 / 100% 100% no-repeat; border: 0; box-shadow: none; }
#kitchen .k-mat .pc {
  position: absolute; top: 60%; width: 92px; height: 92px; margin: -92px 0 0 -46px;
  transform-origin: 50% 100%; transform: rotateX(calc(-90deg + var(--cam)));
}
#kitchen .k-mat .pc svg, #kitchen .k-mat .pc img { width: 100%; height: 100%; display: block; object-fit: contain; }
#kitchen .k-mat .pc.whole { left: 50%; }
#kitchen .k-mat .pc.half  { width: 84px; height: 84px; margin: -84px 0 0 -42px; animation: half-in .3s cubic-bezier(.34,1.6,.64,1); }
#kitchen .k-mat .pc.half.h0 { left: 38%; }
#kitchen .k-mat .pc.half.h1 { left: 62%; }
#kitchen .k-mat.lift .pc.whole { opacity: .25; }
@keyframes half-in { from { scale: .6; } to { scale: 1; } }
/* 잘린 순간 — 도마가 한 번 통 튄다 */
#kitchen .k-mat.chop { animation: chop .28s ease-out; }
@keyframes chop { 40% { translate: 0 0 -6px; } 100% { translate: 0 0 0; } }

/* ---------- 칼 — 작업대 오른쪽 상판에 눕는다 (2026-10-08) ----------
   끌어다 도마 위 통귤에 대면 반으로 잘린다. 놓으면 늘 제자리로 돌아온다.
   도마에 통귤이 있으면(#kitchen.need-knife) 들썩이며 "나를 써라" 한다 — 글자 대신 */
#kitchen .k-knife {
  position: absolute; left: 1090px; top: 300px; width: 210px; height: 63px;
  transform: translateZ(3px); cursor: grab;
}
#kitchen .k-knife svg, #kitchen .k-knife img { width: 100%; height: 100%; display: block; }
#kitchen .k-knife.lift { opacity: .2; }
#kitchen.need-knife .k-knife { animation: knife-call 1.1s ease-in-out infinite; }
@keyframes knife-call { 0%, 60%, 100% { transform: translateZ(3px); } 75% { transform: translateZ(22px) rotate(-6deg); } 90% { transform: translateZ(3px); } }
#kitchen .k-ghost.knife { width: 210px; height: 63px; }

/* ---------- 쓰레기통 — 작업대 왼쪽 앞 (2026-10-08) ----------
   잘못 만든 것을 끌어다 버린다. 받으면 한 번 들썩인다(.gulp) */
.k-bin .f { background: none; border: 0; box-shadow: none; }
.k-bin.art .bb.sp { transform: rotateX(calc(-90deg + var(--cam))); }
.k-bin.art .bb.sp img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.k-bin.art .bb-shade.sp { height: 40px; margin-top: -26px; }
.k-bin.gulp .bb { animation: bin-gulp .4s cubic-bezier(.34,1.6,.64,1); }
@keyframes bin-gulp { 30% { scale: 1.12 .9; } 60% { scale: .95 1.06; } 100% { scale: 1; } }

/* 착즙기 — 하나는 짜개 끝(그림 300,238)에 **엎어** 씌우고(진짜 착즙기처럼 자른 면이 아래), 하나는 옆 상판에서 기다린다.
   짜는 동안(--sq 0→1) 엎은 반쪽이 납작하게 눌린다 */
/* ★ 2026-10-09 : 반쪽이 짜개의 절반만 덮어 부자연스러웠다 (팀장) — 통귤과 같은 폭(월드 약 68px)으로 키웠다.
   19% → 30%. 그리고 짜개 끝(판 39.7%)이 반쪽 위로 삐져나오지 않게 **짜개를 덮어 씌우는 자리**까지 올렸다 —
   둥근 등이 끝 위로 오고, 자른 면은 짜개 허리에 걸린다. 짤 때는 그 자리에서 아래로 눌린다 */
.st-juicer .pc.half.on-ream { left: 35%; top: -1%; width: 30%; aspect-ratio: 1;
  transform-origin: 50% 100%; transform: scaleY(calc(-1 + var(--sq, 0) * .55)) scaleX(calc(1 + var(--sq, 0) * .15)); }
/* (옆에서 기다리던 둘째 반쪽 .wait 은 걷었다 — 2026-10-08 밤부터 하나씩 올리고 짠다) */
/* 다 찼다 — 컵 둘레(그림 x 252~348 · y 440~560)에 점선이 돌며 "꺼내라" 한다 */
.st-juicer .cup-hint { display: none; position: absolute; left: 40%; top: 71%; width: 20%; height: 25%;   /* 2026-10-09 : 점선을 걷었다 — 쓰지 않는다 */
  border: 5px dashed #8E4A4A; border-radius: 22px; opacity: 0; }
.st-juicer.full .cup-hint { animation: cup-hint 1s ease-in-out infinite; }
@keyframes cup-hint { 0%, 100% { opacity: .25; scale: 1; } 50% { opacity: .9; scale: 1.06; } }
.st-juicer.full .juice { animation: none; }
/* 컵을 집어 든 동안 — 컵 속 주스가 빠져 보인다 (하나가 둘로 보이지 않게) */
.st-juicer.lift .juice { opacity: 0; }

/* (옛 매트 그림 규칙은 2026-10-08 에 걷었다 — 가운데는 이제 도마다. 위의 "가운데 도마" 절) */
/* 상품 그림(img)도 svg 와 같은 자리를 채운다 */
.ico img, #kitchen .k-ghost img { width: 100%; height: 100%; display: block; object-fit: contain; }

/* ---------- 기다리는 손님 — 화면 위 가운데의 얼굴판 (2026-10-08) ----------
   ⚠ 가운데에 두는 까닭 — 그 아래가 도마·냄비처럼 낮은 도구라 가리지 않는다. 오른쪽은 냉동고, 왼쪽은 귤 상자가 솟는다
   완성품을 여기로 끌어다 놓으면 건넨다. 뒷벽의 작은 창(.k-pass)을 갈음했다 — 그것이 무엇인지 아무도 몰랐다.
   ⚠ 2D 층이다 (k-cam 밖). 손가락 판정이 3D 면보다 먼저 이것을 집도록 z-index 를 준다 */
#kitchen .k-guest {
  position: absolute; left: calc(50% - 75px * var(--k)); top: calc(14px * var(--k)); z-index: 3;
  width: calc(150px * var(--k)); height: calc(150px * var(--k));
  border-radius: 50%;
  background: #FCE4E4;
  border: calc(6px * var(--k)) solid #FCFCE4;
  box-shadow: 0 0 0 calc(3px * var(--k)) #8E4A4A, 0 calc(6px * var(--k)) 0 calc(3px * var(--k)) rgba(142,74,74,.25);
}
#kitchen .k-guest .face {
  position: absolute; inset: 0; border-radius: 50%; overflow: hidden;
  background-repeat: no-repeat;
  background-size: 260% auto;                 /* 판 480 폭의 머리(약 150px)가 동그라미를 채운다 */
  background-position: 50% calc(var(--hy, 20%) * 1.02);
}
#kitchen .k-guest .face.stub { background: radial-gradient(circle at 50% 46%, #FCE4E4 0 30%, #8E4A4A 30% 33%, #9C9CB4 33%); }
#kitchen .k-guest .want {
  position: absolute; right: -14%; bottom: -10%;
  width: 48%; height: 48%;
  border-radius: 50%; background: #FCFCE4;
  box-shadow: 0 0 0 calc(3px * var(--k)) #8E4A4A;
  display: grid; place-items: center;
}
#kitchen .k-guest .want .ico { width: 70%; height: 74%; }
/* 완성품이 매트에 있으면 "여기로" 하고 한 번씩 부른다 — 글자 대신 */
#kitchen.ready .k-guest { animation: guest-call 1.4s ease-in-out infinite; }
/* 여러 개 시킨 손님 (2026-10-09) — 얼굴판 아래 동그라미 줄. 건넬 때마다 하나씩 감귤빛으로 찬다 */
#kitchen .k-guest .cnt {
  position: absolute; left: 50%; top: 100%; translate: -50% calc(12px * var(--k));
  display: flex; gap: calc(7px * var(--k)); padding: calc(6px * var(--k)) calc(10px * var(--k));
  background: #FCFCE4; border-radius: 40px; box-shadow: 0 0 0 calc(3px * var(--k)) #8E4A4A;
  pointer-events: none;
}
#kitchen .k-guest .cnt[hidden] { display: none; }
#kitchen .k-guest .cnt i {
  width: calc(20px * var(--k)); height: calc(20px * var(--k)); border-radius: 50%;
  background: #FCE4E4; box-shadow: inset 0 0 0 calc(3px * var(--k)) #8E4A4A;
}
#kitchen .k-guest .cnt i.on { background: #FCCC6C; animation: goal-on .4s cubic-bezier(.34,1.6,.64,1); }
/* 하나 받았다 — 얼굴판이 한 번 통 튄다 */
#kitchen .k-guest.got { animation: guest-got .4s cubic-bezier(.34,1.6,.64,1); }
@keyframes guest-got { 40% { scale: 1.12; } 100% { scale: 1; } }
@keyframes guest-call { 0%, 60%, 100% { scale: 1; } 75% { scale: 1.08; } 90% { scale: .98; } }

/* 끌어온 것이 이 위에 있다 */
/* 안 되는 것 — 한 번 흔든다. ⚠ transform 은 상자의 자리라 못 쓴다 — translate 로 흔든다 */
#kitchen .nope { animation: k-nope .3s ease-out; }
@keyframes k-nope { 30% { translate: -8px 0; } 70% { translate: 8px 0; } }

/* 손가락을 따라오는 것 — 2D 다. 3D 위에 얹힌 종이 한 장 */
#kitchen .k-ghost {
  position: absolute; left: 0; top: 0; z-index: 9;
  width: 92px; height: 108px; pointer-events: none;
  filter: drop-shadow(0 8px 0 rgba(142,74,74,.22));
}
#kitchen .k-ghost svg { width: 100%; height: 100%; display: block; }

@media (prefers-reduced-motion: reduce) {
  #guest, #stall, #kitchen, #scene .band, #scene-art, #cabin { transition: none; }
  #guest.happy .fig, #guest.sad .fig, #order, #kitchen .nope, .st-juicer .lever { animation: none; }
  #cabin .sell * { transition: none; }
}

/* ==========================================================================
   ★ 2026-10-08 밤 — 손 걸음 엔진 · 제한 시간 · 놓침 · 배우기 (src/js/stall.js)
   ========================================================================== */

/* 도구 위에 오른 것 · 다 된 것 — 도구 그림(300 판) 안의 백분율 자리 */
.k-box .pieces .pc.tok { left: 36%; top: 26%; width: 28%; aspect-ratio: 1; }
.k-box .pieces .pc.tok.out { top: 18%; animation: tok-ready 1s ease-in-out infinite; }
@keyframes tok-ready { 0%, 100% { translate: 0 0; } 50% { translate: 0 -8px; } }
.k-box.lift .pieces .pc.tok.out { opacity: .2; }
/* 그림 없는 도구(포장대)는 서는 밑그림(150x120) 위에 얹는다 */
.st-pack .pieces .pc.tok { left: 30%; top: -22%; width: 40%; }
.st-pack .f { background: #FCE4E4; }

/* 둘레 원 — 누르는 동안 · 저절로 되는 동안 얼마나 되었나 (--p 0~1) */
.k-box .pieces .ring {
  position: absolute; left: 50%; top: 72%; width: 64px; height: 64px; translate: -50% 0; z-index: 2;   /* 그림 판의 위쪽은 화면 밖이다 — 도구 몸통 아래쪽에 띄운다 */
  border-radius: 50%;
  background: conic-gradient(#FCCC6C calc(var(--p, 0) * 1turn), #FCFCE4 0);
  box-shadow: 0 0 0 4px #8E4A4A;
  opacity: 0; transition: opacity .15s;
}
.st-pack .pieces .ring { top: -60%; width: 48px; height: 48px; }
.k-box.busy .pieces .ring { opacity: 1; }
/* 냄비를 저으면 냄비가 달그락거린다 · 포장대는 한 번씩 눌린다 */
.st-pot.busy .bb.sp img { animation: stir .32s ease-in-out infinite; transform-origin: 50% 90%; }
@keyframes stir { 50% { rotate: 3deg; } }
.st-pack.busy .bb { animation: stir .32s ease-in-out infinite; }

/* 연습 주문의 손가락 — 다음 손짓을 직접 해 보인다 (stall.js 의 paintHand). 점선으로 짚지 않는다.
   transform 은 JS 가 animate() 로 준다. 손끝(그림의 왼쪽 위)이 그 자리에 오게 비켜 앉힌다 */
#kitchen .k-hand {
  position: absolute; left: 0; top: 0; z-index: 30;
  width: 78px; height: 94px; margin: -6px 0 0 -26px;
  transform-origin: 26px 6px;
  pointer-events: none;
  filter: drop-shadow(0 6px 0 rgba(142,74,74,.22));
}
#kitchen .k-hand[hidden] { display: none; }
#kitchen .k-hand svg { width: 100%; height: 100%; display: block; }
/* 연습 주문에는 시간이 없다 — 얼굴 둘레의 원을 숨긴다 */
#kitchen .k-guest.practice .clock { display: none; }

/* 손님 얼굴 둘레의 시계 — 남은 시간이 원의 길이다. 숫자를 쓰지 않는다 */
#kitchen .k-guest .clock {
  position: absolute; left: -16%; top: -16%; width: 132%; height: 132%;
  rotate: -90deg; overflow: visible; pointer-events: none;
}
#kitchen .k-guest .clock circle { fill: none; stroke-width: 7; }
#kitchen .k-guest .clock .bg { stroke: #FCFCE4; filter: drop-shadow(0 0 0 #8E4A4A); }
#kitchen .k-guest .clock .left {
  stroke: #8FA87C; stroke-linecap: round;
  stroke-dasharray: 289.03; stroke-dashoffset: 0;
  transition: stroke .3s;
}
#kitchen .k-guest.hurry .clock .left { stroke: #CC846C; }
#kitchen .k-guest.hurry { animation: hurry .5s ease-in-out infinite; }
@keyframes hurry { 50% { translate: 0 -3px; } }

/* 두 번 놓쳤다 — 처음부터. 되감기 화살표 하나뿐이다 */
#stall-fail {
  position: absolute; inset: 0; z-index: 20;
  display: grid; place-items: center;
  background: rgba(142,74,74,.32);
}
#stall-fail[hidden] { display: none; }
/* ★ 2026-10-09 : 팝업이 되었다 — "실패했습니다" + 홈으로 · 다시 도전하기 (팀장 지시). 동그라미 판을 걷었다 */
#stall-fail .card {
  position: relative; display: flex; flex-direction: column; align-items: center;
  width: 460px; padding: 34px 36px 32px; border-radius: 36px;
  background: #FCFCE4; border: 6px solid #8E4A4A;
  box-shadow: 0 10px 0 rgba(142,74,74,.25);
  font-family: "Jua", "Apple SD Gothic Neo", "Malgun Gothic", sans-serif; color: #8E4A4A;
  animation: exit-in .5s cubic-bezier(.34,1.6,.64,1);
}
#stall-fail .broken { width: 76px; height: 92px; filter: grayscale(.8); opacity: .55; }
#stall-fail .broken svg { width: 100%; height: 100%; display: block; }
#stall-fail .title { margin: 14px 0 6px; font-size: 40px; font-weight: normal; line-height: 1.1; }
#stall-fail .sub { margin: 0 0 26px; font-size: 22px; opacity: .75; }
#stall-fail .btns { display: flex; gap: 16px; width: 100%; }
#stall-fail .btns button {
  flex: 1; min-height: 72px; padding: 0 12px; border-radius: 22px; cursor: pointer;
  font: inherit; font-size: 26px; color: #8E4A4A;
  border: 5px solid #8E4A4A; box-shadow: 0 6px 0 #8E4A4A;
  transition: transform .1s ease-out, box-shadow .1s ease-out;
}
#stall-fail .home  { background: #FCFCE4; }
#stall-fail .retry { background: #FCCC6C; }
#stall-fail .btns button:active, #stall-fail .btns button.pressed { transform: translateY(5px); box-shadow: 0 1px 0 #8E4A4A; }

/* 도마 위 재료 — 여럿이 나란히 선다 (left 는 stall.js 가 적는다) */
#kitchen .k-mat .pc.tok { width: 84px; height: 84px; margin: -84px 0 0 -42px; }
/* 다 만든 것(item-*.png, 300x360) — 2026-10-09 : 84 칸에 넣었더니 컵이 귤보다 작았다 (팀장 : "너무 작게 나타난다").
   그림 둘레에 빈 자리가 넉넉한 판이라 같은 칸이면 컵이 반토막이 된다. 120x144 로 키워 컵이 통귤 폭쯤 되게 한다.
   끌 때(.k-ghost)도 같은 크기다 — 집어 드는 순간 작아지면 다른 물건으로 보인다 */
#kitchen .k-mat .pc.tok:has(> img[src*="item-"]) { width: 120px; height: 144px; margin: -144px 0 0 -60px; }
#kitchen .k-ghost.out:has(> img[src*="item-"]) { width: 120px; height: 144px; }
#kitchen .k-ghost.board > img[src*="item-"] { width: 120px; height: 144px; }
#kitchen .k-ghost.board { display: flex; width: auto; }
#kitchen .k-ghost.board > svg, #kitchen .k-ghost.board > img { width: 84px; height: 84px; flex: none; }

/* ==========================================================================
   ★ 2026-10-08 밤 — 지도 : 잠긴 도(잎) · 잠긴 명소(회색 스티커) · 여정 점선 (src/js/map.js)
   ========================================================================== */
/* 잠긴 도 — 잎 더미가 덮는다. 누르면 잎이 한 번 흔들린다 */
#map-land #prov-leaves { pointer-events: none; }
#map-land #prov-leaves .pile { transform-box: fill-box; transform-origin: 50% 50%; }
#map-land #prov-leaves .pile.shake { animation: leaf-shake .42s ease-out; }
@keyframes leaf-shake { 25% { translate: -3px 0; } 60% { translate: 3px 0; } 100% { translate: 0 0; } }
#map-land .prov[data-lock] { cursor: default; }
#map-land #prov-leaves .edge { fill: none; stroke: #8E4A4A; stroke-width: calc(4px * var(--stroke-k, 1)); stroke-linejoin: round; }
/* 잠긴 도 한가운데의 꼭지는 세우지 않는다 — 아직 앉을 자리가 아니다 */
#prov-calyx .calyx.locked { display: none; }

/* 잠긴 명소 — 스티커가 회색으로 바래고 이름표도 물러난다. 감추지 않는다 (다음에 어디로 가는지 보여야 한다) */
#map-props .prop.locked .card .art { filter: grayscale(1) contrast(.85) brightness(1.06); opacity: .78; }
#map-props .tag.locked { filter: grayscale(1); opacity: .7; }
/* 끝낸 명소 — 이름표 앞에 작은 귤 도장 */
#map-props .prop.done .card::after {
  content: ""; position: absolute; right: -6%; top: -4%;
  width: 28%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 40% 38%, #FCFCE4 0 12%, #FCCC6C 13% 62%, #8E4A4A 63% 72%, transparent 73%);
}

/* 여정 — 도 안의 명소를 열리는 차례대로 잇는 점선. 굵기·간격은 map.js 가 도 크기에 맞춰 적는다 */
#map-land #spot-trail { pointer-events: none; }
#map-land #spot-trail path { fill: none; stroke-linecap: round; }
#map-land #spot-trail path.walked { stroke: #8E4A4A; opacity: .8; }
#map-land #spot-trail path.ahead  { stroke: #9C9CB4; opacity: .7; }
#map-land #spot-trail .start { fill: #FCCC6C; stroke: #8E4A4A; stroke-width: 1.2; }

/* ---------- 시연의 손가락 (2026-10-10) ----------
   중앙 제어의 시연(src/js/remote.js)이 어디를 누르는지 보여 준다. 제작대의 연습 손가락(.k-hand)과 같은 그림·크기다.
   ⚠ body 에 바로 붙는다 — #deck 의 transform 밖이라야 fixed 가 화면에 붙는다
   ⚠ 손끝(그림의 26,6)이 가리키는 자리에 오게 margin 으로 당긴다. 자리는 JS 가 transform 으로 준다 */
#demo-hand {
  position: fixed; left: 0; top: 0; z-index: 9999;
  width: 78px; height: 94px; margin: -6px 0 0 -26px;
  transform-origin: 26px 6px;
  pointer-events: none;
  filter: drop-shadow(0 6px 0 rgba(142,74,74,.22));
}
#demo-hand[hidden] { display: none; }
#demo-hand svg { width: 100%; height: 100%; display: block; }
