/* CLAW 설계툴 — 도구 UI 기본 스타일 (no-build, 의존 0) */

/* 본문 서체 — Pretendard 서브셋을 리포에 동봉(web/fonts/, KS X 1001 2,350자 + 라틴·
   그리스·기호). 윈도우엔 설치돼 있지 않아 CDN 없이는 맑은 고딕으로 대체되는데,
   그러면 자간·굵기가 달라져 조밀한 폼과 SVG 라벨의 줄맞춤이 무너진다.
   설치돼 있으면 local()이 먼저 잡아 내려받지 않는다. 폐쇄망 반입 원칙(이 디렉터리
   파일이 전부)을 지키려 base64가 아니라 파일 참조 — 캐시가 듣고 CSS가 가볍다. */
/* 웨이트는 **범위**로 선언한다. 이 스타일시트는 500(nav·부제)과 700(칩·브레드크럼)도
   쓰는데, 400/600/800만 선언하면 CSS 폰트 매칭이 500은 400으로 내리고 700은 800으로
   올린다 — 700 자리가 ExtraBold로 튄다. 범위로 묶어 500=Regular, 700=SemiBold로
   의도대로 떨어지게 한다 (파일은 그대로 3개).
   ⌂ ▸ ∠ ∧ ∩ ∓ ⊆ ⊗ 는 Pretendard 원본에 아예 없는 글리프라 서브셋으로 넣을 수 없다 —
   시스템 폰트로 대체된다 (도입 전 스택과 동일). 다시 서브셋해도 안 나오니 찾지 말 것. */
@font-face {
  font-family: "Pretendard";
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  src: local("Pretendard Regular"), local("Pretendard-Regular"),
       url("../fonts/Pretendard-Regular.subset.woff2") format("woff2");
}
@font-face {
  font-family: "Pretendard";
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
  src: local("Pretendard SemiBold"), local("Pretendard-SemiBold"),
       url("../fonts/Pretendard-SemiBold.subset.woff2") format("woff2");
}
@font-face {
  font-family: "Pretendard";
  font-weight: 800;
  font-style: normal;
  font-display: swap;
  src: local("Pretendard ExtraBold"), local("Pretendard-ExtraBold"),
       url("../fonts/Pretendard-ExtraBold.subset.woff2") format("woff2");
}

:root {
  --bg: #eef1f6;
  --panel: #ffffff;
  --line: #dfe4ec;
  --text: #1f2937;
  --muted: #6b7280;
  --accent: #2563eb;
  --ok: #14713b;
  --bad: #c22f2f;
  --warn: #b57908;
  --sans: "Pretendard", -apple-system, BlinkMacSystemFont, system-ui, "Apple SD Gothic Neo",
          "Malgun Gothic", sans-serif;
  --mono: "JetBrains Mono", "SF Mono", "Consolas", "D2Coding", ui-monospace, monospace;
  /* 설계 순서 ①~⑤ — 블록도의 층 판·칩·단계 태그가 공유하는 한 벌
     (views/diagram.js LAYERS와 같은 값 — lib/blocks.test.js가 대조한다).
     이 값들은 **흰 글자가 얹히는 배지·태그 색**이라 전부 흰색 대비 4.5:1 이상이어야
     한다. 판의 밝은 면 색은 LAYERS[].fill이 따로 들고 있으니 여기를 밝히지 말 것 */
  --l1: #7c3aed;
  --l2: #2563eb;
  --l3: #1f7a4d;
  --l4: #b45309;
  --l5: #6b7280;
  --radius: 14px;
  --shadow: 0 1px 2px rgba(31, 41, 55, .06), 0 10px 28px rgba(31, 41, 55, .07);
}

