/* TVA ORIGINAL — 붉은 극장 회전 갤러리 (2026-09-17, r5)
   구조 계약(01-plan.md 1-1~1-9, 06-assembly-plan.md): 섹션 = 한 페이지, 세로 스크롤은
   절대 막지 않는다. 위치는 n(=originalWorks.length) 기반 순환 rel 계산 + data-rel 5개
   CSS 규칙만 쓴다. 좌표 배열·PLANES·nth-child 금지.
   r5: 사장님 지시로 시각 층을 CSS 그라디언트에서 사진 이미지 부품으로 전면 교체했다
   (wall/rail/stage/frame/curtain-left-closed/curtain-left-open .webp,
   work/site-working/assets/original-theater/). JS 동작(캐러셀·진행도 t·입력·다이얼로그·
   접근성)은 r4 그대로, 이 파일은 배치·크기·레이어 순서만 담당한다. */

/* ORIGINAL already reserves the complete header height. A sliding header would
   expose the previous white section during direct entry into the theater. */
.site-nav:has(a[href="#original"][aria-current="location"]).is-visible{transition:none}

.original-table{
  --ot-ink:#f3e9dc;
  --ot-velvet:#2a0a10;
  --ot-brass:#c9ac70;
  --ot-rope-dark:#3a2c1b;
  --ot-rope-mid:#7d6844;
  --ot-rope-low:#6d5a38;
  --ot-rope-deep:#2c2114;
  --ot-rope-highlight:var(--ot-brass);
  --ot-rope-shadow:#1e140b;
  --ot-rope-main:#ba9150;
  --ot-rope-hardware-edge:#ab8a54;
  --ot-rope-hardware-top:#80663f;
  --ot-rope-hardware-bottom:#342819;
  --ot-warm-light:#f5ce8b;
  --ot-light-field-core:rgba(255,224,165,.42);
  --ot-light-field-edge:rgba(213,129,61,.17);
  --ot-light-field-falloff:rgba(59,12,17,0);
  --ot-picture-light-core:#fff1c6;
  --ot-picture-light-mid:#f5ce8b;
  --ot-picture-light-dim:#c99a58;
  --ot-picture-light-h:9px;
  --ot-picture-light-glow:0 0 5px 1px rgba(255,157,54,.94),0 0 14px 4px rgba(245,206,139,.48);
  --ot-nav-line:rgba(243,233,220,.36);
  --ot-meta-h:76px;
  --ot-frame-delay:76ms;
  --ot-frame-settle:260ms;
  height:calc(100svh - var(--nav-height));
  min-height:calc(100svh - var(--nav-height));
  padding:0;
  margin:0;
  overflow:clip;
  scroll-margin-top:var(--nav-height);
  background:#2a0a10; /* wall.webp 로드 전 순간에도 빈 흰 화면이 아니게 */
  display:block;
  position:relative;
}
.original-table__accessible{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- 무대 루트 ---------- */
.ot{
  position:relative;
  width:100%;
  height:100%;
  overflow:clip;
  background:#2a0a10;
}
/* ---------- 1. 벽(배경) ---------- */
.ot__wall{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0;pointer-events:none;display:block}

/* r11 A9(사장님 보충: "모든 이미지는 생성해서 자연스럽게") — 벨벳 뒷막 세로 주름.
   생성 이미지(assets/original-theater/host/backdrop-velvet-tile.webp, 1536×1024,
   가로 반복 타일 — 이음새 실측 확인 완료, 페더 보정 불필요) 도착 — repeat-x 로 얹는다.
   wall 위, 무대 world 아래(z-index 0, wall 과 동률이지만 DOM 순서상 나중이라 위에 그려짐). */
.ot__backdrop{
  position:absolute;inset:0;width:100%;height:62%;
  z-index:0;
  pointer-events:none;
  background-image:url(assets/original-theater/host/backdrop-velvet-tile.webp);
  background-repeat:repeat-x;
  background-size:auto 100%;
  mix-blend-mode:multiply;
  opacity:.85;
}

.ot__rail{
  position:absolute;top:0;left:0;width:100%;height:auto;max-width:none;
  /* Preserve rail.webp's native aspect ratio so the four hanging fixtures in
     its 13..168px alpha bbox are visible again. The two CSS fields below stay
     the only broad ceiling illumination. */
  object-fit:contain;object-position:top center;z-index:9;pointer-events:none;display:block
}

/* r11 A8(사장님 보충: 생성 이미지로) — 스포트라이트 4개 아래 빛 원뿔. 생성 이미지
   (assets/original-theater/host/light-cone.webp, 1024×1536, 검은 배경 위 원뿔 —
   mix-blend-mode:screen 이라 검은 부분은 안 보이고 빛만 얹힌다) 도착 — 꼭짓점을
   rail.webp 조명 4등 위치(실측 12.5/37.5/62.5/87.5%, top:9vh 렌즈 높이)에 맞추고
   액자 상단까지 닿도록 세로로 늘렸다. z-index 3 — world(2) 위, 커튼(7) 아래. 넘김
   시 중앙 두 콘(rel0 위)만 밝기 +20%(C2, .is-boosted). */
/* r14(사장님 "전등은 바뀐 것도 없고" — 원뿔이 거의 안 보임): 원인 실측 — 원뿔의
   z-index 가 3(world=2 보다 위·커튼=7 보다 아래)이라 액자(z-index 4~5)보다 뒤에
   깔려, 커튼 배경에서만 보이고 정작 "액자 위 빛 웅덩이"는 액자 자체에 가려 안
   보였다(스크린샷 확대로 확인, qa-r14/spot-crop1.png). 액자보다 위(z-index 6, 커튼
   7 바로 아래 — 커튼이 닫혀 있을 때는 여전히 안 보임)로 올려 원뿔 아랫부분(가장
   넓고 밝은 부분)이 실제로 액자 위에 겹쳐 그려지게 한다. opacity 도 .75→.88 로
   올려 겹쳤을 때 "한눈에 뚜렷한" 웅덩이가 되게 했다(과하게 하얗게 뜨지 않는 선,
   mix-blend-mode:screen 특성상 밑그림이 완전히 안 보이게 되진 않음 — 실측 확인). */
.ot__spotlight{display:none}
.ot__light-field{
  position:absolute;top:1.5vh;width:32vw;height:60vh;z-index:3;pointer-events:none;
  opacity:.68;filter:blur(10px);
  -webkit-mask-image:conic-gradient(from 0deg at 50% 0%,transparent 0deg 142deg,rgba(0,0,0,.24) 154deg,#000 170deg 190deg,rgba(0,0,0,.24) 206deg,transparent 218deg 360deg);
  mask-image:conic-gradient(from 0deg at 50% 0%,transparent 0deg 142deg,rgba(0,0,0,.24) 154deg,#000 170deg 190deg,rgba(0,0,0,.24) 206deg,transparent 218deg 360deg);
  background:linear-gradient(180deg,var(--ot-light-field-core) 0%,var(--ot-light-field-edge) 46%,var(--ot-light-field-falloff) 100%);
}
.ot__light-field--left{left:9vw;transform:rotate(-3deg)}
.ot__light-field--right{right:9vw;transform:rotate(3deg)}
.ot__spotlight{
  position:absolute;top:9vh;left:0;width:18vw;height:58vh;
  transform:translateX(-50%);
  background-image:url(assets/original-theater/host/light-cone.webp);
  background-repeat:no-repeat;
  background-position:top center;
  background-size:100% 100%;
  mix-blend-mode:screen;
  opacity:.88;
  z-index:6;pointer-events:none;
  filter:brightness(1);
  transition:filter 300ms ease;
}
.ot__spotlight--0{left:12.5%}
.ot__spotlight--1{left:37.5%}
.ot__spotlight--2{left:62.5%}
.ot__spotlight--3{left:87.5%}
/* r14 2차(실측 재확인): z-index 만 올려도 광원 이미지 아랫부분은 색이 옅어(원본
   PNG 자체가 아래로 갈수록 흐려지는 haze 형태) mix-blend-mode:screen 이 크림색
   매트지(이미 밝은 바탕) 위에서는 거의 안 보였다(screen(밝음,옅음)≈밝음 그대로 —
   확대 스크린샷으로 확인, qa-r14/spot-after1-zoom.png). 원뿔(대기감, 커튼 배경용)은
   그대로 두고, 그 위에 실제 "웅덩이"용 별도 레이어(::after, 일반 알파 블렌딩 —
   밑바탕 밝기와 무관하게 항상 보임)를 얹어 액자 상단~매트 구간에 따뜻한 빛을
   덧칠한다. 액자(rel0) 상단이 원뿔 박스 자신의 높이 기준 약 29~33% 지점에 오므로
   그 근방(top 22%~62%)에 넓게 걸쳐 자연스러운 웅덩이를 만든다. */
.ot__spotlight::after{
  content:'';
  position:absolute;left:50%;top:22%;width:150%;height:40%;
  transform:translateX(-50%);
  background:radial-gradient(ellipse at center,rgba(255,214,150,.4) 0%,rgba(255,196,120,.22) 45%,rgba(255,196,120,0) 78%);
  pointer-events:none;
}
/* r14: 가운데 두 콘(rel0 바로 위)은 항상 곁불보다 밝게(사장님 "중앙 콘은 더
   밝게") — 평소에도 opacity 를 살짝 더 준다(.88→.97). 넘김 순간의 짧은 200ms
   부스트(.is-boosted)는 여기서 한 번 더 얹힌다(코디네이터 지시 200ms, 기존
   300ms 에서 단축). */
.ot__spotlight--center{opacity:.97}
.ot__spotlight--center::after{background:radial-gradient(ellipse at center,rgba(255,220,160,.52) 0%,rgba(255,200,130,.28) 45%,rgba(255,200,130,0) 78%)}
.ot__spotlight{transition:filter 200ms ease,opacity 200ms ease}
.ot__spotlight--center.is-boosted{opacity:1}
.ot__spotlight--center.is-boosted::after{background:radial-gradient(ellipse at center,rgba(255,228,175,.62) 0%,rgba(255,210,140,.34) 45%,rgba(255,210,140,0) 78%)}

/* r6: top:14px 이던 값이 rail.webp 의 4번째 조명(우상단)과 겹쳤다(1440·1920 실측).
   레일 높이(10vh, min-height 56px) 아래로 내리고 1.5vh 간격을 둔다. */
/* r9: 로고가 rel+2 액자 모서리와 겹칠 만큼 작아 보였다 — 1.3배(92→120px, 148→193px).
   r11 A6(사장님 지시): 우상단→레일 아래 화면 가운데로 이동(left:50%+translateX(-50%)).
   z-index:10 은 커튼(7)·액자(최대 5)보다 항상 위라 닫혀 있을 때도 보인다(그대로 둠) —
   B6(간판-rel+2 겹침)도 중앙으로 옮기며 자동 해소. */
/* r11 A6 재조정(폐기): 겹침 0을 지키려 틈(1024=36.8px~1920=91.5px)에 맞춰
   38~90px 로 줄였는데, 사장님이 "너무 작다"고 재지적 — r12: 조명 원뿔과 겹쳐도
   된다고 지시를 바꿔주셔서 폭을 220~260px 로 키우고, 세로 위치는 그대로 두되
   조명 콘(z-index:3) 보다 위(z-index:10 유지)라 겹쳐도 로고가 가려지지 않는다. */
/* r14b(감시 요원 재검수 — 17-review-r13.md §5,§9 + 코디네이터 재지시 + 실제 자산
   도착): 1차 시도(무대 90px 하강)는 간판↔액자 여유를 요구 16px 의 3배(48px)까지
   과잉 확보하면서 무대 앞면을 1440 에서 122px·1920 에서 207px 화면 밖으로 밀어
   내고 캐릭터 발 여유를 78→12px 로 깎았다(회귀). 감시 요원 실측 예산: 조명 바
   하단(166)~보이는 액자 상단(250, frame.webp 알파 위 5.59% 제외) 사이 순수 여유는
   84px. "그래도 조금은 내리길 원하신다"(코디네이터, ≤58px 하강)에 맞춰 S=36 을
   쓴다(아래 .ot__stage/.ot__world). 실제 marquee-sign.png(1470×845, 사슬 2줄 +
   전구 사인보드) 도착 후: 사슬은 조명 바와 겹쳐도 된다는 지시라, 슬롯을 이미지
   전체 비율(1470:845)로 잡고 위치만 계산 — 사슬 윗부분이 조명 바 속으로 파고들고
   보드(몸통, 알파 스캔 실측 상단 = 전체 높이의 26.6%)만 액자와 16px 이상 떨어지게
   top 을 잡는다(실측 루프로 확정, 아래 참고). */
.ot__marquee{position:absolute;top:var(--ot-sign-top,0px);left:50%;width:clamp(180px,15vw,230px);height:auto;z-index:10;pointer-events:none;transform:translateX(-50%);filter:drop-shadow(0 2px 6px #0008)}

/* ---------- 3. 무대(윗면+앞면, 2D 사진) ---------- */
/* stage.webp 실측(geometry.json): 앞면 윗선(바닥선) = 이미지 높이의 65.3% 지점.
   .ot__world 와 같은 bottom:27% 기준선 위에 놓고 translateY 로 내려 그 65.3% 선이
   액자 하단(rel0 기준, translateZ 원근 때문에 world 의 순수 27% 선보다 더 아래로
   투영된다)과 만나게 한다. 102vw 폭일 때 이론값(H*0.347≈12.87vw)은 40px 모자랐다
   — 실측(qa-r5)으로 15.67vw 로 올려 gapFrameToStageBaseline = -0.04px(1440) 확인했다. */
.ot__stage{
  position:absolute;left:50%;
  /* r21: 위치·크기를 originals.js 의 layoutTheater() 가 실측으로 정한다(전 폭 공통).
     top 을 쓰므로 bottom 은 auto. max-width:none 이 필요한 이유는 전역
     img{max-width:100%} 가 폭을 상자 폭으로 잘라버리기 때문(390 실측으로 확인). */
  top:var(--ot-stage-top,auto);bottom:var(--ot-stage-bottom,27%);
  width:var(--ot-stage-w,102vw);max-width:none;height:var(--ot-stage-h,auto);
  aspect-ratio:2079/756;
  /* r11 A5(사장님 "저긴 바닥이 아냐" 지적, 기획서 그라운딩절): 15.67vw→17.67vw(+2vw
     1차값) — 캐릭터가 리저 앞면 경계선이 아니라 객석 바닥(카펫)에 서 있게 무대를
     더 내린다. 발선(STAGE_FLOOR_FRAC)은 stageImg 의 rect 를 매 프레임 실측이라
     자동 추종 — 액자만 아래 --ot-world-bottom 루프로 재정렬한다. */
  /* r14b(회귀 확인됨 — 17-review-r13.md R1): 1차 시도(+90px)가 무대 앞면을 r12 대비
     1440 에서 90px·1920 에서 90px 추가로 화면 밖으로 밀었다(122.0 vs r12 32.0).
     간판을 가로로 납작하게(위 .ot__logo, 240×50) 다시 잡으면서 S=0 으로 되돌린다 —
     무대는 r12 값 그대로 유지(무대 잘림도 r12 와 완전히 동일하게 복귀, 아래 표 참고). */
  transform:translateX(-50%) translateY(var(--ot-stage-dy,calc(17.67vw + 36px)));
  transform-origin:50% 0%;
  z-index:1;pointer-events:none;display:block;filter:drop-shadow(0 18px 18px rgba(6,0,0,.48));
}
/* r17(검수 r16 F2 — 1920 발 뜸, r13 부터 있던 기존 문제): 17.67vw 계수는 높이 대비
   폭이 넓은(회귀 게이트 고정 높이 900px) 화면일수록 아래로 내리는 절대량이 커진다
   — 1920 에서는 STAGE_FLOOR_FRAC 로 계산한 바닥선이 905.95px 로 뷰포트(900px) 밖에
   나가 JS 의 화면-밖-방지 clamp(innerHeight-12)가 항상 걸려 발이 18px 가량 뜬다
   (실측). 1440 이하는 이 계수로도 바닥선이 뷰포트 한참 안(≤858)이라 문제없다.
   r17 은 1700px 고정 경계에서 17.67vw→16vw 로 값을 "뚝" 바꿔 그 경계에서 무대가
   28px 튀는 새 문제를 남겼다(검수 r17 §8-4). r18: 경계를 없애고 1440px 부터 1920px
   까지 두 계수를 잇는 직선 하나(10.99vw + 132.192px)로 바꾼다 — 1440 에서
   17.67vw+36px(=290.448px)와 정확히 같은 값에서 시작해(그 지점에서 이미 연속,
   1440 이하 폭은 이 미디어쿼리 밖이라 전혀 영향 없음) 1920 에서 16vw+36px(=343.2px,
   F2 가 확인한 값)과 정확히 같은 값으로 끝난다 — 중간의 모든 폭(1690/1700/1710
   포함)이 이 직선 위에 있어 창을 넓혀도 무대가 한 번도 점프하지 않는다(실측
   확인, node measure.mjs). */
@media(min-width:1440px){
  .ot__stage{transform:translateX(-50%) translateY(var(--ot-stage-dy,calc(10.99vw + 132.192px)))}
}

.ot__world{
  position:absolute;
  left:0;right:0;top:0;
  /* r11 A5: 무대를 17.67vw 로 내린 뒤 액자 하단이 무대 앞선(riser_top, 0.6534)보다
     29px 위로 떠 보였다(1440 실측) — 27%→23.8%로 내려 재정렬(아래 실측 루프로 확정). */
  /* r14b: 위 .ot__stage 주석과 같은 이유로 S=0 복귀 — 액자 열은 r12 위치 그대로,
     간판만 슬롯 비율(240×50)로 바꿔 그 위 빈 공간에 들어간다. */
  bottom:var(--ot-world-bottom,27%);
  transform-style:preserve-3d;
  /* r12(사장님 실측: "로프가 손에 없음"): perspective 를 .ot 에 걸어두면 로프·
     캐릭터(모두 .ot 의 형제 원소)까지 같은 3D 장면에 들어가, data-rel=0 액자가
     translateZ 로 앞에 나올 때 z-index(8)와 무관하게 실제 깊이로 로프를 가렸다
     (elementFromPoint 로 z-index:99999 를 줘도 여전히 가려짐 확인 — z-index 문제가
     아니라 진짜 3D 깊이 문제). 캐러셀에 필요한 perspective 를 .ot__world 자신으로
     내려 3D 장면을 world 내부로 가둔다 — 형제(로프·도르래·캐릭터)는 이제 평범한
     2D 스택으로 z-index 대로만 그려진다. origin-y 는 .ot 기준 42% 와 같은 절대
     위치가 되도록 world 자신의 높이(= .ot 높이 × (1-23.9%)) 기준으로 환산
     (0.42/0.761 ≈ 55.19%) — 캐러셀 소실점은 그대로 유지.
  */
  perspective:1500px;
  perspective-origin:50% 55.19%;
  z-index:2;
}
.ot__works{
  position:absolute;inset:0;
  transform-style:preserve-3d;
  z-index:2;
}

/* ---------- 4. 작품 액자(다섯 자리 모두 같은 규격, frame.webp 공유) ---------- */
.original-table__work{
  --ot-fw:var(--ot-frame-w,clamp(240px,36vh,380px));
  position:absolute;
  left:50%;bottom:0;
  width:var(--ot-fw);
  border:0;padding:0;margin:0;
  background:transparent;
  cursor:pointer;
  transform-origin:50% 100%;
  transition:transform var(--ot-frame-settle,560ms) cubic-bezier(.20,.82,.26,1) var(--ot-frame-delay,76ms),opacity 320ms ease,filter 320ms ease;
  -webkit-tap-highlight-color:transparent;
}
.original-table__work:focus-visible{outline:2px solid var(--ot-brass);outline-offset:6px}
.ot__frame{position:relative;display:block;width:100%;aspect-ratio:297/420}
.ot__art{position:absolute;display:block;object-fit:cover;background:#e7ded0}
.ot__frame-shell{position:absolute;inset:0;width:100%;height:100%;display:block;pointer-events:none}

/* rel±1 25vw/33deg, rel±2 47.15vw/-15.5vh/79deg — r3·r4 실측 최종값 그대로(다섯 자리
   모두 화면 안, 가장자리 간격 ≥12px, 캐릭터와의 관계 포함). 시각 부품만 바뀌었을 뿐
   기하는 동일해야 한다 — 전면 재작성 중 옛 주석(27vw/35deg, r3 튜닝 전 출발값)을 잘못
   베껴 썼던 걸 실측으로 잡았다(rel-1.left 193.1→164.1px 로 어긋나 캐릭터 회피 계산이
   깨졌었다). rel0 기본 220px 는 --ot-frame-z 로 t 에 따라 갱신. */
/* r21 표준화: 다섯 자리의 x·y 를 전부 변수로 뺀다 — originals.js 의 layoutTheater()
   가 폭·높이와 무관하게 같은 식으로 값을 넣는다. 변수가 없으면(스크립트 실패) 아래
   폴백이 r20 배치 그대로다.
   --ot-shift : 마스코트 자리를 왼쪽에 내주려고 액자 열 전체를 오른쪽으로 민 양
   --ot-fy    : 액자 하단을 무대 단 윗면 기준선 위에 올리는 세로 이동
   --ot-step  : 이웃(rel±1) 중심거리 — 지시7(액자 사이 12~18%)의 실제 값이 여기 들어간다 */
.original-table__work[data-rel="0"]{--ot-picture-light:var(--ot-picture-light-core);--ot-picture-light-alpha:1;--ot-picture-light-bottom:.70;transform:translate(-50%,-100%) translate3d(0,0,var(--ot-frame-z-center,-180px));z-index:5;filter:brightness(1)}
.original-table__work::before{content:'';position:absolute;left:27%;right:27%;height:var(--ot-picture-light-h);border-radius:999px;z-index:3;pointer-events:none;background:linear-gradient(90deg,transparent 0%,var(--ot-picture-light,var(--ot-picture-light-mid)) 16%,var(--ot-picture-light-core) 50%,var(--ot-picture-light,var(--ot-picture-light-mid)) 84%,transparent 100%);box-shadow:var(--ot-picture-light-glow);opacity:var(--ot-picture-light-alpha,.78)}
.original-table__work::before{top:2.8%}
.original-table__work[data-rel="-1"],.original-table__work[data-rel="1"]{--ot-picture-light:var(--ot-picture-light-mid);--ot-picture-light-alpha:.90;--ot-picture-light-bottom:.62;z-index:4;filter:brightness(.93)}
.original-table__work[data-rel="-1"]{transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-near,23vw)),0,var(--ot-frame-z-near,-480px)) rotateY(20deg)}
.original-table__work[data-rel="1"]{transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-near,23vw),0,var(--ot-frame-z-near,-480px)) rotateY(-20deg)}
.original-table__work[data-rel="-2"],.original-table__work[data-rel="2"]{--ot-picture-light:var(--ot-picture-light-dim);--ot-picture-light-alpha:.68;z-index:2;filter:brightness(.84)}
.original-table__work[data-rel="-2"]{transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-far,38vw)),0,var(--ot-frame-z-far,-900px)) rotateY(30deg)}
.original-table__work[data-rel="2"]{transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-far,38vw),0,var(--ot-frame-z-far,-900px)) rotateY(-30deg)}
.original-table__work[data-rel="hidden"]{opacity:0;pointer-events:none;z-index:0;transform:translate(-50%,-100%) translate3d(0,0,-720px)}
/* r19(검수 r18 N1 — 768 "짠(tada)" 21% 축소): originals.js applyDesktopHost 는 매
   프레임 rel0 액자의 실제 화면 왼쪽 모서리(getBoundingClientRect)로 가로 예산
   (maxWidth)을 재는데, 768 은 화면이 좁아 예산이 168.96px 뿐이다 — idle(aspect
   0.71)은 그 폭을 꽉 채워 239px 로 크게 나오지만, tada/wave(aspect 0.90, 팔을 벌려
   더 넓다)는 같은 폭 168.96px 에 맞추려 높이를 188.7px 까지 더 눌러야 했다(자세가
   바뀔 때마다 21% 쪼그라들었다 펴지는 것처럼 보임 — 26-review-r18.md N1). 액자
   자체(폭 379.69px, 화면 중앙 고정)는 건드리지 않고 화면 오른쪽에 남는 여유
   (768 에서 frame0 오른쪽 여백 194px)를 끌어써 오른쪽으로만 옮기면, 예산이 커져
   tada/wave 도 idle 처럼 안 눌리고 자연스러운 크기 차이(다른 폭들처럼 ±3% 안)로
   끝난다 — setFootFix/applyDesktopHost 로직은 전혀 안 바꿔도 된다(예산을 재는
   frame0RectD.left 자체가 이 CSS 이동을 따라 자동으로 커짐). 1024px 이상은 이미
   예산이 넉넉해(r18 표 "정상") 이 보정이 필요 없어 범위 밖에 둔다. */
