/* 콕핏(1:1) — 리치 셸·도킹·피킹·트윈·3D·업링크
 * index.html <style> 에서 분리(2026-09-15) — 원래 457~1074행.
 * ⚠️ 이어붙인 순서가 캐스케이드다. <link> 순서를 바꾸지 마라(tokens → fleet → cockpit → panels).
 *    scripts/test_css_split.js 가 네 파일을 이어붙여 분리 시점 원본과 바이트 동일한지 검사한다. */
   /* hidden 속성이 #id{display:flex} 특이도를 이기게 — 랜딩(overview)/콕핏에서 뒤 화면 숨김 */
  .cockpit-bar{ display:flex; align-items:center; gap:10px; padding:8px 12px; border-bottom:1px solid var(--line); }
  .cockpit-title{ font-size:14px; color:var(--ink); } .cockpit-title b{ color:var(--accent); }
  .cockpit-badge{ font-size:11px; padding:2px 8px; border:1px solid var(--line); border-radius:10px; color:var(--ink2); }
  .cockpit-badge.ok{ color:var(--ok); border-color:var(--ok); } .cockpit-badge.warn{ color:var(--err); border-color:var(--err); }
  .cockpit-badge.srv{ color:var(--accent); border-color:var(--accent); }   /* 서버 폴백(영상X, 조작O) */
  /* 제어 세션 칩(0.13) — 내 것(ok) / 다른 세션(warn) / 발급만 되고 로봇 미결속(dim) */
  .cockpit-csess{ font-size:11px; padding:2px 8px; border:1px solid var(--line); border-radius:10px; color:var(--ink2); }
  .cockpit-csess.ok{ color:var(--ok); border-color:var(--ok); }
  .cockpit-csess.warn{ color:var(--err); border-color:var(--err); }
  /* 인계·요청·킥 액션 바(HANDOVER §3-5) — 칩 옆의 작은 버튼들. 색은 토큰만. */
  .cockpit-sessbar{ display:inline-flex; gap:4px; }
  .cockpit-sessbar button{ font-size:11px; padding:2px 8px; border:1px solid var(--line); border-radius:10px;
    background:var(--card); color:var(--ink2); cursor:pointer; }
  .cockpit-sessbar button:hover{ color:var(--ink); border-color:var(--accent); }
  .cockpit-csess.dim{ color:var(--dim); }
  .cockpit-sens{ display:inline-flex; align-items:center; gap:10px; font-size:11px; color:var(--ink2); }
  .cockpit-sens label{ display:inline-flex; align-items:center; gap:4px; cursor:pointer; }
  .cockpit-sens input[type=range]{ width:68px; accent-color:var(--accent); }
  .cockpit-sens input[type=checkbox]{ accent-color:var(--accent); }
  .cockpit-sens b{ color:var(--ink); min-width:32px; }
  .cockpit-lat{ font-size:11px; color:var(--dim); }
  .cockpit-spacer{ flex:1; }
  .cockpit-stop{ background:var(--err-bg); border:1px solid var(--err); color:var(--err); padding:6px 14px; border-radius:6px; font-weight:bold; }
  .cockpit-exit{ background:var(--card); border:1px solid var(--line); padding:6px 12px; border-radius:6px; }
  .cockpit-xr{ background:var(--card); border:1px solid var(--line); padding:6px 12px; border-radius:6px; white-space:nowrap; }
  .cockpit-xr:disabled{ opacity:.5; cursor:default; }
  .cockpit-body{ flex:1; display:flex; gap:10px; padding:10px; min-height:0; }
  .cockpit-left{ width:380px; display:flex; flex-direction:column; gap:10px; overflow:auto; }
  .cockpit-cam{ flex:1; border:1px solid var(--line); border-radius:8px; background:var(--bg); display:flex; flex-wrap:wrap;
    gap:8px; padding:10px; align-content:flex-start; justify-content:center; align-items:center; }
  .cockpit-cam-ph{ color:var(--off); font-size:13px; }
  /* 메인(영상) 영역 = 미니맵/입력HUD 오버레이의 위치 기준 */
  .cockpit-main{ position:relative; flex:1; display:flex; min-height:0; }
  .cockpit-main .cockpit-cam{ flex:1; }
  /* 조작 프리셋 탭 */
  .cockpit-presets{ display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden; }
  .cockpit-presets button{ background:var(--card); border:none; border-right:1px solid var(--line); color:var(--ink2); padding:5px 10px; font-size:12px; cursor:pointer; }
  .cockpit-presets button:last-child{ border-right:none; }
  .cockpit-presets button.on{ background:var(--accent); color:var(--on-accent); }
  .cockpit-msgbtn{ background:var(--card); border:1px solid var(--line); color:var(--ink2); padding:5px 10px; border-radius:6px; font-size:12px; cursor:pointer; }
  .cockpit-msgbtn.on{ background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
  .cockpit-vpick, .cockpit-probe, .cockpit-detect{ background:var(--card); border:1px solid var(--line); color:var(--ink2); padding:5px 10px; border-radius:6px; font-size:12px; cursor:pointer; }
  .cockpit-vpick.on{ background:var(--link-ok); color:#04223a; border-color:var(--link-ok); font-weight:600; }
  .cockpit-probe.on{ background:#2f7fe6; color:#fff; border-color:#2f7fe6; font-weight:600; }   /* 프로브 = 시야 프러스텀과 동일 파란색 */
  .cockpit-detect.on{ background:#e0b341; color:#241a04; border-color:#e0b341; font-weight:600; }
  /* 파지 접근 각도 토글(set_grasp_tilt) — 2-state. ⚠️ 낙관적 갱신 금지: command.capability 에는 응답
     토픽이 없어 Result 가 안 돌아온다. 보낸 뒤 status.robot_info 가 바뀌는 것을 보고 반영하고,
     그 사이는 .pending(흐리게)으로 '반영 대기'를 정직하게 보여준다. */
  .cockpit-tilt{ display:inline-flex; border:1px solid var(--line); border-radius:6px; overflow:hidden; }
  .cockpit-tilt button{ background:var(--card); border:none; border-right:1px solid var(--line);
    color:var(--ink2); padding:5px 9px; font-size:12px; cursor:pointer; white-space:nowrap; }
  .cockpit-tilt button:last-child{ border-right:none; }
  .cockpit-tilt button.on{ background:var(--accent); color:var(--on-accent); font-weight:600; }
  .cockpit-tilt button.pending{ opacity:.5; font-style:italic; }
  /* SAM 기하 보정 토글(set_sam_refine) — 파지각 토글과 **같은 규율**(낙관적 갱신 금지, pending 표시).
     항상 보이게 두는 것이 요점이다: 숨은 모드는 같은 장면에서 다른 결과를 내는 재현 불가 버그를 만든다. */
  /* 진단 칩 — 기본값이 아닐 때만 뜬다(평소 비표시). 숨은 모드는 막되 화면은 안 어지럽힌다. */
  .cockpit-diag{ background:color-mix(in srgb, var(--busy) 18%, transparent); border:1px solid var(--busy);
    color:var(--busy); padding:4px 9px; border-radius:6px; font-size:11.5px; white-space:nowrap; cursor:help; }
  /* 파지 접근이 '고정 각도'(파라미터 폼으로 숫자 입력) — 3단 중 어느 것도 아니다. */
  .cockpit-tilt.fixed{ border-color:var(--busy); }
  /* 로봇 설정(set_*) — 베이스/미션/포즈/배터리 **밑**에 칩 한 줄로. bool 은 체크만 적고
     눌러서 양방향 토글한다(글자로 "켬/끔" 을 쓰면 칩이 길어져 한 줄에 안 들어간다). */
  .cockpit-settings{ display:flex; flex-wrap:wrap; gap:4px; margin:6px 0 8px; }
  .cockpit-settings[hidden]{ display:none; }
  .cockpit-settings .se-chip{ display:inline-flex; align-items:center; gap:4px; padding:2px 7px;
    border-radius:5px; border:1px solid var(--line); background:var(--card); color:var(--ink2);
    font-size:11px; line-height:1.6; white-space:nowrap; }
  .cockpit-settings .se-chip b{ color:var(--ink); font:11px ui-monospace,monospace; }
  .cockpit-settings .se-chip.dim{ opacity:.55; }
  .cockpit-settings .se-chip.dim b{ color:var(--dim); }
  .cockpit-settings button.se-chip{ font-family:inherit; cursor:pointer; }
  .cockpit-settings button.se-chip:hover{ border-color:var(--accent); }
  .cockpit-settings .se-chip.bool.on{ color:var(--link-ok); border-color:color-mix(in srgb, var(--link-ok) 45%, transparent); }
  /* 기본값이 아닌 상태 — 막지는 않고 눈에 띄게만(누르면 기본값으로 되돌아간다) */
  .cockpit-settings .se-chip.bad{ color:var(--busy); border-color:var(--busy);
    background:color-mix(in srgb, var(--busy) 12%, transparent); }
  .cockpit-cam.vpick-armed{ outline:2px solid var(--link-ok); outline-offset:-2px; }
  .cockpit-cam.probe-armed{ outline:2px solid #2f7fe6; outline-offset:-2px; }
  /* ── 작업 진행 중 — 카메라 프레임에서 빛이 새어나오는 글로우(capability_info / base_state=BUSY) ──
     ::after 오버레이인 이유: 부모의 inset box-shadow 는 배경 레이어에 그려져 in-flow 자식(video) **아래**로
     깔린다(채우기 모드에선 padding 폭 띠만 보임). outset 은 .dock-stack{overflow:auto} 가 클립한다.
     pointer-events 를 만들지 않는 것도 중요 — 카메라 클릭은 피킹·프로브의 입력 경로다(::after 는 hit target 없음). */
  .cockpit-cam{ position:relative; }   /* ::after 기준. JS 가 canvas 만들 때 imperative 로 걸던 것을 명시화 */
  /* 글로우 레이어가 패널 밖으로 새면 조상(.dock-stack{overflow:auto})이 스크롤바를 만든다 → 진행 중일 때만 클립 */
  .cockpit-cam.busy{ overflow:hidden; }
  /* 프레임 **테두리에서만** 빛이 흘러나오게(Gemini/Apple Intelligence 류 일렁임).
     ★ 붙는 자리 = **영상 프레임(.cockpit-vbox)**. 패널(.cockpit-cam)에 붙이면 contain 레터박스 여백까지
       감싸서 빛 띠가 영상과 어긋나 붕 뜬다(사용자 지적 2026-07-29). vbox 는 트랙 비율로 잠긴 박스라
       이게 곧 '카메라 프레임' 이고, 클릭 마커(.cockpit-cam-mark)도 같은 박스 기준이다.
       영상이 없을 때(플레이스홀더)는 붙을 프레임이 없으므로 패널로 폴백한다(:has).
     기법: ① 요소는 고정하고 conic-gradient 의 **각도만** @property 로 애니메이트한다
              (요소를 회전시키면 마스크까지 같이 돌아 테두리 밴드가 어긋난다)
           ② padding 폭 링만 남기는 mask XOR — content-box/border-box 두 마스크를 exclude 합성
           ③ blur 로 번지게 → "새어나오는" 느낌. 중앙(영상)은 마스크에서 잘려 깨끗하다.
     성능: 애니메이션 대상이 커스텀 프로퍼티 1개 + opacity 라 레이아웃/리플로우가 없다. */
  @property --cam-glow-a{ syntax:"<angle>"; initial-value:0deg; inherits:false; }
  @property --cam-glow-b{ syntax:"<angle>"; initial-value:180deg; inherits:false; }
  /* ★ 빛은 프레임 테두리에서 **안쪽으로만** 번진다 — Siri/Gemini 의 화면 테두리 글로우와 같은 모델.
     바깥으로 빼면 영상이 패널을 꽉 채울 때(대부분) 위아래가 패널 클립 경계에 걸려 **툭 잘린다**
     (사용자 지적 2026-07-29). 안쪽만 쓰면 바깥 경계가 곧 **영상 테두리 자신**이라 잘린 티가 안 난다
     (vbox 의 overflow:hidden 이 영상과 같은 둥근 사각형으로 깎아준다 — 그래서 건드리지 않는다).
     레이어는 프레임과 같은 크기(inset:0)로 깔고, 어디까지 번질지는 아래 마스크가 정한다. */
  .cockpit-cam.busy .cockpit-vbox::before, .cockpit-cam.busy .cockpit-vbox::after,
  .cockpit-cam.busy:not(:has(.cockpit-vbox))::before, .cockpit-cam.busy:not(:has(.cockpit-vbox))::after{
    content:""; position:absolute; inset:0; pointer-events:none; z-index:4;   /* 마커(z=5) 아래 */
    border-radius:6px; will-change:opacity, transform;
    --cam-peak:.9; }                                 /* 최대 밝기 — 두 층이 이 값을 비율로 나눠 쓴다 */
  /* ★ 알파에 직접 그라데이션을 준다 — 네 변에서 각각 **안쪽으로 사라지는** 마스크 4장을 합친다
     (mask 다중 레이어 기본 합성 = add). 종전의 XOR 링은 두께 안쪽 알파가 통째로 1 이라, 아무리 얇혀도
     심지가 '딴딴한 덩어리' 로 보였다(사용자 지적 2026-07-29) — blur 는 그 덩어리의 가장자리만 깎을 뿐
     알파 곡선 자체를 못 만든다. 깊이는 min(%, px) 라 작은 PiP 에서도 프레임을 통째로 덮지 않는다.
     네 변이 겹치는 모서리는 알파가 더해져 살짝 더 밝다 — 안쪽 글로우의 자연스러운 결이다. */
  .cockpit-cam.busy .cockpit-vbox::after, .cockpit-cam.busy .cockpit-vbox::before,
  .cockpit-cam.busy:not(:has(.cockpit-vbox))::after, .cockpit-cam.busy:not(:has(.cockpit-vbox))::before{
    -webkit-mask:
      linear-gradient(to bottom, #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) top/100% var(--cam-depth) no-repeat,
      linear-gradient(to top,    #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) bottom/100% var(--cam-depth) no-repeat,
      linear-gradient(to right,  #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) left/var(--cam-depth) 100% no-repeat,
      linear-gradient(to left,   #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) right/var(--cam-depth) 100% no-repeat;
    mask:
      linear-gradient(to bottom, #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) top/100% var(--cam-depth) no-repeat,
      linear-gradient(to top,    #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) bottom/100% var(--cam-depth) no-repeat,
      linear-gradient(to right,  #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) left/var(--cam-depth) 100% no-repeat,
      linear-gradient(to left,   #000 0, rgba(0,0,0,.5) 30%, rgba(0,0,0,.13) 64%, transparent 100%) right/var(--cam-depth) 100% no-repeat; }
  /* 색은 둘레를 따라 **끊기지 않고 이어지는 스펙트럼**(파랑→보라→자홍→호박→초록→파랑).
     ★아른거림: 회전만으로는 기계적이다. 회전(13s/21s)·숨(5.3s/7.9s)의 주기를 **서로 배수가 아니게**
     두고 미세 확대(scale)를 얹으면 두 층의 간섭이 계속 어긋나 패턴이 반복되지 않는다.
     주광 — 테두리 근처에서 밝고 프레임 15% 안쪽에서 완전 투명. 깊이는 %(프레임 비례)라 PiP·전체화면 어디서나
     같은 비율로 보인다. 값(15/50/0.9/5s)은 실물 비교 후 사용자 확정(2026-07-29). */
  .cockpit-cam.busy .cockpit-vbox::after,
  .cockpit-cam.busy:not(:has(.cockpit-vbox))::after{
    --cam-depth:15%;
    filter:blur(5px);                                /* 네 변 이음새만 다듬는 정도 — 감쇠는 마스크가 만든다 */
    background:conic-gradient(from var(--cam-glow-a),
      color-mix(in srgb, var(--accent) 78%, transparent) 0deg,
      color-mix(in srgb, #8f6fe6 72%, transparent) 72deg,
      color-mix(in srgb, #e2557a 68%, transparent) 144deg,
      color-mix(in srgb, #e0a33f 66%, transparent) 216deg,
      color-mix(in srgb, var(--link-ok) 70%, transparent) 288deg,
      color-mix(in srgb, var(--accent) 78%, transparent) 360deg);
    animation:cam-swirl-a 5s linear infinite, cam-breathe-a 4.1s ease-in-out infinite; }
  /* 자락 — 같은 그라데이션을 더 깊게, 훨씬 옅게. 두 겹이 겹쳐 감쇠 곡선에 두께가 생긴다. */
  .cockpit-cam.busy .cockpit-vbox::before,
  .cockpit-cam.busy:not(:has(.cockpit-vbox))::before{
    --cam-depth:50%;
    filter:blur(11px);
    background:conic-gradient(from var(--cam-glow-b),
      color-mix(in srgb, var(--accent) 36%, transparent) 0deg,
      color-mix(in srgb, #8f6fe6 34%, transparent) 90deg,
      color-mix(in srgb, #e2557a 30%, transparent) 180deg,
      color-mix(in srgb, var(--link-ok) 32%, transparent) 270deg,
      color-mix(in srgb, var(--accent) 36%, transparent) 360deg);
    animation:cam-swirl-b 8.1s linear infinite, cam-breathe-b 6.7s ease-in-out infinite; }
  @keyframes cam-swirl-a{ to{ --cam-glow-a:360deg } }
  @keyframes cam-swirl-b{ to{ --cam-glow-b:-180deg } }   /* 반대 방향 — 두 층의 위상차가 일렁임 */
  /* 숨 = 밝기 + 미세 확대. 확대가 있어야 '빛이 번졌다 잦아든다' 로 읽힌다(밝기만이면 깜빡임).
     주기(4.1s/6.7s)는 회전(5s/8.1s)과 배수 관계가 아니라 네 리듬이 계속 어긋난다 = 아른거림. */
  @keyframes cam-breathe-a{ 0%,100%{ opacity:calc(var(--cam-peak) * .57); transform:scale(1) }
                             50%{ opacity:var(--cam-peak); transform:scale(1.012) } }
  @keyframes cam-breathe-b{ 0%,100%{ opacity:calc(var(--cam-peak) * .3); transform:scale(1) }
                             50%{ opacity:calc(var(--cam-peak) * .66); transform:scale(1.03) } }
  /* 접근성 — 점멸/모션 민감: 회전·맥동 없이 은은한 정적 글로우만(진행 중 신호는 유지) */
  @media (prefers-reduced-motion: reduce){
    .cockpit-cam.busy .cockpit-vbox::before,
    .cockpit-cam.busy:not(:has(.cockpit-vbox))::before{ display:none }
    .cockpit-cam.busy .cockpit-vbox::after,
    .cockpit-cam.busy:not(:has(.cockpit-vbox))::after{ animation:none; opacity:.5; transform:none } }
  /* 책상(피킹 평면) 높이 바 — 상단 중앙, 무장 시 노출(사용자가 클릭 평면 높이 설정) */
  .cockpit-deskbar{ position:absolute; left:50%; top:50px; transform:translateX(-50%); z-index:41;
    display:flex; gap:7px; align-items:center; font-size:12px; color:var(--ink);
    background:color-mix(in srgb, var(--bg) 90%, transparent); border:1px solid var(--accent);
    border-radius:7px; padding:4px 10px; box-shadow:0 4px 14px rgba(0,0,0,.45); }
  .cockpit-deskbar b{ font:12px ui-monospace,monospace; min-width:46px; text-align:center; color:var(--accent); }
  .cockpit-deskbar button{ width:22px; height:22px; border-radius:4px; border:1px solid var(--line); background:var(--card); color:var(--ink); cursor:pointer; font-size:14px; line-height:1; padding:0; }
  .cockpit-deskbar button:hover{ border-color:var(--accent); color:var(--accent); }
  /* 6DOF 피킹 어댑터 — 평면 클릭 → x/y/z·rx/ry/rz 슬라이더/버튼 조정 → 고스트 실시간 → [실행].
     콕핏 좌측(3D 트윈 중앙을 안 가림, 이전엔 시뮬 안 우상단이라 로봇을 가렸음). */
  /* 폭 — 잠금 버튼(22px+간격)이 들어와도 슬라이더가 안 찌그러지게. 240px 시절 슬라이더가 70px 였는데
     버튼을 끼우니 43px 로 줄어 조절이 안 됐다(실사용 지적). 기존 요소는 그대로 두고 패널을 넓힌다. */
  .cockpit-pickpanel{ position:absolute; left:8px; top:80px; z-index:40; width:296px;
    background:color-mix(in srgb, var(--bg) 92%, transparent); border:1px solid var(--accent); border-radius:8px;
    padding:9px 11px; box-shadow:0 6px 22px rgba(0,0,0,.55); font-size:12px; }
  .cockpit-pickpanel .pk-head{ font-weight:700; color:var(--ink); margin-bottom:7px; display:flex; justify-content:space-between; align-items:center; }
  .cockpit-pickpanel .pk-reach{ font-size:11px; font-weight:600; color:var(--dim); }
  .cockpit-pickpanel .pk-reach.ok{ color:var(--link-ok); }
  .cockpit-pickpanel .pk-reach.bad{ color:var(--err); }
  .cockpit-pickpanel .pk-reach.warn{ color:var(--busy); font-weight:600; }
  .cockpit-pickpanel .pk-row{ display:flex; align-items:center; gap:5px; margin:3px 0; }
  .cockpit-pickpanel .pk-row label{ width:26px; color:var(--ink2); font-weight:600; }
  .cockpit-pickpanel .pk-sld{ flex:1; min-width:88px; accent-color:var(--accent); }
  .cockpit-pickpanel .pk-val{ width:58px; text-align:right; color:var(--ink); font:11px ui-monospace,monospace; }
  .cockpit-pickpanel .pk-dec, .cockpit-pickpanel .pk-inc{ width:22px; height:22px; border-radius:4px; border:1px solid var(--line); background:var(--card); color:var(--ink); cursor:pointer; font-size:14px; line-height:1; padding:0; }
  .cockpit-pickpanel .pk-dec:hover, .cockpit-pickpanel .pk-inc:hover{ border-color:var(--accent); color:var(--accent); }
  .cockpit-pickpanel .pk-acts{ display:flex; gap:6px; margin-top:9px; }
  .cockpit-pickpanel .pk-acts button{ flex:1; font:inherit; font-size:12px; border-radius:5px; padding:5px; cursor:pointer; border:1px solid var(--line); background:var(--card); color:var(--ink); }
  .cockpit-pickpanel .pk-go{ background:var(--link-ok); color:#04223a; border-color:var(--link-ok); font-weight:700; }
  .cockpit-pickpanel .pk-go:disabled{ background:var(--card); color:var(--dim); border-color:var(--line); font-weight:400; cursor:not-allowed; }
  .cockpit-pickpanel .pk-reset{ flex:0 0 auto; }
  .cockpit-pickpanel .pk-reset:hover{ border-color:var(--accent); color:var(--accent); }
  .cockpit-pickpanel .pk-cancel:hover{ border-color:var(--err); color:var(--err); }
  .cockpit-pickpanel .pk-manip{ display:flex; align-items:center; gap:5px; margin:7px 0 2px; }
  .cockpit-pickpanel .pk-manip label{ width:38px; color:var(--ink2); font-weight:600; font-size:11px; }
  .cockpit-pickpanel .pk-bar{ flex:1; height:7px; border-radius:4px; background:var(--card); border:1px solid var(--line); overflow:hidden; }
  .cockpit-pickpanel .pk-bar > i{ display:block; height:100%; width:0; transition:width .12s; background:var(--dim); }
  .cockpit-pickpanel .pk-bar > i.hi{ background:var(--link-ok); }
  .cockpit-pickpanel .pk-bar > i.mid{ background:#e0b341; }
  .cockpit-pickpanel .pk-bar > i.lo{ background:var(--err); }
  .cockpit-pickpanel .pk-mval{ width:30px; text-align:right; color:var(--ink); font:11px ui-monospace,monospace; }
  /* 자가충돌 근사 여유(clearance_m) — manipulability 가 못 잡는 축이라 별도 행이다. 캡슐 근사라
     얇은 구간은 '안전' 도 '충돌' 도 아닌 판정 불능이라, 초록/노랑/주황/빨강 4단으로 구분한다. */
  .cockpit-pickpanel .pk-clear{ display:flex; align-items:center; gap:5px; margin:2px 0 2px; }
  .cockpit-pickpanel .pk-clear[hidden]{ display:none; }
  .cockpit-pickpanel .pk-clear label{ width:64px; color:var(--ink2); font-weight:600; font-size:11px; }
  .cockpit-pickpanel .pk-cval{ flex:1; text-align:right; font:11px ui-monospace,monospace; color:var(--ink); }
  .cockpit-pickpanel .pk-cval.ok{ color:var(--link-ok); }
  .cockpit-pickpanel .pk-cval.mid{ color:var(--help); }
  .cockpit-pickpanel .pk-cval.warn{ color:var(--busy); font-weight:700; }
  .cockpit-pickpanel .pk-cval.bad{ color:var(--err); font-weight:700; }
  /* 보낼 orient 요약 — 축 3개 × 상태 3개라 행만으로는 요청 전체가 안 읽힌다. 실행 직전에 보는 줄. */
  .cockpit-pickpanel .pk-intent{ margin:4px 0 0; padding-top:4px; border-top:1px solid var(--line);
    color:var(--ink2); font:11px ui-monospace,monospace; letter-spacing:-.2px; }
  .cockpit-pickpanel .pk-intent[hidden]{ display:none; }
  /* 팔 수동 지정 — 로봇 자동 선택(타깃 y 부호)이 중앙선 근처에서 관통하는 팔을 고를 수 있다
     (실측 y=+0.029: 좌팔 −8.2cm 관통 / 우팔 +9.6cm). 빈 값=미전송=자동. */
  .pk-armrow{ display:flex; align-items:center; gap:6px; margin:4px 0 0; font-size:11px; }
  .pk-armrow label{ color:var(--ink2); font-weight:600; }
  .pk-armrow select{ background:var(--card); color:var(--ink); border:1px solid var(--line);
    border-radius:5px; padding:2px 6px; font:inherit; font-size:11px; }
  .pk-armrow .pk-armhint{ color:var(--dim); }
  /* 거부·경고 사유 + 파지점 출처. **문장은 로봇이 만든 그대로** 띄운다 — 측정값이 들어 있어
     그 숫자가 다음 수를 정한다. 경고(노랑)는 [실행]을 막지 않는다: 개폐폭 초과 판정이 미검증
     근사(부푼 마스크)라 거부에서 경고로 내려왔다. 판단은 사람이 한다. */
  .pk-why{ display:flex; flex-direction:column; gap:4px; margin:6px 0 0; font-size:11px; line-height:1.45; }
  .pk-why[hidden]{ display:none; }
  .pk-why > div{ padding:5px 7px; border-radius:5px; word-break:break-word; }
  .pk-why .why-bad{ background:color-mix(in srgb, var(--err) 16%, transparent);
    border:1px solid var(--err); color:var(--err); font-weight:600; }
  .pk-why .why-warn{ background:color-mix(in srgb, var(--busy) 14%, transparent);
    border:1px solid var(--busy); color:var(--busy); }
  .pk-why .why-src{ color:var(--dim); border:1px dashed var(--line); }
  .pk-why .why-src.ok{ color:var(--link-ok); border-color:color-mix(in srgb, var(--link-ok) 45%, transparent); }
  .pk-why .why-src.warn{ color:var(--busy); border-color:color-mix(in srgb, var(--busy) 45%, transparent); }
  /* 로봇 미션 패널 — 광고된 available_missions 를 그린다(플릿 미션 뷰와 별개) */
  .cockpit-missions{ display:flex; flex-direction:column; gap:8px; font-size:12.5px; }
  .cockpit-missions .mi-state{ color:var(--ink2); padding:6px 8px; border-radius:7px;
    background:color-mix(in srgb, var(--ink) 6%, transparent); border:1px solid var(--line); }
  .cockpit-missions .mi-state.on{ color:var(--ink); border-color:var(--busy);
    background:color-mix(in srgb, var(--busy) 14%, transparent); font-weight:600; }
  .cockpit-missions .mi-state.bad{ color:var(--err); border-color:var(--err); }
  .cockpit-missions .mi-run{ display:flex; gap:6px; }
  .cockpit-missions select{ flex:1; min-width:0; background:var(--card); color:var(--ink);
    border:1px solid var(--line); border-radius:7px; padding:6px 8px; font-size:12.5px; font-family:inherit; }
  .cockpit-missions select:disabled{ opacity:.5; }
  .cockpit-missions .mi-go{ background:var(--accent); color:var(--on-accent); border:none;
    border-radius:7px; padding:6px 12px; font-size:12.5px; font-weight:600; cursor:pointer; white-space:nowrap; }
  .cockpit-missions .mi-go:disabled{ opacity:.4; cursor:default; }
  .cockpit-missions .mi-hint{ color:var(--dim); font-size:11px; }
  /* 손목 힘(FT) — tared:false 면 게이지를 안 그린다(숫자를 힘으로 읽으면 안 되는 상태) */
  .cockpit-ft{ display:flex; flex-direction:column; gap:10px; font-size:12px; }
  .cockpit-ft .ft-none{ color:var(--dim); }
  .cockpit-ft .ft-arm{ display:flex; flex-direction:column; gap:4px; }
  .cockpit-ft .ft-head{ display:flex; align-items:center; gap:6px; }
  .cockpit-ft .ft-head b{ color:var(--ink2); font-size:11px; text-transform:uppercase; letter-spacing:.04em; }
  .cockpit-ft .ft-mag{ flex:1; text-align:right; font:12px ui-monospace,monospace; color:var(--ink); }
  .cockpit-ft .ft-tare{ background:var(--card); color:var(--ink); border:1px solid var(--line);
    border-radius:6px; padding:2px 8px; font-size:11px; cursor:pointer; }
  .cockpit-ft .ft-tare:hover{ border-color:var(--accent); }
  .cockpit-ft .ft-bar{ height:6px; border-radius:4px; background:var(--card);
    border:1px solid var(--line); overflow:hidden; }
  .cockpit-ft .ft-bar > i{ display:block; height:100%; width:0; background:var(--link-ok); transition:width .12s; }
  .cockpit-ft .ft-axes{ color:var(--dim); font:11px ui-monospace,monospace; }
  .cockpit-ft .ft-warn:not(:empty){ color:var(--busy); font-size:11px; }
  .cockpit-ft .ft-arm.untared .ft-mag{ color:var(--busy); font-weight:600; }
  .cockpit-ft .ft-arm.untared .ft-bar{ opacity:.35; }
  .cockpit-ft .ft-arm.suspect .ft-bar > i{ background:var(--busy); }
  /* 대화(ai_chat) — 파라미터 폼이 아니라 입력창 하나 */
  .cockpit-chat{ display:flex; flex-direction:column; gap:8px; height:100%; min-height:180px; }
  .cockpit-chat .ch-log{ flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:6px;
    font-size:12.5px; line-height:1.5; }
  .cockpit-chat .ch-row{ padding:6px 9px; border-radius:9px; max-width:92%; word-break:break-word; }
  .cockpit-chat .ch-me{ align-self:flex-end; background:var(--accent); color:var(--on-accent); }
  .cockpit-chat .ch-robot{ align-self:flex-start; background:var(--card);
    border:1px solid var(--line); color:var(--ink); }
  .cockpit-chat .ch-sys{ align-self:center; color:var(--busy); font-size:11.5px; }
  .cockpit-chat .ch-in{ display:flex; gap:6px; }
  .cockpit-chat input{ flex:1; min-width:0; background:var(--card); color:var(--ink);
    border:1px solid var(--line); border-radius:8px; padding:7px 10px; font-size:12.5px; font-family:inherit; }
  .cockpit-chat input:focus{ outline:2px solid var(--accent); outline-offset:1px; }
  .cockpit-chat input:disabled{ opacity:.5; }
  .cockpit-chat .ch-send{ background:var(--accent); color:var(--on-accent); border:none;
    border-radius:8px; padding:7px 12px; font-size:12.5px; font-weight:600; cursor:pointer; white-space:nowrap; }
  .cockpit-chat .ch-send:disabled{ opacity:.4; cursor:default; }
  /* AI 대화 토글 — 두뇌가 배선된 로봇에서만 켤 수 있다. 끄고 보내면 로봇은 기록만 한다(어노테이션). */
  .cockpit-chat .ch-ai{ display:flex; align-items:center; gap:4px; font-size:11.5px; color:var(--dim);
    cursor:pointer; user-select:none; white-space:nowrap; }
  .cockpit-chat .ch-ai:has(input:disabled){ opacity:.4; cursor:default; }
  .cockpit-chat .ch-ai input{ width:auto; margin:0; cursor:inherit; }
  .cockpit-chat .ch-hint:not(:empty){ color:var(--dim); font-size:11px; }
  /* 피킹 ready 바 — 스캔 물체 클릭 시 6DOF 패널 대신 컴팩트 확인(라벨+도달성+[실행]) — 스캔 오버레이 유지 */
  /* ready 바 — 한 줄이 기본이고, 사유 배너·팔 선택이 붙으면 아래로 감긴다(flex-basis:100%).
     기존 자식 구조는 그대로 두고 wrap 만 켠다 — 구조를 바꾸면 [실행]/✕ 정렬이 흐트러진다. */
  .cockpit-pickready{ position:absolute; left:8px; top:80px; z-index:40; display:flex; align-items:center;
    flex-wrap:wrap; gap:8px; max-width:min(420px, calc(100% - 16px));
    background:color-mix(in srgb, var(--bg) 92%, transparent); border:1px solid var(--accent); border-radius:8px;
    padding:7px 10px; box-shadow:0 6px 22px rgba(0,0,0,.55); font-size:12px; }
  .cockpit-pickready .pk-why, .cockpit-pickready .pk-armrow{ flex-basis:100%; margin:0; }
  .cockpit-pickready .pr-lab{ font-weight:700; color:var(--ink); max-width:130px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .cockpit-pickready .pk-reach{ font-size:11px; font-weight:600; color:var(--dim); }
  .cockpit-pickready .pk-reach.ok{ color:var(--link-ok); }
  .cockpit-pickready .pk-reach.bad{ color:var(--err); }
  .cockpit-pickready .pk-reach.warn{ color:var(--busy); font-weight:600; }
  .cockpit-pickready button{ font:inherit; font-size:12px; border-radius:5px; padding:4px 10px; cursor:pointer; border:1px solid var(--line); background:var(--card); color:var(--ink); }
  .cockpit-pickready .pr-go{ background:var(--link-ok); color:#04223a; border-color:var(--link-ok); font-weight:700; }
  .cockpit-pickready .pr-go:disabled{ background:var(--card); color:var(--dim); border-color:var(--line); font-weight:400; cursor:not-allowed; }
  .cockpit-pickready .pr-x:hover{ border-color:var(--err); color:var(--err); }
  .cockpit-cam.vpick-armed video, .cockpit-cam.vpick-armed .cockpit-vbox,
  .cockpit-cam.probe-armed video, .cockpit-cam.probe-armed .cockpit-vbox{ cursor:crosshair; }
  /* 피킹 무장 중 영상 — 끌기가 텍스트 선택/이미지 드래그로 새지 않게. pointerdown 에서
     preventDefault 하면 뒤따르는 click 이 안 와서 점 피킹이 죽으므로 **CSS 로만** 막는다. */
  .cockpit-cam.vpick-armed video{ user-select:none; -webkit-user-drag:none; }
  /* 끌기 중 사각형 — 마스크 윤곽(점선)과 헷갈리지 않게 실선. */
  .cockpit-dragbox{ position:absolute; z-index:6; pointer-events:none; border:2px solid #e06c9f;
    background:rgba(224,108,159,0.14); border-radius:2px; }
  /* 미니맵(좌측하단) — 표준맵 + 로봇 포즈 */
  /* 3D 맵뷰(미니맵 패널 토글) — 벽 반투명 압출 + 뷰 프리셋 */
  .cockpit-mmwrap{ position:relative; height:100%; min-height:210px; }
  .cockpit-mm3d{ position:absolute; inset:0; border:1px solid var(--line); border-radius:8px; overflow:hidden; background:#0a0e14; }
  .cockpit-mmbar{ position:absolute; top:6px; right:6px; z-index:8; display:flex; gap:4px; }
  .cockpit-mmbar button{ background:color-mix(in srgb, var(--bg) 82%, transparent); border:1px solid var(--line); color:var(--ink2);
    padding:2px 8px; border-radius:6px; font-size:11px; cursor:pointer; }
  .cockpit-mmbar button.on{ background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
  .cockpit-minimap{ position:absolute; left:12px; bottom:12px; width:200px; max-width:32%; aspect-ratio:3/2; z-index:6;
    background:#0a0e14 center/contain no-repeat; border:1px solid var(--accent); border-radius:8px; box-shadow:0 2px 12px rgba(0,0,0,.5); overflow:hidden; }
  .cockpit-minimap.empty{ display:flex; align-items:center; justify-content:center; border-color:var(--line); }
  .cockpit-mm-ph{ color:var(--off); font-size:10px; text-align:center; line-height:1.4; padding:4px; }
  .cockpit-mm-dot{ position:absolute; width:12px; height:12px; }
  .cockpit-mm-dot::before{ content:""; position:absolute; inset:0; border-radius:50%; background:var(--accent); box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 35%,transparent); }
  .cockpit-mm-dot::after{ content:""; position:absolute; left:50%; top:-7px; transform:translateX(-50%); border-left:4px solid transparent; border-right:4px solid transparent; border-bottom:7px solid var(--accent); }
  /* 입력 시각화 HUD(우측하단) — 좌/우 스틱 도트 + 트리거 바, 세기 반응형 */
  .cockpit-inhud{ position:absolute; right:12px; bottom:12px; z-index:6; pointer-events:none; display:flex; align-items:flex-end; gap:8px; padding:8px 10px;
    background:color-mix(in srgb, var(--bg) 78%, transparent); border:1px solid var(--line); border-radius:10px; }
  .cockpit-inhud .hud-pad{ position:relative; width:56px; height:56px; border:1px solid var(--line); border-radius:8px; background:var(--card2); }
  .cockpit-inhud .hud-lbl{ position:absolute; top:2px; left:0; right:0; text-align:center; font-size:8px; color:var(--off); }
  .cockpit-inhud .hud-dot{ position:absolute; width:12px; height:12px; margin:-6px; border-radius:50%; background:var(--off); }
  .cockpit-inhud .hud-dot.on{ background:var(--accent); box-shadow:0 0 6px var(--accent); }
  .cockpit-inhud .hud-trig{ width:10px; height:56px; border:1px solid var(--line); border-radius:4px; background:var(--card2); position:relative; overflow:hidden; }
  .cockpit-inhud .hud-trig i{ position:absolute; left:0; right:0; bottom:0; height:0; background:var(--busy); }
  .cockpit-inhud .hud-stop{ align-self:center; font-size:10px; font-weight:bold; color:var(--off); border:1px solid var(--line); border-radius:5px; padding:4px 6px; }
  .cockpit-inhud .hud-stop.on{ color:var(--bg); background:var(--err); border-color:var(--err); }
  .cockpit-inhud.blocked{ border-color:var(--err); }         /* 다른 제어 세션이 로봇을 잡고 있다 */
  /* 인간개입 오버레이(A2): WAIT_INPUT/NEED_HELP → help.action */
  .cockpit-intervene{ position:absolute; inset:0; z-index:20; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,.45); }
  .cockpit-intervene[hidden]{ display:none; }   /* [hidden] 특이도: display:flex 가 UA [hidden] 을 이겨 빈 45% 오버레이가 카메라를 상시 덮던 버그 */
  .cockpit-intervene .iv-card{ background:var(--card); border:1px solid var(--accent); border-radius:12px; padding:18px 22px; max-width:82%;
    box-shadow:0 8px 30px rgba(0,0,0,.5); text-align:center; }
  .cockpit-intervene .iv-title{ font-size:15px; font-weight:600; color:var(--accent); margin-bottom:8px; }
  .cockpit-intervene .iv-msg{ font-size:13px; color:var(--ink); margin-bottom:14px; white-space:pre-wrap; }
  .cockpit-intervene .iv-btns{ display:flex; gap:8px; flex-wrap:wrap; justify-content:center; }
  /* 입력 대기 위젯 — 로봇이 options 를 주면 셀렉트, 없으면 자유 텍스트(값은 help.action.context 로 제출) */
  /* 파지 자세 축별 잠금 — 🔒 고정(운영자 의도) / 🔓 로봇이 최적 탐색. 로봇이 대역을 광고할 때만 뜬다 */
  /* 잠금 버튼 — ±버튼과 같은 22×22 박스로 통일(행 높이·정렬이 흔들리지 않게) */
  .cockpit-pickpanel .pk-lock{ flex:0 0 auto; width:22px; height:22px; padding:0; border-radius:4px;
    border:1px solid var(--line); background:var(--card); cursor:pointer; font-size:12px; line-height:1; }
  .cockpit-pickpanel .pk-lock:hover{ border-color:var(--accent); }
  /* 위치 행(X/Y/Z)엔 잠금이 없다 — 같은 크기의 빈 칸으로 열을 맞춘다(슬라이더 폭이 행마다 달라지면 지저분) */
  .cockpit-pickpanel .pk-lockpad{ flex:0 0 auto; width:22px; }
  .cockpit-pickpanel .pk-row.free .pk-sld{ opacity:.45; }        /* 자동 탐색 축 — 내 지시가 아니다 */
  .cockpit-pickpanel .pk-row.free .pk-val{ color:var(--link-ok); font-style:italic; }

  /* 파라미터 폼 고급 옵션 — 로봇이 advanced:true 로 선언한 것들(안전장치류)은 접어둔다 */
  .param-adv{ margin:6px 0 2px; border-top:1px solid var(--line); padding-top:6px; }
  .param-adv > summary{ cursor:pointer; font-size:12px; color:var(--dim); padding:2px 0; user-select:none; }
  .param-adv > summary:hover{ color:var(--ink2); }
  .param-adv[open] > summary{ margin-bottom:4px; }
  .cockpit-intervene .iv-field{ display:block; width:100%; margin-top:10px; box-sizing:border-box;
    background:var(--bg); border:1px solid var(--line); color:var(--ink); border-radius:6px;
    padding:7px 10px; font-size:13px; font-family:inherit; }
  .cockpit-intervene .iv-field:focus{ outline:2px solid var(--accent); outline-offset:1px; }
  .cockpit-intervene .iv-btns button{ background:var(--accent); color:var(--on-accent); border:none; border-radius:7px; padding:8px 16px; font-size:13px; cursor:pointer; }
  .cockpit-intervene .iv-btns button:hover{ filter:brightness(1.1); }
  .cockpit-intervene .iv-btns button.iv-sec{ background:var(--card); color:var(--ink); border:1px solid var(--line); }
  .cockpit-intervene .iv-btns button.iv-take{ background:var(--busy); color:var(--bg); }
  .cockpit-intervene .iv-btns button.iv-danger{ background:var(--err); color:var(--bg); }
  .cockpit-intervene .iv-prog{ font-size:11px; font-weight:500; color:var(--dim); background:color-mix(in srgb, var(--ink) 8%, transparent); border-radius:10px; padding:1px 8px; margin-left:6px; vertical-align:middle; }
  /* 자발 개입(개입 시작) — RUNNING 중 operator 가 teleop 인수 요청. STOP 왼쪽. */
  .cockpit-takeover{ background:var(--busy); color:var(--bg); border:none; border-radius:7px; padding:6px 12px; font-size:12px; font-weight:600; cursor:pointer; margin-right:6px; }
  .cockpit-takeover:hover{ filter:brightness(1.1); }
  .cockpit-takeover[hidden]{ display:none; }
  /* 마우스 프리셋 잠금 안내 */
  .cockpit-mouselock{ position:absolute; left:50%; top:14px; transform:translateX(-50%); z-index:7; font-size:12px; color:var(--ink); cursor:pointer;
    background:color-mix(in srgb, var(--accent) 22%, var(--bg)); border:1px solid var(--accent); border-radius:20px; padding:6px 14px; }
  /* 카메라 기본=기본해상도(contain — 전체 보임, 필요시 채우기 토글), .fill=채우기(cover — 잘라서 꽉) */
  /* 기본: 박스를 트랙 비율(aspect-ratio, JS 주입)로 잠가 패널·PiP 크기에 맞춰 '비율 유지' 축소/확대 — 왜곡·레터박스 없음 */
  .cockpit-vbox{ position:relative; flex:0 1 auto; margin:auto; max-width:100%; max-height:100%; min-width:0; min-height:0; display:flex; overflow:hidden; aspect-ratio:4/3; }
  /* 영상 클릭 마커 — 피킹/프로브 클릭 지점(vbox % 고정, 리사이즈 추종) + 로봇 회신 x,y,z·도달성 라벨 */
  .cockpit-cam-mark{ position:absolute; z-index:5; pointer-events:none; transform:translate(-50%,-50%); color:#e0b341; }
  .cockpit-cam-mark.ok{ color:#33e07a; } .cockpit-cam-mark.bad{ color:#ff5a5a; }
  .cockpit-cam-mark::before{ content:""; display:block; width:13px; height:13px; margin:0 auto;
    border:2px solid currentColor; border-radius:50%; box-shadow:0 0 0 1px rgba(0,0,0,.55); }
  .cockpit-cam-mark .cm-lab{ position:absolute; left:50%; top:calc(100% + 4px); transform:translateX(-50%);
    white-space:nowrap; font-size:11px; padding:2px 7px; border-radius:5px; color:var(--ink);
    background:color-mix(in srgb, var(--bg) 86%, transparent); border:1px solid currentColor; }
  .cockpit-vbox video{ width:100%; height:100%; border-radius:6px; background:#000; display:block; object-fit:contain; }
  .cockpit-cam{ display:flex; align-items:center; justify-content:center; }
  /* 채우기 모드: 박스가 패널을 꽉 채우고(비율 해제) 영상은 cover 크롭 */
  .cockpit-cam.fill .cockpit-vbox{ flex:1 1 auto; aspect-ratio:auto; margin:0; }
  .cockpit-cam.fill .cockpit-vbox video{ object-fit:cover; }
  .cockpit-camwrap .cockpit-cam{ align-items:stretch; align-content:stretch; }
  .cockpit-camfit{ background:var(--card); border:1px solid var(--line); color:var(--ink2); padding:5px 10px; border-radius:6px; font-size:12px; cursor:pointer; }
  .cockpit-camfit.on{ background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
  /* 로봇 소리 상태 — "보내는데 안 들린다" 를 사람이 알 수 있게. 자동재생 차단 시 클릭 유도. */
  .cockpit-audio-badge{ position:absolute; bottom:6px; left:6px; font-size:11px; padding:2px 7px;
    border-radius:10px; color:var(--ink2); background:color-mix(in srgb, var(--bg) 80%, transparent);
    pointer-events:none; }
  .cockpit-audio-badge[data-blocked="1"]{ color:var(--bg); background:var(--busy);
    pointer-events:auto; cursor:pointer; }
  .cockpit-vlabel{ position:absolute; top:4px; left:6px; pointer-events:none; font-size:10px; color:var(--ink2); background:color-mix(in srgb, var(--bg) 80%, transparent); padding:0 4px; border-radius:3px; }
  .cockpit-pane{ border:1px solid var(--line); border-radius:8px; background:var(--card); padding:8px 10px; }
  .cockpit-h{ font-size:10px; text-transform:uppercase; color:var(--dim); letter-spacing:.5px; margin-bottom:6px; }
  .cockpit-dim{ color:var(--off); font-size:11px; }
  .cockpit-status{ margin:0 0 6px; font:12px/1.5 Consolas,monospace; color:var(--ink2); white-space:pre-wrap;
    border-bottom:1px solid var(--line); padding-bottom:6px; }
  /* 상태 전체 트리 — 로봇이 올린 모든 필드(접이식) */
  .cockpit-statustree{ font:11px/1.5 Consolas,monospace; color:var(--ink2); }
  .cockpit-statustree details{ margin-left:2px; padding-left:10px; border-left:1px solid var(--line); }
  .cockpit-statustree summary{ cursor:pointer; color:var(--accent2); list-style-position:outside; margin-left:-4px;
    padding:2px 4px; border-radius:4px; }
  .cockpit-statustree summary:hover{ background:color-mix(in srgb, var(--accent) 14%, transparent); }
  /* 메시지 로그 접이식 — 줄 전체가 클릭 타깃, 펼치면 페이로드 JSON */
  .cockpit-log details.logmsg summary{ cursor:pointer; padding:1px 4px; border-radius:4px; }
  .cockpit-log details.logmsg summary:hover{ background:color-mix(in srgb, var(--accent) 14%, transparent); color:var(--ink); }
  .cockpit-log details.logmsg pre{ margin:2px 0 6px 16px; padding:4px 8px; max-height:180px; overflow:auto;
    background:var(--card); border:1px solid var(--line); border-radius:5px; color:var(--ink2); }
  .cockpit-statustree summary i{ color:var(--dim); font-style:normal; font-size:10px; }
  .cockpit-statustree .st-row{ display:flex; gap:8px; padding-left:12px; }
  .cockpit-statustree .st-k{ color:var(--dim); flex:none; }
  .cockpit-statustree .st-v{ color:var(--ink); word-break:break-all; }
  /* 스킬 블록 — 균일 그리드 칸(텍스트 길이 무관) + 아이콘/라벨 고정 구조 */
  .cockpit-skills{ display:grid; grid-template-columns:repeat(auto-fill, minmax(128px, 1fr)); gap:6px;
    align-content:start; }   /* 패널 높이에 행이 늘어나 블록이 뚱뚱해지는 것 방지 */
  .cockpit-skill{ display:flex; align-items:center; gap:6px; min-width:0; min-height:30px; text-align:left;
    background:var(--card2); border:1px solid var(--line); border-radius:6px; padding:5px 8px; font-size:12px; }
  .cockpit-skill .ico{ flex:0 0 auto; font-size:13px; line-height:1; }
  .cockpit-skill .nm{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .cockpit-skill.fixed{ border-color:var(--accent); }
  .cockpit-skill.custom{ border-color:color-mix(in srgb, var(--accent) 35%, var(--line)); }  /* 커스텀=옅은 블루 테두리 */
  .cockpit-skill.disabled{ opacity:.4; filter:grayscale(.6); cursor:not-allowed; border-color:var(--line); }
  .cockpit-skill:hover{ border-color:var(--accent); color:var(--accent); }
  .cockpit-pad{ display:grid; grid-template-columns:repeat(3,40px); grid-template-rows:repeat(3,34px); gap:4px; justify-content:center;
    grid-template-areas:". up ." "left mid right" ". down ."; }
  .cockpit-pad button{ background:var(--card2); border:1px solid var(--line); border-radius:5px; font-size:14px; touch-action:none; }
  .cockpit-pad button:active{ border-color:var(--accent); color:var(--accent); }
  .cockpit-pad.blocked{ opacity:.45; }                       /* 다른 제어 세션 활성 — 스틱 잠금 */
  .cockpit-pad button:disabled{ cursor:not-allowed; }
  .cockpit-pad .cockpit-pad-stop{ color:var(--err); }
  .latency-chart{ width:100%; height:84px; display:block; background:var(--card2); border-radius:5px; }
  .cockpit-legend{ display:flex; gap:12px; margin-top:5px; font-size:10px; }
  .cockpit-legend .lg::before{ content:"●"; margin-right:3px; }
  .cockpit-legend .lg-srv{ color:var(--ok); } .cockpit-legend .lg-net{ color:var(--help); } .cockpit-legend .lg-op{ color:var(--accent2); }
  .cockpit-legend .lg b{ color:var(--ink); font-weight:600; }   /* 실시간 RTT 값 */
  .cockpit-legend .lg.off{ opacity:.45; } .cockpit-legend .lg.off b{ color:var(--dim); font-weight:400; }
  .cockpit-legend .lg.down::before{ color:var(--err); } .cockpit-legend .lg.down b{ color:var(--err); font-weight:700; }   /* 링크 끊김 */
  .cockpit-lattoggle{ margin-left:auto; background:none; border:none; color:var(--accent2); font-size:10px; cursor:pointer; padding:0 2px; }
  .cockpit-lat-detail{ margin-top:5px; display:flex; flex-direction:column; gap:2px; }
  .cockpit-lat-detail[hidden]{ display:none; }
  .cockpit-lat-detail .latleg{ display:flex; justify-content:space-between; font-size:10px; color:var(--ink2); padding:1px 4px; border-top:1px solid var(--line); }
  .cockpit-lat-detail .latleg b{ color:var(--ink); font-weight:600; }
  .cockpit-log{ margin:0; padding:6px 12px; height:88px; overflow:auto; font:11px/1.4 Consolas,monospace; color:var(--dim); white-space:pre-wrap;
    border-top:1px solid var(--line); background:var(--bg); }
  /* ⚠️ body 가 user-select:none 이라(드래그 선택으로 UI 가 파랗게 뜨는 것 방지) **로그·상태값이
     복사가 안 됐다** — 붙여넣어 공유해야 하는 텍스트가 정확히 이것들이다(2026-08-28 신고).
     읽어서 옮길 목적의 영역만 선택 가능으로 되돌린다(버튼·탭은 그대로 선택 불가). */
  .cockpit-log, .cockpit-status, .cockpit-statustree, #log, #toast,
  .dock-body pre, .dock-body code{ user-select:text; -webkit-user-select:text; cursor:text; }
  /* 접이식 트리의 summary 는 클릭이 주 용도라 커서는 pointer 로 되돌린다(선택은 가능). */
  .cockpit-statustree summary{ cursor:pointer; }

  /* ── 도킹/탭 콕핏(CockpitDock) ── */
  .dock{ position:relative; flex:1; min-height:0; display:flex; padding:8px; }
  .dock-grid{ flex:1; display:flex; flex-direction:column; min-height:0; }
  .dock-row{ flex:1; display:flex; min-height:0; gap:0; }
  /* zone 리사이즈 스플리터 — 드래그로 좌/우 폭·하단 높이 조절(레이아웃 영속) */
  /* 스플리터: 평소에도 은은한 그립 바가 보이게(발견성) — 드래그로 존 크기 조절 */
  .dock-split{ flex:none; background:color-mix(in srgb, var(--line) 45%, transparent); border-radius:4px; transition:background .12s; position:relative; }
  .dock-split::after{ content:""; position:absolute; background:var(--ink2); opacity:.35; border-radius:2px; }
  .dock-split-v{ width:8px; cursor:col-resize; align-self:stretch; }
  .dock-split-v::after{ left:3px; top:50%; width:2px; height:26px; transform:translateY(-50%); }
  .dock-split-h{ height:8px; cursor:row-resize; }
  .dock-split-h::after{ top:3px; left:50%; height:2px; width:26px; transform:translateX(-50%); }
  .dock-split:hover, .dock-split.dragging{ background:color-mix(in srgb, var(--accent) 55%, transparent); }
  .dock.dragging .dock-split{ pointer-events:none; }   /* 탭 드래그 중엔 스플리터 비활성(드롭 방해 방지) */
  .dock-zone{ display:flex; flex-direction:column; min-height:0; min-width:0; border:1px solid var(--line); border-radius:7px; overflow:hidden; background:var(--card); }
  .dock-side{ width:340px; flex:none; } .dock-center{ flex:1; } .dock-zone.dock-bottom{ height:172px; flex:none; margin-top:6px; }
  .dock-zone.empty{ display:none; }
  /* 탭 드래그 중엔 빈 zone 도 접힌 스트립으로 노출 — display:none 이면 드롭 불가라 되돌릴 수 없다 */
  .dock.dragging .dock-zone.empty{ display:flex; opacity:.55; border-style:dashed; }
  .dock.dragging .dock-side.empty{ width:34px; }
  .dock.dragging .dock-zone.dock-bottom.empty{ height:34px; }
  .dock-tabs{ display:flex; gap:2px; padding:4px 4px 0; background:var(--card2); border-bottom:1px solid var(--line); overflow-x:auto; flex:none; }
  .dock-tab{ display:flex; align-items:center; gap:5px; padding:4px 8px; border:1px solid transparent; border-bottom:none;
    border-radius:6px 6px 0 0; cursor:pointer; color:var(--ink2); font-size:12px; white-space:nowrap; user-select:none; }
  .dock-tab.active{ background:var(--bg); color:var(--ink); border-color:var(--line); }
  .dock-tab.dragging{ opacity:.5; }
  .dock-tab .dock-badge{ font-size:10px; color:var(--accent); } .dock-tab .dock-badge:empty{ display:none; }
  .dock-tab .dock-float{ background:none; border:none; color:var(--dim); cursor:pointer; font-size:11px; padding:0 2px; line-height:1; }
  .dock-tab .dock-float:hover{ color:var(--accent); }
  /* ⚠️ scrollbar-gutter 는 **떨림 방지 장치**다(지우지 마라). 스택 안에는 폭에 따라 높이가 정해지는
     내용(aspect-ratio 로 잠근 영상 박스)이 있다 — 스크롤바가 뜨면 폭이 줄고, 폭이 줄면 높이가 줄어
     넘침이 사라지고, 그러면 스크롤바가 사라져 폭이 늘고… 가 레이아웃 속도로 반복돼 영상이 초당
     수십 번 확대·축소한다(2026-08-28 사용자 실측 재현). gutter 를 고정하면 스크롤바 유무가 폭을
     바꾸지 않아 이 되먹임이 성립하지 않는다. */
  .dock-stack{ flex:1; position:relative; min-height:0; overflow:auto; scrollbar-gutter:stable; }
  .dock-body{ display:none; height:100%; box-sizing:border-box; }
  .dock-body.active{ display:block; }
  .dock-zone.drop-hint{ outline:2px dashed var(--accent); outline-offset:-2px; }
  .dock-float-layer{ position:absolute; inset:0; pointer-events:none; z-index:20; }
  .dock-pip{ position:absolute; pointer-events:auto; display:flex; flex-direction:column; min-width:180px; min-height:120px;
    background:var(--card); border:1px solid var(--accent); border-radius:8px; box-shadow:0 8px 28px rgba(0,0,0,.45); overflow:hidden; }
  .dock-pip.moving{ opacity:.92; } .dock-pip-bar{ display:flex; align-items:center; justify-content:space-between; gap:6px;
    padding:4px 8px; background:var(--card2); border-bottom:1px solid var(--line); cursor:move; font-size:12px; color:var(--ink); flex:none; }
  .dock-pip-dock{ background:none; border:none; color:var(--ink2); cursor:pointer; font-size:13px; }
  .dock-pip-body{ flex:1; min-height:0; overflow:auto; scrollbar-gutter:stable; }  /* gutter: .dock-stack 과 같은 이유 */
  .dock-pip-body .dock-body{ display:block; }
  .dock-pip-resize{ position:absolute; right:0; bottom:0; width:14px; height:14px; cursor:nwse-resize;
    background:linear-gradient(135deg,transparent 50%,var(--line) 50%); }
  /* 패널 내부 공통 */
  .dockp{ padding:8px; height:100%; box-sizing:border-box; }

  /* ── 업링크 패널 — 오퍼레이터별 실측/목표. 색은 토큰만 쓴다(테마 두 벌 다 따라간다). */
  .up-total{ display:flex; align-items:baseline; gap:6px; font:12px/1.3 ui-sans-serif,system-ui; color:var(--ink2); }
  .up-total .up-big{ font:600 18px/1 ui-monospace,monospace; color:var(--ink); font-variant-numeric:tabular-nums; }
  .up-total .up-big.short{ color:var(--err); }         /* 목표의 90% 미만 = 못 따라가고 있다 */
  .up-total .up-slash{ font:11px/1 ui-monospace,monospace; color:var(--dim); font-variant-numeric:tabular-nums; }
  .up-total .up-pct{ font:600 11px/1 ui-monospace,monospace; color:var(--ok); }
  .up-total .up-pct.short{ color:var(--err); }
  .up-total .up-n{ margin-left:auto; font-size:11px; color:var(--dim); }
  /* 합계 내역 — 영상/커맨드. 합계 하나만 보면 커맨드가 자리를 먹고 있는 것을 가릴 수 없다. */
  .up-split{ display:flex; align-items:baseline; gap:8px; margin-top:2px;
             font:10px/1.3 ui-monospace,monospace; color:var(--dim); font-variant-numeric:tabular-nums; }
  .up-split:empty{ display:none; }
  .up-split b{ font-weight:600; color:var(--ink2); }
  .up-split .up-deg{ margin-left:auto; color:var(--err); font-weight:600; }
  .up-rows{ margin-top:6px; display:flex; flex-direction:column; gap:3px; }
  .uprow{ display:flex; align-items:center; gap:6px; font-size:10px; color:var(--ink2);
          padding:2px 0; border-top:1px solid var(--line); }
  .uprow.empty{ color:var(--dim); justify-content:center; padding:8px 0; border-top:none; }
  .uprow .upid{ flex:0 0 84px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--ink); }
  .uprow .upbar{ flex:0 0 64px; height:6px; border-radius:3px; background:var(--card2); overflow:hidden; }
  .uprow .upbar i{ display:block; height:100%; background:var(--accent); }
  .uprow .upkbps{ flex:0 0 78px; text-align:right; font:11px/1 ui-monospace,monospace;
                  color:var(--ink); font-variant-numeric:tabular-nums; }
  .uprow .upkbps em{ font-style:normal; color:var(--dim); }
  .uprow .upchip{ flex:0 0 auto; padding:1px 5px; border-radius:3px; font-size:9px; font-weight:600;
                  border:1px solid var(--line); }
  .uprow .upchip.ok{ color:var(--ok); border-color:color-mix(in srgb,var(--ok) 45%,transparent); }
  /* 주의 = --busy(앰버). ⚠️ `--warn` 은 이 레포에 **정의가 없다** — 쓰면 색이 조용히 안 먹는다. */
  .uprow .upchip.warn{ color:var(--busy); border-color:color-mix(in srgb,var(--busy) 45%,transparent); }
  .uprow .upchip.err{ color:var(--err); border-color:color-mix(in srgb,var(--err) 45%,transparent); }
  .uprow .upchip.dim{ color:var(--dim); }
  .uprow .upraw{ flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
                 color:var(--dim); font:10px/1 ui-monospace,monospace; }
  /* 커맨드채널 줄 — 오퍼레이터 자기 행에 붙는 하위 줄이라 구분선을 넣지 않는다(넣으면 남의 행처럼 보인다). */
  .uprow.upctl{ border-top:none; padding-top:0; gap:6px; }
  .uprow .upid.ctl{ color:var(--dim); font-size:9px; padding-left:8px; }
  .uprow .upctlv{ flex:0 0 148px; font:10px/1 ui-monospace,monospace; color:var(--ink2);
                  font-variant-numeric:tabular-nums; }
  .up-note{ margin-top:6px; font-size:10px; line-height:1.45; color:var(--dim); }
  .up-note b{ color:var(--ink2); }
  .up-note code{ font:10px/1 ui-monospace,monospace; color:var(--ink2); }
  .dockp-cam{ padding:0; } .dockp-cam .cockpit-cam{ border:none; border-radius:0; height:100%; }
  /* 시뮬레이션 패널 */
  .sim-wrap{ display:flex; flex-direction:column; gap:8px; height:100%; box-sizing:border-box; padding:8px; position:relative; }
  .sim-svg{ flex:none; width:100%; } .sim-joints{ display:flex; flex-direction:column; gap:4px; overflow:auto; }
  .sim-jrow{ display:flex; align-items:center; gap:6px; font-size:11px; color:var(--ink2); }
  .sim-jrow .nm{ width:44px; color:var(--ink); } .sim-jrow .bar{ flex:1; height:8px; background:var(--card2); border-radius:4px; overflow:hidden; position:relative; }
  .sim-jrow .fill{ position:absolute; top:0; bottom:0; background:var(--accent); opacity:.8; } .sim-jrow .val{ width:52px; text-align:right; color:var(--ink); font-variant-numeric:tabular-nums; }
  .sim-ph{ color:var(--off); font-size:12px; padding:12px; }
  .sim-3d{ position:relative; flex:1; width:100%; min-height:200px; }
  .sim-3d canvas{ display:block; border-radius:6px; }
  /* 모델 맞춤 도구(GLB) — 트윈 우상단 오버레이. 정면·위축·크기·땅높이를 눌러 맞추고 서버에 저장한다. */
  .sim-fit{ position:absolute; top:8px; right:8px; z-index:6; display:flex; flex-wrap:wrap; gap:3px; align-items:center; max-width:60%;
    font-size:11px; background:color-mix(in srgb,var(--bg) 82%,transparent); border:1px solid var(--line); border-radius:6px; padding:3px 6px; }
  .sim-fit[hidden]{ display:none; }
  .sim-fit .fit-k{ color:var(--dim); margin-right:2px; }
  .sim-fit .fit-v{ font:11px ui-monospace,monospace; color:var(--ink2); }
  .sim-fit button{ font-size:11px; padding:1px 6px; border:1px solid var(--line); border-radius:4px; background:var(--card); color:var(--ink2); cursor:pointer; }
  .sim-fit button.pri{ background:var(--accent); color:var(--on-accent); border-color:transparent; }
  .sim-3d-hint{ position:absolute; top:8px; left:8px; font-size:11px; color:var(--dim);
    background:color-mix(in srgb,var(--bg) 72%,transparent); padding:2px 7px; border-radius:5px; pointer-events:none; }
  /* 디텍션 색상 레전드 — 트윈 하단 **절대 오버레이**(플로우에서 빼 트윈이 위아래로 밀리지 않게). 탐지 0 이면 숨김. */
  .sim-legend{ position:absolute; left:12px; right:12px; bottom:10px; z-index:5; display:flex; flex-wrap:wrap; align-items:center;
    gap:12px; font-size:11px; color:var(--ink2); padding:4px 8px; border-radius:6px;
    background:color-mix(in srgb,var(--bg) 68%,transparent); pointer-events:none; }
  .sim-legend[hidden]{ display:none; }   /* 작성자 규칙이 UA [hidden]{display:none} 를 이기므로 명시 */
  .sim-legend .lg{ display:inline-block; width:11px; height:11px; border-radius:3px; margin-right:4px; vertical-align:-1px; }
  .sim-legend .lg.ok{ background:#33e07a; } .sim-legend .lg.bad{ background:#ff5a5a; } .sim-legend .lg.cam{ background:#2f7fe6; }   /* cam = 시야 프러스텀과 동일 파란색 */
  /* 패널 컨텍스트 보정: 카메라/미니맵이 패널을 채우도록(오버레이 → 플로우) */
  .cockpit-camwrap{ position:relative; width:100%; height:100%; }
  .cockpit-camwrap .cockpit-cam{ height:100%; border:none; border-radius:0; }
  .dock-body .cockpit-minimap{ position:relative; left:auto; bottom:auto; width:100%; max-width:none; height:100%; aspect-ratio:auto; margin:0; }
  pre.cockpit-status.dockp{ margin:0; white-space:pre-wrap; }
  /* ===== 사이트 전체 3D(site3d) — 건물 × 층 스택. 렌더는 site3d.js(window.Site3D), DOM 분리 ===== */
  #site3dview{ flex:1; display:flex; flex-direction:column; min-height:0; }
  #site3dview[hidden]{ display:none; }       /* [hidden] 이 #id{display:flex} 특이도를 이기게(#stage 와 같은 이유) */
  .s3-bar{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; padding:7px 12px; border-bottom:1px solid var(--line); }
  .s3-bar h2{ font-size:14px; color:var(--accent); margin:0; white-space:nowrap; }
  .s3-bar .btn{ padding:3px 9px; font-size:12px; }
  .s3-bar .btn.on{ background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
  .s3-spacer{ flex:1; }
  .s3-wrap{ position:relative; flex:1; min-height:0; margin:8px; border:1px solid var(--line); border-radius:6px; overflow:hidden; }
  #site3dcanvas{ position:absolute; inset:0; }
  /* 정보 패널 — 이 뷰는 "무엇이 안 보이는지"(층 파싱 실패·엘리베이터 미등록·궤적 휘발)를 말해야 한다.
     빈 화면은 데이터가 없는 것인지 코드가 죽은 것인지 구분이 안 된다. */
  #site3dinfo{ position:absolute; right:10px; top:10px; z-index:3; max-width:290px; max-height:calc(100% - 20px);
    overflow:auto; background:color-mix(in srgb,var(--bg) 86%,transparent); border:1px solid var(--line);
    border-radius:6px; padding:8px 10px; font-size:11px; color:var(--ink2); line-height:1.55; }
  #site3dinfo b{ color:var(--ink); }
  #site3dinfo .warn{ color:var(--help); }
  #site3dinfo .row{ margin-top:4px; }
