/* 캐스트 스튜디오 — 영상 탭 ① 캐릭터 · ② 배경 상세(2026-10-03 오너 승인 A · `web/ui/videoLabStudio.js`).
   배치: 왼쪽 = 프로젝트 셸 왼쪽 패널의 목록(에피소드 탭과 같은 패널) · 가운데 = 무대(머리 줄 · 큰 그림 · 버전) · 오른쪽 = 재료.
   ⚠️ 행동 버튼은 스크롤되는 내용 위에 띄우지 않는다 — 머리 줄은 스크롤 밖 고정 칸이다(옛 떠 있는 줄 폐기 · 오너 2026-10-03). */

/* ── 단계 화면: 스튜디오가 남은 높이를 채운다(보드 화면과 같은 규약 · canvas.css `.vlab-screen--board`) ── */
body.shell-v2 .vlab-screen--studio { display: flex; flex-direction: column; height: calc(100dvh - var(--top-h, 56px)); min-height: 0; }
/* 머리 줄은 본문 격자에 맞춘다(2026-10-04 오너: *"화면 폭은 넓은데 칩 줄은 왜 좁은가, 아래 여백도 없다"*) —
   왼쪽 = 무대 제목 시작선(.vstu-stage 의 --sp-xl) · 오른쪽 = 재료 단 내용 끝선(.vstu-insp 의 --sp-lg).
   위아래 --sp-sm(12) — 2026-10-05 단계 칩(두 줄 높이)을 걷고 [캐릭터 | 배경] 한 줄이 되며 15 → 12(시안 승인 · 머리 줄 119 → 60).
   ⚠️ 기본 `.vlab-tabs-wrap`(max-width 1080 · margin auto)을 그대로 두면 세로 flex 안에서 내용 폭으로 줄어 가운데 몰린다. */
body.shell-v2 .vlab-screen--studio > .vlab-tabs-wrap { flex: none; max-width: none; margin: 0; padding: var(--sp-sm) var(--sp-lg) var(--sp-sm) var(--sp-xl); }
body.shell-v2 .vlab-screen--studio > .vlab-screen-sub { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; border-top: 1px solid var(--hairline); }
.vstu-host { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.vstu-host > .vstu, .vstu-page > .vstu { flex: 1 1 auto; }
/* 페이지 모드 껍데기(모달 모듈의 `inlineShell` — 오버레이 없이 본문에 선다) */
.vstu-page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.vstu-empty { flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-sm); color: var(--ink-48); font-size: var(--fs-caption); }
/* 영상 프로젝트 없는 기획안의 쪽 안내 — 카드 없이 가운데 두 줄(제목 = 왜 비었나 · 설명 = 언제 생기나 · 팀리드 시안 검토) */
.vlab-noproj-side { text-align: center; padding: var(--sp-xl); }
.vlab-noproj-side-title { color: var(--ink-80); font-size: var(--fs-body); font-weight: 500; }
.vlab-noproj-side-sub { max-width: 480px; }

/* ── 스튜디오 ── */
.vstu { display: grid; grid-template-columns: minmax(0, 1fr) 352px; min-height: 0; height: 100%; }
.vstu-stage { display: flex; flex-direction: column; min-width: 0; min-height: 0; padding: 0 var(--sp-xl) var(--sp-lg); }
.vstu-head { display: flex; align-items: center; gap: var(--sp-sm); min-height: 68px; flex: none; }
.vstu-title { display: flex; align-items: center; gap: var(--sp-xs); min-width: 0; flex: 0 1 auto; }
.vstu-sp { flex: 1 1 auto; }
.vstu-actions { display: flex; align-items: center; gap: var(--sp-xs); flex: none; }
.vstu-name {
  appearance: none; border: 0; background: none; color: var(--ink); font: inherit;
  font-size: 22px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.25;
  padding: 4px 8px; margin-left: -8px; border-radius: var(--r-sm); min-width: 6ch; max-width: 100%;
  field-sizing: content;
}
.vstu-name::placeholder { color: var(--ink-48); }
.vstu-name:hover:not([readonly]) { background: var(--surface-1); }
.vstu-name:focus { outline: none; background: var(--surface-1); box-shadow: var(--ring-focus); }
.vstu-chip { flex: none; font-size: 12px; line-height: 1; padding: 6px 10px; border-radius: var(--r-pill); border: 1px solid var(--hairline); color: var(--ink-48); white-space: nowrap; }
.vstu-chip--ok { color: var(--positive); border-color: rgba(34, 197, 94, 0.4); }
.vstu-chip--run { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent); }
.vstu-chip--bad { color: var(--danger); border-color: var(--danger-ring); }
.vstu-save { flex: none; font-size: 12px; color: var(--ink-48); white-space: nowrap; }
.vstu-save--bad { color: var(--danger); }