/* r20 의 701~900 전용 액자 밀기는 r21 에서 --ot-shift(전 폭 공통)로 흡수·삭제. */

/* ---------- 6. 커튼(사진 두 장 겹침 + 교차페이드, originals.js applyCurtains 가 위치·
   투명도를 매 프레임 인라인으로 정한다 — 여기서는 레이어·기준점만) ---------- */
.ot__curtain{position:absolute;top:0;bottom:0;left:0;width:100%;overflow:visible;pointer-events:none;z-index:6}
.ot__curtain--right{transform:scaleX(-1)}
.ot__curtain-img{position:absolute;top:0;left:0;width:auto;max-width:none;display:block;will-change:transform,opacity}
.ot__curtain-img--open{opacity:0}

/* ---------- 5. 캐릭터: 왼쪽 커튼을 붙잡았다가 물러난다(r4 규칙 그대로) ---------- */
/* !important 4개 — character-direction.css(내 소유 아님)가 런타임에 주입하는
   `.original-host[data-character-direction]` 규칙과 특이성이 같아 나중에 이긴다. */
.original-table .original-host{
  position:absolute !important;
  left:0 !important;right:auto !important;top:auto !important;bottom:20% !important;
  height:clamp(150px,16vh,190px) !important;
  width:clamp(150px,16vh,190px) !important;
  aspect-ratio:1/1;
  /* r23: 커튼(6)·전경 바닥(8)·로프(9)보다 앞에 선 실제 전경 배우. */
  z-index:10 !important;
  pointer-events:none;
  opacity:0;
  /* r10: 좌우 반전(--host-flip, walk1/walk2 가 about-walk 자산 그대로라 오른쪽을
     보고 있어 왼쪽으로 걸을 땐 scaleX(-1) 필요 — idle/pull 은 항상 1). */
  /* r10: 좌우 반전(--host-flip, walk1/walk2 가 about-walk 자산 그대로라 오른쪽을
     보고 있어 왼쪽으로 걸을 땐 scaleX(-1) 필요 — idle/pull 은 항상 1). r12: perspective
     를 .ot__world 로 내려 캐릭터는 더 이상 3D 장면에 안 걸린다(위 .ot__world 주석) —
     translateZ 보정 불필요, Z=0 그대로. */
  transform:translate(calc(var(--host-x,0px) + var(--host-motion-x,0px)),calc(var(--host-foot-fix,0px) + var(--host-rest-y,0px))) rotate(var(--host-lean,0deg)); /* 2026-10-08 r5: scaleX(var(--host-flip,1)) 삭제 — 완성 그림 반전 금지(정본 v6 규칙 4), 호스트는 늘 정방향 */
  transform-origin:50% 100%;
  /* r7: 60ms 페이드조차 "잡는 구간 첫 프레임부터 커튼 앞에 온전히"를 어겼다 — 40ms
     캡처에서 opacity가 아직 40/60≈0.67이라 반쯤 비쳐 보였다. 즉시 전환으로 바꾼다. */
  transition:none;
}
.original-table .original-host[data-host-visible="true"]{opacity:1}
/* The pose artwork carries the pull/release gesture. Keep the inner stage rigid so the measured
   shoe anchor, its shadow and the foreground floor remain one grounded unit. */