* { box-sizing: border-box; }
body {
  margin: 0;
  font: 14px/1.5 var(--sans);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

header {
  display: flex;
  align-items: center;
  gap: 22px;
  padding: 12px 24px;
  background: transparent;
}
header h1 { font-size: 24px; font-weight: 800; letter-spacing: .02em; margin: 0; }
header h1 .sub { font-size: 12px; font-weight: 500; letter-spacing: 0; color: var(--muted); margin-left: 10px; }
/* 탭 — 흰 알약 트레이 위 활성 칩 (블록도 보드와 같은 카드 언어) */
nav {
  display: flex;
  gap: 2px;
  padding: 4px;
  border-radius: 999px;
  background: var(--panel);
  box-shadow: var(--shadow);
}
nav a {
  padding: 7px 13px;
  border-radius: 999px;
  text-decoration: none;
  color: var(--muted);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
nav a.active { background: var(--text); color: #fff; font-weight: 600; }
nav a:hover:not(.active) { color: var(--text); background: var(--bg); }
.health {
  margin-left: auto;
  font-size: 11px;
  color: var(--muted);
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--panel);
  white-space: nowrap;
}
.health.ok { color: var(--muted); }
.health.ok::before,
.health.bad::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 7px;
  vertical-align: 0;
  background: var(--ok);
}
.health.bad { color: var(--bad); }
.health.bad::before { background: var(--bad); }

/* 헤더 기체 선택기 (06 §8, v1.26 정리) — 셀렉트 하나가 곧 알약이다. 종전에는 알약 래퍼 안에
   셀렉트 둘·배지가 앉아 테두리가 겹으로 보였다(사용자 제기 "지저분하다") */
header { flex-wrap: wrap; }
.profile-pick {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.profile-pick + .health { margin-left: 0; }
.profile-pick select {
  font-size: 12px;
  padding: 4px 10px;
  max-width: 260px;
  border-radius: 999px;
  background: var(--panel);
}
.profile-pick .badge { padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; }
.profile-pick .badge.warn { background: #fdeae8; color: #b3352b; }
.aircraft-page .unreadable td { color: var(--bad); }
.aircraft-page tr.selected td { background: #eef5ff; }
.aircraft-page textarea.doc { width: 100%; min-height: 420px; font-family: ui-monospace, Menlo, monospace; font-size: 12px; }
/* 기체 탭 대표 그림 (06 §8) — 전면 무대의 주 그림. 그림 위에는 조작(‹ ›)만 얹고 이름·상태·사실은 그림 아래
   캡션으로 둔다(다른 탭의 그림처럼). 밝은 탭이라 스튜디오 배경도 밝게, 배경은 여기 한 곳에서 칠한다(렌더러는 투명) */
.aircraft-hero { position: relative; height: clamp(320px, 58vh, 640px); border-radius: var(--radius); overflow: hidden;
  background: radial-gradient(120% 95% at 50% 40%, #ffffff 0%, #eef2f7 52%, #d6dde8 100%); }
.aircraft-hero .hero-canvas { position: absolute; inset: 0; }
.aircraft-hero canvas { display: block; width: 100%; height: 100%; cursor: grab; outline: none; }
.aircraft-hero canvas:active { cursor: grabbing; }
.aircraft-hero canvas:focus-visible { box-shadow: inset 0 0 0 2px var(--accent); }
.aircraft-hero .hero-status { position: absolute; inset: 0; display: grid; place-items: center; padding: 0 24px;
  text-align: center; color: var(--muted); font-size: 13px; pointer-events: none; }
/* display:grid가 UA의 [hidden] 규칙을 이긴다 — 숨김을 여기서 다시 세운다(없으면 「불러오는 중」이 그림 위에 남는다) */
.aircraft-hero .hero-status[hidden] { display: none; }
.aircraft-hero .hero-nav { position: absolute; top: 50%; z-index: 2; width: 48px; height: 48px; transform: translateY(-50%);
  display: grid; place-items: center; padding: 0 0 3px; border: 1px solid var(--line); border-radius: 50%;
  background: rgba(255, 255, 255, .82); color: var(--text); font-size: 30px; line-height: 1; cursor: pointer; box-shadow: var(--shadow); }
.aircraft-hero .hero-nav.prev { left: 16px; }
.aircraft-hero .hero-nav.next { right: 16px; }
.aircraft-hero .hero-nav:hover { background: #ffffff; }
.aircraft-hero .hero-nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.hero-caption { display: grid; gap: 4px; margin: 10px 2px 18px; }
.hero-caption .hero-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }
.hero-caption .hero-name { font-size: 16px; font-weight: 700; color: var(--text); }
.hero-caption .hero-badge { padding: 2px 8px; border-radius: 999px; background: var(--bg); color: var(--muted); font-size: 11px; }
.hero-caption .hero-badge.preview { background: #fdf2d7; color: #946200; }
.hero-caption .hero-count { margin-left: auto; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.hero-caption .hero-facts, .hero-caption .hero-note { margin: 0; color: var(--muted); font-size: 12px; }
/* .hero-nav의 display:grid가 UA의 [hidden]을 이긴다 — 숨김을 여기서 다시 세운다 */
.aircraft-hero .hero-nav[hidden] { display: none; }
@media (max-width: 560px) {
  .aircraft-hero .hero-nav { width: 40px; height: 40px; font-size: 26px; }
}
.aircraft-page .notice { margin: 0 0 10px; padding: 8px 12px; border-radius: 8px; background: #fdf2d7; color: #6b4a00; font-size: 12px; }

/* ── 전역 질문 위젯 (D2) — 탭 밖 크롬 ─────────────────────────────────────
   어느 탭에서든 뜨므로 헤더와 같은 밝은 스킨이다(어두운 스킨은 전부 탭 로컬
   접두라는 규약 — .tab-dark 주석 참조). 이 리포 최초의 position:fixed·
   z-index 20: 경쟁자가 sticky 거터의 1뿐이라 크게 갈 이유가 없고, 탭 콘텐츠
   무엇 위에도 떠야 하는 상시 크롬이라 한 자리 위면 족하다. */
.ask-fab {
  position: fixed;
  right: 22px;
  bottom: 22px;
  z-index: 20;
  padding: 9px 16px;
  border-radius: 999px; /* .health와 같은 알약 언어 */
  background: var(--text);
  color: #fff;
  border: none;
  font-weight: 600;
  box-shadow: var(--shadow);
  cursor: pointer;
}
.ask-panel {
  position: fixed;
  right: 22px;
  bottom: 68px;
  z-index: 20;
  width: min(440px, calc(100vw - 44px));
  max-height: min(560px, calc(100vh - 110px));
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px;
}
/* 다크 탭(영향성·가상환경)에서는 body 바닥이 #0b0b0d라 --text 알약이 잠긴다
   (대비 ~1.5:1) — 윤곽만 보강한다. 페이지 다크 판정은 기존 :has 규약 그대로. */
body:has(.tab-dark) .ask-fab { border: 1px solid rgba(255, 255, 255, .35); }
.ask-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 8px; }
.ask-close { border: none; background: none; font-size: 18px; line-height: 1; cursor: pointer; color: var(--muted); }
.ask-row { display: flex; gap: 8px; }
.ask-input { flex: 1; min-width: 0; }
.ask-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }

/* 가이드 투어 (D1) — 질문 위젯과 같은 전역 크롬. 트리거는 [? 질문] 왼쪽에 서고
   카드는 **반대쪽 아래**다: 투어가 도는 동안 질문 패널을 함께 열 수 있어야 한다.
   진행 중에는 카드가 낮아(스테퍼 + 한 줄) 가상환경 판독(.wv-hud — 캔버스 안쪽
   좌하단)보다 아래에 앉지만, **마무리 카드는 길어져 그 위를 덮을 수 있다**
   (캔버스 바닥이 뷰포트 바닥에서 200 px 안쪽이라 — 리뷰 실측). 재생이 끝난 뒤라
   가리는 것이 마지막 프레임의 판독뿐이어서 그대로 두되, 높이를 조인다. */
.tour-fab { right: 116px; }
.tour-card {
  position: fixed;
  left: 22px;
  bottom: 22px;
  z-index: 20;
  width: min(420px, calc(100vw - 44px));
  max-height: min(420px, calc(100vh - 220px));
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 16px;
}
.tour-head { display: flex; align-items: center; justify-content: space-between; margin: 0 0 8px; }
.tour-close { border: none; background: none; font-size: 18px; line-height: 1; cursor: pointer; color: var(--muted); }
.tour-row-input { display: flex; gap: 8px; }
.tour-input { flex: 1; min-width: 0; }
/* 진행 스테퍼 — 다섯 단계가 한 줄에. 색이 아니라 **글자 굵기와 바탕**으로도
   갈린다(색각 이상에서 단계가 통째로 사라지지 않게). */
.tour-steps { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 8px; }
.tour-step { font-size: 11px; padding: 3px 9px; border-radius: 999px; background: var(--bg); color: var(--muted); }
.tour-step.done { background: #e8f3ec; color: var(--ok); }
.tour-step.active { background: var(--text); color: #fff; font-weight: 600; }
.tour-step.failed { background: #fdeaea; color: var(--bad); font-weight: 600; }
.tour-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.tour-rows { display: grid; gap: 5px; }
.tour-row { display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; font-size: 12px; }
.tour-row .k { color: var(--muted); min-width: 74px; }
.tour-row .v { font-family: var(--mono); font-weight: 600; }

main { padding: 8px 24px 24px; max-width: 1440px; margin: 0 auto; }

.panel {
  background: var(--panel);
  border: none;
  border-radius: var(--radius);
  padding: 18px 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
}
.panel h2 { font-size: 15px; font-weight: 800; margin: 0 0 12px; }
.row { display: flex; gap: 12px; align-items: end; flex-wrap: wrap; }
.grow { flex: 1; }

label.field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
label.field.check { flex-direction: row; align-items: center; gap: 6px; padding-bottom: 7px; }
label.field.check input { width: auto; }
/* 스케줄이 덮고 있는 자리 — 값의 정본은 게인 탭 표다 (views/blocks.js scheduleLocks).
   비활성이라 못 고친다는 것만으로는 "왜"가 안 보여서 자물쇠 줄을 함께 단다 */
label.field.locked { opacity: 0.75; }
label.field.locked input { background: #f2f2f7; color: var(--muted); cursor: not-allowed; }
.lock-note { font-size: 10px; color: var(--l1); letter-spacing: -0.2px; }

/* 폼 정렬 — 필드 그리드·옵션 그룹 박스 (시뮬 탭 등 조밀 폼) */
.field-grid { display: flex; flex-wrap: wrap; gap: 12px 18px; align-items: stretch; }
.opt-group { border: 1px solid var(--line); border-radius: 6px; padding: 8px 12px 10px; background: #fafbfd; }
.opt-group .g-title { font-size: 11px; color: var(--muted); font-weight: 600; margin-bottom: 6px; }
.opt-group .row-inner { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
input, select, textarea {
  font: inherit;
  padding: 6px 9px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  color: var(--text);
  max-width: 100%; /* 고정폭 입력(230px 결과 id, 340px 슬라이더 등)이 폰 폭을 뚫지 않게 */
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .14);
}
input:invalid { border-color: var(--bad); }
input.num { width: 90px; font-family: var(--mono); }

button {
  font: inherit;
  font-weight: 600;
  padding: 7px 15px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  cursor: pointer;
}
button:hover:not(:disabled) { background: var(--bg); }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover:not(:disabled) { background: #1d4ed8; }
button.danger { color: var(--bad); }
button:disabled { opacity: 0.5; cursor: default; }

table { border-collapse: collapse; width: 100%; font-size: 13px; }
.scroll-x { overflow-x: auto; }  /* 넓은 편집 표 — 패널 밖 넘침 방지 */
input.num-sm { width: 72px; font-family: var(--mono); }
/* 편집 표 내부 입력 — 셀 폭에 맞춰 균일 정렬 (클래스 지정 시 그 폭 우선) */
td > input:not(.num):not(.num-sm) { width: 100%; min-width: 64px; box-sizing: border-box; font-family: var(--mono); }
td > select { width: 100%; min-width: 90px; }
th, td { padding: 5px 10px; border-bottom: 1px solid var(--line); text-align: right; }
th { color: var(--muted); font-weight: 600; white-space: nowrap; }
td:first-child, th:first-child { text-align: left; }
td.num { font-family: var(--mono); }
tr:hover td { background: #fafbfd; }

/* 편집 표(미션·웨이포인트 등) — 내용 폭 표: 칸 폭은 th가 고정, 입력은 칸을
   채움(전역 td>input 100% 규칙 활용). 좌측 정렬 + 버튼 줄바꿈 금지 */
table.edit { width: auto; }
table.edit th, table.edit td { text-align: left; }
table.edit td button { white-space: nowrap; }
table.edit .c-sm { width: 84px; }   /* 짧은 수치 칸 */
table.edit .c-md { width: 130px; }  /* 이름·헤딩·선택 칸 */

/* 근사식 표(게인 뷰) — 칸마다 줄 수가 다름(구간 수 vs 구간-1 점프 수).
   세로 중앙정렬(기본값)이면 짧은 칸 내용이 긴 칸 옆에서 붕 떠 보임 → 상단
   고정. 여러 줄 서술형 칸은 좌측 정렬 + 고정폭 폰트로 줄 시작·수치를 정렬 */
.fit-table td, .fit-table th { vertical-align: top; }
.fit-table td.col-lines { text-align: left; font-family: var(--mono); font-size: 12px; }
.fit-table td.col-lines > div { white-space: nowrap; line-height: 1.6; }

.flag { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 11px; }
.flag.ok { background: #e0f2e5; color: var(--ok); }
.flag.bad { background: #fbe2e2; color: var(--bad); }
/* warn 잉크는 var(--warn)이 아니다 — #b57908은 이 배경(11px)에서 대비 ~3.3:1로 AA(4.5:1) 미달,
   #b25000은 ~4.7:1로 통과. "정합성" 명목으로 변수로 되돌리면 접근성이 조용히 깨진다 */
.flag.warn { background: #fdf1de; color: #b25000; }
.flag.na { background: #eceef1; color: #5b6673; }

.progress { height: 10px; background: var(--bg); border-radius: 5px; overflow: hidden; flex: 1; }
.progress > div { height: 100%; background: var(--accent); width: 0%; transition: width .2s; }
.progress-line { display: flex; align-items: center; gap: 12px; margin: 10px 0; }
.progress-label { font-size: 12px; color: var(--muted); font-family: var(--mono); min-width: 220px; }

.error-box {
  background: #fbe2e2;
  color: var(--bad);
  border-radius: 6px;
  padding: 10px 12px;
  margin: 10px 0;
  white-space: pre-wrap;
  font-family: var(--mono);
  font-size: 12px;
}
.hint { font-size: 12px; color: var(--muted); }
.placeholder { color: var(--muted); padding: 40px; text-align: center; }

/* 플롯 캔버스 — 애플 스타일 카드 (V-n·마진 히트맵·시뮬 차트 공통) */
canvas.plot { background: #fff; border: 1px solid #d2d2d7; border-radius: 12px; box-shadow: 0 1px 4px rgba(0, 0, 0, .04); max-width: 100%; }
.legend { display: flex; gap: 16px; font-size: 12px; color: var(--muted); margin-top: 6px; flex-wrap: wrap; }
.legend .chip { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; }

/* 시뮬 재생 궤적 뷰 — 입체·평면·측면·정면 4장.
   열 수를 4의 약수로만 둔다(1 또는 2). flex-wrap이나 auto-fit이면 폭에 따라 3+1로
   갈라져 마지막 줄에 외톨이가 남는다 — 가상의 걱정이 아니라, 최대폭(컨테이너 1206)에서
   3열(320×3+16=976)이 실제로 들어간다.
   열 상한을 --plane-px(sim.js의 PLANE_PX가 주입)로 묶는 이유: 3D 뷰는 캔버스 아래
   캡션(.hint)을 달고 있고 폭 제약이 없어 그 max-content가 열 폭을 지배한다. 캡션은
   드래그 중 방위·고각 자릿수가 계속 바뀌므로, 상한을 안 묶으면 회전할 때마다 열 폭이
   흔들려 오른쪽 열 캔버스가 가로로 밀린다. 넓은 폭에서 죽은 거터가 생기는 것도 막는다.
   minmax의 min이 0인 것은 좁은 폭에서 열이 캔버스보다 작아질 수 있게 허용하기
   위함(넘침 방지) — 그때 축척은 아래 canvas 규칙이 지킨다. */
.triview {
  display: grid;
  gap: 8px;
  align-items: start;
  justify-content: start;
  grid-template-columns: minmax(0, var(--plane-px, 320px));
}
@media (min-width: 760px) {
  /* 2열 하한. 불변식 = main과 재생 패널 사이에 가로 패딩·테두리를 더하는 요소가
     없다(중간 래퍼는 있으나 무스타일). 그래서 가용폭 = 뷰포트 − 74px
     (main padding 20×2 + panel padding 16×2 + border 1×2).
     뷰포트 760 → 760 − 17(세로 스크롤바) − 74 = 669 > 필요 648(=320×2+gap 8).
     미디어 쿼리는 스크롤바를 포함한 뷰포트로 평가되므로 스크롤바를 빼는 게 맞다.
     패널을 컬럼 레이아웃 등 가로 여백이 있는 것으로 감싸면 이 산수를 다시 볼 것. */
  .triview { grid-template-columns: repeat(2, minmax(0, var(--plane-px, 320px))); }
}
/* 컨테이너가 캔버스보다 좁아지면 canvas.plot의 max-width가 폭만 줄이는데,
   makeCanvas가 인라인으로 박은 height는 그대로 남아 비율이 깨진다 — N–E 평면이
   타원으로 그려져 선회반경을 잘못 읽게 되는 식. height를 고유비(width/height
   속성 — makeCanvas가 논리×dpr로 넣으므로 비율 동일)에 맡겨 균일 축소시킨다.
   축척은 보존되고 해상도만 떨어진다. 데스크톱(비제약)에서는 width가 인라인 값
   그대로라 height:auto도 같은 수치로 계산된다 — 무해. 원래 .triview에만 있던
   규칙을 전 플롯으로 확장 — 폰에서는 모든 탭의 플롯이 같은 방식으로 깨졌다.
   !important는 인라인 스타일을 이기기 위한 것 — 특이도로는 불가능하다.
   측정: 뷰포트 380에서 306×320(직사각) → 306×306(정사각). */
canvas.plot { height: auto !important; }

/* ── 블록도(블록 다이어그램 허브) — 시뮬링크 스타일, .bd 스코프 ───────────
   레퍼런스 디자인(control_law_block_diagram)을 이식 — 다른 뷰와 충돌 방지 */
.bd { --bd-ink-soft: #5a6b7d; --bd-edge: #d6dde6; }

/* 상태 칩 — fcs-context 태그([확정]/[기본값]/[TBD]/[설계 유의])와 동일 의미 */
.bd .chip { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; white-space: nowrap; }
.bd .chip.ok { background: #e3f4ea; color: var(--ok); }
.bd .chip.dft { background: #fdf2d7; color: #946200; }
.bd .chip.tbd { background: #fdeae8; color: #b3352b; }
.bd .chip.note { background: #f1ecfe; color: var(--l1); }

/* 제목 줄 + 층 칩 — 보드가 화면의 주인공이라 그 위에 카드를 하나 더 얹지 않는다.
   칩은 판에서 색을 받아 오고(views/diagram.js LAYERS), 아래 테두리로 판의 옆면 색을
   깔아 "이 칩 = 저 판"이 색만으로 읽힌다 */
.bd .pagetop { margin: 2px 0 14px; }
.bd .pagetop h1 { margin: 0; font-size: 34px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; }
/* 설명과 층 칩이 한 줄 — center 정렬이라 칩의 세로 중심이 설명 줄과 맞는다 */
.bd .subline { display: flex; align-items: center; justify-content: space-between; gap: 10px 24px; flex-wrap: wrap; margin-top: 7px; }
.bd .subline p { margin: 0; font-size: 13px; font-weight: 500; color: var(--muted); }
.bd .layerchips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.bd .lchip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 5px 7px;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 9px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
}
/* filter는 자식 .n 배지(층 정체성 색)까지 같이 밝힌다 — 배경만 눌러 대비를 준다 */
.bd .lchip:hover { box-shadow: inset 0 0 0 999px rgba(31, 41, 55, .05); }
.bd .lchip .n {
  width: 17px;
  height: 17px;
  border-radius: 50%;
  color: #fff;
  font-size: 10px;
  font-weight: 800;
  display: inline-grid;
  place-items: center;
}
/* 보드 아래 조작 줄 — 재생 컨트롤 + 형상 코드 */
.bd .boardbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 6px; }
.bd .boardbar > .row { flex: 1; }

/* 브레드크럼 (시뮬링크 주소창) */
.bd .crumbbar { display: flex; align-items: center; gap: 6px; background: var(--panel); border-radius: var(--radius) var(--radius) 0 0; padding: 9px 14px; font-size: 13px; box-shadow: var(--shadow); position: relative; z-index: 1; }
.bd .crumbbar .home-btn { display: inline-flex; align-items: center; gap: 6px; border: none; background: none; cursor: pointer; font-size: 13px; font-weight: 700; color: var(--accent); padding: 2px 6px; border-radius: 6px; }
.bd .crumbbar .home-btn:hover { background: #e8f1fa; }
.bd .crumbbar .sep { color: #9aa7b4; }
.bd .crumbbar .cur { font-weight: 800; }
.bd .crumbbar .up-btn { margin-left: auto; border: 1px solid var(--bd-edge); background: var(--panel); border-radius: 6px; padding: 3px 10px; font-size: 12px; cursor: pointer; font-weight: 600; }
.bd .crumbbar .up-btn:hover { background: #eef2f7; }

/* 캔버스 — 최상위는 보드가 바닥 그림자를 달고 떠 있어야 하므로 배경·테두리 없이
   페이지 위에 그대로 놓는다. 서브페이지 블록도는 종전대로 카드 안. */
.bd .canvas-wrap {
  background: var(--panel);
  border-radius: var(--radius);
  overflow-x: auto;
  box-shadow: var(--shadow);
  padding: 10px 0 16px;
}
.bd .canvas-wrap.top {
  background: none;
  box-shadow: none;
  padding: 0;
}
.bd .canvas-wrap.sub { border-radius: 0 0 var(--radius) var(--radius); }
.bd .canvas-wrap svg { display: block; width: 100%; height: auto; }
.bd .canvas-wrap.top svg { min-width: 1180px; overflow: visible; }
.bd .canvas-wrap.sub svg { min-width: 720px; max-width: 900px; margin: 0 auto; }

/* ── 최상위 = 층 보드 (views/diagram.js가 기하를 계산, 여기는 도색만) ────────
   **반드시 .top 스코프**: .blk/.body/.wire 클래스 이름을 서브시스템 블록도와
   공유하는데, 보드 규칙(특히 filter: url(#bd-blk))이 그쪽까지 덮으면 그 SVG에는
   그 filter가 없어 요소가 통째로 안 그려진다. */
.bd .top .board-shadow { opacity: .17; filter: url(#bd-ground); }
.bd .top .face { stroke: none; }
.bd .top .plate-sheen { stroke: #ffffff; stroke-opacity: .55; stroke-width: 1; }
.bd .top .plate .body { stroke: none; }   /* 판은 외곽선 없이 — 면 색만으로 층이 읽힌다 */
/* 클릭 대상은 책등 라벨(.plate-tag)뿐 — 판 전체를 대상으로 두면 보드의 빈 배경
   아무 데나 눌러도 페이지가 넘어간다 (판이 화면을 타일링하므로) */
.bd .top .plate-tag { cursor: pointer; }
.bd .top .plate-tag:focus { outline: none; }
.bd .top .plate-num { font-size: 9px; font-weight: 800; fill: #ffffff; }
.bd .top .plate-name { font-size: 10.5px; font-weight: 600; letter-spacing: .4px; fill: var(--text); fill-opacity: .82; }
.bd .top .plate-tag:hover .plate-name,
.bd .top .plate-tag:focus .plate-name { fill-opacity: 1; text-decoration: underline; }
.bd .top .plate-tag:focus-visible .plate-name { fill: var(--accent); fill-opacity: 1; }
.bd .top .plate-tag:focus-visible .plate-badge { stroke: var(--accent); stroke-width: 2.5; }

.bd .top .blk { cursor: pointer; filter: url(#bd-blk); }
/* 떠오르는 것은 안쪽 .lift뿐이다 — 히트 도형(.blk-hit)은 제자리에 남는다.
   filter는 전환 대상이 아니다: url() 참조는 보간이 안 돼(discrete) 중간에 통째로
   튀므로, 넣어 두면 글로우만 80ms 늦게 딸깍 켜진다 */
.bd .top .blk .lift { transition: transform .16s ease; }
.bd .top .blk:focus { outline: none; }
.bd .top .blk .body { fill: none; stroke: var(--text); stroke-opacity: .45; stroke-width: .8; stroke-linejoin: round; filter: none; }
.bd .top .blk .ttl { font-size: 14.5px; font-weight: 800; fill: var(--text); }
.bd .top .blk .ttl2 { font-size: 9px; fill: #3b4453; }

/* 가리키면 파랗게 물들며 살짝 떠오른다 — "이건 눌러서 들어가는 판"이라는 신호.
   평소엔 전부 흰색이어야 한다: 소속(설계 단계)은 블록 색이 아니라 **앉은 판**이
   말하므로, 한 블록만 늘 파랗던 예전 상태는 없는 상태를 있는 것처럼 읽히게 했다.

   - 면 색은 SVG fill 속성이지만 CSS가 이긴다 (프레젠테이션 속성이 제일 약하다).
   - f-top/f-front/f-side는 views/diagram.js slab()이 붙인다 — 이름이 어긋나면
     이 반응이 조용히 죽으므로 lib/blocks.test.js가 짝을 판다.
   - 세 면의 명암 순서(윗면 > 앞면 > 옆면)를 파란색에서도 지킨다. 안 그러면
     가리키는 순간 입체가 뒤집힌 것처럼 보인다.
   - 앞면 글자는 9px이라 앞면을 #3b82f6로 두면 흰 글자가 3.7:1로 AA 미달 —
     #1d4ed8로 내려 6.7:1. 윗면 제목은 #2563eb 위 흰색 5.2:1.
   - translateY는 뷰박스 단위다(화면 px 아님) — 보드 축척이 ~0.8이라 6이 약 5px.
   - 변위는 **반드시 .lift에만**. 히트 도형을 움직이면 호버가 진동하고 클릭이
     씹힌다 (views/diagram.js 블록 조립부 주석에 재현 조건).
   - :focus까지 묶는 이유: 키보드로 탭해도 같은 것이 보여야 어디 있는지 안다. */
.bd .top .blk:hover .lift, .bd .top .blk:focus .lift { transform: translateY(-6px); }
.bd .top .blk:hover, .bd .top .blk:focus { filter: url(#bd-sel); }
/* 선택 파랑 3면 — 설계 흐름 탭 .fd의 실행 중 블록이 같은 세 값을 쓴다(같은 파일 아래쪽, 서로 참조) */
.bd .top .blk:hover .f-top, .bd .top .blk:focus .f-top { fill: #2563eb; }
.bd .top .blk:hover .f-front, .bd .top .blk:focus .f-front { fill: #1d4ed8; }
.bd .top .blk:hover .f-side, .bd .top .blk:focus .f-side { fill: #1e3a8a; }
/* :active까지 함께 — 누른 채 블록 밖으로 끌면 :hover는 풀린다. 지금은 :focus가
   대신 버텨 주지만(크롬은 mousedown에 tabindex 요소를 포커스한다), 나중에 누가
   .plate-tag처럼 :focus-visible로 좁히는 순간 남색 면에 회색 글자(1.13:1)가 된다 */
.bd .top .blk:hover .ttl, .bd .top .blk:focus .ttl, .bd .top .blk:active .ttl,
.bd .top .blk:hover .ttl2, .bd .top .blk:focus .ttl2, .bd .top .blk:active .ttl2 { fill: #ffffff; }
.bd .top .blk:hover .body, .bd .top .blk:focus .body { stroke: #ffffff; stroke-opacity: .9; stroke-width: 1.6; }
/* 누르는 동안은 한 단 더 짙게 — 클릭이 "먹혔다"는 신호. 여기서 블록을 내려앉히면
   포인터가 윗변 근처일 때 mouseup이 블록 밖에서 나 이동이 씹힌다(재현 확인) */
.bd .top .blk:active .f-top { fill: #1d4ed8; }
.bd .top .blk:active .f-front { fill: #1e40af; }
.bd .top .blk:active .f-side { fill: #172554; }
/* α 리미터 작동 경보 (views/replayoverlay.js가 .alarm을 토글) — 속성이 아니라
   클래스여야 하는 이유는 그쪽 주석 참조. 호버 테두리(0-5-0)와 명시도가 같아
   **뒤에 와야** 이긴다: 경보 중 마우스가 지나간다고 경보가 지워지면 안 된다 */
.bd .top .blk.alarm .body { stroke: #ef4444; stroke-opacity: 1; stroke-width: 2.6; }

/* 배선 — 보조 신호(스케줄 주입·피드백)만 흐르는 대시. 주 경로는 실선이라
   블록 사이 52px 간격에서도 화살촉이 또렷하다 */
.bd .top .wire {
  stroke: var(--text);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  animation: none;
  stroke-dasharray: none;
}
/* dasharray 주기(8+5=13)는 @keyframes bd-flow의 -13과 반드시 같아야 한다 —
   어긋나면 한 주기마다 패턴이 그 차이만큼 되튄다 */
.bd .top .wire.gs, .bd .top .wire.fb {
  stroke-dasharray: 8 5;
  animation: bd-flow 2.2s linear infinite;
}
.bd .top marker path { fill: var(--text); }
.bd .top .branch { fill: var(--text); }
.bd .top .siglabel {
  font-size: 10.5px;
  font-style: normal;
  fill: #4b5563;
  paint-order: stroke;
  stroke: rgba(255, 255, 255, .94);
  stroke-width: 4px;
  stroke-linejoin: round;
}
.bd .top .siglabel.gs-ink { fill: var(--l1); }
.bd .hint-row { margin-top: 10px; font-size: 12px; color: var(--bd-ink-soft); }

/* 서브시스템 블록도 (.sub) — 최상위 보드와 같은 카드 언어의 2D판.
기하(SVG 좌표)는 그대로 — 스타일 계층만으로 전 다이어그램에 일괄 적용.
rx는 SVG2 지오메트리 프로퍼티 (미지원 브라우저는 마크업 rx로 폴백). */
.bd .sub .blk .body, .bd .sblk .body {
  fill: #ffffff;
  stroke: #cfd6e0;
  stroke-width: 1.2;
  rx: 10px;
  filter: drop-shadow(0 2px 7px rgba(31, 41, 55, .1));
}
.bd circle.body { /* 합산점(Σ) — 블록 그룹 밖 단독 원도 동일 처리 */
  fill: #ffffff;
  stroke: #cfd6e0;
  stroke-width: 1.2;
  filter: drop-shadow(0 1px 4px rgba(31, 41, 55, .1));
}
.bd .sub .blk { cursor: pointer; }
.bd .sub .blk:hover .body, .bd .sub .blk:focus .body {
  stroke: var(--accent);
  filter: drop-shadow(0 4px 12px rgba(37, 99, 235, .24));
}
.bd .sub .blk:focus { outline: none; }
.bd .sub .blk .ttl { font-size: 14px; font-weight: 800; fill: var(--text); text-anchor: middle; }
.bd .sub .blk .ttl2 { font-size: 11.5px; fill: var(--muted); text-anchor: middle; }
.bd .sblk .ttl { font-size: 13px; font-weight: 700; fill: var(--text); text-anchor: middle; }
.bd .sblk .ttl2 { font-size: 10.5px; fill: var(--muted); text-anchor: middle; }
.bd .bname { font-size: 11px; fill: var(--muted); text-anchor: middle; }
.bd .pnum { font-size: 11px; font-weight: 800; fill: var(--accent); text-anchor: middle; }
.bd .pname { font-size: 11px; fill: var(--muted); text-anchor: middle; }
/* 포트 라벨 정렬 수정자 — 배지 **옆**에 붙는 라벨은 좌표가 배지 오른쪽 끝 바로
   뒤라 중앙정렬이면 자기 폭 절반만큼 왼쪽으로 밀려 배지를 덮는다(실측 29px).
   마크업에 text-anchor 속성을 써도 위 규칙이 이기므로 클래스여야 한다 */
.bd .pname.a-start { text-anchor: start; }
/* 배선 — 신호 흐름이 보이도록 대시가 진행 방향으로 흐른다 */
.bd .sub .wire {
  stroke: var(--text);
  stroke-width: 1.7;
  stroke-linecap: round;
  fill: none;
  stroke-dasharray: 8 5;
  animation: bd-flow 2.2s linear infinite;
}
.bd .sub .wire.gs { stroke: var(--l1); }
.bd .sub .wire.soft { stroke: #aab2c0; animation: none; stroke-dasharray: 6 4; }
.bd .sub .wire.ff { stroke: var(--l4); }
@keyframes bd-flow { to { stroke-dashoffset: -13; } }
/* 모션 감축 — 애니메이션을 세우는 규칙들(.bd .top .wire.gs 0-4-0, .bd .sub .wire 0-3-0)이
   이 규칙(0-2-0)보다 특이도가 높아 !important 없이는 진다. 접근성 오버라이드는
   !important의 정당한 용도다 — 같은 화면에서 replayoverlay.js가 reduceMotion()으로
   리미터 점멸을 끄는데 배선만 계속 흐르면 설정을 절반만 지키는 셈이 된다 */
@media (prefers-reduced-motion: reduce) {
  .bd .wire { animation: none !important; }
  /* 블록이 떠오르는 것은 **즉시** 바뀌게 한다 — 색과 높이(정보)는 남기고 움직임만 뺀다 */
  .bd .top .blk .lift { transition: none !important; }
  /* 로그인 게이트 — 해체·둥둥·조립 연출 전부 정지 (views/login.js도 JS에서 같은
     판정으로 scatter 자체를 생략한다 — 여기는 이중 방어) */
  .login-gate .top .blk .lift { animation: none !important; transition: none !important; }
  .login-gate { transition: none !important; }
}
/* 화살촉·내부 소자 곡선 — 마크업의 프레젠테이션 속성을 CSS로 재도색 (인라인 style 아님) */
.bd .sub marker path[fill="#3b3b3b"] { fill: var(--text); }
.bd .sub marker path[fill="#b45309"] { fill: var(--l4); }
.bd .canvas-wrap path[stroke="#111"] { stroke: #48484a; }
.bd .sub .siglabel { font-size: 11px; fill: var(--muted); text-anchor: middle; }
.bd .sub .branch { fill: var(--text); }
.bd .canvas-note { font-size: 11px; fill: #8d95a3; }
.bd .sumsign { font-size: 12px; font-weight: 800; fill: var(--text); text-anchor: middle; }
/* 게인 스케줄 관련 표기는 층 ①의 보라를 공유한다. 서브시스템 SVG에는 옛 보라
   (#8a5cf6)가 마크업 속성으로 박혀 있어(주로 점선 프레임), 같은 그림 안에서 두 보라가
   섞이지 않도록 여기서 한 번에 새 값으로 되돌린다 */
.bd .annot { font-size: 11px; fill: var(--l1); }
.bd .sub [stroke="#8a5cf6"] { stroke: var(--l1); }

/* ── 2계층 시각 문법 — 코드 대응(틴트) vs 설명용(점선) (06 §1) ──────────────
   틴트는 views/blocks.js renderSubPage가 .canvas-wrap.sub 인라인 --sub-tint /
   --sub-edge로 주입한다 (정본 = views/subsystems.js SUB_TINT ← diagram.js 판 색).
   전부 .bd .sub 스코프 — .blk/.body를 최상위 보드(.bd .top)와 공유하므로
   넓히면 358행의 filter 사고가 재현된다 (lib/blocks.test.js가 스코프를 판다).
   틴트 규칙(0-4-0)은 기존 .bd .sub .blk .body(457행, 동률)보다 **뒤라서** 이긴다.
   hover(0-5-0)는 계속 이겨 accent 테두리가 유지된다 (면은 틴트 그대로 — 의도). */
.bd .sub [data-code] .body, .bd .sub .body[data-code] {
  fill: var(--sub-tint, #ffffff);
  stroke: var(--sub-edge, #cfd6e0);
}
/* 틴트 위 보조 잉크 — --muted(#6b7280)는 파랑 틴트 위 3.83:1로 AA 미달,
   --accent(.pnum)도 4.09:1 미달. 실측으로 정한 대체 잉크 (테스트가 대비를 잰다) */
.bd .sub [data-code] .ttl2 { fill: #4b5563; }
.bd .sub [data-code] .pnum { fill: var(--text); }
/* 설명용(nblk) — 엔진에 없는 도해 요소: 흰 면 + 회색 점선, 그림자 없음 */
.bd .sub .nblk .body, .bd .sub .body.nblk {
  fill: #ffffff;
  stroke: #9aa5b4;
  stroke-dasharray: 6 4;
  filter: none;
}
.bd .sub .nblk .ttl { fill: var(--muted); }
/* 설명용 배선 — 점 대시. soft(구성 입력·피드백, 실존 신호)와 다른 어휘다 */
.bd .sub .wire.note { stroke: #b6bec9; stroke-dasharray: 2 5; stroke-linecap: round; animation: none; }

/* 캔버스 상단 범례 — 클래스명이 legend가 아닌 이유: 전역 .legend .chip(240행)이
   10×10 정사각형을 강제해 뭉개진다 */
.bd .subkey {
  display: flex; align-items: center; gap: 6px 16px; flex-wrap: wrap;
  padding: 12px 16px 0; font-size: 11px; color: var(--bd-ink-soft);
}
.bd .subkey .sw { width: 16px; height: 11px; border: 1.2px solid; border-radius: 3px; flex: none; }
.bd .subkey .sw.note { background: #ffffff; border: 1.4px dashed #9aa5b4; }

/* 하위 페이지 헤더 */
.bd .pagehead { display: flex; align-items: center; gap: 10px; margin: 2px 0 12px; flex-wrap: wrap; }
.bd .pagehead .step-tag { font-size: 11.5px; font-weight: 800; color: #fff; border-radius: 6px; padding: 2px 9px; white-space: nowrap; }
.bd .pagehead h2 { font-size: 16px; font-weight: 800; margin: 0; }
.bd .pagehead .eng { color: var(--bd-ink-soft); font-size: 12px; }
.bd .pagehead .chips { margin-left: auto; display: flex; gap: 5px; }

/* 설계 노트 */
.bd .notes { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); margin-top: 12px; padding: 16px 20px 18px; font-size: 13px; }
.bd .notes h4 { font-size: 12px; color: var(--bd-ink-soft); text-transform: uppercase; letter-spacing: .04em; margin: 11px 0 6px; }
.bd .notes h4:first-child { margin-top: 0; }
.bd .notes ul { list-style: none; margin: 0; padding: 0; }
.bd .notes li { padding: 2.5px 0 2.5px 14px; position: relative; }
.bd .notes li::before { content: ""; position: absolute; left: 0; top: .72em; width: 6px; height: 6px; border-radius: 2px; background: #b8c4d0; }
.bd .notes .chip { margin-left: 4px; vertical-align: 1px; }
.bd .mono { font-family: var(--mono); font-size: .9em; background: #eef2f7; padding: 1px 5px; border-radius: 4px; white-space: nowrap; }

/* 모드 시퀀스 체인 (유도 페이지) */
.bd .chain { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 6px 0 8px; padding: 9px 10px; background: #f6f8fb; border-radius: 8px; }
.bd .chain .sig { font-family: var(--mono); font-size: 11.5px; background: #fff; border: 1px solid var(--bd-edge); border-radius: 6px; padding: 3px 7px; white-space: nowrap; }
.bd .chain .ctl { font-size: 11.5px; font-weight: 700; background: var(--text); color: #fff; border-radius: 6px; padding: 3px 8px; white-space: nowrap; }
.bd .chain .arr { color: var(--bd-ink-soft); font-size: 12px; }

/* 블록도 내 파라미터 연동 수치 (data-p) — 스키마 기본값+적용 편집값으로 채워지고
   편집 폼 입력과 실시간 동기 (views/blocks.js bindSvgParams) */
.bd svg [data-p] { font-weight: 800; fill: #0071e3; }

/* ── 블록도 매뉴얼 (views/manual.js) ────────────────────────────────────────
   예전엔 읽는 글이라며 본문 폭을 68ch로 묶었는데(가독성 통설, 70~80자), 상자
   (.man-fold 등)는 패널 폭(~1300px)까지 그대로 넓어서 글만 일찍 꺾이고 오른쪽에
   빈 여백이 남았다. 상자를 글줄에 맞춰 좁히는 시도도 해봤지만 사용자가 다시
   지적: "좁히라는게 아니라 칸 너비만큼 충분히 활용하라" — 상자를 좁히지 말고
   글이 상자 폭을 그대로 쓰게 한다. 문자수 상한을 없앤다(.hint·p·li·dd 전부) */
.bd .manual { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); margin-top: 14px; padding: 22px 26px 26px; }
.bd .manual > h2 { font-size: 22px; margin: 0 0 4px; letter-spacing: -.02em; }
.bd .manual > .hint { margin: 0 0 4px; }
.bd .manual p, .bd .manual li, .bd .manual dd { line-height: 1.65; }
.bd .man-sec { border-top: 1px solid var(--bd-edge); margin-top: 20px; padding-top: 16px; }
.bd .man-sec > h3 { font-size: 15px; margin: 0 0 10px; }
/* 배경 Q&A는 넓은 화면에서 2단(auto-fit + minmax(420px,100%))으로 세로 스크롤을
   절반으로 줄인다. 좁아지면 자동으로 1단 */
.bd .man-bg { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(420px, 100%), 1fr)); gap: 4px 32px; }
.bd .man-q { margin-bottom: 14px; }
.bd .man-q h4 { font-size: 13.5px; margin: 0 0 4px; color: var(--accent); }
.bd .man-q p { margin: 0 0 6px; font-size: 13px; color: #38414f; }

/* 블록 항목 — 머리줄(단계 배지·이름·한 줄 역할·이동 버튼)과 본문 */
.bd .man-blk { border-top: 1px solid var(--bd-edge); margin-top: 18px; padding-top: 14px; }
.bd .man-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.bd .man-head h3 { font-size: 16px; margin: 0; }
.bd .man-head .step-tag { font-size: 10.5px; font-weight: 700; color: #fff; border-radius: 999px; padding: 2px 9px; }
.bd .man-role { font-size: 12.5px; color: var(--muted); }
.bd .man-go { margin-left: auto; font-size: 12px; padding: 4px 10px; }
/* 본문 1단(사용자 지적 — 칸 폭은 넓은데 2단으로 쪼개 놓으니 왼쪽을 다 읽고 오른쪽 위로
   되짚어야 해 가독성이 떨어졌다. 예전엔 "설명 vs 찾아 쓰는 것"으로 성격을 갈라 나란히
   뒀지만, 나머지 매뉴얼 섹션(흐름·근거·설계 노트)이 전부 1단인 것과도 어긋났다):
   무엇/왜/비행 영향 → 튜닝 → 흔한 증상과 대처 순서로 위에서 아래로 읽는다.
   글줄 폭은 이제 상자(.man-fold)를 그대로 채운다 — 위 .bd .manual p 규칙에서 68ch
   문자수 상한을 없앴다(사용자 지적: 상자를 좁히지 말고 칸 너비를 다 쓰게) */
.bd .man-body { margin-top: 8px; display: grid; grid-template-columns: 1fr; }
.bd .man-body h5 { font-size: 11.5px; color: var(--bd-ink-soft); text-transform: uppercase; letter-spacing: .05em; margin: 12px 0 4px; }
.bd .man-col:first-child > h5:first-child { margin-top: 4px; }
.bd .man-body p { margin: 0; font-size: 13px; color: #38414f; }
.bd .man-body ul { margin: 0; padding-left: 18px; font-size: 13px; color: #38414f; }
.bd .man-body li { padding: 2px 0; }

/* 증상 → 대처. dt가 증상이라 눈에 먼저 걸려야 한다 */
.bd .man-tr { margin: 0; }
.bd .man-tr dt { font-size: 13px; font-weight: 700; color: var(--text); margin-top: 9px; padding-left: 17px; position: relative; }
.bd .man-tr dt::before { content: "!"; position: absolute; left: 0; top: 0; width: 13px; height: 13px; line-height: 13px; text-align: center; font-size: 10px; border-radius: 4px; background: #fdeae8; color: #b3352b; }
.bd .man-tr dt:first-child { margin-top: 2px; }
.bd .man-tr dd { margin: 2px 0 0 17px; font-size: 13px; color: #38414f; }
.bd .man-order { margin: 0; padding-left: 20px; font-size: 13px; color: #38414f; }
.bd .man-order li { padding: 3px 0; }

/* ── 접히는 매뉴얼 섹션 (.man-fold) ───────────────────────────────────────
   매뉴얼은 전부 접힌 채로 시작한다 — 그림을 보러 온 사람이 글을 스크롤로 밀어내지
   않게. 대신 **닫힌 줄이 내용을 말한다**: 요약 자리에 신호 사슬 한 줄·한 줄 역할·
   개수를 실어, 열지 않아도 여기 뭐가 있는지는 알게 한다.
   (게인 사전이 쓰던 네 줄을 그대로 일반화한 것 — 저장소에서 스타일이 붙은
   details는 여기 하나뿐이라 새 장치를 만들 이유가 없었다) */
.bd .man-fold { margin-top: 8px; border: 1px solid var(--bd-edge); border-radius: 10px; padding: 0 14px; }
.bd .man-fold > summary { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px; cursor: pointer; padding: 11px 0; list-style-position: inside; }
.bd .man-fold > summary::marker { color: var(--bd-ink-soft); }
.bd .man-fold[open] > summary { border-bottom: 1px solid var(--bd-edge); }
.bd .man-fold > summary:hover { color: var(--accent); }
/* 요약은 오른쪽으로 밀어 라벨과 갈라 둔다 — 훑을 때 왼쪽 열만 보면 목차가 된다.
   좁아지면 flex-wrap이 줄을 바꾸므로 그때는 라벨 아래로 내려온다 */
.bd .man-brief { margin-left: auto; font-size: 11.5px; color: var(--bd-ink-soft); text-align: right; }
.bd .man-brief.chain { font-family: var(--mono); font-size: 11px; color: var(--accent); }
.bd .man-fold > :last-child { margin-bottom: 12px; }

/* 게인 사전 — 이름줄(코드·단위·기본값)과 설명 다섯 줄.
   각 줄이 다른 질문에 답하므로 라벨을 왼쪽에 고정폭으로 세워 훑기 쉽게 한다 */
.bd .man-gtitle { font-size: 14px; font-weight: 700; }
.bd .man-gcount { margin-left: auto; font-size: 11px; color: var(--bd-ink-soft); }
.bd .man-ref { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.bd .man-lead { margin: 10px 0 2px; font-size: 12.5px; color: #38414f; }
.bd .man-ggo { margin: 0 0 4px; }
.bd .man-gain { border-top: 1px dashed var(--bd-edge); padding: 9px 0 3px; }
.bd .man-gname { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; margin-bottom: 3px; }
.bd .man-gname code { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--accent); background: #eef4ff; padding: 1px 7px; border-radius: 5px; }
.bd .man-unit { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.bd .man-def { font-size: 11px; color: var(--bd-ink-soft); background: #f1f4f8; border-radius: 4px; padding: 1px 6px; }
/* 「설계값」 = 지금 계산에 쓰는 기체 문서의 값 — 레지스트리 「기본」과 다른 층임을 색으로 (v1.35) */
.bd .man-def.man-design { background: #e3f4ea; color: var(--ok); font-weight: 700; }
.bd .man-design-note { font-size: 11.5px; color: var(--muted); }
/* 라벨과 본문을 **격자 두 칸**으로 — inline-block + min-width로 하면 본문이
   두 줄째부터 라벨 아래로 흘러들어 어느 줄이 어느 질문의 답인지 안 보인다 */
.bd .man-gtext p { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 0 10px;
  margin: 2px 0; font-size: 12.5px; color: #38414f; }
.bd .man-gtext .lb { font-weight: 700; font-size: 11.5px; color: var(--bd-ink-soft); }
.bd .man-gtext .g-chain .lb { color: var(--l3); }
.bd .man-gtext .g-sym { grid-template-columns: 178px minmax(0, 1fr);
  background: #fdf7f6; border-radius: 5px; padding: 4px 8px; margin: 4px 0; }
.bd .man-gtext .g-sym .lb { color: #b3352b; }
@media (max-width: 760px) { .bd .man-gtext p, .bd .man-gtext .g-sym { grid-template-columns: 1fr; } }

/* ── 페이지 매뉴얼 (블록도 하위 페이지) ────────────────────────────────────
   .manual을 그대로 쓴다 — 위 .man-* 규칙을 통째로 물려받으려고(이제 문자수 상한
   없이 상자 폭을 그대로 쓴다). .inpage는 홈 카드보다 여백이 좁다: 그림 바로 밑에
   붙는 판이라 그림과의 거리가 멀어지면 "이 그림의 설명"으로 안 읽힌다 */
.bd .manual.inpage { margin-top: 12px; padding: 16px 20px 18px; }
.bd .manual.inpage > .man-fold:first-child { margin-top: 0; }

/* 이 그림 읽기 — 항상 펼침. 설계 노트(.notes)와 같은 목소리로 보이게 맞춘다:
   한 페이지에 판이 둘인데 서로 다른 서식이면 어느 쪽이 본문인지 헷갈린다 */
/* 소제목은 이제 summary가 맡고(fold), 신호 사슬은 닫힌 줄의 .man-brief.chain으로
   올라갔다 — 여기 남는 것은 펼친 뒤의 본문 서식뿐이다 */
/* 번호가 곧 그림의 읽는 순서라 눈에 걸려야 한다 — 기본 마커보다 진하게 */
.bd .man-flow-steps { margin: 0; padding-left: 22px; font-size: 13px; color: #38414f; }
.bd .man-flow-steps li { padding: 3px 0; }
.bd .man-flow-steps li::marker { font-weight: 800; color: var(--bd-ink-soft); }
.bd .man-flow p { margin: 0 0 6px; font-size: 13px; color: #38414f; }

/* 라벨에 단계 배지가 붙는 경우(블록 설명) — 배지와 제목을 한 줄에 세운다 */
.bd .man-gtitle { display: inline-flex; align-items: center; gap: 8px; }
.bd .man-gtitle .step-tag { font-size: 10.5px; font-weight: 700; color: #fff; border-radius: 999px; padding: 2px 9px; }

/* fold 안의 설계 노트 — .notes는 스스로 판(배경·그림자·여백)이라 그대로 넣으면
   판 안의 판이 된다. fold가 이미 테두리를 가지므로 .plain이 안쪽 판을 벗긴다.
   파라미터 폼을 감싼 .notes는 .plain이 없어 판을 그대로 유지한다 */
.bd .notes.plain { background: none; box-shadow: none; border-radius: 0; margin-top: 0; padding: 0 0 6px; }

/* 하위 페이지의 루트 띠 — 같은 글을 되풀이하는 대신 한 줄로 가리킨다 */
.bd .man-parent { border-top: 1px solid var(--bd-edge); margin-top: 14px; padding-top: 12px; font-size: 13px; }

/* 홈 목차 — .man-head를 본문 없이 쓴 카드. 층은 색으로, 지원은 회색으로 갈린다 */
.bd .man-idx { margin-top: 14px; }
.bd .man-idx h4 { font-size: 12px; color: var(--bd-ink-soft); text-transform: uppercase; letter-spacing: .04em; margin: 0 0 8px; }
.bd .man-idx-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: 8px; }
.bd .man-card { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; text-align: left; padding: 10px 12px; background: var(--panel); border: 1px solid var(--bd-edge); border-radius: 10px; cursor: pointer; font: inherit; color: inherit; }
.bd .man-card:hover { border-color: var(--accent); }
.bd .man-card-head { display: flex; align-items: center; gap: 7px; }
.bd .man-card-head b { font-size: 13.5px; }
.bd .man-card-role { font-size: 12px; color: var(--muted); }
.bd .man-card-n { font-size: 11px; color: var(--bd-ink-soft); background: #f1f4f8; border-radius: 4px; padding: 1px 6px; }
.bd .man-nodraw { font-size: 11px; color: var(--warn); background: #fdf6e7; border-radius: 4px; padding: 1px 6px; }
.bd .man-find { margin-left: auto; font-size: 11px; padding: 2px 8px; }

/* 3겹의 셋째 — 그림 속 게인 이름이 눌린다.
   **이름표 텍스트에만** 준다: 색 블록 전체를 누르게 하면 틴트(= 엔진 코드에 있다)와
   뜻이 겹쳐 "색칠된 건 다 눌린다"는 오독이 생긴다. 스코프가 .bd .sub인 것은 2계층
   시각 문법 규칙 (blocks.test.js가 스코프 이탈을 막는다) */
.bd .sub .gain-anchor { cursor: pointer; text-decoration: underline dotted 1.5px; text-underline-offset: 2.5px; }
.bd .sub .gain-anchor:hover { fill: var(--accent); }
.bd .sub .gain-anchor:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.bd .sub .gain-hit { fill: var(--accent); font-weight: 800; }
.bd .man-gain.hit { background: #eef4ff; border-radius: 6px; }
.bd .man-gain:focus { outline: none; }
/* 강조는 잠깐 번쩍이고 사라진다 — 모션을 끈 사용자에게는 색만 남긴다 */
@media (prefers-reduced-motion: no-preference) {
  .bd .man-gain.hit { animation: man-flash 1.2s ease-out; }
  .bd .sub .gain-hit { animation: man-flash 1.2s ease-out; }
  @keyframes man-flash { from { background: #cfe0ff; } to { background: transparent; } }
}

/* ── 전면 다크 탭 — 공용 스킨 (.tab-dark 스코프) ───────────────────────────
   검은 캔버스가 화면의 중심인 탭들이 쓴다: **영향성**(IR 영향 그래프)과
   **가상환경**(3D 세계). 패널만 밝으면 경계마다 스킨이 끊기므로 탭 전체를 Apple
   다크 팔레트(js/lib/influence.js SKIN과 동일 값)로 통일한다.
   스코프는 뷰 루트의 클래스 하나: 다른 탭·헤더는 불변이고, 페이지 배경은 :has로
   이 뷰가 마운트된 동안만 어둡다(라우터가 뷰를 떼면 셀렉터도 함께 풀린다).
   color-scheme: dark — 네이티브 위젯(스핀버튼·스크롤바·자동완성)까지 다크로.

   탭별 규칙은 자기 접두를 쓴다(.inf-* / .wv-*). 여기 있는 것은 **둘 다 쓰는 것**만:
   팔레트·폼 위젯·표, 그리고 아래 「전면 배치 뼈대」(머리·칩·패널). 그 뼈대를 탭마다
   베끼면 같은 레이아웃이 두 벌이 된다(02 §5.5). */
body:has(.tab-dark) { background: #0b0b0d; color-scheme: dark; } /* color-scheme: 문서 스크롤바는 body 소유 */
/* 헤더는 배경이 투명해 페이지 색을 그대로 입는다 — 다크에서는 제목이 묻히므로
   글자만 밝게 되돌린다 (탭 트레이·헬스 칩은 자체 배경이 있어 그대로 둔다) */
body:has(.tab-dark) header h1 { color: rgba(255, 255, 255, .92); }
body:has(.tab-dark) header h1 .sub { color: rgba(235, 235, 245, .6); }
.tab-dark { color: rgba(255, 255, 255, .92); color-scheme: dark; }
.tab-dark .panel {
  background: #1c1c1e;
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: none; /* 어두운 바닥에서는 그림자가 테를 흐리기만 한다 */
}
.tab-dark .hint, .tab-dark .legend, .tab-dark th { color: rgba(235, 235, 245, .6); }
.tab-dark th, .tab-dark td { border-bottom-color: rgba(255, 255, 255, .08); }
.tab-dark tr:hover td { background: rgba(255, 255, 255, .045); }
.tab-dark input, .tab-dark select, .tab-dark button {
  background: #2c2c2e;
  color: rgba(255, 255, 255, .92);
  border-color: rgba(255, 255, 255, .16);
}
.tab-dark input::placeholder { color: rgba(235, 235, 245, .3); }
/* 위의 .tab-dark input(0-1-1)이 전역 input:invalid와 동특이도·후순위라 이겨 버린다 —
   위반 표시(빨강)를 스코프 안에서 복원 (다크 systemRed) */
.tab-dark input:invalid { border-color: #ff453a; }
.tab-dark button.primary { background: #0a84ff; border-color: #0a84ff; color: #fff; }
.tab-dark .error-box { background: rgba(255, 69, 58, .16); color: #ff6961; }

/* 게인 칩(code) — 설계변수 id·런 라벨의 시각 단위. max-width로 컨테이너 폭을 절대
   못 넘고, 폭이 잠긴 곳(처방 카드 등)에서는 anywhere가 id 중간에서라도 꺾는다 —
   쌍 스윕 런 라벨(a@+0.10+b@+0.10)이 박스 밖으로 밀려 나가던 자리 */
.tab-dark code {
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
  background: rgba(255, 255, 255, .07);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 6px;
  padding: 0 6px;
}

/* ── 전면 배치 뼈대 (v0.49 영향성 → v0.51 가상환경 → v0.53 전 탭 공용) ─────
   블록도 최상위와 같은 규약: 주 그림은 **카드에 넣지 않는다**(.bd .canvas-wrap.top).
   이 탭들의 주 질문은 그림이 답하므로 그림이 화면이고, 나머지는 패널에 넣어
   눌렀을 때만 나온다. 그래서 폭도 이 탭들에서만 넓힌다 — 카드가 없으니 1440에
   묶어 둘 이유가 없고, 캔버스 논리폭이 그대로 들어가는 폭이 필요하다.

   **뼈대에는 색이 없다.** v0.52까지 이 규칙들은 전부 `.tab-dark` 안에 있었는데,
   그 스코프는 "주 그림이 검은 캔버스인 탭"이라는 뜻이지 배치의 이름이 아니다.
   밝은 탭(엔벨로프·시뮬·트림·마진·게인·자동설계·타면·결과)도 같은 배치를 쓰게
   되면서 여기를 밝은 팔레트 기준으로 되돌리고, 어두운 값은 아래 .tab-dark 절이
   덮는다. 배치 클래스는 `.tab-page` — 폭도 여기서 넓힌다. */
body:has(.tab-page) main { max-width: 1580px; }

.tab-top { margin: 2px 0 12px; }
.tab-top h1 { margin: 0; font-size: 32px; font-weight: 800; letter-spacing: -.02em; line-height: 1.1; }
.tab-subline { display: flex; align-items: center; justify-content: space-between; gap: 10px 24px; flex-wrap: wrap; margin-top: 7px; }
/* 한 줄 설명 — 폭 상한을 두지 않는다. 92ch로 묶으면 1580px 칸에서 절반(~712px)만 쓰고
   문장이 중간에 꺾여 오른쪽이 빈다(블록 매뉴얼 68ch를 없앤 것과 같은 이유 — 상자를
   좁히지 말고 칸 너비를 다 쓰게, 사용자 지적). 대개 한 줄이라 폭이 넓어도 줄 수는 안 는다 */
.tab-subline p { margin: 0; font-size: 13px; font-weight: 500; color: var(--muted); }

/* `.tab-stage`에는 규칙이 없다 — 그것이 이 배치의 요지다. 캔버스·SVG는 자기 테두리·
   그림자를 이미 갖고 있고, 그 바깥에 배경도 테두리도 두지 않는 것이 "칸 없이 페이지
   위에"다. 클래스는 구조 표식으로만 남긴다 (영향성의 .inf-stage와 같은 자리). */

/* 무대를 둘로 — 지도와 세로 프로파일처럼 **같이 봐야 뜻이 서는** 그림 두 장.
   좁아지면 접힌다(자동 배치): 억지로 2열을 유지하면 둘 다 못 읽는 폭이 된다 */
.tab-stage .stage-pair { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.tab-stage .stage-pair > * { flex: 0 1 auto; min-width: 0; }

/* 전면 조작줄 — 주 그림 바로 위/아래. 실행 버튼이 패널 안에 들어가면 "이 탭에서
   무엇을 하는가"가 클릭 뒤로 숨는다 (시뮬 실행·그리기·격자 생성이 그 자리다) */
.tab-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 12px 0 0; }
.tab-actions .grow { flex: 1; }
.tab-actions .hint { margin: 0; }
.tab-status { margin: 8px 0 0; min-height: 18px; font-size: 12px; color: var(--muted); }

/* 판독 시트 — 무대 바로 아래, 늘 보이는 표·수치판.
   그림은 자기 테두리를 갖지만 표는 갖지 않는다 — 회색 바닥에 표를 그대로 얹으면
   덜 만든 화면으로 읽힌다. 그렇다고 패널에 넣으면 편집이 클릭 뒤로 숨는다(게인
   탭의 셀 편집이 그 자리다). 그래서 무대와 패널 사이에 한 층을 둔다 — 영향성의
   정량 판독대(.inf-readout)가 하던 일을 이름만 일반화한 것이다. */
.tab-sheet:not(:empty) {
  margin-top: 12px;
  background: var(--panel);
  border-radius: var(--radius);
  padding: 14px 16px;
  box-shadow: var(--shadow);
}
.tab-dark .tab-sheet {
  background: #1c1c1e;
  border: 1px solid rgba(255, 255, 255, .09);
  box-shadow: none;
}

/* ── 패널 — 나머지는 전부 눌렀을 때만 ──────────────────────────────────── */
.tab-chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 0; align-items: center; }
/* 분류 라벨 — 칩이 여덟 개를 넘으면 순서만으로는 묶음이 안 읽힌다. 라벨은 묶음이
   바뀌는 지점에만 선다 (lib/stage.js chipModels.startsGroup) */
.tab-chipgroup {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--muted);
  margin-left: 6px;
  white-space: nowrap;
}
.tab-chipgroup:first-child { margin-left: 0; }
.tab-chip {
  padding: 6px 13px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(31, 41, 55, .04);
}
.tab-chip:hover { background: #f4f6fa; }
.tab-chip[aria-expanded="true"] { background: var(--text); border-color: var(--text); color: #fff; }
.tab-chip .n { font-family: var(--mono); opacity: .7; margin-left: 5px; }
.tab-drawer:not(:empty) {
  margin-top: 10px;
  background: var(--panel);
  border-radius: var(--radius);
  padding: 16px 18px;
  box-shadow: var(--shadow);
}
/* 패널 하나에 여러 덩이가 들어갈 때만 — 첫 덩이는 위 여백 없이 시작한다 */
.tab-sect + .tab-sect { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line); }
.tab-sect h2 { font-size: 15px; font-weight: 800; margin: 0 0 8px; }
.tab-sect h3 { font-size: 13px; font-weight: 700; margin: 14px 0 6px; }

/* 어두운 탭에서의 같은 뼈대 — 값만 덮는다 (배치는 위가 정본) */
.tab-dark .tab-subline p { color: rgba(235, 235, 245, .6); }
.tab-dark .tab-chipgroup { color: rgba(235, 235, 245, .5); }
.tab-dark .tab-chip {
  background: rgba(255, 255, 255, .05);
  border-color: rgba(255, 255, 255, .12);
  color: rgba(235, 235, 245, .75);
  box-shadow: none;
}
.tab-dark .tab-chip:hover { background: rgba(255, 255, 255, .1); }
.tab-dark .tab-chip[aria-expanded="true"] { background: #0a84ff; border-color: #0a84ff; color: #fff; }
.tab-dark .tab-drawer:not(:empty) {
  background: #1c1c1e;
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 14px;
  padding: 14px 16px;
  box-shadow: none;
}
.tab-dark .tab-sect + .tab-sect { border-top-color: rgba(255, 255, 255, .09); }
.tab-dark .tab-status { color: rgba(235, 235, 245, .6); }

/* ── 영향성 탭 — 정량 판독대 (v0.49) ──────────────────────────────────────
   위 뼈대 위에 이 탭만 얹는 것. 선택한 파라미터 하나에 대해 세 단이 각각 한 줄씩.
   없는 단은 빈칸이 아니라 **사유**를 낸다 (판정 불가를 0으로 위장하지 않는 이 화면의
   규약과 같다). `.inf-stage`는 규칙이 없다 — 위 `.tab-stage`와 같은 이유다. */
.inf-dark .inf-readout {
  margin-top: 12px;
  background: #1c1c1e;
  border: 1px solid rgba(255, 255, 255, .09);
  border-radius: 14px;
  padding: 12px 14px;
}
.inf-dark .inf-rohead { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.inf-dark .inf-rohead .name { font-family: var(--mono); font-size: 13px; font-weight: 700; }
.inf-dark .inf-rorows { display: grid; gap: 8px; margin-top: 10px; }
.inf-dark .inf-rorow {
  display: grid;
  /* 단 이름 · 무엇을 잰 것인가 · 큰 숫자 · Δ — 숫자 열이 세로로 줄 맞아야 훑힌다.
     남는 폭은 **끝에서** 흡수한다(justify-content:start): 가운데 열을 1fr로 두면
     넓은 화면에서 라벨과 숫자가 화면 양끝으로 갈라져 한 줄로 안 읽힌다 */
  grid-template-columns: 76px minmax(130px, 220px) minmax(230px, 300px) minmax(150px, auto);
  justify-content: start;
  gap: 4px 16px;
  align-items: baseline;
}
.inf-dark .inf-tier { font-size: 11px; font-weight: 700; letter-spacing: .3px; white-space: nowrap; }
.inf-dark .inf-what { font-size: 12px; color: rgba(235, 235, 245, .6); }
/* 큰 숫자 — 이 화면에서 제일 크다. 눈이 먼저 닿아야 하는 사실이 여기 있다 */
.inf-dark .inf-num { font-family: var(--mono); font-size: 21px; font-weight: 700; line-height: 1.25; white-space: nowrap; }
.inf-dark .inf-num .to { color: #ffffff; }
.inf-dark .inf-num .from { color: rgba(235, 235, 245, .55); }
.inf-dark .inf-num .arrow { color: rgba(235, 235, 245, .4); margin: 0 7px; font-weight: 400; }
.inf-dark .inf-num .unit { font-size: 12px; font-weight: 600; color: rgba(235, 235, 245, .5); margin-left: 4px; }
.inf-dark .inf-chg { font-family: var(--mono); font-size: 13px; font-weight: 700; white-space: nowrap; }
.inf-dark .inf-at { font-size: 11px; color: rgba(235, 235, 245, .45); }
.inf-dark .inf-why { font-size: 12px; color: rgba(235, 235, 245, .45); grid-column: 3 / -1; }

/* ── 가상환경 탭 (v0.51) — 3D 세계가 화면이다 ──────────────────────────────
   위 뼈대(.tab-top·.tab-chips·.tab-drawer)를 그대로 쓰고, 여기는 캔버스와 그 위에
   얹는 것만 정의한다. 조작줄은 캔버스 **바로 위**다: 시점·결과를 바꾸면 눈이 곧장
   화면으로 돌아와야 하는데, 아래에 두면 캔버스 높이만큼 눈이 왕복한다. */
.wv-stage { position: relative; }
/* 캔버스는 페이지 위에 그대로 — 자기 테두리·그림자를 갖되 그 바깥에 카드는 없다
   (영향성 캔버스와 같은 처리). 높이는 JS가 정한다(2:1 상한 + 뷰포트 상한). */
.wv-stage canvas {
  display: block;
  width: 100%;
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, .08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), 0 18px 44px rgba(0, 0, 0, .55);
  background: #0d1117;
  touch-action: none;
}
/* 판독(t·고도·속도·자세)은 캔버스 **위에** 얹는다 — 3D를 보면서 읽는 값이라
   아래로 내리면 시선이 왕복한다. pointer-events:none — 드래그·클릭은 캔버스 몫이다. */
.wv-hud {
  position: absolute;
  left: 14px;
  bottom: 14px;
  pointer-events: none;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.7;
  color: rgba(255, 255, 255, .92);
  background: rgba(10, 10, 12, .55);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 10px;
  padding: 7px 11px;
  max-width: min(560px, calc(100% - 28px));
  backdrop-filter: blur(6px);
}
.wv-hud .k { color: rgba(235, 235, 245, .5); margin-right: 3px; }
.wv-hud .v { font-weight: 700; margin-right: 10px; }
/* 교신 자막 (F1) — 캔버스 상단 중앙. 판독(.wv-hud 좌하단)·조작줄과 겹치지 않는
   자리이고, pointer-events:none으로 드래그·클릭은 캔버스 몫(.wv-hud와 같은 규율).
   번들에 CSS를 넣지 않는 이유: build/world.css가 생겨도 호스트가 로드하지 않는다. */
.wv-cc {
  position: absolute;
  top: 14px;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
  max-width: min(720px, calc(100% - 28px));
  background: rgba(10, 10, 12, .62);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 10px;
  padding: 8px 14px;
  font-size: 15px;
  line-height: 1.5;
  color: rgba(255, 255, 255, .95);
  text-align: center;
  backdrop-filter: blur(6px);
}
.wv-cc .spk { color: #ffd60a; font-weight: 700; margin-right: 8px; font-size: 12px; }
.wv-bar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.wv-bar.top { margin: 0 0 10px; }
/* 환경 슬라이더 — 패널 안. 라벨과 슬라이더가 세로로 붙어야 7개가 한눈에 훑힌다 */
.wv-env { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 18px; }
.wv-env label { display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: rgba(235, 235, 245, .6); }
.wv-env input { width: 100%; }
.wv-wp {
  font-family: var(--mono);
  font-size: 12px;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: 6px;
  padding: 1px 4px 1px 7px;
  display: inline-flex;
  gap: 5px;
  align-items: center;
}
.wv-wp button { padding: 0 5px; line-height: 1.4; }

/* 처방 카드("게인 박스") — min()으로 좁은 컨테이너에서는 카드가 먼저 줄어든다
   (280px 고정 하한이면 카드가 패널 밖으로 나간다). 내용은 위 code 규칙이 가둔다 */
.inf-dark .knob-card {
  flex: 1;
  min-width: min(280px, 100%);
  max-width: 420px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .1);
  border-radius: 12px;
}

/* ── 코드 표면 — VS Code 언어 (views/codeview.js) ──────────────────────────
   Autocode 탭이 내는 것은 FCC에 그대로 실릴 코드다. 회색 판에 통짜로 부으면 주석과
   실행문이 같은 무게로 읽히므로 편집기와 같은 문법 색을 입힌다. 색값은 VS Code
   Dark+ 계열 — 개발자가 이미 그 대응(초록=주석, 주황=문자열, 파랑=키워드)을 몸으로
   알고 있어서, 다른 팔레트를 새로 만드는 것은 학습만 늘린다.

   본문은 절대 줄바꿈하지 않는다(white-space: pre) — 줄바꿈하면 거터의 줄 수와
   본문의 줄 수가 갈려 줄번호가 통째로 거짓이 된다. 넘치면 가로 스크롤이고,
   거터는 sticky라 가로로 밀어도 왼쪽에 남는다. */
.cv {
  --cv-bg: #1e1e1e;
  --cv-fg: #d4d4d4;
  --cv-gut: #858585;
  margin: 0;
}
.cv-scroll {
  display: flex;
  align-items: flex-start;
  gap: 0;
  margin: 0;
  max-height: min(72vh, 900px);
  overflow: auto;
  background: var(--cv-bg);
  color: var(--cv-fg);
  border-radius: var(--radius);
  border: 1px solid #2b2b2b;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .04), 0 18px 44px rgba(0, 0, 0, .3);
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  tab-size: 4;
  -webkit-user-select: text;
  user-select: text;
  color-scheme: dark;
}
.cv-scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cv-gut {
  position: sticky;
  left: 0;
  z-index: 1;
  flex: 0 0 auto;
  padding: 14px 12px 14px 16px;
  text-align: right;
  white-space: pre;
  color: var(--cv-gut);
  background: var(--cv-bg);
  border-right: 1px solid #2b2b2b;
  /* 줄번호는 읽는 대상이 아니라 좌표다 — 드래그 복사에 섞이면 코드가 오염된다 */
  -webkit-user-select: none;
  user-select: none;
}
.cv-code {
  display: block;
  flex: 1 0 auto;
  padding: 14px 18px;
  white-space: pre;
  font: inherit;
}
.cv-note { margin: 6px 2px 0; }

/* 토큰 — lib/highlight.js의 종류 이름이 그대로 클래스가 된다 */
.cv-com { color: #6a9955; }
.cv-str { color: #ce9178; }
.cv-num { color: #b5cea8; }
.cv-kw  { color: #569cd6; }
.cv-typ { color: #4ec9b0; }
.cv-fn  { color: #dcdcaa; }
.cv-pre { color: #c586c0; }
.cv-op  { color: #d4d4d4; }

/* 편집기 탭줄 — 파일이 열여섯 개라 버튼 열여섯 개를 늘어놓으면 무엇이 무엇인지
   안 보인다. 역할 라벨로 묶고, 열린 파일만 위로 튀어나온 탭이 된다 */
.cv-tabs {
  display: flex;
  align-items: stretch;
  flex-wrap: wrap;
  gap: 0;
  margin: 10px 0 0;
  border-bottom: 1px solid var(--line);
}
.cv-tabs .role {
  display: flex;
  align-items: center;
  padding: 0 10px 0 4px;
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  white-space: nowrap;
}
.cv-tab {
  border: 1px solid transparent;
  border-bottom: none;
  background: none;
  border-radius: 8px 8px 0 0;
  padding: 6px 12px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}
.cv-tab:hover { color: var(--text); background: rgba(31, 41, 55, .05); }
.cv-tab[aria-selected="true"] {
  background: var(--panel);
  border-color: var(--line);
  color: var(--text);
  font-weight: 700;
  margin-bottom: -1px;
}
.tab-dark .cv-tabs { border-bottom-color: rgba(255, 255, 255, .12); }
.tab-dark .cv-tabs .role { color: rgba(235, 235, 245, .5); }
.tab-dark .cv-tab { color: rgba(235, 235, 245, .6); }
.tab-dark .cv-tab:hover { color: #fff; background: rgba(255, 255, 255, .06); }
.tab-dark .cv-tab[aria-selected="true"] {
  background: #1e1e1e;
  border-color: #2b2b2b;
  color: #fff;
}
/* 어두운 탭에서는 코드판이 페이지와 같은 결이라 그림자를 걷는다 */
.tab-dark .cv-scroll { box-shadow: none; }

/* ── 좁은 화면 (폰, ≤759px) ────────────────────────────────────────────────
   원칙: 레이아웃을 새로 설계하지 않는다 — 넘치는 것을 제자리에서 가두거나
   스크롤로 돌린다. 759는 .triview의 2열 하한(760) 바로 아래 — 겹치지 않는
   최대값이다(740으로 두면 741~759px 창에서 깨진 헤더가 되돌아온다). */
@media (max-width: 759px) {
  /* 탭 10개는 한 줄에 안 들어간다 — 줄바꿈 대신 가로 스크롤 한 줄(iOS 사파리
     관례). 헤더는 wrap: 1줄째 제목+헬스, 2줄째 전폭 nav */
  header { flex-wrap: wrap; gap: 2px 10px; padding: 8px 12px; }
  header h1 { font-size: 15px; }
  nav { order: 3; flex-basis: 100%; overflow-x: auto; padding: 2px 0; } /* 세로 여백 = 포커스 링 잘림 방지 */
  nav a { white-space: nowrap; padding: 6px 10px; }
  .health { margin-left: auto; white-space: nowrap; }

  main { padding: 10px; }
  .panel { padding: 12px; }
  .panel h2 { font-size: 14px; }
  /* 패널이 마지막 방어선 — .scroll-x 없이 놓인 넓은 표·고정폭 요소가 있어도
     패널 밖(둥근 모서리 밖)으로는 못 나간다. 데스크톱에는 걸지 않는다:
     넓을 때는 애초에 넘칠 일이 없고, 불필요한 스크롤 컨테이너만 는다 */
  .panel { overflow-x: auto; }
  /* 판독 시트도 같은 마지막 방어선 — .scroll-x 없이 놓인 넓은 표(자동 설계 운영점
     표 등)가 있어도 둥근 모서리 밖으로는 못 나간다 */
  .tab-sheet { overflow-x: auto; }
  .progress-label { min-width: 0; }

  /* 표 안 칩은 폰에서 꺾지 않는다 — anywhere가 표의 min-content를 줄여 id가
     글자 단위 세로 쌓임(5줄짜리 칩)으로 변한다. 칩을 한 줄로 두고 표 전체를
     .scroll-x/.panel 가로 스크롤로 돌린다. 카드 안 칩은 그대로 꺾인다(폭이
     잠긴 곳의 넘침 방지가 원래 목적). 데스크톱은 두 줄 칩이 낫다 — 미디어 한정 */
  .tab-dark table code { white-space: nowrap; }

  /* 판독대는 4열 최소폭이 ~634px이라 폰에서 그대로는 넘친다. 여기만은 가로
     스크롤로 돌릴 수 없다 — 이 화면의 주 표면이라 큰 숫자가 화면 밖에 있으면
     안 보이는 것과 같다. 단 이름만 왼쪽에 두고 나머지를 세로로 쌓는다 */
  body:has(.tab-page) main { max-width: none; }
  .tab-top h1 { font-size: 24px; }
  .tab-chips { gap: 6px; }
  .tab-drawer:not(:empty) { padding: 12px; }
  /* HUD가 폰에서는 캔버스를 절반 넘게 덮는다 — 전폭으로 눕혀 아래에 붙인다 */
  .wv-hud { left: 8px; right: 8px; bottom: 8px; max-width: none; font-size: 11px; }
  .inf-dark .inf-rorow {
    grid-template-columns: 68px minmax(0, 1fr);
    row-gap: 1px;
  }
  .inf-dark .inf-rorow > :nth-child(n + 3), .inf-dark .inf-why { grid-column: 2; }
  .inf-dark .inf-num { font-size: 18px; white-space: normal; }
  .inf-dark .inf-readout { padding: 10px 12px; }
}

/* ── 검증 탭 — 판정판 (views/verify.js) ─────────────────────────────────
   무대 위 카드들: 캔버스가 자기 테두리를 갖듯 판정 카드도 자기 판을 갖는다.
   판정 어휘는 기존 .flag 3-상태 그대로 — 새 색을 만들지 않는다. */
.vf-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.vf-verdict { font-size: 14px; padding: 4px 14px; font-weight: 700; }
.vf-ident { margin: 6px 0 12px; font-family: var(--mono); }
.vf-cards { display: flex; gap: 12px; flex-wrap: wrap; align-items: stretch; }
.vf-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px 18px;
  flex: 1 1 200px;
  max-width: 320px;
}
.vf-card h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.vf-card .vf-sub { margin: 4px 0 0; font-size: 12px; color: var(--muted); }
.vf-card .vf-detail { margin: 8px 0 0; font-size: 12px; }
.vf-code { font-family: var(--mono); font-size: 12px; }
.vf-log {
  font-family: var(--mono);
  font-size: 12px;
  background: var(--bg);
  border-radius: 8px;
  padding: 10px 12px;
  overflow-x: auto;
  max-height: 260px;
}

/* ── 검증 탭 v2 — 유닛 그리드·커버리지 소스 뷰어·MC/DC 진리표·증적 보고서 ── */
.vf-h { font-size: 15px; font-weight: 800; margin: 0 0 8px; }
.vf-unitrow { cursor: pointer; }
.vf-unitrow:hover td { background: #f4f6fa; }

.vf-viewer { margin-top: 16px; }
.vf-src {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.45;
  background: #1e1e1e;            /* 코드판은 어둡다 — Autocode 편집기와 같은 규약 */
  color: #d4d4d4;
  border-radius: 10px;
  padding: 8px 0;
  overflow-x: auto;
  max-height: 520px;
  overflow-y: auto;
}
.vf-ln { display: flex; white-space: pre; }
.vf-no {
  width: 46px; flex: none; text-align: right; padding-right: 8px;
  color: rgba(235, 235, 245, .35); user-select: none;
}
.vf-cnt {
  width: 64px; flex: none; text-align: right; padding-right: 10px;
  color: rgba(235, 235, 245, .5); border-right: 1px solid rgba(255,255,255,.12);
  user-select: none;
}
.vf-codeln { padding-left: 12px; }
.vf-ln.hit  .vf-codeln { background: rgba(52, 199, 89, .13); }
.vf-ln.miss .vf-codeln { background: rgba(255, 69, 58, .25); }
.vf-ln.part .vf-codeln { background: rgba(255, 159, 10, .22); }
.vf-ln.jus  .vf-codeln { background: rgba(191, 90, 242, .20); }
.vf-ln.miss .vf-cnt, .vf-ln.miss .vf-no { color: #ff6961; }

.vf-tt {
  margin: 6px 8px 10px 122px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 12px;
  font-family: var(--sans);
  white-space: normal;
  max-width: 640px;
}
.vf-tt summary { cursor: pointer; font-size: 12px; font-weight: 700; }
.vf-tt table { width: auto; }
.vf-tt td, .vf-tt th { padding: 2px 10px; }
.vf-conds { margin: 8px 0 8px 18px; padding: 0; }
.vf-tt-T { color: var(--ok); font-weight: 700; }
.vf-tt-F { color: var(--bad); font-weight: 700; }
.vf-tt-x { color: var(--muted); }

.vf-report { max-width: 900px; }
.vf-report h1 { font-size: 22px; margin: 6px 0 4px; }
.vf-report h2 { font-size: 15px; font-weight: 800; margin: 22px 0 8px; }
.vf-report table { margin: 6px 0; }
.vf-report .vf-tt { margin: 6px 0 10px; }
.vf-doc-tt { page-break-inside: avoid; }

/* 인쇄 — 전용 오버레이(.vf-printonly)만 낸다. 화면에서는 숨김 */
.vf-printonly { display: none; }
@media print {
  body.vf-printing > *:not(.vf-printonly) { display: none !important; }
  body.vf-printing .vf-printonly {
    display: block;
    padding: 24px;
    background: #fff;
  }
  .vf-printonly .vf-tt { border: 1px solid #ccc; }
  .vf-printonly .vf-src { max-height: none; }
}

/* ── 기체 문서 절별 폼 (views/profileform.js, 06 §8) ─────────────────────────
   칸 서술은 서버가 준다. 덮어쓴 칸(형상 변형)은 옅은 호박색 — 예제 배지와 같은 색 언어다 */
.pf-meta, .pf-vbar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 10px; }
.pf-sect { border-top: 1px solid rgba(0, 0, 0, .08); padding: 6px 0; }
.pf-sect > summary { cursor: pointer; font-weight: 600; display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline; }
.pf-sect > summary .hint { font-weight: 400; }
.pf-row { display: grid; grid-template-columns: minmax(170px, 260px) 1fr; gap: 4px 14px; padding: 6px 4px; align-items: start; }
.pf-row.ov, .pf-group.ov { background: #fff4d6; border-radius: 6px; }
.pf-row.ov-part, .pf-group.ov-part { background: #fffaeb; border-radius: 6px; }
.pf-row.pf-need .pf-edit input { outline: 2px dashed #946200; }
.pf-label { font-size: 12px; }
.pf-unit { color: var(--muted); margin-left: 4px; font-size: 11px; }
.pf-badge { display: inline-flex; gap: 4px; align-items: center; margin-left: 6px; padding: 0 7px; border-radius: 999px; font-size: 11px; font-weight: 700; background: #fdf2d7; color: #946200; }
.pf-mini { font-size: 11px; padding: 1px 7px; }
.pf-null { margin-left: 8px; font-size: 12px; white-space: nowrap; }
/* 「필수」·「선택」 — 색만으로 가르지 않고 글로 말한다. 필수는 빨간 테 글, 선택은 흐린 테 글 */
.pf-req, .pf-opt { display: inline-block; margin-left: 6px; padding: 0 5px; border: 1px solid currentColor; border-radius: 4px;
  font-size: 10px; font-weight: 700; line-height: 15px; vertical-align: 1px; }
.pf-req { color: var(--bad); }
.pf-opt { color: var(--muted); border-color: var(--line); font-weight: 600; }
.pf-legend .pf-req, .pf-legend .pf-opt { margin-left: 0; }
.pf-count { font-size: 11px; font-weight: 400; color: var(--muted); }
.pf-edit input.pf-num { width: 104px; }
.pf-edit input.pf-wide { width: 100%; max-width: 420px; }
.pf-edit .bad { outline: 2px solid var(--bad); }
.pf-grid { display: inline-grid; gap: 4px; }
.pf-table td, .pf-table th { padding: 2px 4px; font-size: 12px; }
.pf-json { width: 100%; min-height: 70px; font-family: ui-monospace, Menlo, monospace; font-size: 12px; }
.pf-group { border-left: 3px solid rgba(0, 0, 0, .08); padding: 4px 0 4px 10px; margin: 4px 0; }
.pf-group-head { font-weight: 600; font-size: 12px; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.pf-multi { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: 12px; }
.pf-reg { margin: 0 0 6px; }
.pf-param { display: grid; grid-template-columns: 150px 110px 1fr; gap: 6px; align-items: center; font-size: 12px; margin: 2px 0; }
.pf-coef { margin: 0 0 8px; }
.pf-csv { margin-top: 6px; font-size: 12px; }
.aircraft-page .stale { opacity: .45; } /* 편집 뒤의 옛 검증·저장 결과 — 지우면 레이아웃이 바뀌어 누르던 버튼이 밀린다 */

/* ── 로그인 게이트 (세션 모드) — 부팅 오버레이, 탭 아님 (views/login.js) ──────
   블록도(TOP_SVG 클론)가 뒤에 깔려 해체된 채 둥둥 떠다니고, 로그인하면 조립
   순서대로 착착 제자리로 돌아온다. 아래 규칙들은 반드시 .bd .top 규칙들(위쪽)
   **뒤에 있어야** 같은 특이도(.login-gate .top …)에서 이긴다. */
.login-gate {
  position: fixed;
  inset: 0;
  z-index: 40; /* ask/tour(20) 위 — 게이트는 화면 전부를 가려야 한다 */
  background: var(--bg);
  display: grid;
  place-items: center;
  overflow: hidden;
  transition: opacity .45s ease; /* .done — 조립 끝난 보드가 #blocks로 크로스페이드 */
}
.login-gate.done { opacity: 0; pointer-events: none; }
.login-gate .login-board {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  overflow: visible;     /* .bd .canvas-wrap의 overflow-x:auto를 눌러 흩어진 블록이 안 잘리게 */
  pointer-events: none;  /* 연출 전용 — 블록 호버·클릭(.blk:hover 규칙 일체) 무력화 */
}
.login-gate .login-board svg { min-width: 0; width: min(1320px, 94vw); height: auto; overflow: visible; }
/* 떠오르는 것은 .lift뿐 — 히트 도형 불변 규약을 게이트에서도 그대로 지킨다.
   rotate가 블록 중심을 돌게 transform-box/origin을 세운다 (SVG 기본은 뷰포트 원점) */
.login-gate .top .blk .lift {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  transition: transform .95s cubic-bezier(.34, 1.56, .64, 1); /* 오버슛 — "착" */
  will-change: transform;
}
/* 해체 상태 — 흩어진 위치·둥둥 위상은 블록마다 인라인 CSS 변수(--sx… lib/logingate.js).
   조립(views/login.js assemble)은 현재 자세를 인라인 행렬로 얼린 뒤 이 클래스를
   떼므로, 여기서는 transition을 꺼 초기 진입이 번쩍이지 않게만 한다 */
.login-gate.scattered .top .blk .lift {
  transition: none;
  animation: login-float var(--fdur, 5s) ease-in-out var(--fdel, 0s) infinite alternate;
}
@keyframes login-float {
  from { transform: translate(var(--sx, 0px), var(--sy, 0px)) rotate(var(--srot, 0deg)); }
  to {
    transform: translate(calc(var(--sx, 0px) + var(--fx, 9px)), calc(var(--sy, 0px) + var(--fy, 11px)))
      rotate(calc(var(--srot, 0deg) + 3deg));
  }
}
/* 블록이 아닌 것(판·배선·라벨·값 슬롯·그림자)은 해체 동안 숨고, 조립이 시작되면
   지연을 두고 배어난다 — 블록이 반쯤 모였을 때 판이 나타나야 "조립되는 곳"이 보인다 */
.login-gate .top .plate,
.login-gate .top .wires,
.login-gate .top .labels,
.login-gate .top .sigval,
.login-gate .top .board-shadow { transition: opacity .8s ease .45s; }
.login-gate.scattered .top .plate,
.login-gate.scattered .top .wires,
.login-gate.scattered .top .labels,
.login-gate.scattered .top .sigval,
.login-gate.scattered .top .board-shadow { opacity: 0; transition: none; }

.login-card {
  position: relative;
  z-index: 1; /* 보드 위 */
  width: min(360px, calc(100vw - 48px));
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px 24px 16px;
  display: grid;
  gap: 10px;
}
.login-card h1 { margin: 0; font-size: 22px; font-weight: 800; }
.login-card h1 .sub { display: block; font-size: 12px; font-weight: 500; color: var(--muted); margin-top: 3px; }
.login-card form { display: grid; gap: 8px; }
.login-card input {
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font: inherit;
  background: var(--panel);
}
.login-card input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.login-card .primary {
  padding: 10px 12px;
  border: none;
  border-radius: 8px;
  background: var(--text);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  cursor: pointer;
}
.login-card .primary:disabled { opacity: .55; cursor: default; }
.login-card .login-msg { margin: 0; min-height: 18px; font-size: 12px; color: var(--muted); }
.login-card .login-msg.bad { color: var(--bad); }
.login-card .login-msg.ok { color: var(--ok); }
.login-card .login-alt { text-align: center; }
.login-card .alt { border: none; background: none; color: var(--muted); font-size: 12px; cursor: pointer; text-decoration: underline; }
.login-card .alt:hover { color: var(--text); }

/* 헤더 세션 알약 — health와 같은 알약 언어. data-view 없음(nav 가드) */
.session-box {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 11px;
  color: var(--muted);
  padding: 5px 7px 5px 12px;
  border-radius: 999px;
  background: var(--panel);
  white-space: nowrap;
}
.session-box .who { font-weight: 700; color: var(--text); }
.session-box a { color: var(--accent); text-decoration: none; font-weight: 600; }
.session-box a:hover { text-decoration: underline; }
.session-box .logout {
  border: none;
  background: var(--bg);
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px;
  color: var(--text);
  cursor: pointer;
}
.session-box .logout:hover { background: var(--line); }

/* ── 관리자 화면 (#admin — 탭 아님, views/admin.js) ───────────────────── */
.admin-page { display: grid; gap: 16px; max-width: 1100px; }
.admin-page .panel { background: var(--panel); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px; }
.admin-page h2 { margin: 0 0 10px; font-size: 16px; }
.admin-page table { width: 100%; border-collapse: collapse; font-size: 13px; }
.admin-page th { text-align: left; font-size: 11px; color: var(--muted); font-weight: 600; }
.admin-page th, .admin-page td { padding: 7px 10px; border-bottom: 1px solid var(--line); }
.admin-page tr.pending td { background: #fdf2d7; } /* 승인 대기 — 기체 탭 notice와 같은 노랑 */
.admin-page .rowbtns { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.admin-page .rowbtns button { font-size: 11px; padding: 3px 9px; border-radius: 999px; border: 1px solid var(--line); background: var(--panel); cursor: pointer; }
.admin-page .rowbtns button:hover { background: var(--bg); }
.admin-page .rowbtns button.danger { color: var(--bad); border-color: #f3c8c8; }
.admin-page .muted { color: var(--muted); font-size: 12px; }
.admin-page .notice { margin: 0 0 10px; padding: 8px 12px; border-radius: 8px; background: #fdeaea; color: var(--bad); font-size: 12px; }

/* ── 설계 흐름 다이어그램 (v1.35, 디자인 v2 — 사용자 선택: 스테퍼 레일 + 블록도 3D 블록) ───
   진행 레일이 사슬의 진척을, 3면 입체 블록(블록도 층판 팔레트 그대로)이 단계를, 실행 중 블록은
   블록도 선택과 같은 파란 3면(#2563eb/#1d4ed8/#1e3a8a)으로 **부양**해 두 탭의 시각 문법을
   통일한다(사용자 요구 "블록 클릭한 것처럼 확실히 파란색·둥둥"). 상태·조작·저장은 블록 아래
   판에 — 색만으로 말하지 않는다(상태 칩·문구 동반). .fd 스코프 — 다른 뷰와 충돌 방지 */
.fd { min-width: 1080px; }
.fd .fd-doc { display: flex; align-items: center; gap: 10px; font-size: 13px; margin-bottom: 30px;
  background: #f6f8fb; border-radius: 10px; padding: 9px 14px; }
.fd .fd-doc-t { font-weight: 800; font-size: 11px; color: #64748b; letter-spacing: .04em;
  background: #eef2f7; border-radius: 999px; padding: 1px 8px; white-space: nowrap; }
.fd .fd-doc a { margin-left: auto; white-space: nowrap; }

/* 진행 레일 — 마디(번호 노드)를 잇는 막대. 채움은 앞에서부터 이어 돈 데까지(뷰가 폭을 계산) */
.fd .fd-rail { position: relative; display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px; }
.fd .fd-bar, .fd .fd-fill { position: absolute; top: 15px; height: 4px; border-radius: 2px; }
.fd .fd-bar { left: 8.33%; right: 8.33%; background: #e5e9f0; }
.fd .fd-fill { left: 8.33%; width: 0; background: linear-gradient(90deg, #34d399, #2563eb); }
.fd .fd-step { position: relative; padding-top: 46px; }
/* 마디 = 큰 숫자 하나(사용자 결정 "원은 없애고 숫자는 크고 감각적으로") — 레일 위에 앉되
   판색 패딩이 뒤의 선을 끊어 숫자가 선에 긁히지 않는다. 상태는 숫자 색이 말한다(칩·문구 동반) */
.fd .fd-node { position: absolute; top: 0; left: 50%; transform: translateX(-50%); z-index: 2;
  font-size: 31px; line-height: 30px; font-weight: 800; letter-spacing: -.04em;
  font-variant-numeric: tabular-nums; color: #c9d1dc; background: var(--panel); padding: 0 10px; }
/* 큰 글자(31px) 대비 3:1 이상으로 — 판정의 정본은 판의 칩·문구지만 숫자만 봐도 틀리지 않게(리뷰) */
.fd .fd-step[data-tone="ok"] .fd-node { color: #16a34a; }
.fd .fd-step[data-tone="warn"] .fd-node { color: #d97706; }
.fd .fd-step[data-tone="bad"] .fd-node { color: var(--bad); }
.fd .fd-step[data-tone="run"] .fd-node { color: var(--accent);
  text-shadow: 0 0 14px rgba(37, 99, 235, .45); }

/* 3면 입체 블록 — 블록도 문법(윗면·오른 옆면·앞면, 시점 앞-오른쪽 위). 면 색은 층판
   팔레트(diagram.js LAYERS fill) 그대로 — 두 탭이 같은 팔레트를 말한다 */
.fd .fd-blk3 { position: relative; margin: 14px 12px 0 4px; height: 56px; }
.fd .fd-blk3 .f-front { position: absolute; inset: 0; background: var(--fd-f); display: flex;
  align-items: center; justify-content: center; gap: 6px; font-weight: 800; font-size: 13.5px;
  color: #243041; border-radius: 2px; box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5); }
.fd .fd-blk3 .f-top { position: absolute; left: 5px; top: -10px; width: 100%; height: 10px;
  background: var(--fd-t); transform: skewX(-45deg); border-radius: 2px 2px 0 0; }
.fd .fd-blk3 .f-side { position: absolute; top: -5px; right: -10px; width: 10px; height: 100%;
  background: var(--fd-s); transform: skewY(-45deg); }
.fd .fd-blk3 .fd-ground { position: absolute; left: 8%; right: 0; bottom: -9px; height: 8px;
  border-radius: 50%; background: rgba(15, 23, 42, 0); filter: blur(3px); }
.fd .fd-manual { font-size: 9px; font-weight: 800; color: #9a5b00;
  background: rgba(255, 255, 255, .72); border-radius: 999px; padding: 1px 6px; white-space: nowrap; }
.fd .fd-step[data-key="doc"] { --fd-t: #e9def9; --fd-s: #b79ce8; --fd-f: #cfbdf0; }
.fd .fd-step[data-key="envelope"] { --fd-t: #d8e6fa; --fd-s: #93b3e6; --fd-f: #b7cdf0; }
.fd .fd-step[data-key="seed"] { --fd-t: #d5efdf; --fd-s: #8fcaa8; --fd-f: #b5dfc5; }
.fd .fd-step[data-key="design"] { --fd-t: #fbe3cd; --fd-s: #e2ad7c; --fd-f: #f1c9a3; }
.fd .fd-step[data-key="eval"] { --fd-t: #e2e6ed; --fd-s: #b3bac7; --fd-f: #c9cfd9; }
.fd .fd-step[data-key="apply"] { --fd-t: #fdf1de; --fd-s: #e0c084; --fd-f: #f3ddb2; }

/* 실행 중 — 블록도 선택 문법 그대로(파란 3면 #2563eb/#1d4ed8/#1e3a8a — .bd .top .blk hover와
   같은 세 값, 같은 파일 위쪽 참조) + 부유. 진행은 판의 칩·진행바가 함께
   말한다(색·움직임만으로 말하지 않는다). 모션 감축 환경은 들림만 남긴다(블록도 규칙과 동일) */
.fd .fd-step[data-tone="run"] .fd-blk3 { animation: fd-float 2.4s ease-in-out infinite; }
.fd .fd-step[data-tone="run"] .fd-blk3 .f-top { background: #2563eb; }
.fd .fd-step[data-tone="run"] .fd-blk3 .f-front { background: #1d4ed8; color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .25); }
.fd .fd-step[data-tone="run"] .fd-blk3 .f-side { background: #1e3a8a; }
.fd .fd-step[data-tone="run"] .fd-blk3 .fd-ground { background: rgba(30, 64, 175, .3);
  animation: fd-ground 2.4s ease-in-out infinite; }
@keyframes fd-float { 0%, 100% { transform: translateY(-7px); } 50% { transform: translateY(-12px); } }
@keyframes fd-ground { 0%, 100% { transform: translateY(7px) scaleX(.94); opacity: .8; }
  50% { transform: translateY(12px) scaleX(.86); opacity: .55; } }
@media (prefers-reduced-motion: reduce) {
  .fd .fd-step[data-tone="run"] .fd-blk3 { animation: none; transform: translateY(-7px); }
  /* 그림자는 바닥에 남긴다 — 키프레임의 translateY(7px) 보정이 사라지므로 정적으로 준다(리뷰 지적) */
  .fd .fd-step[data-tone="run"] .fd-blk3 .fd-ground { animation: none; transform: translateY(7px); }
}

/* 블록 아래 판 — 상태·조작·저장. 실행 중에는 파란 링으로 블록과 짝을 이룬다 */
.fd .fd-panel { background: var(--panel); border-radius: 10px; margin-top: 16px;
  padding: 10px 12px 9px; min-height: 112px; display: flex; flex-direction: column;
  box-shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 4px 14px rgba(15, 23, 42, .07); }
.fd .fd-step[data-tone="run"] .fd-panel { box-shadow: 0 0 0 1.5px rgba(37, 99, 235, .35),
  0 4px 16px rgba(37, 99, 235, .14); }
.fd .fd-status { font-size: 12px; line-height: 1.45; flex: 1; }
.fd .fd-prog { height: 5px; border-radius: 3px; background: #e5e9f0; margin-top: 7px; overflow: hidden; }
.fd .fd-prog i { display: block; height: 100%; border-radius: 3px; background: var(--accent); }
.fd .fd-act { display: flex; gap: 10px; align-items: center; margin-top: 9px; font-size: 12.5px; }
.fd .fd-art { margin-top: 9px; padding-top: 7px; border-top: 1px solid #eef1f6; font-size: 11px;
  color: #64748b; }
.fd .fd-art-t { font-weight: 800; font-size: 9.5px; color: #94a3b8; letter-spacing: .05em;
  margin-right: 6px; }