/* 무대 — 큰 그림 · 빈 자리 · 뼈대. 그림은 칸 안에 들어맞게(잘리지 않게) 줄인다. */
.vstu-canvas { position: relative; flex: 1 1 auto; min-height: 0; border-radius: var(--r-xl); background: var(--surface-1); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.vstu-canvas > img, .vstu-canvas > .vstu-shot > img { display: block; max-width: 100%; max-height: 100%; object-fit: contain; }
.vstu-shot { position: relative; display: flex; max-width: 100%; max-height: 100%; cursor: zoom-in; border-radius: var(--r-md); }
.vstu-shot:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.vstu-qlabel { position: absolute; font-size: 11px; line-height: 1; padding: 5px 8px; border-radius: 7px; background: rgba(0, 0, 0, 0.55); color: #fff; pointer-events: none; }
.vstu-qlabel--0 { left: 10px; top: 10px; } .vstu-qlabel--1 { left: calc(50% + 10px); top: 10px; }
.vstu-qlabel--2 { left: 10px; top: calc(50% + 10px); } .vstu-qlabel--3 { left: calc(50% + 10px); top: calc(50% + 10px); }
.vstu-ghost { display: grid; gap: 14px; width: 84%; max-height: 86%; aspect-ratio: 3 / 2; }
.vstu--char .vstu-ghost { grid-template-columns: repeat(3, 1fr); }
.vstu--loc .vstu-ghost { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; aspect-ratio: 16 / 9; }
.vstu-ghost > span { border-radius: var(--r-md); background: var(--surface-2); }
/* 만드는 중 반짝임 — 무대 뼈대와 버전 칸이 한 규칙을 쓴다(리뷰 M23 — 같은 그라디언트가 두 벌이었고 버전 칸은 모션 줄이기를 무시했다).  ⚠️ 버전 칸은 `.vstu-ver.vstu-ver--run` 두 클래스로 — 한 클래스면 아래 `.vstu-ver`(같은 특이도 · 뒤에 선다)의 background 에 덮여 반짝임이 안 보였다(4차 검증).*/
.vstu-ghost.is-running > span, .vstu-ver.vstu-ver--run { background: linear-gradient(110deg, var(--surface-2) 30%, var(--surface-3) 50%, var(--surface-2) 70%); background-size: 220% 100%; animation: vstu-shimmer 1.4s linear infinite; }
@keyframes vstu-shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
@media (prefers-reduced-motion: reduce) { .vstu-ghost.is-running > span, .vstu-ver.vstu-ver--run { animation: none; } }
.vstu-ghost-word { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); font-size: var(--fs-caption); color: var(--ink-80); padding: 6px 12px; border-radius: var(--r-pill); background: rgba(0, 0, 0, 0.45); }
/* ⚠️ 가운데를 transform 으로 잡지 않는다 — `.btn:hover` 의 scale(1.02)(특이도가 더 높다)이 translateX(-50%) 를 덮어 호버하면 단추가 폭의 절반만큼 미끄러졌고,
   포인터가 빠져나가며 들어감·나감이 되풀이돼 툴팁이 못 떴다(2026-10-05 · 가드 dropCentering.test.mjs). */
.vstu-drop { position: absolute; inset-inline: 0; bottom: var(--sp-md); width: fit-content; margin-inline: auto; }
.vstu-canvas.is-dragover { box-shadow: inset 0 0 0 2px var(--accent); }

.vstu-caps { display: grid; grid-template-columns: repeat(3, 1fr); flex: none; padding-top: var(--sp-xs); color: var(--ink-48); font-size: 12px; text-align: center; }
/* 그림 + 칸 이름 틀(`studioFrame` — 2026-10-04 오너). 칸 이름은 카드 **안**, 그림 바로 아래 같은 폭이라 세 칸의 가운데와 맞는다.
   크기는 카드(container-type: size)에서 잰다 — 칸 이름 줄 몫(--vstu-cap-h)을 그림 높이에서 뺀다. */
.vstu--char .vstu-canvas { container-type: size; --vstu-cap-h: 28px; }
.vstu-frame { display: flex; flex-direction: column; align-items: stretch; min-width: 0; min-height: 0; }
.vstu-frame > .vstu-caps { padding-top: var(--sp-xs); word-break: keep-all; overflow-wrap: anywhere; text-wrap: balance; }
/* 칸 이름 열은 줄어들 수 있어야 한다(minmax 0) — 글자 최소 폭이 틀을 그림보다 넓히면 그림이 틀 왼쪽에 붙고 칸 이름 가운데가
   그림 칸에서 7·20·33px 밀렸다(2026-10-04 그림 있는 상태 · 1024 영어 'Front full body · head excluded' — 한 줄 최대 폭이 틀 폭이 됐다). 틀 폭은 그림이 정한다. */
.vstu-frame > .vstu-caps { grid-template-columns: repeat(3, minmax(0, 1fr)); contain: inline-size; } /* 글자는 틀 폭에 기여하지 않는다(늘어나 채울 뿐) */
.vstu-frame--ghost > .vstu-caps { column-gap: 14px; } /* 빈 자리 칸 사이 간격(.vstu-ghost gap)과 같아야 가운데가 맞는다 */
/* 그림은 카드 안 여백(--vstu-inset = --sp-lg)을 두고 선다 — 무대가 그림보다 납작하지 않으면(1024 페이지 · 390 · 배경 전 폭) 그림이 카드 좌우
   끝에 붙고 둥근 카드 모서리 안에 각진 그림 모서리가 섰다(2026-10-04 그림 있는 상태 실측 — 좌우 여백 0). 그림 모서리도 둥글게 자른다. */
.vstu-canvas { --vstu-inset: var(--sp-lg); }
.vstu-shot { overflow: hidden; }
.vstu-frame > .vstu-shot { max-width: calc(100cqw - 2 * var(--vstu-inset)); }
.vstu-frame > .vstu-shot > img { display: block; width: auto; height: auto; max-width: calc(100cqw - 2 * var(--vstu-inset)); max-height: calc(100cqh - var(--vstu-cap-h) - 2 * var(--vstu-inset)); object-fit: contain; }
.vstu-frame--ghost { width: min(84cqw, calc((86cqh - var(--vstu-cap-h)) * 1.5)); }
.vstu-frame--ghost > .vstu-ghost { width: 100%; max-height: none; }
/* [이미지 올리기](`.vstu-drop` — 무대 바닥 --sp-md 위 절대 위치)가 서면 빈 자리 틀은 그 버튼 띠(바닥 여백 + 버튼 28 + 틈 --sp-xs)를
   위아래로 비운 높이 안에 든다 — 1단(무대 430px) · 1024 시트에서 버튼이 칸 이름 줄에 11px 겹쳤다(2026-10-04 레이아웃 감사). 배경 빈 자리도 같다. */
.vstu-canvas { --vstu-drop-band: calc(var(--sp-md) + 28px + var(--sp-xs)); }
.vstu-canvas:has(> .vstu-drop) > .vstu-frame--ghost { width: min(84cqw, calc((100cqh - var(--vstu-cap-h) - 34px - 2 * var(--vstu-drop-band)) * 1.5)); } /* 34px = 칸 이름이 세 줄까지 접힐 몫(영어 1024 두 줄 · 800 세 줄) */
.vstu-canvas:has(> .vstu-drop) > .vstu-ghost { max-height: calc(100% - 2 * var(--vstu-drop-band)); }
.vstu-caps:empty { display: none; }
.vstu-notes { display: flex; flex-direction: column; gap: var(--sp-xs); flex: none; padding-top: var(--sp-sm); }
.vstu-notes:empty { display: none; }
.vstu-warn { font-size: 13px; line-height: 1.5; color: var(--ink-80); padding: 8px 12px; border-radius: var(--r-md); background: var(--surface-1); border: 1px solid var(--hairline); }

/* 버전 줄 — 지나간 생성. 누르면 큰 그림으로 고르고 다시 누르면 푼다. 지우기는 호버(·초점) 때만 보인다.
   ⚠️ 고른 타일의 테(outline 2px + offset 2px = 타일 밖 4px)가 가로 스크롤 상자에 잘린다 — `overflow-x: auto` 는 세로도 auto 로
      만들어 왼쪽 끝 타일의 왼쪽과 모든 타일의 아래가 잘렸다(2026-10-03 정리). 그 4px 를 안여백으로 주고 같은 값의 음수 마진으로
      되돌린다 — 첫 타일의 x 와 무대(위 큰 그림)의 높이는 그대로다. */
.vstu-vers { display: flex; gap: 10px; align-items: center; flex: none; padding: var(--sp-sm) 4px 4px; margin: 0 -4px -4px; overflow-x: auto; scrollbar-width: thin; }
.vstu-vers:empty { display: none; }
.vstu-ver { position: relative; flex: none; width: 120px; aspect-ratio: 3 / 2; padding: 0; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-2) center / cover no-repeat; cursor: pointer; overflow: hidden; }
.vstu--loc .vstu-ver { aspect-ratio: 16 / 9; }
.vstu-ver > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vstu-ver.is-on { outline: 2px solid var(--ink); outline-offset: 2px; }
.vstu-ver:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.vstu-ver:disabled { cursor: default; }
.vstu-ver-no { position: absolute; left: 6px; top: 6px; font-size: 11px; font-weight: 600; line-height: 1; padding: 4px 6px; border-radius: 6px; background: rgba(0, 0, 0, 0.6); color: #fff; }
.vstu-ver-ok { position: absolute; right: 6px; top: 6px; width: 18px; height: 18px; border-radius: 50%; background: var(--positive); color: #000; display: grid; place-items: center; font-size: 11px; font-weight: 700; }
.vstu-ver-tag { position: absolute; left: 6px; bottom: 6px; font-size: 10px; line-height: 1; padding: 4px 7px; border-radius: var(--r-pill); background: rgba(0, 0, 0, 0.6); color: #fff; }
.vstu-ver-tag--bad { color: var(--danger); }
.vstu-ver-x { position: absolute; right: 4px; bottom: 4px; opacity: 0; transition: opacity .15s ease; }
.vstu-ver-wrap { position: relative; flex: none; }
.vstu-ver-wrap:hover .vstu-ver-x, .vstu-ver-wrap:focus-within .vstu-ver-x { opacity: 1; }
@media (hover: none) { .vstu-ver-x { opacity: 1; } }
.vstu-vers-full { flex: none; font-size: 11px; color: var(--ink-48); padding: 4px 8px; border-radius: var(--r-pill); border: 1px solid var(--hairline); }

/* 재료 — 오른쪽 단(단독 스크롤). 구획 사이는 헤어라인 하나. */
.vstu-insp { min-height: 0; overflow-y: auto; border-left: 1px solid var(--hairline); padding: 4px var(--sp-lg) var(--sp-lg); scrollbar-gutter: stable; }
.vstu-sec { padding: var(--sp-md) 0; border-bottom: 1px solid var(--hairline); }
.vstu-sec:last-child { border-bottom: 0; }
.vstu-sec-hd { display: flex; align-items: center; gap: 6px; min-height: 28px; margin-bottom: var(--sp-xs); }
/* (접이 구획 `.vstu-sec--fold` 는 2026-10-05 오너 ⑪ 로 걷었다 — '목소리 · 행동'이 늘 펼쳐진다. 그것이 유일한 접이 구획이었다.) */
/* 목소리 칸 — 여러 줄로 감기는 한 문장(⑪ · 줄바꿈은 넣지 않는다 — `videoLabCharacterModal` 목소리 칸 주석). */
.vstu-insp textarea.vstu-autogrow { field-sizing: content; min-height: 0; max-height: 240px; resize: none; }
.vstu-sec-title { flex: 1 1 auto; margin: 0; font-size: var(--fs-fine); font-weight: 600; color: var(--ink-80); }
.vstu-sec-tools { display: flex; align-items: center; gap: 2px; flex: none; }
.vstu-sec-bd { display: flex; flex-direction: column; gap: var(--sp-sm); }
/* 캔버스 카드 구획(배경 카드의 [수정] — `openLocationModal` 의 cardSection) — 꽂힌 상자 안의 칸도 다른 구획과 같은 간격. */
.vstu-cardsec-body { display: flex; flex-direction: column; gap: var(--sp-sm); }
.vstu-field { display: flex; flex-direction: column; gap: 6px; }
.vstu-field-label { font-size: 12px; color: var(--ink-48); }
.vstu-req { color: var(--ink-48); }
.vstu-insp textarea, .vstu-insp input[type="text"] {
  width: 100%; box-sizing: border-box; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-1);
  color: var(--ink); font: inherit; font-size: 14px; line-height: 1.6; padding: 10px 12px; resize: vertical;
}
.vstu-insp textarea { field-sizing: content; min-height: 4.6em; }
.vstu-insp textarea:focus, .vstu-insp input[type="text"]:focus { outline: none; border-color: var(--accent); box-shadow: var(--ring-focus); }
.vstu-insp textarea[readonly], .vstu-insp input[readonly] { color: var(--ink-80); background: transparent; resize: none; }
/* 필수 칸 안내(2026-10-16 오너 · `videoLabStudioFocus.requiredFieldHint`) — 생성에 꼭 필요한 칸이 비면 테두리는 위험색, 칸 바로 아래 빨간 한 줄.
   [생성]을 누르면 그 칸으로 데려가며 안내를 한 번 흔든다(`is-flash`). 초점이 들어와도 위험색을 지킨다(무엇이 비었는지가 먼저다). */
.vstu-insp textarea.is-need, .vstu-insp input[type="text"].is-need, .vstu-insp textarea.is-need:focus, .vstu-insp input[type="text"].is-need:focus { border-color: var(--danger); }
/* 같은 장치를 쓰는 스튜디오 밖 칸 — 노드 편집 창 프롬프트([AI로 고치기]) · 콘티 초안 원문([시작]). */
.vlab-text.is-need, .vlab-text.is-need:focus, .vlab-draft-source.is-need, .vlab-draft-source.is-need:focus { border-color: var(--danger); }
.vstu-name.is-need { box-shadow: inset 0 -1px 0 var(--danger); }
.vstu-name.is-need:focus { box-shadow: var(--ring-focus), inset 0 -1px 0 var(--danger); }
.vstu-stage > .vstu-need { padding: 0 0 var(--sp-xs); }
.vstu-need.is-flash { animation: vstu-need-shake 0.36s ease-in-out; }
@keyframes vstu-need-shake { 0%, 100% { transform: none; } 25% { transform: translateX(-4px); } 75% { transform: translateX(4px); } }
@media (prefers-reduced-motion: reduce) { .vstu-need.is-flash { animation: none; font-weight: 600; } }
/* 읽기 전용 칸은 입력을 권하지 않는다(2026-10-04 리드 · 감사 5) — 편집칸 모양(테두리·안여백·크기조절·큰 최소 높이) 없이 글만 선다.
   비어 있으면 '아직 적힌 내용이 없습니다'(자리표시 — 모달 JS 가 읽기 전용일 때 바꿔 단다). 두 상세 공용.
   ⚠️ 입력칸 테두리 가드(`v2/fieldAffordance.test.mjs` ③)는 비활성과 같이 읽기 전용도 뺀다 — 고칠 수 없는 칸은 입력칸이 아니다. */
body.shell-v2 .vstu-insp :is(textarea, input)[readonly] { border-color: transparent; padding: 0; min-height: 0; box-shadow: none; }
.vstu-insp ::placeholder { color: var(--ink-48); }
.vstu-tag { flex: none; font-size: 11px; line-height: 1; padding: 4px 7px; border-radius: var(--r-pill); background: var(--surface-2); color: var(--ink-48); }

/* 화풍 — 고른 것 한 줄(누르면 작은 창에 격자) */
.vstu-pick { appearance: none; display: flex; align-items: center; gap: var(--sp-sm); width: 100%; padding: 8px 10px 8px 8px; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-1); color: var(--ink); font: inherit; font-size: 14px; text-align: left; cursor: pointer; }
.vstu-pick:hover { background: var(--surface-2); }
.vstu-pick:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.vstu-pick:disabled { cursor: default; color: var(--ink-48); background: transparent; }
.vstu-pick.is-readonly:disabled { color: var(--ink-80); border-color: transparent; padding-inline: 0; } /* 읽기 전용 = 고른 화풍 한 줄(캐럿은 JS 가 안 그린다) */
.vstu-pick-thumb { flex: none; width: 34px; height: 51px; border-radius: 6px; object-fit: cover; background: var(--surface-2); display: grid; place-items: center; font-size: 11px; color: var(--ink-48); }
.vstu-pick-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vstu-pick-caret { flex: none; color: var(--ink-48); }

/* 참조 두 칸 */
.vstu-refs { display: flex; gap: 10px; flex-wrap: wrap; }
/* [레퍼런스로 프로필 쓰기](2026-10-19) — 참조 칸 바로 아래 한 줄 */
.vstu-refs-actions { margin-top: 10px; }
.vstu-refs-actions[hidden] { display: none; }
.vstu-ref { position: relative; width: 76px; height: 76px; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-1) center / cover no-repeat; padding: 0; overflow: hidden; }
.vstu-ref > img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
.vstu-ref--add { border-style: dashed; color: var(--ink-48); display: grid; place-items: center; cursor: pointer; }
.vstu-ref--add:hover { color: var(--ink); background-color: var(--surface-2); }
.vstu-ref--add:disabled { cursor: default; opacity: .5; }
.vstu-ref-x { position: absolute; right: 2px; top: 2px; opacity: 0; transition: opacity .15s ease; }
.vstu-ref:hover .vstu-ref-x, .vstu-ref:focus-within .vstu-ref-x { opacity: 1; }
@media (hover: none) { .vstu-ref-x { opacity: 1; } }