.original-table .original-host__stage{position:absolute;inset:0;transform:none;transform-origin:50% 100%;transform-style:preserve-3d;backface-visibility:hidden}
.original-table .original-host__shadow{
  position:absolute;left:50%;bottom:var(--host-foot-fix,0px);
  width:70%;height:16%;
  transform:translate(-50%,50%);
  background:radial-gradient(ellipse at center,rgba(0,0,0,.35) 0%,rgba(0,0,0,.35) 40%,rgba(0,0,0,0) 72%);
  pointer-events:none;
}
/* The greeting resolves into a quiet living hold instead of a frozen cutout. The cabinet breathes
   from its foot pivot and the floor shadow answers by a few percent; no layout property moves and
   the shoes remain on the same ground line. */
.original-table .original-host[data-pull-phase="settled"] .original-host__stage{
  animation:ot-host-idle-alive 2800ms cubic-bezier(.45,0,.2,1) infinite;
  will-change:transform;
}
.original-table .original-host[data-pull-phase="settled"] .original-host__shadow{
  animation:ot-host-shadow-alive 2800ms cubic-bezier(.45,0,.2,1) infinite;
  will-change:transform,opacity;
}
@keyframes ot-host-idle-alive{
  0%,28%,100%{transform:none}
  48%{transform:none}
  64%{transform:none}
}
@keyframes ot-host-shadow-alive{
  0%,28%,100%{transform:translate(-50%,50%) scaleX(1);opacity:1}
  48%{transform:translate(-50%,50%) scaleX(.975);opacity:.92}
  64%{transform:translate(-50%,50%) scaleX(.992);opacity:.97}
}
/* r12(사장님 실측 지적으로 재설계): 자세별 <img> 11장을 쌓고 opacity 로 끄고 켜던
   방식이 헤드리스 크롬에서 빠른 순환(스트로크 1/2, ~120~140ms 간격) 중 "오래된
   레이어가 그대로 남아 보이는" 합성 버그를 냈다 — canvas drawImage 로 <img> 원소
   자체를 직접 읽으면 내용은 항상 올발랐는데(DOM/CSS 상태 정상), 화면 페인트만
   어긋났다(headless 컴포지터 문제로 추정). <img> 를 하나만 두고 JS 가 매 프레임
   src 만 바꾸는 방식으로 바꿔 이 실패 유형 자체를 제거했다(겹쳐 보일 레이어가
   없음 — 교체가 원자적). 전환은 여전히 즉시(크로스페이드 없음, src 교체 자체가
   그렇다). */
.original-table .original-host__pose-active{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;object-position:center bottom;transition:none;max-width:none;transform-origin:50% 100%}

/* r11 A1: 도르래 — 레일과 같은 층, 항상 보임(로프 x 와 같은 8%). 로프 — 캐릭터의
   손 그립 위치로 JS 가 매 프레임 left/top/height/rotate 를 갱신(applyRope()).
   DOM 순서상 커튼(.ot__curtain) 뒤·캐릭터(.original-host) 앞이지만, 실제 보임은
   z-index 로 정한다: 커튼(7)보다 위, 캐릭터(8)보다 아래 — 로프가 커튼 앞에 있되
   캐릭터 손 뒤에서 이어지는 느낌. */
/* r21 (4) 사장님 "도르래가 허공에 떠 있다": 천장 레일 검은 바 아랫선에서 도르래까지
   내려오는 줄. 요소는 originals.js 가 만들고(updatePulleyRope) top/height/left 를
   실측으로 넣는다 — 폭·높이가 바뀌어도 항상 레일에 붙는다. */
/* r21 (8) 사장님 "마스코트가 허공에 떠 있다": layoutTheater 가 자리를 정하고 발끝이
   무대 단 윗면 접지선에 닿은 뒤에만 보이게 한다(originals.js 가 data-placed 를 단다).
   진입 첫 프레임의 CSS 폴백 크기·위치가 화면에 보이던 것이 그 장면의 원인이었다. */