/* 작은 창(생성 · 화풍 · 더보기) — `.pop`(trendControls.css) 위에 얹는다 */
.vstu-pop--end { left: auto; right: 0; transform-origin: top right; }
.vstu-pop { z-index: 60; }
.vstu-gen { width: min(340px, calc(100vw - 32px)); display: flex; flex-direction: column; gap: var(--sp-xs); padding: 6px; }
.vstu-gen textarea { width: 100%; box-sizing: border-box; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-1); color: var(--ink); font: inherit; font-size: 14px; line-height: 1.5; padding: 10px 12px; resize: none; min-height: 4.5em; }
.vstu-gen textarea:focus { outline: none; border-color: var(--accent); }
.vstu-gen-row { display: flex; align-items: center; gap: var(--sp-xs); }
.vstu-gen-row .vlab-model { max-width: 170px; }
/* 모델 셀렉트(`.vlab-model--md` — `flex: 0 0 auto` · 바닥 180px)가 줄어들 수 있어야 줄이 창 밖으로 넘치지 않는다(배경 실측 1512·1000px —
   셀렉트 170px 일 때 [생성]이 창 오른쪽 끝에서 34px 밖으로 나갔다 · 캐릭터도 모델 이름이 길면 같은 넘침이라 2026-10-03 정리에서 두 상세에
   함께 건다). 긴 모델 이름은 셀렉트 안에서 … 로 잘린다(`.vlab-model` 의 text-overflow). 버튼은 글자 폭을 지킨다. */
.vstu-gen-row .vlab-model { flex: 0 1 auto; min-width: 0; }
.vstu-gen-row > .btn { flex: none; }
/* 배경 [변형 만들기] 작은 창(2026-10-19) — 바꿀 것 한 줄(생성 창 글상자와 같은 모양) + 예시 칩(입력란을 채울 뿐) */
.vstu-gen .vstu-var-input { width: 100%; box-sizing: border-box; border-radius: var(--r-md); border: 1px solid var(--hairline); background: var(--surface-1); color: var(--ink); font: inherit; font-size: 14px; line-height: 1.5; padding: 9px 12px; }
.vstu-gen .vstu-var-input:focus { outline: none; border-color: var(--accent); }
.vstu-var-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* 변형 카드 머리 안내 — 이름 아래 · 그림 위 한두 줄(낮은 글 · 기준 이름만 링크) */
.vstu-variant { display: flex; flex-direction: column; gap: 2px; font-size: 13px; line-height: 1.5; color: var(--ink-80); }
.vstu-variant[hidden] { display: none; }
.vstu-variant-head { display: flex; align-items: center; gap: 4px; min-width: 0; flex-wrap: wrap; }
.vstu-variant-ico { flex: none; color: var(--ink-48); margin-right: 2px; }
.vstu-variant-link { appearance: none; border: 0; background: none; padding: 0; font: inherit; color: var(--ink); font-weight: 600; text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--ink-48); cursor: pointer; border-radius: var(--r-sm); }
.vstu-variant-link:hover { text-decoration-color: currentColor; }
.vstu-variant-link:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.vstu-variant-fact { font-size: 12px; color: var(--ink-48); }
.vstu-styles { width: min(420px, calc(100vw - 32px)); display: grid; grid-template-columns: repeat(4, 1fr); align-items: start; gap: 8px; padding: 6px; max-height: calc(100dvh - var(--top-h, 56px) - 2 * var(--sp-sm)); overflow-y: auto; overscroll-behavior: contain; }
.vstu-style { appearance: none; padding: 0; border-radius: var(--r-md); overflow: hidden; border: 1px solid var(--hairline); background: var(--surface-1); color: var(--ink); font: inherit; font-size: 11px; cursor: pointer; text-align: left; }
.vstu-style:hover { border-color: var(--divider-soft); }
.vstu-style.is-on { outline: 2px solid var(--ink); outline-offset: 1px; }
.vstu-style:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.vstu-style img, .vstu-style-auto { width: 100%; aspect-ratio: 2 / 3; object-fit: cover; display: grid; place-items: center; color: var(--ink-48); font-size: 12px; background: var(--surface-2); }
.vstu-style-name { display: block; padding: 5px 6px 6px; line-height: 1.35; word-break: keep-all; overflow-wrap: break-word; text-wrap: balance; } /* 이름이 유일한 구분 글자라 자르지 않고 줄을 바꾼다(8개 중 5개 말줄임 · 영어 7개 — 2026-10-04 감사) */
/* 중국어는 띄어쓰기가 없어 keep-all 이면 이름 전체가 한 덩어리 → 마지막 한 글자만 다음 줄로 떨어졌다('透明图层数码绘 / 画' · 2026-10-04). 글자 사이에서 고르게 꺾는다. */
.vstu-style-name:lang(zh) { word-break: normal; }
.vstu-menu { min-width: 200px; display: flex; flex-direction: column; padding: 2px; }
.vstu-menu-item { appearance: none; border: 0; background: none; color: var(--ink); font: inherit; font-size: 13px; text-align: left; padding: 9px 10px; border-radius: var(--r-sm); cursor: pointer; }
.vstu-menu-item:hover:not(:disabled), .vstu-menu-item:focus-visible { background: var(--surface-2); outline: none; }
.vstu-menu-item:focus-visible { box-shadow: var(--ring-focus); } /* 호버와 구분되는 초점 링 */
.vstu-menu-item:disabled { color: var(--ink-48); cursor: default; }
.vstu-menu-item.is-danger { color: var(--danger); }
.vstu-menu-sep { border: 0; border-top: 1px solid var(--hairline); margin: 4px 6px; }