.original-host:not([data-placed="true"]){visibility:hidden!important}
.ot__pulley-hang{display:none;position:absolute;width:7px;transform:translateX(-50%);z-index:9;pointer-events:none;
  background:linear-gradient(90deg,var(--ot-rope-dark) 0%,var(--ot-rope-mid) 38%,var(--ot-rope-highlight) 52%,var(--ot-rope-low) 70%,var(--ot-rope-deep) 100%);
  box-shadow:0 0 2px rgba(0,0,0,.65)}
.ot__pulley-hang::before{content:'';position:absolute;top:-5px;left:-5px;width:15px;height:10px;border:1px solid var(--ot-rope-hardware-edge);border-radius:2px;background:linear-gradient(var(--ot-rope-hardware-top),var(--ot-rope-hardware-bottom));box-shadow:0 2px 3px #0008}
.ot__pulley{position:absolute;left:23%;top:0;width:clamp(64px,6vw,94px);z-index:9;pointer-events:none;transform:translateX(-50%);opacity:0;visibility:hidden}
.original-host__rope{
  position:absolute;inset:0;width:100%;height:100%;
  /* The cord is in front of the curtains but behind the actor, so the glove actually encloses
     the grip instead of the stroke painting across the hand and forearm. */
  z-index:9;pointer-events:none;opacity:0;will-change:opacity;
}
.original-host__rope svg{display:block;width:100%;height:100%;overflow:visible}
.original-host__rope path{fill:none;stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
.original-host__rope-shadow{stroke:var(--ot-rope-shadow);stroke-width:8;opacity:.76}
.original-host__rope-line{stroke:var(--ot-rope-main);stroke-width:5;filter:drop-shadow(0 1px 1px rgba(0,0,0,.55))}

/* The measured rope-pull host is the sole actor. Keep the later articulated cutout in the DOM
   for markup compatibility, but never composite it over this coherent pose set. */
.original-table .ot__mascot{display:none!important}

/* Desktop only: the header remains opaque.  The overhead rig is fixed to the
   viewport so it can sit in that header without opening the stage crop (opening
   the crop exposed the off-canvas stage/curtain bitmaps and created horizontal
   page overflow). Mobile keeps its existing in-stage routing and crop. */
@media (min-width:768px){
  /* Keep the pulley box as an invisible geometry anchor so the operating cord starts exactly
     below the navigation bar; only the cord itself is visible in the finished composition. */
  .ot__pulley{position:fixed;left:23%;top:19px;width:clamp(56px,3.6vw,60px);z-index:31}
  .ot__pulley-hang{z-index:31}
}


/* Retired cutout rules retained below for markup compatibility. */
.ot__mascot{
  position:absolute;left:clamp(10px,2.1vw,38px);bottom:clamp(18px,3.2vh,42px);
  width:clamp(170px,30vh,310px);height:clamp(181px,32vh,330px);
  z-index:9;pointer-events:none;opacity:0;visibility:hidden;
}
.original-host[data-arrived="true"] + .ot__mascot{opacity:1;visibility:visible}
.ot__mascot-stage{position:absolute;inset:0;display:block;transform-origin:50% 100%;will-change:transform}
.ot__mascot-stage img{display:block;width:100%;height:100%;object-fit:contain;object-position:center bottom;max-width:none}
.ot__mascot-shadow{
  position:absolute;left:50%;bottom:1.2%;width:68%;height:8%;z-index:-1;
  transform:translateX(-50%);border-radius:50%;
  background:radial-gradient(ellipse at center,rgba(0,0,0,.46) 0%,rgba(0,0,0,.25) 44%,rgba(0,0,0,0) 73%);
  filter:blur(2px);transform-origin:50% 50%;
}
.ot__mascot.is-reacting .ot__mascot-stage{animation:ot-mascot-react 420ms cubic-bezier(.22,.8,.3,1) both}
.ot__mascot.is-reacting .ot__mascot-shadow{animation:ot-mascot-shadow 420ms cubic-bezier(.22,.8,.3,1) both}
@keyframes ot-mascot-react{0%{transform:rotate(0)}22%{transform:rotate(-1.2deg)}54%{transform:rotate(.5deg)}100%{transform:rotate(0)}}
@keyframes ot-mascot-shadow{0%,100%{transform:translateX(-50%) scaleX(1);opacity:1}22%{transform:translateX(-50%) scaleX(.95);opacity:.9}54%{transform:translateX(-50%) scaleX(1.03);opacity:1}}

/* Portrait preserves the near pair as shallow, physical side peeks. Together with the four
   thumbnails this makes the five-project carousel legible before any interaction. */
.ot__thumbs{display:none}
@media(max-width:900px) and (orientation:portrait),(max-width:700px){
  /* Keep the far pair paintable at opacity 0: a card can then travel continuously from the
     far matrix into a near side peek. Only truly unused cards are visibility-hidden. */
  .original-table__work[data-rel="-2"],.original-table__work[data-rel="2"]{opacity:0;pointer-events:none;visibility:visible}
  .original-table__work[data-rel="hidden"]{opacity:0;pointer-events:none;visibility:hidden}
  .original-table__work[data-rel="-1"],.original-table__work[data-rel="1"]{opacity:.92;pointer-events:auto;visibility:visible}
  .ot__thumbs{position:absolute;right:clamp(12px,4vw,32px);bottom:clamp(52px,8.2vh,88px);z-index:10;display:flex;gap:clamp(5px,1.1vw,10px);align-items:center}
  .ot__thumb{width:clamp(38px,8.1vw,62px);aspect-ratio:1060/1484;padding:2px;border:1px solid rgba(201,172,112,.88);background:#27070b;box-shadow:0 2px 7px #000a;cursor:pointer}
  .ot__thumb img{display:block;width:100%;height:100%;object-fit:cover}
  .ot__thumb:focus-visible{outline:2px solid var(--ot-brass);outline-offset:3px}
  .ot__mascot{width:clamp(156px,22.5vh,226px);height:clamp(166px,24vh,240px)}
}
@media(min-width:901px) and (max-width:1439px){
  .ot__mascot{width:clamp(160px,26vh,270px);height:clamp(171px,28vh,288px)}
}

/* ---------- 7. 하단 메타(이전/카운터/다음/진행선) ----------
   r16(사장님 "숫자와 화살표 진행스크롤은 제거"): .ot__meta·.ot__nav·.ot__counter·
   .ot__index·.ot__progress·.ot__progress-fill 규칙 전체 삭제(HTML 요소도 같이 삭제
   — 위 index.html 주석 참고). 남는 작품 넘기기 수단(키보드·인접 액자 클릭·휠·스와이프)
   은 이 규칙들과 무관해 그대로 동작한다. */
.ot__counter{position:absolute;left:50%;bottom:clamp(18px,3.4vh,42px);z-index:11;display:flex;align-items:center;gap:clamp(8px,1.1vw,16px);margin:0;color:var(--ot-ink);font:12px/1 var(--mono);letter-spacing:.16em;text-shadow:0 1px 3px #000;transform:translateX(-50%)}
.ot__counter i{display:block;width:clamp(38px,6vw,86px);height:1px;background:linear-gradient(90deg,transparent,var(--ot-nav-line),transparent)}
.ot__counter b{font-weight:400;opacity:.7}

/* ---------- 상세창 이미지(다이얼로그, 기존 유지) ---------- */
.original-detail-image{display:block;width:min(100%,420px);max-height:55vh;object-fit:contain;margin:20px auto}
.original-detail-image[hidden]{display:none}

/* ---------- reduced-motion: 즉시 최종 상태 ---------- */
/* r10: 예비동작·리듬당김·폴짝·착지 스쿼시(@keyframes) 도 이중 방어 — JS 쪽에서도
   motionReduced() 일 때 data-pull-phase 를 비우고 is-hopping/is-landing 을 아예
   추가하지 않지만, CSS 에서도 애니메이션 자체를 꺼서 어떤 경로로도 안 움직인다. */
@media(prefers-reduced-motion:reduce){
  .original-table__work{transition:none}
  .original-table .original-host{transition:none}
  .original-table .original-host__pose-active{transition:none}
  .original-table .original-host__stage,.original-table .original-host__shadow{animation:none !important}
  .ot__mascot.is-reacting .ot__mascot-stage,.ot__mascot.is-reacting .ot__mascot-shadow{animation:none!important}
}
.reduced-motion .original-table__work{transition:none}
.reduced-motion .original-table .original-host{transition:none}
.reduced-motion .original-table .original-host__pose-active{transition:none}
.reduced-motion .original-table .original-host__stage,.reduced-motion .original-table .original-host__shadow{animation:none !important}
.reduced-motion .ot__mascot.is-reacting .ot__mascot-stage,.reduced-motion .ot__mascot.is-reacting .ot__mascot-shadow{animation:none!important}

/* =========================================================
   모바일(조각 5) — 390×844 · 430×932. 같은 부품, 무대만 130vw 로 키워 중앙만 보이게.
   ========================================================= */
@media(max-width:700px){
  html{overflow-x:hidden}
  .original-table{
    display:flex;flex-direction:column;
    height:calc(100svh - var(--nav-height));
  }
  .ot{
    display:block;min-height:0;
    padding-bottom:0;
    /* r15(모바일 하단 조작줄 잘림 실측 — 390×844 에서 .ot__meta 하단이 .ot 하단보다
       96.7px, 430×932 에서 82.9px 아래): 세로 합계(간판 96 + 무대 718.7 + 조작줄 62)가
       .ot 내용 높이(708)를 168.8px 넘겨 overflow:hidden 에 잘렸고, 액자 하단도 .ot 밖
       (+195px)으로 밀려 중앙 작품 아래쪽이 잘렸다. 원인은 world 높이·액자·무대가
       모두 vw/vh 고정식이라 화면 높이에 맞춰 줄어들 길이 없던 것. 이제 조작줄 높이는
       상수(--ot-meta-h)로 고정하고, world 는 남는 높이를 채우며(flex:1), 액자 폭은
       world 높이에서 역산(cqh)해 어떤 화면 높이에서도 조작줄·액자가 .ot 안에 든다.
       r16(사장님 "조작줄 제거 + 빈 공간 남기지 말고 채운다"): 조작줄 자체가 없어져
       --ot-meta-h 를 0 으로 낮춘다 — .ot__world(flex:1)는 이 변수와 무관하게 이미
       flexbox 가 자동으로 남는 높이를 채우지만(형제 .ot__meta 가 사라져 자동 확장),
       .ot__stage 의 bottom 계산은 이 변수를 명시적으로 더하고 있어 0으로 낮추지
       않으면 옛 조작줄 높이(62px)만큼 무대 아래에 빈 띠가 그대로 남는다. */
    --ot-meta-h:0px;
    /* 액자 열 세로 오프셋: 무대 앞면선(riser_top 0.6534)에 액자 하단을 세운다 —
       무대 높이(130vw×756/2079=47.27vw)의 (0.79-0.6534)=13.66% → 6.46vw 를 바닥선
       (world 하단 2px 위)에서 더 올린다. */
    /* r16(실측 발견 — 겹침 0 회귀 대응): r15 가 이 값을 리저선 기준으로 고치며 액자
       하단이 발선 바로 위(390 기준 34px)까지 내려와, 캐릭터가 그 사이에 들어갈
       세로 여유가 거의 없어졌다(조작줄이 있던 시절엔 발선이 훨씬 아래라 안 걸림).
       65px 더 올려 액자와 발선 사이에 실측 ~95-100px 여유를 확보 — 위로는 간판
       (logo) 하단까지 아직 65px 이상 남아 있어 안전하다(실측 확인, qa-r16). 이
       여유 안에서 originals.js 의 동적 상한(겹침 0 안전망)이 최종 크기를 정한다. */
    /* r18(검수 r17 §1 — "모바일 확대가 화면에 안 나온다", 원인은 originals.js 의
       setFootFix 를 자세별로 다시 클램프하도록 고쳐 상당 부분 해소됨. 남은 부족분은
       세로 예산 자체가 작아서 — 이 예산(액자 하단↔발선)을 34px 더 넓힌다. 실측
       (390/430/360, node measure.mjs): 필요한 최소 추가치는 430 이 가장 커서(27.8px)
       그 값에 여유를 둔 34px 를 세 폭 모두에 균일 적용 — originals.js 가 실제 필요한
       크기(0.9 확대 목표)까지만 min() 으로 쓰므로 남는 폭(390/360)은 자동으로 과하게
       커지지 않는다(예산이 남아도 목표 이상 안 큼). 위 간판(logo) 여유는 34px 를
       더해도 세 폭 모두 12px 이상 남아 겹침 0 유지(실측 확인). */
    /* r21: --ot-mob-fy 폐지 — 세로 오프셋은 전 폭 공통 --ot-fy(JS 실측). */
  }

  /* r21: 간판도 전 폭 공통 규칙으로 — 천장 레일 아랫선에 사슬이 닿도록 JS 가 넣는
     --ot-sign-top 을 모바일에서도 그대로 쓴다(r20 까지는 flex 흐름 배치라 변수가 안 먹어
     390 실측 2.8px 뜸). */
  .ot__marquee{
    position:absolute;order:1;flex:0 0 auto;
    margin:0;left:auto;right:10px;top:0;
    /* r14b(감시 요원 재검수 R3 — 17-review-r13.md §9-3[C]): 데스크톱 규칙의
       transform:translateX(-50%) 가 여기서 리셋되지 않아, position:static 흐름
       배치인 모바일에서는 그 절반-폭만큼 왼쪽으로 밀려 있었다(390 실측 중심오차
       −74.8px). 마퀴 이미지가 넓어질수록(1470:845) 더 벌어지는 구조적 버그라
       바로잡는다 — margin:auto 로 이미 중앙 정렬되므로 transform 은 필요 없다. */
    left:50%;right:auto;
    transform:translateX(-50%);
    width:clamp(150px,42vw,180px);height:auto;aspect-ratio:auto;max-height:none;
  }
  .ot__light-field{top:1.5vh;width:78vw;height:48vh;opacity:.6}
  .ot__light-field--left{left:-42vw}.ot__light-field--right{right:-42vw}
  .original-table__work[data-rel="-1"]{transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-near,23vw)),0,var(--ot-frame-z-near,-480px)) rotateY(20deg)}
  .original-table__work[data-rel="1"]{transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-near,23vw),0,var(--ot-frame-z-near,-480px)) rotateY(-20deg)}
  .original-table__work[data-rel="-2"]{transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-far,38vw)),0,var(--ot-frame-z-far,-900px)) rotateY(30deg)}
  .original-table__work[data-rel="2"]{transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-far,38vw),0,var(--ot-frame-z-far,-900px)) rotateY(-30deg)}
  .ot__counter{bottom:max(18px,env(safe-area-inset-bottom));font-size:10px}

  /* r12(사장님 실측 지적: 캐릭터가 중앙 작품을 30% 가림) — 액자 열을 올리고 그
     아래 무대 앞 바닥에 캐릭터 전용 자리를 새로 낸다(권장안 채택). 기존엔 world
     높이가 액자 높이와 정확히 같아(bottom:0 앵커) 바닥 여유가 0이라, bottom:24.5%
     로 세운 캐릭터가 액자 아래쪽과 반드시 겹쳤다. world 를 22vh 만큼 키우고 액자를
     그만큼 위로(bottom:22vh) 올려, 새로 생긴 아래쪽 22vh 띠를 캐릭터 전용 바닥으로
     쓴다. */
  .ot__world{
    position:absolute;
    /* r15: 고정식 높이 대신 간판·조작줄을 뺀 나머지를 채운다. container-type:size 로
       자식(액자)이 이 높이를 cqh 로 읽어 폭을 역산한다. */
    min-height:0;height:auto;
    container-type:normal;
    margin:0;
    inset:0 0 var(--ot-world-bottom,0);
  }
  .ot__works{position:absolute;inset:0}
  /* 무대: 130vw 로 키워 좁은 화면에서도 앞면 곡선이 잘리지 않고 중앙만 보이게.
     translateY 실측(qa-r5): 390 폭 +7.1px, 430 폭 -7.8px — 두 값을 절충해 두 폭 모두
     ≤8px(화면 높이의 1% 미만) 로 맞췄다(완전한 0px 은 두 폭 동시에 안 나온다).
     r12: world 가 22vh 커진 만큼 무대 자체도 같이 내려야 앞선이 새 바닥 위치와
     맞는다(실측 루프로 재조정, 아래 참고). */
  /* r15: 무대 바닥선(STAGE_FLOOR_FRAC 0.79, 무대 하단에서 0.21×47.27vw=9.93vw 위)이
     정확히 world 하단 2px 위(=조작줄 바로 위)에 오도록 .ot 하단 기준으로 세운다.
     translateY(31.5vw) 는 r12 의 "world 22vh 키움"에 맞춘 값이라 함께 제거. */
  /* r21: 모바일 전용 무대 재정의 삭제 — 위 .ot__stage 공통 규칙(JS 실측)이 전 폭을 담당한다. */

  /* r15: 액자 폭 = min(86vw, world 높이에서 역산). 액자 높이 = 폭×1491/1055 이므로
     (world 높이 - 위 여유 2svh - 앞면선 오프셋(2px+6.46vw)) × 1055/1491 이 상한.
     390×844·430×932 는 86vw 가 그대로 들어가고, 실기기 사파리처럼 svh 가 짧으면
     그만큼 자동으로 줄어 .ot 밖으로 나가지 않는다. */
  /* r21: 모바일 전용 액자 폭/세로 오프셋 재정의 삭제 — --ot-frame-w / --ot-fy 공통. */
  /* r21 (7): 모바일 전용 rel±2 재정의 삭제 — 이 규칙은 바깥 액자를 **화면 중앙**
     (translate3d(0,0,-320px))에 숨겨 두고 있어서, 넘길 때 그 액자가 가운데에서
     바깥으로 나오며 rel0 위를 지나갔다(390×844 실측 24,965px² 겹침, opacity 0.54).
     이제 전 폭 공통 규칙(--ot-step2-l/r)을 쓰므로 양옆 가장자리가 살짝 보이고
     전환 중에도 가운데를 지나지 않는다. */

  /* r12: 20vh 로 축소(사장님 지시), 새로 생긴 바닥 띠(world 하단 22vh) 안에 왼쪽
     정렬. 발 기준선은 데스크톱과 같은 무대 앞면선(STAGE_FLOOR_FRAC, JS 가 매 프레임
     실측)을 그대로 쓴다 — 이 CSS 크기는 "안 보일 때 잠깐 스치는 폴백"일 뿐 실제
     크기·위치는 originals.js 의 applyMobileHost 가 매 프레임 정한다. */
  .original-table .original-host{
    position:absolute !important;
    left:0 !important;right:auto !important;bottom:4% !important;
    height:clamp(120px,20svh,150px) !important;
    width:clamp(120px,20svh,150px) !important;
  }

  .ot__mascot{
    left:max(8px,env(safe-area-inset-left));bottom:max(12px,env(safe-area-inset-bottom));
    width:clamp(169px,23.5svh,218px);height:clamp(180px,25svh,232px);
  }
  .ot__thumbs{right:max(10px,env(safe-area-inset-right));bottom:max(56px,8.8svh);gap:5px}
  .ot__thumb{width:clamp(34px,9.2vw,44px)}

  /* r16: 모바일 .ot__meta/.ot__progress/.ot__nav 규칙 삭제(요소 자체가 없어짐). */
}
@media(min-width:701px) and (max-width:900px){
  .ot__marquee{top:0}
}
@media(max-width:700px) and (min-height:0){
  .original-table{padding-bottom:max(env(safe-area-inset-bottom),0px)}
}

/* r44 spatial repair: one camera, one ground plane, equal physical A3 frames. */
.ot__floor{position:absolute;inset:var(--ot-horizon-y,62%) 0 0;z-index:0;pointer-events:none;
  background:radial-gradient(ellipse at 48% 15%,#ba85452b,transparent 60%),
    repeating-linear-gradient(0deg,#d2a67609 0 1px,transparent 1px 4px),
    repeating-linear-gradient(176deg,transparent 0 48px,#09050450 49px 51px,transparent 52px 115px),
    linear-gradient(#332018,#291710 42%,#180d09 100%);
  box-shadow:inset 0 24px 35px #08010080}
.ot__floor::after{content:'';position:absolute;inset:30% 0 0;background:radial-gradient(ellipse at 22% 25%,#ffdca02b,transparent 23%),radial-gradient(ellipse at 77% 18%,#ffdca01c,transparent 24%);filter:blur(12px)}
.ot__backdrop{height:80%}
.ot__rail,.ot__light-field{display:none}
.ot__rig{position:absolute;inset:0;z-index:3;pointer-events:none}
.ot__rig::before{content:'';position:absolute;left:0;right:0;top:10px;height:5px;background:linear-gradient(#68645b,#151311);box-shadow:0 2px 4px #0009}
.ot__lamp{position:absolute;left:var(--lamp-x);top:15px;width:24px;height:40px;transform:translateX(-50%)}
.ot__lamp::before{content:'';position:absolute;left:10px;top:0;width:4px;height:12px;background:#282622}
.ot__lamp i{position:absolute;left:0;top:10px;width:24px;height:29px;border-radius:5px 5px 7px 7px;background:linear-gradient(90deg,#141413,#54514a 40%,#252522 75%,#0e0f0e);border:1px solid #85817455;box-shadow:0 3px 5px #0009}
.ot__lamp i::after{content:'';position:absolute;bottom:-2px;left:1px;right:1px;height:6px;border-radius:50%;background:#fff0c4;box-shadow:0 2px 8px #ffe2a955}
.ot__lamp b{position:absolute;top:40px;left:50%;width:22vw;height:59vh;transform:translateX(-50%);filter:blur(10px);opacity:.7}
.ot__lamp b::before{content:'';position:absolute;inset:0;clip-path:polygon(47% 0,53% 0,100% 100%,0 100%);background:linear-gradient(#ffe2a540,#f4c57b18 45%,transparent 88%)}
.ot__marquee{width:clamp(180px,16vw,244px)}
.ot__world{bottom:0!important;perspective:var(--ot-perspective,1500px);perspective-origin:50% var(--ot-horizon-y,62%)}
.ot__works::before{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:
    radial-gradient(ellipse var(--ot-shadow-far-w,92px) 8px at calc(50% - var(--ot-shadow-far-x,36vw)) var(--ot-shadow-far-y,67%),#07010066 0%,#0701002e 52%,transparent 76%),
    radial-gradient(ellipse var(--ot-shadow-near-w,112px) 10px at calc(50% - var(--ot-shadow-near-x,23vw)) var(--ot-shadow-near-y,69%),#07010078 0%,#07010035 52%,transparent 76%),
    radial-gradient(ellipse var(--ot-shadow-center-w,132px) 12px at 50% var(--ot-shadow-center-y,71%),#0701008f 0%,#07010042 54%,transparent 78%),
    radial-gradient(ellipse var(--ot-shadow-near-w,112px) 10px at calc(50% + var(--ot-shadow-near-x,23vw)) var(--ot-shadow-near-y,69%),#07010078 0%,#07010035 52%,transparent 76%),
    radial-gradient(ellipse var(--ot-shadow-far-w,92px) 8px at calc(50% + var(--ot-shadow-far-x,36vw)) var(--ot-shadow-far-y,67%),#07010066 0%,#0701002e 52%,transparent 76%);
  filter:blur(2px)}
.original-table__work{top:var(--ot-frame-base-y,70%);bottom:auto;transform-origin:50% 100%;transform-style:preserve-3d;--ot-depth-light:1;--ot-picture-light-h:5px}
.original-table__work[data-rel="0"]{transform:translate(-50%,-100%) translate3d(0,0,var(--ot-frame-z-center,-180px));z-index:5;filter:none}
/* Left frames yaw positive and right frames yaw negative: all four faces turn inward. */
.original-table__work[data-rel="-1"]{--ot-depth-light:.93;transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-near,23vw)),0,var(--ot-frame-z-near,-480px)) rotateY(20deg);z-index:4;filter:none}
.original-table__work[data-rel="1"]{--ot-depth-light:.93;transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-near,23vw),0,var(--ot-frame-z-near,-480px)) rotateY(-20deg);z-index:4;filter:none}
.original-table__work[data-rel="-2"]{--ot-depth-light:.84;transform:translate(-50%,-100%) translate3d(calc(-1 * var(--ot-frame-x-far,38vw)),0,var(--ot-frame-z-far,-900px)) rotateY(30deg);z-index:2;filter:none}
.original-table__work[data-rel="2"]{--ot-depth-light:.84;transform:translate(-50%,-100%) translate3d(var(--ot-frame-x-far,38vw),0,var(--ot-frame-z-far,-900px)) rotateY(-30deg);z-index:2;filter:none}
/* The former per-card oval shadow is now a real support in the frame's transparent lower
   strip. Ground shadows live on .ot__works::before and remain fixed during selection. */
.original-table__work::after{content:'';position:absolute;left:50%;bottom:-.1%;width:34%;height:2.2%;transform:translateX(-50%) translateZ(-8px);z-index:0;opacity:var(--ot-support-alpha,.2);
  background:linear-gradient(180deg,#86261a,#3c0705 62%,#180202);clip-path:polygon(12% 48%,88% 48%,100% 100%,0 100%);filter:drop-shadow(0 2px 2px #090100b8)}
.original-table__work[data-rel="0"]{--ot-support-alpha:.18}
.original-table__work[data-rel="-1"],.original-table__work[data-rel="1"]{--ot-support-alpha:.48}
.original-table__work[data-rel="-2"],.original-table__work[data-rel="2"]{--ot-support-alpha:.64}
/* Each artwork light has a visible dark-brass housing physically attached to its top rail. */
.original-table__work::before{top:11.85%;left:33%;right:33%;height:2.15%;transform:translateY(-55%) translateZ(10px);border:1px solid #9b6a35;border-radius:3px;
  background:linear-gradient(180deg,#1b120c 0%,#7b542d 35%,#25150c 70%,#0b0705 100%);box-shadow:0 1px 1px #000,0 4px 5px #ffe4a778,0 10px 18px #e6ab4852;opacity:1}
.ot__frame{z-index:1;transform:translateY(6.4%);transform-style:preserve-3d}
/* The complete framed object follows one continuous oval orbit. Earlier secondary lift,
   overshoot, shadow-pulse and light-catch beats made every wheel notch feel like a heavy
   landing. Keep the frame rigid and let WAAPI own the only moving transform. */
.ot__works.is-turning .original-table__work{transition:none;will-change:transform,opacity}
@media(prefers-reduced-motion:reduce){
  .ot__works.is-turning,
  .ot__works.is-turning .original-table__work,
  .ot__works.is-turning .original-table__work .ot__frame,
  .ot__works.is-turning::before,
  .ot__works.is-turning .original-table__work .ot__frame::after{animation:none!important}
}
.reduced-motion .ot__works.is-turning,
.reduced-motion .ot__works.is-turning .original-table__work,
.reduced-motion .ot__works.is-turning .original-table__work .ot__frame,
.reduced-motion .ot__works.is-turning::before,
.reduced-motion .ot__works.is-turning .original-table__work .ot__frame::after{animation:none!important}
/* A recessed duplicate shell supplies actual Z thickness on the yawed frames. */
.ot__frame::before{content:'';position:absolute;inset:5.6% 6% 6.2%;z-index:0;pointer-events:none;transform:translateZ(-16px);
  background:linear-gradient(90deg,#250303,#64100c 48%,#2a0403);box-shadow:0 0 0 clamp(5px,.7vw,10px) #430706,0 3px 5px #050000b8}
/* Attached housing spill points down over the mat; it is light, not another object shadow. */
.ot__frame::after{content:'';position:absolute;left:10%;right:10%;top:5.6%;height:40%;pointer-events:none;z-index:2;
  background:radial-gradient(ellipse 43% 92% at 50% 0%,#ffe8b04d 0%,#efb65b24 48%,transparent 82%);filter:blur(6px);mix-blend-mode:screen}
.ot__art{z-index:1;object-fit:contain;background:#f3eedf;filter:brightness(var(--ot-depth-light,1))}
.ot__frame-shell{z-index:2;filter:brightness(var(--ot-depth-light,1))}
.ot__curtain-img{filter:drop-shadow(10px 8px 10px #08000080)}
.ot__curtain::after{content:'';position:absolute;left:0;bottom:2%;width:21%;height:3%;background:radial-gradient(ellipse,#060100b0,transparent 70%);filter:blur(5px);z-index:-1}
.ot__mascot{left:clamp(22px,5vw,90px);bottom:1.8%;width:auto;height:clamp(225px,35vh,350px);aspect-ratio:1116/1090;filter:sepia(.06) drop-shadow(1px 2px 2px #1c0a0060)}
.ot__mascot-stage{transform:none!important;animation:none!important;will-change:auto}
.ot__mascot-rig{display:block;width:100%;height:100%;overflow:visible}
.ot__mascot-body{transform:none}
.ot__mascot-arm{transform-box:view-box;transform-origin:850px 540px}
.ot__counter{bottom:22px;font-size:12px;letter-spacing:.1em}
.ot__instruction{position:absolute;left:50%;bottom:44px;transform:translateX(-50%);z-index:11;white-space:nowrap;color:#e5d6be;margin:0;font:11px/1.4 var(--mono);letter-spacing:.03em;text-shadow:0 1px 3px #000}
.original-table .scroll-cue,.original-table .section-scroll-cue{display:none}
@media(max-width:900px) and (orientation:portrait){
  .ot__mascot{left:3%;height:clamp(205px,26svh,285px);bottom:1.5%}
  .ot__thumbs{bottom:9%;right:4%;gap:8px}
  .ot__thumb{width:clamp(40px,8vw,62px);min-height:44px;padding:2px}
  .ot__lamp{--lamp-x:30%!important}.ot__lamp:nth-child(2){--lamp-x:43%!important}.ot__lamp:nth-child(3){--lamp-x:57%!important}.ot__lamp:nth-child(4){--lamp-x:70%!important}
}
@media(max-width:700px){
  .ot__mascot{left:3%;height:clamp(178px,24svh,230px);bottom:2.5%}
  .ot__floor{top:var(--ot-horizon-y,62%)}
  .ot__works::before{background:radial-gradient(ellipse var(--ot-shadow-center-w,96px) 10px at 50% var(--ot-shadow-center-y,66%),#07010086 0%,#0701003d 54%,transparent 78%)}
  .ot__thumbs{right:9px;bottom:11%;gap:5px}
  .ot__thumb{width:clamp(34px,9vw,42px)}
  .ot__instruction{bottom:39px;font-size:9px;left:auto;right:10px;width:45%;transform:none;white-space:normal;text-align:center}
  .ot__counter{left:74%;bottom:20px;font-size:11px;gap:7px}
  .ot__counter i{width:23px}
  .ot__lamp{width:16px;height:30px}
  .ot__lamp i{width:16px;height:20px;top:8px}
  .ot__lamp b{top:30px;width:40vw;height:50vh}
  .ot__lamp::before{left:7px}
}
@media(prefers-reduced-motion:reduce){.ot__mascot-arm{animation:none!important;transform:none!important}}
.reduced-motion .ot__mascot-arm{animation:none!important;transform:none!important}

/* Gallery dragging is navigation, not native image/text selection. */
.ot{-webkit-user-select:none;user-select:none}
.ot img{-webkit-user-drag:none}
@media(max-height:540px) and (orientation:landscape){
  .ot__marquee{width:124px}
  .ot__instruction{bottom:30px;font-size:9px}
  .ot__counter{bottom:12px;font-size:10px}
}