/* 시트 모드(캔버스에서 연 경우) — 오버레이 위 큰 시트 */
.sm-panel.vstu-sheet { width: min(1280px, 94vw); max-width: none; height: min(88vh, 100%); padding: 0; overflow: hidden; display: flex; flex-direction: column; border-radius: var(--r-xl); }
.sm-panel.vstu-sheet > .vstu { flex: 1 1 auto; }
/* 시트는 패널 테두리 안이라 무대 좌우를 --sp-lg 로 같게 둔다(왼쪽만 20 · 오른쪽 30 이던 비대칭 — 2026-10-04 감사).
   패널은 상단바 아래 영역 가운데에 선다 — 상단바 아래선에 붙던 것(위 0 · 아래 54)을 위아래 여백으로. */
.vstu--sheet .vstu-stage { padding-inline: var(--sp-lg); }
@media (min-width: 811px) { body.shell-v2 .sm-overlay:has(> .vstu-sheet) { padding: calc(var(--top-h, 56px) + var(--sp-lg)) var(--sp-lg) var(--sp-lg); } }

/* 단 배치는 창 폭이 아니라 **스튜디오 폭**으로 정한다(2026-10-04 오너 결정 D2 나 — 1024~1100 에서도 2단 유지).
   창 폭(옛 ≤1100)으로 접으면 왼쪽 패널 264px 를 빼고도 무대가 넉넉한 1024 에서 1단이 돼 주 입력(프롬프트)이 첫 화면 밖이었다.
   · 스튜디오 < 1000px → 재료 단 300px(1024 페이지 760 · 1024 시트 962)
   · 스튜디오 < 680px → 1단(무대 폭 380 미만이면 시트 세 칸이 한 칸 ~80px 로 너무 좁다 · 721~943 창의 페이지) — 그 폭에서는 스튜디오
     칸(`.vstu-host`)이 스스로 스크롤한다(화면은 여전히 고정 높이 — 칩 줄이 위에 남는다).
   · 창 ≤810 은 지금과 같다 — 화면 전체가 스크롤하는 1단(아래 @media). */
.vstu-host, .sm-panel.vstu-sheet { container: vstu / inline-size; }
.vstu-host { overflow-y: auto; }
@container vstu (max-width: 999px) { .vstu { grid-template-columns: minmax(0, 1fr) 300px; } }
@container vstu (max-width: 679px) {
  .vstu { grid-template-columns: 1fr; height: auto; }
  .vstu-canvas { flex: none; height: min(56vh, 520px); }
  .vstu-insp { border-left: 0; border-top: 1px solid var(--hairline); overflow: visible; }
  .vstu-page > .vstu, .sm-panel.vstu-sheet > .vstu { flex: 0 0 auto; min-height: auto; }
}
/* ★ 비율 B(2026-10-05 오너 ④ · 팀리드 시안 승인 1) — 에셋 탭 **페이지 모드만**(`.vstu--page`). 캔버스 위 시트(`.vstu--sheet`)는 같은 `.vstu`
   규칙을 쓰므로 범위를 좁히지 않으면 시트도 바뀐다.
   · 재료 단을 넓힌다: 1440 → 400 · 1909 → 480(한국어 프롬프트 한 줄 33~41자 — 옛 352 는 28.5자라 3~4줄이 5~6줄로 꺾였다).
   · 무대는 **왼쪽 정렬**(가운데로 모으면 머리 줄 [캐릭터 | 배경]과 이름·프레임의 왼선이 어긋난다).
   ★★ 2026-10-06 오너 *"화면이 넓어질 때 가운데 미리보기가 고정이라 이상"* — 10-05 의 960 폭·560 높이 상한을 걷고, 프레임이
     **그림 비율대로** 무대 폭을 채운다(높이 = 폭 ÷ 비율 — 어두운 빈 띠가 위아래로 생기지 않는다). 높이가 모자라면 flex 가 줄인다.
     운영 시트 원본은 1920×1088(캐릭터·배경 둘 다 — 2026-10-06 실측)이라 1080p 창에서도 확대가 아니다.
     옛 주석의 *"원본 768×512"* 는 목 픽스처의 크기였다. 비율(`--vstu-ar`)은 그림이 뜨면 JS 가 실측값으로 덮는다(`trackShotRatio`).
   ⚠️ 스튜디오 폭 1000 이상에서만 — 그 아래는 위 300px · 1단 규칙이 이겨야 한다(조건 없이 건 첫 시안이 860 영어에서 무대를 ~200px 로 눌렀다). */
@container vstu (min-width: 1000px) {
  .vstu--page { grid-template-columns: minmax(0, 1fr) clamp(400px, 30%, 480px); }
  .vstu--page .vstu-stage { container: vstage / inline-size; }
  .vstu--page .vstu-canvas {
    flex: 0 1 auto;
    height: calc((100cqw - 2 * var(--vstu-inset)) / var(--vstu-ar, 1.7647) + 2 * var(--vstu-inset) + var(--vstu-cap-h, 0px));
  }
  /* 그림이 프레임 폭을 채운다(작은 원본도 — 프레임과 그림 사이 빈 띠가 없다). 배경 4분할 칸 이름은 shot 기준이라 그대로 맞는다. */
  .vstu--page .vstu-frame > .vstu-shot > img,
  .vstu--page.vstu--loc .vstu-canvas > .vstu-shot > img {
    /* 폭을 채우되 높이가 모자란 창(넓고 낮은 창)에서는 높이에 맞춘다 — 프레임은 flex 로 줄고 그림은 그 안에서 비율대로 */
    width: min(calc(100cqw - 2 * var(--vstu-inset)), calc((100cqh - var(--vstu-cap-h, 0px) - 2 * var(--vstu-inset)) * var(--vstu-ar, 1.7647)));
    max-height: none;
  }
}
/* 버전 썸네일을 키운다(120 → 160 · 오너 ④) — 간격은 토큰 사다리로(옛 10px 는 사다리 밖). */
.vstu--page .vstu-ver { width: 160px; }
.vstu--page .vstu-vers { gap: var(--sp-xs); }
/* 1단(좁은 창 ≤810) — 무대 위 · 재료 아래, 화면이 통째로 스크롤 */
@media (max-width: 810px) {
  .vstu { grid-template-columns: 1fr; height: auto; }
  body.shell-v2 .vlab-screen--studio { height: auto; min-height: calc(100dvh - var(--top-h, 56px)); }
  .vstu-host { overflow-y: visible; }
  .vstu-canvas { flex: none; height: min(56vh, 520px); }
  .vstu-insp { border-left: 0; border-top: 1px solid var(--hairline); overflow: visible; }
  .sm-panel.vstu-sheet { overflow-y: auto; }
}
@media (max-width: 810px) {
  body.shell-v2 .vlab-screen--studio > .vlab-tabs-wrap { padding: var(--sp-sm) var(--sp-md); } /* 무대·재료의 좁은 폭 여백(--sp-md)과 같은 선 */
  .vstu-stage { padding: 0 var(--sp-md) var(--sp-md); }
  .vstu-head { flex-wrap: wrap; min-height: 0; padding-block: var(--sp-sm); } /* 머리 줄–무대 간격 0 이던 것(2026-10-04 감사) */
  .vstu--sheet .vstu-stage { padding-inline: var(--sp-md); } /* 시트 무대 왼쪽 20 이 재료 단 15 와 어긋났다 */
  .vstu-canvas:has(> .vstu-frame--ghost), .vstu-canvas:has(> .vstu-ghost) { height: min(40vh, 360px); } /* 그림이 없을 땐 빈 무대가 첫 화면을 다 채우지 않게 — 캐릭터(칸 이름 틀) · 배경(4분할 빈 자리) 둘 다(3차 검증: 배경이 빠져 있었다) */
  .vstu-insp { padding: 4px var(--sp-md) var(--sp-md); }
  .sm-panel.vstu-sheet { width: 100vw; height: 100dvh; border-radius: 0; }
}

/* ── 왼쪽 목록(프로젝트 셸 왼쪽 패널로 옮겨 붙는다 · paneMap `.vlab-cast-pane`) ── */
.vlab-cast-pane { display: flex; flex-direction: column; gap: 2px; padding: 4px 0 12px; } /* 패널 제목·도구 버튼·행 배경이 한 왼선(감사 12·24·22 → 12) */
/* [AI 자동 생성](흰 주 버튼 · 남는 폭) + [+ 새 캐릭터/배경](보조 · 내용 폭)이 한 줄(2026-10-06 오너 — 10-04 D3 나의 돋보기 아이콘을 대신한다). */
.vlab-cast-tools { display: flex; align-items: center; gap: 6px; padding: 4px 0 10px; }
.vlab-cast-tools > .btn { flex: none; min-width: 0; justify-content: center; white-space: nowrap; }
.vlab-cast-tools > .vlab-cast-ai { flex: 1 0 auto; } /* 주 버튼은 글자보다 줄지 않는다(영어에서 보조에 밀려 잘렸다 · 독립 검증 2026-10-06) */
/* [전체 생성 N │ ⓒ](2026-10-26) — 도구 줄 아래 한 줄 · 패널 폭 전부(좁은 패널에서 도구 줄에 끼우면 글자가 잘린다) */
.vlab-cast-batchrow { padding-top: 0; }
.vlab-cast-tools > .vlab-cast-batch { flex: 1 1 auto; }
.vlab-cast-row { appearance: none; display: flex; align-items: center; gap: 12px; width: 100%; padding: 8px 10px; border: 0; border-radius: var(--r-md); background: none; color: var(--ink); font: inherit; text-align: left; cursor: pointer; }
.vlab-cast-row:hover { background: var(--surface-1); }
.vlab-cast-row[aria-current="true"] { background: var(--surface-2); }
.vlab-cast-row:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.vlab-cast-ava { flex: none; width: 40px; height: 40px; border-radius: 10px; background: var(--surface-3); display: grid; place-items: center; overflow: hidden; color: var(--ink-48); font-size: 15px; font-weight: 600; }
.vlab-cast-ava > img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 시트(3칸)면 왼쪽 칸 — 얼굴 클로즈업 — 만 보이게: 그림을 칸 셋 폭으로 늘이고 왼쪽에 붙인다 */
.vlab-cast-ava.is-sheet > img { width: 300%; max-width: none; object-position: left 30%; }
/* 배경 4분할이면 왼쪽 위 칸(정면)만 */
.vlab-cast-ava.is-quad > img { width: 200%; height: 200%; max-width: none; object-position: left top; }
.vlab-cast-ava > .vlab-imgfb { width: 100%; height: 100%; }
.vlab-cast-name { flex: 1 1 auto; min-width: 0; font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vlab-cast-name.is-placeholder { color: var(--ink-48); }
/* 배경 변형 행(2026-10-19) — 이름 아래 "‹기준›의 변형" 한 줄 · 기준 바로 아래라 조금 들여 무리를 보인다 */
.vlab-cast-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.vlab-cast-txt > .vlab-cast-name { flex: none; }
.vlab-cast-sub { font-size: 12px; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vlab-cast-row.is-variant { padding-left: 22px; }
.vlab-cast-row.is-variant--2 { padding-left: 34px; } /* 변형의 변형(다단 · 그 이상도 2단에서 멈춘다) */
.vlab-cast-st { flex: none; width: 18px; height: 18px; border-radius: 50%; box-sizing: border-box; display: grid; place-items: center; font-size: 11px; font-weight: 700; line-height: 1; }
.vlab-cast-st--ok { background: var(--positive); color: #000; }
.vlab-cast-st--run { border: 2px solid color-mix(in srgb, var(--accent) 25%, transparent); border-top-color: var(--accent); animation: vstu-spin 1s linear infinite; }
.vlab-cast-st--pick { border: 2px solid var(--ink); } /* 확정 대기 — 흰 테두리(지금 할 일 = 흰 [이 시트로 확정]과 같은 색 · 주황은 이 시스템에 없는 색 · 리뷰 M22) */
.vlab-cast-st--none { border: 1.5px dashed var(--ink-48); opacity: .7; }
.vlab-cast-st--bad { background: var(--danger); color: #fff; }
.vlab-cast-st--legacy { background: var(--surface-3); color: var(--ink-80); }
@keyframes vstu-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .vlab-cast-st--run { animation: none; } }
.vlab-cast-empty { padding: 8px 12px; font-size: 13px; color: var(--ink-48); }
/* 에셋 탭 격자일 때 목록 대신 서는 수 한 줄(2026-10-23 · gridMode) */
.vlab-cast-count { padding: 4px 12px; font-size: 12px; color: var(--ink-48); }

/* ── 에셋 탭 [영상 클립 | 오디오] 쪽(2026-10-05 옛 프로젝트 › 라이브러리 합침 · videoLabLibrarySide) — 캐릭터·배경 목록과 같은 행 · 오른쪽은 라이브러리 상세 ── */
.vlab-lib-filter { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 10px; }
.vlab-lib-ava > video { width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.vlab-lib-ava > img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vlab-lib-txt { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.vlab-lib-meta { font-size: 12px; color: var(--ink-48); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vlab-lib-meta:empty { display: none; }
.vlab-lib-note { padding: 8px 12px; font-size: 12px; color: var(--ink-48); }
/* 생성 기록(2026-10-23 · 모든 생성을 한 목록에 — 같은 카드끼리 묶음) — 줄마다 꼬리표 '캔버스에 선택됨'|'캔버스 미사용' + 메타 한 줄.
   미사용 줄은 썸네일을 살짝 낮춰 선택된 장과 가른다. 실패 줄은 문구 + [다시 시도] 한 줄. */
.vlab-lib-note--fail { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vlab-lib-line { display: flex; align-items: center; gap: 6px; min-width: 0; }
.vlab-lib-line > .vlab-lib-meta { flex: 1 1 auto; min-width: 0; }
.vlab-lib-tag { flex: none; font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: var(--r-pill); border: 1px solid var(--hairline); color: var(--ink-48); white-space: nowrap; }
.vlab-lib-tag--on { border-color: transparent; background: color-mix(in srgb, var(--positive) 18%, transparent); color: var(--ink-80); }
.vlab-lib-row[data-canvas="unused"] .vlab-lib-ava, .vlab-lib-row[data-canvas="gone"] .vlab-lib-ava { opacity: .7; }
body.shell-v2 .vlab-lib-detail .vlab-lib-download { align-self: flex-start; margin-top: var(--sp-sm); }
/* 영상 상세의 '만들 때 쓴 글'(2026-10-30 · videoLabLibrarySide.cardTextBlock) — 정보 줄(.lib-kv)과 같은 위선 · 글은 줄바꿈 그대로 고정폭 상자(안에서 스크롤).
   오너 2026-10-30 "좀 더 크고 길게 · 폰트 줄이고 · 글이 많을 텐데" → 글씨 11px · 상자 높이는 영상 높이에 맞춰 화면을 따라가고(최소 320) · 글이 있는 영상만 정보 칸을 480px 로 넓힌다. */
body.shell-v2 .vlab-lib-cardtext { display: flex; flex-direction: column; gap: var(--sp-xs); padding: var(--sp-sm) 0; border-top: 1px solid var(--hairline-soft); font-size: var(--fs-fine); line-height: var(--lh-caption); min-width: 0; }
body.shell-v2 .vlab-lib-cardtext-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-sm); }
body.shell-v2 .vlab-lib-cardtext-body { margin: 0; max-height: max(320px, calc(100dvh - 460px)); overflow: auto; padding: var(--sp-sm); border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--surface-1); color: var(--ink-80); font-family: var(--font-mono); font-size: 11px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; user-select: text; }
/* 상세는 라이브러리 시트(`.lib-ins`)를 그대로 쓴다 — 무대 가운데 읽기 폭으로 세우고, 시트 머리의 닫기 자리(오른쪽 여백)는 비운다(닫을 것이 없다). */
.vlab-lib-detail { flex: 1 1 auto; min-height: 0; width: 100%; max-width: calc(720px + 2 * var(--sp-lg)); margin: 0 auto; box-sizing: border-box; display: flex; flex-direction: column; padding: var(--sp-md) var(--sp-lg); }
/* 시트와 달리 발을 바닥에 붙이지 않는다 — 본문이 짧은 오디오에서 미리보기와 [가져오기] 사이가 화면 높이만큼 비었다(1440×900 시안 실측).
   머리·본문·발의 왼선을 하나로(시트의 안쪽 여백은 유리 테두리용이다 — 여기엔 테두리가 없다). */
body.shell-v2 .vlab-lib-detail .lib-ins-head { padding: 0 0 var(--sp-md); }
body.shell-v2 .vlab-lib-detail .lib-ins-body { flex: none; overflow: visible; padding: 0; }
body.shell-v2 .vlab-lib-detail .lib-ins-foot { padding: var(--sp-md) 0 0; margin-top: var(--sp-md); }
body.shell-v2 .vlab-lib-detail .lib-ins-foot .btn--block { min-height: 40px; }
/* 가져오기 한 줄(inline) — 선택 상자가 남는 폭 · 회차 칸 고정 · [가져오기]는 내용 폭(꽉 찬 막대 금지 · 팀리드 시안 검토). 안내는 버튼 쪽 오른쪽. */
body.shell-v2 .vlab-lib-detail .lib-ins-import-row { display: flex; align-items: center; gap: var(--sp-xs); }
body.shell-v2 .vlab-lib-detail .lib-ins-import-row .lib-ins-select { flex: 1 1 auto; min-width: 0; }
body.shell-v2 .vlab-lib-detail .lib-ins-import-row .lib-ins-ep { flex: none; width: 120px; }
body.shell-v2 .vlab-lib-detail .lib-ins-import-row > .btn { flex: none; }
body.shell-v2 .vlab-lib-detail .lib-ins-import--inline .lib-ins-hint { text-align: right; }
/* 클립·이미지 — 큰 미리보기 │ 오른쪽 정보 칸(캐릭터 스튜디오와 같은 2단 · 팀리드 시안 검토).
   묶음(머리 + 미리보기 + 정보 칸) 폭 = 남은 화면 높이 × 원본 비율(`--ar` — 상세가 메타데이터·이미지 로드 때 상자에 단다) + 정보 칸이고,
   그 묶음을 상세 영역 **가운데**에 둔다. 제목·부제는 묶음 안이라 미리보기와 같은 왼선이다(2026-10-05 오너 운영 피드백 — 왼쪽 정렬이면
   1920 세로 영상 오른쪽이 ~800px 비었다). 가로 클립·이미지는 묶음이 영역을 거의 채워 전과 같게 보인다. 좁으면(컨테이너 < 760) 한 단. */
.vlab-lib-detail--clip, .vlab-lib-detail--image { max-width: none; --ar: 0.5625; --info: 320px; }
.vlab-lib-detail--clip:has(.vlab-lib-cardtext) { --info: 480px; } /* 만들 때 쓴 글이 있는 영상만 정보 칸을 넓힌다(위 '만들 때 쓴 글' 주석) */
body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) > .lib-ins {
  width: min(100%, calc((100dvh - 250px) * var(--ar) + var(--sp-xl) + var(--info)));
  margin-inline: auto;
}
body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body {
  display: grid; column-gap: var(--sp-xl); align-content: start;
  grid-template-columns: minmax(0, 1fr) var(--info);
}
body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body > .lib-ins-preview { grid-column: 1; grid-row: 1 / span 20; width: 100%; margin: 0; }
body.shell-v2 .vlab-lib-detail--image .lib-ins-preview > img { width: 100%; height: auto; max-height: none; }
body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body > :not(.lib-ins-preview) { grid-column: 2; }
body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-preview + .lib-kv { border-top: 0; padding-top: 0; }
@container vstu (max-width: 759px) {
  body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) > .lib-ins { width: 100%; }
  body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body { grid-template-columns: minmax(0, 1fr); }
  body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body > * { grid-column: 1 !important; grid-row: auto !important; }
  body.shell-v2 :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) .lib-ins-body > .lib-ins-preview { width: min(100%, calc(60dvh * var(--ar))); justify-self: center; margin-bottom: var(--sp-sm); }
}

/* ── 에셋 [이미지]·[영상] 격자(2026-10-23 팀 요청 — Higgsfield 'All assets' 같은 촘촘한 큰 타일 · videoLabLibrarySide `gallery`) ──
   폭 1440 에서 네 칸(무대 ≈ 1100px) · 좁으면 두 칸 → 한 칸 · 가로 스크롤 없음. 타일 비율 = 그 항목의 프로젝트 화면비(`--tile-ar`) —
   정지 프레임이 오기 전에 자리가 정해진다. 그림은 칸을 채운다(cover). 마우스를 올리면 영상이 소리 없이 돈다(`is-playing`). */
.vlab-lib-gallery-wrap { padding: var(--sp-sm) var(--sp-md) var(--sp-lg); box-sizing: border-box; min-width: 0; }
.vlab-lib-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); gap: 6px; align-items: start; }
.vlab-lib-gallery--wide { grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); gap: 8px; } /* 가로 판은 넓은 칸(1440 폭 = 3칸 · 칸 너비 약 1.5배) */
.vlab-lib-gallery-item { min-width: 0; }
.vlab-lib-tile { position: relative; aspect-ratio: var(--tile-ar, 0.5625); /* 판(`.vlab-lib-gallery`)이 정한 한 비율 */ border-radius: var(--r-md); overflow: hidden; background: var(--surface-2); cursor: pointer; outline: none; isolation: isolate; }
.vlab-lib-tile:focus-visible { box-shadow: var(--ring-focus); }
.vlab-lib-tile-media { position: absolute; inset: 0; display: grid; place-items: center; }
.vlab-lib-tile-media > video, .vlab-lib-tile-media > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; background: #000; }
.vlab-lib-tile-ico { color: var(--ink-48); }
/* 이미지 타일은 자르지 않는다 — 세로 착장·소품 그림이 가로 칸에서 몸통 한가운데만 남아 무엇인지 알 수 없었다(2026-10-23 스테이징 데이터 캡처). 영상·인물·배경은 칸을 채운다. */
.vlab-lib-tile[data-kind="image"] .vlab-lib-tile-media > img { object-fit: contain; background: #0d0d0d; }
.vlab-lib-tile[data-canvas="unused"] .vlab-lib-tile-media, .vlab-lib-tile[data-canvas="gone"] .vlab-lib-tile-media { filter: brightness(.82); transition: filter var(--t-fast) var(--ease); }
.vlab-lib-tile:is(:hover, :focus-visible, .is-playing) .vlab-lib-tile-media { filter: none; }
/* 재생 표식 — 영상 타일 가운데 작은 원 · 돌기 시작하면 걷는다 */
.vlab-lib-tile-play { position: relative; z-index: 1; width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; background: rgba(0, 0, 0, .45); color: #fff; transition: opacity var(--t-fast) var(--ease); pointer-events: none; }
.vlab-lib-tile.is-playing .vlab-lib-tile-play, .vlab-lib-tile:hover .vlab-lib-tile-play { opacity: 0; }
/* 오디오는 반대 — 평소엔 아이콘만, 올리면 재생 단추가 선다(누르면 창에서 듣는다) */
.vlab-lib-tile-play--audio { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); opacity: 0; }
.vlab-lib-tile:is(:hover, :focus-visible) .vlab-lib-tile-play--audio { opacity: 1; }
.vlab-lib-tile:is(:hover, :focus-visible) .vlab-lib-tile-media.is-blank > .vlab-lib-tile-ico { opacity: 0; }
/* 왼쪽 위 칩 줄 — 종류([전체]) · 캔버스 꼬리표(이미지·영상) · 단계(캐릭터·배경). 그림 위라 어두운 바탕(선택됨·완성 = 초록 기운) */
.vlab-lib-tile-chips { position: absolute; z-index: 2; top: 8px; left: 8px; right: 80px; display: flex; flex-wrap: wrap; gap: 4px; pointer-events: none; }
.vlab-lib-tile-chips > * { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; line-height: 16px; padding: 0 6px; border-radius: var(--r-pill); border: 0; background: rgba(0, 0, 0, .55); color: rgba(255, 255, 255, .86); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
.vlab-lib-tile-chips > .vlab-lib-tile-kind { color: #fff; font-weight: 600; }
.vlab-lib-tile-chips > .vlab-lib-tag--on, .vlab-lib-tile-chips > .vlab-lib-tile-badge--ok { background: color-mix(in srgb, var(--positive) 55%, rgba(0, 0, 0, .55)); color: #fff; }
.vlab-lib-tile-chips > .vlab-lib-tile-badge--bad { background: color-mix(in srgb, var(--danger) 60%, rgba(0, 0, 0, .55)); color: #fff; }
/* 캐릭터 시트(가로 3칸)는 왼쪽 칸(얼굴)만 — 목록 아바타와 같은 자르기. 배경 시트는 자르지 않는다(16:9 타일에 통째로 · `videoLabCastTiles`). */
.vlab-lib-tile-media.is-sheet > img { width: 300%; max-width: none; right: auto; object-position: left 30%; }
/* 그림 없는 타일(오디오 · 아직 그림 없는 캐릭터·배경) — 종류 아이콘 */
.vlab-lib-tile-media.is-blank { background: linear-gradient(160deg, var(--surface-3), var(--surface-2)); }
.vlab-lib-tile-media.is-blank > .vlab-lib-tile-ico { color: var(--ink-48); }
/* 행동(크게 보기 · 내려받기) — 올리거나 초점이 들어올 때만 */
.vlab-lib-tile-acts { position: absolute; z-index: 2; top: 6px; right: 6px; display: flex; gap: 4px; opacity: 0; transition: opacity var(--t-fast) var(--ease); }
.vlab-lib-tile:is(:hover, :focus-within) .vlab-lib-tile-acts { opacity: 1; }
@media (hover: none) { .vlab-lib-tile-acts { opacity: 1; } } /* 터치 — 올릴 수가 없다 */
body.shell-v2 .vlab-lib-tile-acts > .vlab-lib-tile-btn { width: 32px; height: 32px; min-height: 32px; padding: 0; border-radius: var(--r-sm, 8px); background: rgba(0, 0, 0, .55); color: #fff; border: 0; backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); }
body.shell-v2 .vlab-lib-tile-acts > .vlab-lib-tile-btn:hover { background: rgba(0, 0, 0, .75); }
/* 아래 제목 + 메타(시각 · 회차) — 그림 위 그라데이션 */
.vlab-lib-tile-cap { position: absolute; z-index: 1; left: 0; right: 0; bottom: 0; padding: 28px 10px 8px; display: flex; flex-direction: column; gap: 1px; background: linear-gradient(to top, rgba(0, 0, 0, .72), rgba(0, 0, 0, 0)); color: #fff; pointer-events: none; }
.vlab-lib-tile-name { font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vlab-lib-tile-meta { font-size: 11px; color: rgba(255, 255, 255, .72); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vlab-lib-tile-meta:empty { display: none; }
@media (max-width: 720px) { .vlab-lib-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } .vlab-lib-gallery-wrap { padding-inline: var(--sp-sm); } }
@media (max-width: 360px) { .vlab-lib-gallery { grid-template-columns: minmax(0, 1fr); } }
@media (prefers-reduced-motion: reduce) { .vlab-lib-tile-acts, .vlab-lib-tile-play, .vlab-lib-tile-media { transition: none; } }
/* 크게 보기 창 — 공용 모달 껍데기(sm-*) 안에 상세(`.vlab-lib-detail`)를 그대로 세운다 · 머리 줄 = [‹] n / N [›] … [×].
   창이 상세 2단 배치의 컨테이너다(`@container vstu` — 무대와 같은 760 경계). */
.sm-panel.vlab-lib-lightbox-panel { container: vstu / inline-size; width: min(1200px, 100%); max-height: calc(100dvh - 2 * var(--sp-lg)); overflow-y: auto; padding: var(--sp-sm) var(--sp-sm) var(--sp-md); gap: 0; }
.vlab-lib-lightbox-nav { display: flex; align-items: center; gap: 4px; padding: 0 0 var(--sp-xs); }
.vlab-lib-lightbox-count { font-size: 12px; color: var(--ink-48); font-variant-numeric: tabular-nums; min-width: 56px; text-align: center; }
.vlab-lib-lightbox-nav > .vlab-lib-lightbox-close { margin-left: auto; }
.vlab-lib-lightbox .vlab-lib-detail { padding: 0 var(--sp-md); }
body.shell-v2 .vlab-lib-lightbox :is(.vlab-lib-detail--clip, .vlab-lib-detail--image) > .lib-ins { width: min(100%, calc((100dvh - 200px) * var(--ar) + var(--sp-xl) + var(--info))); }
@media (max-width: 720px) { .vlab-lib-lightbox.sm-overlay { padding: 0; } .sm-panel.vlab-lib-lightbox-panel { width: 100vw; max-height: 100dvh; height: 100dvh; border-radius: 0; } }

/* 에셋 탭 캐릭터·배경 스튜디오의 [← 전체 보기](머리 줄 · 그 쪽 격자로) */
.vlab-tabs-bar > .vlab-cast-back { flex: none; }

/* ── 캐릭터 스튜디오가 더하는 것(videoLabCharacterModal · 2026-10-03) — 배치 정본은 위 `.vstu-*` ── */
/* 재료 단 밖으로 띄우는 작은 창(화풍 격자 · 전체 프롬프트)의 기준 — 재료 단은 세로 스크롤 상자라 그 안의 절대 위치 창은 잘린다. */
.vstu { position: relative; }
/* 페이지 모드 껍데기(`inlineShell` 의 .vstu-page)가 남은 높이를 스튜디오에 넘긴다. */
.vstu-host > .vstu-page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.vstu-page > .vstu { flex: 1 1 auto; }
.vstu-chips { display: flex; align-items: center; gap: 6px; flex: none; }
.vstu-chips:empty { display: none; }
.vstu-pop--float { top: 0; }
.vstu-gen-label { flex: none; font-size: 12px; color: var(--ink-48); }
.vstu-gen-row .vstu-sp { flex: 1 1 auto; }
.vstu-sec-title .vstu-tag { margin-left: 6px; vertical-align: 1px; font-weight: 500; }
.vstu-insp .vlab-char-prompt-box { margin-top: 0; }
.vstu-prompt-state:empty { display: none; }
.vstu-insp .vlab-imp-error { padding: 2px 0 0; }
/* 무대 알림 — 오류는 붉은 테두리 상자, 같은 인물 표기 같은 사실은 낮은 글 한 줄. */
.vstu-notes .vlab-imp-error { padding: 8px 12px; border-radius: var(--r-md); background: var(--surface-1); border: 1px solid var(--danger-ring); font-size: 13px; line-height: 1.5; }
.vstu-fact { font-size: 12px; color: var(--ink-48); }
/* 버전 타일 — 추가 지시가 있던 장의 점(문장은 title) */
.vstu-ver-dot { position: absolute; right: 8px; bottom: 8px; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5); pointer-events: none; }
.vstu-ver-wrap:hover .vstu-ver-dot, .vstu-ver-wrap:focus-within .vstu-ver-dot { opacity: 0; }
@media (hover: none) { .vstu-ver-dot { display: none; } }
.vstu-ver > .vlab-imgfb { width: 100%; height: 100%; }
/* 참조 칸 — 서명 실패분은 사실 한 마디(칸은 자리를 지킨다 · 인덱스가 곧 삭제 키다) */
.vstu-ref .vlab-img-empty { display: grid; place-items: center; width: 100%; height: 100%; font-size: 10px; color: var(--ink-48); text-align: center; }
.vstu-ref.is-zoomable { cursor: zoom-in; }
.vstu-ref.is-zoomable:focus-visible { outline: none; box-shadow: var(--ring-focus); }
.vstu-ref > .vlab-imgfb { width: 100%; height: 100%; }
/* 사람이 아닌 존재 — 체크 한 줄 + 추정 칩 */
.vstu-check { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-80); cursor: pointer; }
.vstu-check input { width: 16px; height: 16px; margin: 0; accent-color: var(--accent); }
.vstu-check input:disabled { cursor: default; }
/* 화풍 — 고른 것 한 줄의 썸네일 · 작은 창 타일의 크게 보기 */
.vstu-pick-thumb { overflow: hidden; }
.vstu-pick-thumb > img, .vstu-pick-thumb > .vlab-imgfb { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 칸 + 크게 보기(형제 버튼 · 리뷰 M14 — 옛 판은 칸 버튼 안의 role=button 이었다) */
.vstu-style-wrap { position: relative; min-width: 0; }
.vstu-style-wrap > .vstu-style { display: block; width: 100%; }
.vstu-style:disabled { opacity: .45; cursor: default; }
.vstu-style > .vlab-imgfb { width: 100%; aspect-ratio: 2 / 3; }
.vstu-style-zoom { appearance: none; border: 0; padding: 0; font: inherit; cursor: zoom-in; position: absolute; right: 4px; top: 4px; width: 22px; height: 22px; border-radius: 6px; display: grid; place-items: center; font-size: 12px; background: rgba(0, 0, 0, 0.55); color: #fff; opacity: 0; transition: opacity .15s ease; }
.vstu-style-wrap:hover .vstu-style-zoom, .vstu-style-zoom:focus-visible { opacity: 1; }
.vstu-style-zoom:focus-visible { outline: none; box-shadow: var(--ring-focus); }
@media (hover: none) { .vstu-style-zoom { opacity: 1; } }
.vstu-shot > .vlab-imgfb { min-width: 320px; min-height: 200px; }
/* 이름 칸 — 새 셸의 입력 기본 모양(framer.css `body.shell-v2 input` · 0,1,2)이 제목 모양(.vstu-name · 0,1,0)을 덮는다. 한 단 높여 다시 건다. */
body.shell-v2 .vstu-name { border: 0; background: none; padding: 4px 8px; font-size: 22px; letter-spacing: -0.01em; border-radius: var(--r-sm); }
body.shell-v2 .vstu-name:hover:not([readonly]) { background: var(--surface-1); }
body.shell-v2 .vstu-name:focus { outline: none; background: var(--surface-1); box-shadow: var(--ring-focus); }
/* 프롬프트 편집칸 — 재료 단의 주인공이라 처음부터 넉넉하다(목업 a1 · 늘어나는 것은 `field-sizing` 그대로). */
.vstu-insp .vstu-prompt { min-height: 180px; }
/* 1단(≤810) — 시트는 패널이 통째로 스크롤한다. 스튜디오가 패널 높이로 줄면(최소 0) 무대 내용이 재료 단 위로 넘친다(실측 780px). */
@media (max-width: 810px) {
  .sm-panel.vstu-sheet > .vstu, .vstu-page > .vstu { flex: 0 0 auto; min-height: auto; }
}
/* 좁은 화면(≤810) — 전체 화면 시트. 옛 팝업의 규칙(canvas-modals.css 의 영상탭 패널 목록)이 이 패널 이름을 못 잡아 같은 것을 건다:
   겉 여백 0 · 뒤 문서 스크롤 잠금(WebKit 은 html 에도) · 머리 줄은 스크롤해도 위에 붙는다 · 누를 자리는 44px. */
@media (max-width: 810px) {
  body.shell-v2 .sm-overlay:has(> .vstu-sheet) { padding: 0; align-items: stretch; }
  html:has(body.shell-v2 > .sm-overlay > .vstu-sheet),
  body.shell-v2:has(> .sm-overlay > .vstu-sheet) { overflow: hidden; overscroll-behavior: none; }
  /* 머리 줄(높이 56 — 390·600·780 실측)이 위에 붙어 있으니 초점이 그 밑으로 들어가지 않게 스크롤 기준을 그만큼 내린다(`.specs/spec_video_lab/deleted-tests-2026-10-03.md` 참고 #25). */
  .sm-panel.vstu-sheet { overscroll-behavior: contain; scroll-padding-top: 64px; }
  .vstu--sheet .vstu-head { position: sticky; top: 0; z-index: 3; background: var(--canvas); }
  .vstu--sheet .vstu-actions .btn { min-height: 44px; }
  .vstu--sheet .vstu-actions .btn--icon-only { min-width: 44px; }
  /* 작은 창(생성 · ⋯ · 화풍 · 전체 프롬프트)은 앱 공용 관례대로 **화면 아래 시트**다(trendControls.css ≤810 `.pop` — 좌우·아래 16 ·
     스크롤에 따라가지 않는다). 위 데스크톱 규칙(오른쪽 끝 맞춤 · 버튼 아래 top · 고정 폭)이 그 관례를 덮어 생성 창이 화면 오른쪽 끝에 붙고
     화풍·전체 프롬프트 창은 버튼 근처 top 에서 바닥까지 늘어났다(2026-10-04 실측 800·390). 떠 있는 창의 top 은 JS 가 이 폭에서 비운다. */
  .vstu-pop, .vstu-pop--end, .vstu-pop--float, .vstu-styles { top: auto; left: 16px; right: 16px; width: auto; transform-origin: bottom center; }
  .vstu-gen { width: auto; }
}

/* ── 배경 스튜디오가 더하는 것(videoLabLocationModal · 2026-10-03) — 배치 정본은 위 `.vstu-*` · 캐릭터 구획과 같은 부품을 쓴다 ── */
/* 큰 그림 — 칸 이름(`.vstu-qlabel` · 정면·탑뷰 / 후면·측면)이 그림의 네 모서리에 서려면 그림 상자(`.vstu-shot`)가 **그려진 그림과 같은
   상자**여야 한다. 무대가 그림보다 납작하면(넓은 창 · 1단 시트) img 가 무대 폭으로 늘어난 채 내용만 가운데 그려져(object-fit: contain)
   칸 이름이 그림 밖 여백에 섰다(실측 1920×1000 페이지 29px · 1000px 시트 46px 어긋남). 무대를 크기 컨테이너로 삼아 그림의 상한을
   무대 기준(cq)으로 주면 그림이 제 비율로 줄고 상자가 그림을 감싼다. ⚠️ 무대는 이미 부모가 크기를 정한다(2단 = flex 남는 높이 ·
   1단 = 고정 높이) — 크기 제한(size containment)이 내용으로 크기를 정하던 자리를 빼앗지 않는다. */
.vstu--loc .vstu-canvas { container-type: size; }
.vstu--loc .vstu-canvas > .vstu-shot { align-items: flex-start; }
.vstu--loc .vstu-canvas > .vstu-shot > img { width: auto; height: auto; max-width: calc(100cqw - 2 * var(--vstu-inset)); max-height: calc(100cqh - 2 * var(--vstu-inset)); }
/* 생성 작은 창 — 배경은 [생성] 옆에 ⓘ(다시 생성 렌즈 툴팁)가 하나 더 선다. 넘침 방지(셀렉트가 줄어든다)는 두 상세 공용 규칙이다
   (위 「작은 창」 구획의 `.vstu-gen-row .vlab-model` — 2026-10-03 정리로 캐릭터에도 걸었다). */
/* 📄 전체 프롬프트 — 저장 전 · 저장 안 된 설명 · 다시 생성 변주 같은 사실 한 줄(낮은 글 · `.vstu-fact`)은 창의 간격(gap)만 쓴다. */
/* 보류한 칸의 '다른 곳의 값' 한 줄(`conflictOtherValue.js` · 2026-10-04 팀리드 결정 B) — 캐스트 스튜디오·오디오 대본 공용(이 파일은 전역으로 실린다).
   칸 바로 아래 칸 왼쪽 선에 붙고, 칸과의 간격은 담는 쪽의 gap(칸 묶음 6px · 카드 6px)을 그대로 쓴다. 긴 값은 두 줄 말줄임 → [펼치기].
   ⚠️ 새 색은 없다 — 글은 기존 잉크 두 단계, 버튼은 btn(). 좁은 폭에선 버튼이 다음 줄로 내려간다(가로 넘침 0). */
.conflict-slot { min-width: 0; }
.conflict-other { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px var(--sp-xs); min-width: 0; font-size: 12px; line-height: 1.5; color: var(--ink-48); }
.conflict-other-label { flex: none; }
.conflict-other-label::after { content: ':'; }
.conflict-other-val { flex: 1 1 10em; min-width: 0; color: var(--ink-80); white-space: pre-wrap; overflow-wrap: anywhere; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; }
.conflict-other.is-open .conflict-other-val { display: block; -webkit-line-clamp: unset; }
.conflict-other-val.is-empty { color: var(--ink-48); font-style: italic; }
.conflict-other-tools { display: inline-flex; align-items: center; gap: 2px; flex: none; margin-left: auto; }
/* 바이블 — 행(<label>)이 72px 라벨 + 입력칸이라 줄은 행 밖에서 입력칸 왼쪽 선에 맞춘다. */
.conflict-other.script-bible-held { margin: 0 0 6px calc(72px + var(--sp-sm)); }
/* 이름 — 머리 줄 바로 아래(머리 줄 높이가 아래 여백을 이미 갖는다) · 무대와의 간격은 무대 쪽 간격 단위. */
.vstu-stage > .conflict-slot { margin-bottom: var(--sp-sm); }
