/* 도서관 서사 프로토타입 — 프로덕션 CSS 와 격리한다.
   style.css 를 통째로 가져오면 전역 규칙(body·버튼·헤더)이 섞여 들어오므로
   폰트와 색·버튼·라벨 토큰만 옮겨 적는다. 값은 style.css :root 와 같다. */

@font-face {
  font-family: 'Pretendard';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url('../fonts/pretendard-ext.woff2') format('woff2');
  unicode-range: U+1100-11FF, U+3130-318F, U+A960-A97F, U+AC00-D7AF, U+D7B0-D7FF;
}

:root {
  --bg: #05070c;
  --text-primary: #e8eaef;
  --text-secondary: #aab2c0;
  --text-tertiary: #8e97a6;
  --text-quaternary: #7a8494;
  --accent: #5b8cff;
  --accent-solid: #3563d6;
  --accent-solid-hover: #3f6fe8;
  --accent-rgb: 91, 140, 255;
  --moved: #ffb454;
  --added: #5ee0ff;
  --deleted: #ff6b8b;
  --warm: #ffd7a0;
  --border-medium: rgba(255, 255, 255, 0.10);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-accent: 0 10px 30px -8px rgba(53, 99, 214, 0.45);
  --radius-xs: 3px;
  /* 모노 서체에 Pretendard 를 잇는다 — 모노 UI 안의 한글이 시스템 서체(Apple SD Gothic Neo·맑은 고딕)로 떨어져 한 페이지에 한글이 두 벌이었다.
     Pretendard 는 이미 받아 둔 한글 부분만 쓰므로 요청이 늘지 않는다 */
  --font-sans: 'IBM Plex Sans', 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'IBM Plex Mono', 'Pretendard', 'JetBrains Mono', 'SF Mono', monospace;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  --gutter: clamp(20px, 3.2vw, 44px);
  /* 크기 토큰 — 큰 화면에서 고정 px 라 왜소하던 것(2560 에서 헤드라인이 화면 높이의 3.5 %). 화면 크기에 따라 자란다 */
  --fs-h2: clamp(30px, 1.6vw + 1.6vh, 84px);
  --fs-p: clamp(14px, 0.2vw + 0.2vh + 11px, 19px);
  --ui: clamp(11px, 0.35vw + 5.5px, 13.5px);
  /* 오버레이 글자 — 3D 배경 위 모노 글자의 공통 헤일로, 계기판 판(블러 없음) */
  --halo: 0 0 1px rgba(5, 7, 12, 0.9), 0 0 12px rgba(5, 7, 12, 0.7);
  --plate: rgba(8, 11, 18, 0.8);
  --plate-edge: rgba(150, 180, 230, 0.12);
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background: var(--bg); scrollbar-width: none; }
/* 스크롤바 숨김 — 진행은 색인 채움선이 보여 준다. (R4a) 레일은 한 번 재생(시작 → 마감 → 푸터)이라 문서 끝이 곧 푸터다 — End 키·스크롤 끝이 정직하다.
   숨김은 그대로 둔다(보이는 스크롤바는 폭을 먹어 모든 배치가 옮겨 간다) */
html::-webkit-scrollbar, body::-webkit-scrollbar { display: none; }
body { background: var(--bg); color: var(--text-primary); font-family: var(--font-sans);
  word-break: keep-all; overflow-wrap: anywhere; line-height: 1.7; overflow-x: hidden; }
html.booting:not(.fb) body { overflow: hidden; }   /* 문서 모드로 넘어간 뒤 모듈이 늦게 booting 을 붙여도 스크롤을 막지 않는다 */
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
/* 모노 UI 안의 한글 — 라틴용 넓은 자간 대신 한글 자간. 글꼴도 한글 조판으로 잇는다 — 한글 글리프는 이미 Pretendard 였지만
   어절 사이 공백은 IBM Plex Mono(0.6em)로 그려져 '도면 변경과  앱 화면의'처럼 글자 폭의 66 % 만큼 벌어졌다. 공백·괄호는 Plex Sans 폭(약 0.26em) */
.ko { letter-spacing: 0.03em; font-family: 'Pretendard', var(--font-sans); }
/* 키보드 초점 — 프로덕션과 같은 링 */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius-xs); }
.btn:focus-visible { outline-offset: 4px; }
/* 본문으로 건너뛰기 — 키보드 첫 Tab 에만 뜬다(프로덕션 .skip-link 와 같은 자리·문법). 로딩 막(z 50) 위 */
.skip { position: fixed; left: 50%; top: 12px; z-index: 60; transform: translate(-50%, calc(-100% - 24px)); padding: 0.72rem 1.4rem;
  font: 600 12.4px/1.2 var(--font-sans); letter-spacing: 0.06em; color: #fff; background: var(--accent-solid); border-radius: 2px; text-decoration: none;
  box-shadow: var(--shadow-sm); transition: transform 0.2s var(--ease-out); }
.skip:focus, .skip:focus-visible { transform: translate(-50%, 0); }
.copies:focus, .copies:focus-visible { outline: none; }   /* 건너뛴 자리(본문 전체 화면)에 테두리를 두르지 않는다 */

#gl { position: fixed; inset: 0; width: 100vw; height: 100vh; height: 100lvh; display: block; z-index: 0; touch-action: pan-y; }
/* ── 시연 무대(R4a) ── .demo = 무대 + 레일. 무대는 화면 높이로 붙어 서고(sticky — 합성 스레드에서 스크롤과 같은 프레임에 움직인다) 레일이 스크롤 거리를 준다.
   레일이 끝나면(장면 끝 + 마감 머무름) 무대가 문서 흐름대로 위로 걷히고 아래 푸터가 이어진다. 다른 문서 아래에 붙이면 그 문서가 끝난 자리에서 무대가 올라와 선다.
   contain: layout — 무대 안 층(캔버스·카피·목업·인출선·축선 번호·노트)의 position: fixed 가 화면 대신 무대를 기준으로 선다(붙어 있는 동안 좌표는 화면과 같다).
   쌓임 — 무대는 한 쌓임 맥락(z 0 자리)이다. 안의 층은 모두 z ≤ 6 이라 바깥의 색인(z 6)·머리띠(10)·어둠막(15)·인트로·로딩 막과의 위아래가 예전과 같다.
   높이는 동적 화면(dvh — 예전 fixed inset 0 이 따르던 보이는 화면). 캔버스는 그대로 100lvh(주소창 아래까지).
   스크롤 앵커링 끔(R4a 수정) — 무대 높이(dvh)가 바뀌면(주소창 · 창 크기) 그 뒤의 레일이 밀리고, 브라우저 스크롤 앵커링이 레일을 제자리에 두려고 scrollY 를 옮겼다
   (390×844 높이 −56 px → scrollY −56 · 창 크기 변경 뒤 t 복원 +103 / −84 / +267 px — 예전 fixed 무대엔 없던 회귀). 시연 안의 스크롤 자리는 장면 t 좌표이고
   크기 변경 뒤 자리는 페이지가 직접 되살린다(relayout → restore) — 브라우저가 따로 옮기면 그 사이 스크롤 사건이 옛 곡선으로 t 를 잘못 읽는다.
   덩어리 범위로만 끈다(overflow-anchor 는 그 요소와 자손을 앵커 후보에서 뺀다) — 임베드에서 호스트 문서의 앵커링은 그대로 */
.demo { position: relative; overflow-anchor: none; }
.stage { position: -webkit-sticky; position: sticky; top: 0; height: 100vh; height: 100dvh; contain: layout; }
/* 무대가 붙어 있지 않을 때(마감 뒤 푸터 쪽 · 임베드에서 위 문서를 읽는 동안) 시연 색인은 걷는다 — 색인은 무대 밖(어둠막 딥 위로 올라서야 해서)에 fixed 로 선다 */
html.idx-away .index { opacity: 0 !important; visibility: hidden; transition: opacity 0.2s, visibility 0s linear 0.2s; }

/* 그레인·비네트는 3D 후처리(OutputPass)로 옮겼다 — DOM 회색 잡음은 평균이 0 이 아니라 어두운 곳을 띄웠다.
   대신 3D 위에 알파로 덮이는 어둠 층(목업 어둠막·카피 가림막·색인 가장자리)만 알파를 평균 0 잡음으로 흔들어 8bit 계조 등고선을 막는다.
   잡음 무늬(--dither)는 페이지가 로드 때 64² 로 한 번 만든다. 잡음 평균(0.98)만큼 층 알파를 --dk(= 1/0.98)로 되돌려 검정이 뜨지 않게 한다 */
.dim, .copies::before, .copies::after {
  -webkit-mask-image: var(--dither, none); mask-image: var(--dither, none);
  -webkit-mask-size: var(--dpx, 64px) var(--dpx, 64px); mask-size: var(--dpx, 64px) var(--dpx, 64px); }

/* ── 문서 모드(폴백) ── WebGL2 없음 · 소프트웨어 GL(SwiftShader·llvmpipe 등) · 모듈을 못 돌림 → html.fb(페이지·머리 안전장치가 붙인다),
   JS 끔 → html.no-js(머리 스크립트가 못 돌아 남은 클래스). 3D 레일 대신 카피 전문을 이 페이지의 실제 렌더(정지 화면)와 함께 문서 흐름으로 편다 — 빈 스크롤 레일 없음.
   선택자는 'html.fb body …, html.no-js body …' 두 벌이다 — :is() 를 모르는 옛 브라우저가 바로 폴백 대상이라 쓰지 않았고, body 를 넣어 뒤쪽 절의 html.landed … 보다 앞선다 */
.doc-why, .poster { display: none; }
html.fb body #gl, html.no-js body #gl,
html.fb body .boot, html.no-js body .boot,
html.fb body .steps, html.no-js body .steps,
html.fb body .callouts, html.no-js body .callouts,
html.fb body .hudbox, html.no-js body .hudbox,
html.fb body .sheet, html.no-js body .sheet,
html.fb body .hint, html.no-js body .hint,
html.fb body .dim, html.no-js body .dim,
html.fb body .mock, html.no-js body .mock,
html.fb body .bubbles, html.no-js body .bubbles,
html.fb body .rail, html.no-js body .rail,
html.fb body .veil, html.no-js body .veil,
html.fb body .index-fill, html.no-js body .index-fill { display: none; }
html.fb body, html.no-js body { overflow-x: hidden; overflow-y: auto; }
html.fb body .stage, html.no-js body .stage { position: static; height: auto; contain: none; }   /* (R4a) 문서 모드 — 무대는 그냥 문서 흐름(카피·노트가 순서대로) */
html.fb body .top, html.no-js body .top { position: sticky; top: 0; background: rgba(5, 7, 12, 0.98); border-bottom: 1px solid rgba(255, 255, 255, 0.06); pointer-events: auto;
  padding-left: max(var(--gutter), calc((100% - 1100px) / 2)); padding-right: max(var(--gutter), calc((100% - 1100px) / 2)); }
html.fb body .top .wm, html.no-js body .top .wm { opacity: 1; }
html.fb body .intro, html.no-js body .intro { position: static; display: grid; justify-items: start; place-content: start; gap: 18px; opacity: 1; visibility: visible; pointer-events: auto;
  max-width: calc(1100px + 2 * var(--gutter)); margin: 0 auto; padding: clamp(56px, 12vh, 150px) var(--gutter) 26px; }
html.fb body .intro-mark, html.no-js body .intro-mark { opacity: 1; width: min(520px, 76vw); transform: none; }
html.fb body .intro-sub, html.no-js body .intro-sub { opacity: 1; transform: none; letter-spacing: 0.42em; }
html.fb body .doc-why, html.no-js body .doc-why { display: block; max-width: calc(1100px + 2 * var(--gutter)); margin: 0 auto; padding: 0 var(--gutter);
  font: 400 13px/1.7 var(--font-sans); color: var(--text-secondary); }
html.fb body .doc-why a, html.no-js body .doc-why a { color: var(--accent); text-underline-offset: 3px; }
html.fb body .index, html.no-js body .index { position: static; transform: none; opacity: 1; display: flex; flex-wrap: wrap; gap: 0 22px; z-index: auto;
  max-width: calc(1100px + 2 * var(--gutter)); margin: 0 auto; padding: 22px var(--gutter) 10px; }
html.fb body .index::before, html.no-js body .index::before { display: none; }
html.fb body .index a, html.no-js body .index a { justify-content: flex-start; text-shadow: none; }
html.fb body .index a b, html.no-js body .index a b { width: auto; opacity: 1; transform: none; }
html.fb body .copies, html.no-js body .copies { position: static; inset: auto; z-index: auto; pointer-events: auto; max-width: calc(1100px + 2 * var(--gutter)); margin: 0 auto; padding: 30px var(--gutter) 24px; }
html.fb body .copies::before, html.no-js body .copies::before,
html.fb body .copies::after, html.no-js body .copies::after { display: none; }
html.fb body .copy, html.no-js body .copy { position: static; width: auto; max-width: 660px; visibility: visible; margin: 0 0 clamp(64px, 9vh, 96px); scroll-margin-top: 96px; }
html.fb body .copy > *, html.no-js body .copy > *,
html.fb body .copy h2 .ln > *, html.no-js body .copy h2 .ln > * { opacity: 1; transform: none; }
html.fb body .copy .k, html.no-js body .copy .k { position: static; margin-bottom: 18px; flex-wrap: wrap; white-space: normal; }
html.fb body .copy h2, html.no-js body .copy h2 { font-size: clamp(28px, 1.5vw + 18px, 46px); margin-bottom: 16px; }
html.fb body .copy p, html.no-js body .copy p { font-size: clamp(15px, 0.25vw + 13.5px, 18px); }
html.fb body .poster, html.no-js body .poster { display: block; margin: 0 0 44px; }
html.fb body .poster img, html.no-js body .poster img { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; background: #0b0f17;
  border: 1px solid var(--border-medium); border-radius: 3px; }
html.fb body .poster figcaption, html.no-js body .poster figcaption { margin-top: 10px; font: 400 11px/1.5 var(--font-mono); letter-spacing: 0.14em; color: var(--text-tertiary); }
html.fb body .note, html.no-js body .note { position: static; opacity: 1; z-index: auto; pointer-events: auto; text-align: left; text-shadow: none;
  max-width: calc(1100px + 2 * var(--gutter)); margin: 0 auto; padding: 0 var(--gutter) 30px; }
html.fb body .note .note-long, html.no-js body .note .note-long { display: inline; }
html.fb body .legal-in, html.no-js body .legal-in { max-width: 1100px; }
@media (max-width: 820px) {
  html.fb body .copy, html.no-js body .copy { margin-bottom: 56px; }
  html.fb body .copy h2, html.no-js body .copy h2 { font-size: 28px; }
  html.fb body .poster, html.no-js body .poster { margin: 0 calc(-1 * var(--gutter)) 36px; }
  html.fb body .poster img, html.no-js body .poster img { border-radius: 0; border-left: 0; border-right: 0; }
  html.fb body .poster figcaption, html.no-js body .poster figcaption { padding: 0 var(--gutter); }
}
/* 컨텍스트 손실 덮개(r10) — 캔버스가 검게 빈 동안 지금 장의 정지 화면(위 포스터와 같은 그림)을 캔버스 바로 위(카피·계기판 아래)에 덮는다.
   3 초 안에 복구되지 않으면 페이지가 문서 모드로 넘긴다(html.fb — 덮개는 내린다) */
.gl-lost { position: fixed; inset: 0; z-index: 1; background: var(--bg) center / cover no-repeat; opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.3s var(--ease-out), visibility 0s linear 0.3s; }
html.gl-lost-on .gl-lost { opacity: 1; visibility: visible; transition: opacity 0.3s var(--ease-out); }
html.fb body .gl-lost { display: none; }

/* ── 법정 푸터 ── index.html 푸터와 같은 내용·같은 문법(새 정보 없음). (R4a) 시연이 한 번 끝나고 무대가 위로 걷히면 문서 흐름대로 이어지는 마지막 구획 — End 키·스크롤 끝.
   예전(막간 로고가 선 동안만 화면 아래에 떠오르던 fixed 판 · html.legal-on)은 막간과 함께 걷었다. 바탕은 페이지 바탕(--bg) 한 겹.
   position: relative · z 7 — 무대가 contain 을 모르는 옛 브라우저(무대 안 층이 화면에 fixed 로 남는다)에서도 푸터가 무대 층(z ≤ 6) 위로 올라와 읽힌다. 머리띠(10)·어둠막(15) 아래 */
.legal { position: relative; z-index: 7; padding: 36px var(--gutter) 52px; background: var(--bg); border-top: 1px solid rgba(255, 255, 255, 0.07);
  font-size: clamp(12px, 0.25vw + 7.5px, 14px); line-height: 1.6; color: var(--text-tertiary); }   /* 1512 이하 12 px · 2560 13.9 px */
.legal-in { max-width: calc(1180px); margin: 0 auto; display: grid; gap: 7px; }
.footer-top { display: flex; justify-content: space-between; align-items: center; gap: 6px 24px; flex-wrap: wrap; }
.footer-left { display: flex; align-items: center; gap: 4px 16px; flex-wrap: wrap; }
.footer-logo { display: flex; align-items: center; gap: 0.55rem; }
.footer-logo img { height: 1.5em; width: auto; display: block; filter: brightness(0) invert(0.59); }   /* r15 P2 U2 — 흐린 파랑(불투명도 0.7)이 떠 보였다 — 필터 실루엣으로 글자 색(--text-tertiary 휘도 150)과 같은 단색 */
.footer-logo span { font-size: 1.07em; font-weight: 500; color: var(--text-secondary); }
.footer-copy { font-size: 0.93em; }
.footer-divider { border: 0; height: 1px; background: rgba(255, 255, 255, 0.07); margin: 3px 0; }
.footer-info { display: flex; flex-wrap: wrap; gap: 1px 20px; }
.footer-info span { letter-spacing: 0.01em; }
.footer-info em { font-style: normal; color: var(--text-secondary); margin-right: 0.2em; }
.footer-links { display: flex; flex-wrap: wrap; gap: 2px 20px; }
.footer-links a { color: var(--text-tertiary); text-decoration: none; transition: color 0.2s; }
.footer-links a:hover { color: var(--text-secondary); }
.footer-links a:focus-visible { outline-offset: 4px; }
@media (max-width: 820px) {
  /* 좁은 화면 — 가운데 정렬 */
  .legal { padding: 30px 20px 40px; text-align: center; }
  .footer-top, .footer-left, .footer-info, .footer-links { justify-content: center; }
  .footer-top { flex-direction: column; }
  .footer-info { gap: 0 12px; }
  .legal { font-size: 11px; }
}

/* ── 로딩 막 ── */
.boot { position: fixed; inset: 0; z-index: 50; display: grid; place-items: center; background: var(--bg);
  transition: opacity 0.9s var(--ease-io), visibility 0s linear 0.9s; }
.boot.out { opacity: 0; visibility: hidden; }
html:not(.js) .boot { display: none; }
.boot-in { width: min(380px, 70vw); display: grid; gap: 22px; }
.boot-mark { width: 100%; height: auto; fill: var(--text-primary); clip-path: inset(0 100% 0 0);
  animation: boot-reveal 1.25s var(--ease-io) 0.1s forwards; }   /* 로고는 로딩 막이 뜨자마자 시간으로 그려진다 — 진행률에 묶으면 멈춘 자리에서 반쪽 로고로 굳는다 */
@keyframes boot-reveal { to { clip-path: inset(0 0 0 0); } }
/* 인계 — 같은 프레임에 인트로 로고가 이 자리에서 이어받는다. 막은 투명해지고(아래 베일이 같은 색) 막대·숫자만 걷힌다 */
.boot.handoff { background: transparent; }
.boot.handoff .boot-mark { visibility: hidden; }
.boot.handoff .boot-bar, .boot.handoff .boot-meta { opacity: 0; transition: opacity 0.3s; }
.boot-bar { height: 1px; background: rgba(255, 255, 255, 0.12); position: relative; overflow: visible; }
.boot-bar i { position: absolute; left: 0; top: -0.5px; height: 2px; width: 0; background: var(--accent);
  box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.8); transition: width 0.25s var(--ease-out); }
.boot-meta { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.14em; color: var(--text-tertiary); font-variant-numeric: tabular-nums; }
.boot-meta > span:first-child { color: var(--text-secondary); }

/* ── 머리띠 ── */
.top { position: fixed; top: 0; left: 0; right: 0; z-index: 10; display: flex; align-items: center; gap: 22px;
  padding: 22px var(--gutter); font-family: var(--font-mono); font-size: var(--ui); letter-spacing: 0.16em; text-shadow: var(--halo);
  background: linear-gradient(180deg, rgba(5, 7, 12, 0.8), rgba(5, 7, 12, 0.45) 55%, rgba(5, 7, 12, 0)); pointer-events: none; }
.top a, .top .tag { pointer-events: auto; }
.top .wm { display: block; height: clamp(15px, 0.45vw + 8px, 21px); color: var(--text-primary); opacity: 0; transition: opacity 0.5s; }   /* 1512 15 px · 1920 16.6 · 2560 19.5 */
html.landed .top .wm { opacity: 1; }
html.wm-now .top .wm { transition: none; }   /* 날아온 로고가 앉는 프레임엔 전환 없이 먼저 선다 — 예전엔 0.35 초 투명해졌다 다시 섰다 */
.top .wm svg { height: 100%; width: auto; fill: currentColor; display: block; }
.top .tag { color: var(--text-secondary); display: inline-flex; align-items: center; gap: 9px; }
.top .tag i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.9); animation: pulse 2.4s var(--ease-io) 3; }   /* 무한 애니메이션은 브라우저가 화면 전체(3D 캔버스 포함)를 계속 다시 합성하게 만든다 — 세 번만 */
@keyframes pulse { 50% { opacity: 0.35; } }
.top .out { margin-left: auto; color: var(--text-secondary); text-decoration: none; transition: color 0.2s; }
.top .out:hover { color: var(--accent); }
/* 화살표는 선 그림(SVG) — 글자(↗ →)는 IBM Plex 에 없어 Menlo 로 떨어졌다 */
.btn-arrow { display: inline-block; width: 0.78em; height: 0.78em; vertical-align: -0.04em; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: 1.35; stroke-linecap: round; stroke-linejoin: round; transition: transform 0.2s var(--ease-out); }
.btn .btn-arrow { width: 1.05em; height: 0.9em; vertical-align: -0.12em; }

/* ── 인트로 ── 로더 로고가 그대로 커지며(FLIP) 인트로 로고가 되고, 머리띠의 제자리로 날아가 앉는다. 위치·크기는 페이지가 transform 으로 준다 */
.intro { position: fixed; inset: 0; z-index: 20; display: grid; place-content: center; justify-items: center; gap: 26px;
  pointer-events: none; }
.intro-mark { width: min(700px, 74vw); height: auto; fill: var(--text-primary); opacity: 0; transform-origin: 0 0; will-change: transform; }
.intro-sub { font-family: var(--font-mono); font-size: clamp(10px, 1vw, 12px); letter-spacing: 0.5em; color: var(--text-tertiary);
  opacity: 0; transform: translateY(8px); }
.intro-sub b { color: var(--accent); font-weight: 400; }
html.intro-on .intro-mark { opacity: 1; }
html.intro-grow .intro-sub { opacity: 1; transform: none; transition: opacity 0.8s 0.55s, transform 0.8s 0.55s var(--ease-out); }
html.intro-fly .intro-sub { opacity: 0; transition: opacity 0.35s; }
/* 걷힘 — 날아와 앉았으면 같은 자리의 머리띠 로고가 이미 서 있고, 스크롤로 건너뛰었으면 0.2 초에 사라진다(예전엔 한 프레임에 팝) */
html.landed .intro { opacity: 0; visibility: hidden; transition: opacity 0.2s, visibility 0s linear 0.2s; }

/* ── 계기판 ── 도면 표제란처럼 판 위에(블러 없음). 판은 글자와 형제로 둔다. 불투명도는 페이지가 t 로 매 프레임 쓴다 — CSS 전환을 겹치면 스크럽이 뒤따라온다 */
.hudbox { position: fixed; top: 76px; right: var(--gutter); z-index: 6; width: calc(var(--ui) * 25); pointer-events: none; opacity: 0; }   /* 25 칸 — 칩 'Report' 양옆 여백 4 px 이상(23.5 칸일 때 1–2 px) */
.hud-plate { position: absolute; inset: 0; background: var(--plate); border: 1px solid var(--plate-edge); border-radius: var(--radius-xs); }
.hud { position: relative; padding: 10px 14px 12px; font-family: var(--font-mono); font-size: var(--ui); text-shadow: var(--halo); }
.hud-row { display: flex; justify-content: space-between; gap: 18px; padding: 0.62em 0; line-height: 1.2;
  border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
.hud-row > span { letter-spacing: 0.16em; color: var(--text-tertiary); white-space: nowrap; }
.hud-row > b { font-weight: 500; color: var(--text-primary); font-variant-numeric: tabular-nums; letter-spacing: 0.04em; white-space: nowrap; }
/* 단계 칩 — DACID 다섯 단계와 Verit 네 화면이 같은 자리·같은 문법(한 칸에 겹쳐 두고 하나만 켠다 — 계기판 높이가 변하지 않게) */
/* 단계가 없는 동안(모델·사용 단계)은 칩 자리를 접는다 — 빈 판이 남지 않게. 칩이 켜질 때 판이 함께 펴진다 */
.pipes-wrap { overflow: hidden; max-height: 6em; transition: max-height 0.35s var(--ease-out); }
.hud.compact .pipes-wrap { max-height: 0; }
.pipes { display: grid; margin-top: 12px; }
.pipes > .pipe { grid-area: 1 / 1; }
.pipe { list-style: none; display: flex; gap: 3px; opacity: 0; transition: opacity 0.5s; }
.pipe.on { opacity: 1; }
.pipe li { flex: 1; white-space: nowrap; text-align: center; padding: 0.5em 0 0.42em; font-size: calc(var(--ui) * 0.9); letter-spacing: 0; line-height: 1;
  color: var(--text-tertiary); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: var(--radius-xs); transition: all 0.35s; }
.pipe li.done { color: #cfe0ff; border-color: rgba(var(--accent-rgb), 0.55); background: rgba(var(--accent-rgb), 0.14); }
.pipe li.now { color: #fff; border-color: var(--accent); background: rgba(var(--accent-rgb), 0.3);
  box-shadow: 0 0 14px rgba(var(--accent-rgb), 0.45); }
.pipe-sub { margin-top: 0.7em; min-height: 1.3em; line-height: 1.3; font-size: var(--ui); letter-spacing: 0.06em; color: var(--text-secondary); white-space: nowrap; }
/* 리포트 창 계기판 접기(r15 P2 U4 — 개발자님 결정 D) — 키·값 네 행을 높이 0 으로(페이지가 --hf 0 → 1 을 스크롤로 준다). 접는 동안만(.hf) 넘침을 자른다 —
   늘 자르면 글자 헤일로(text-shadow)가 행 가장자리에서 잘려 평소 모양이 달라진다. 'Report' 칩 아래 2 px 진행 막대(--rp) */
.hud.hf .hud-row { overflow: hidden; max-height: calc((1 - var(--hf, 0)) * 2.6em); padding-top: calc(0.62em * (1 - var(--hf, 0))); padding-bottom: calc(0.62em * (1 - var(--hf, 0)));
  opacity: clamp(0, calc(1 - var(--hf, 0) * 1.6), 1); border-bottom-color: rgba(255, 255, 255, calc(0.07 * (1 - var(--hf, 0)))); }
.hud.hf .pipes { margin-top: calc(12px - 10px * var(--hf, 0)); }
#pipe li.now:last-child { background-image: linear-gradient(90deg, var(--accent) 0 calc(var(--rp, 0) * 100%), transparent 0); background-size: 100% 2px; background-position: left bottom; background-repeat: no-repeat; }

/* ── 장 색인 ── 색인 상자 둘레에만 작은 원형 헤일로(.copies::after — 반경 ≈ 120 px, 알파 ≤ 0.42). 예전 전체 높이 띠(알파 0.72, 폭 gutter+190)는
   색인이 없는 하늘·동쪽 면·열람실 창까지 눌렀다(첫 화면 하늘 −65 %). 헤일로 자리·크기는 페이지가 색인 상자에서 준다(--ix·--iy·--iw·--ih).
   책상 구간(폰 인계·목업·잠금 — 밝은 상판 위에서 둥근 얼룩으로 읽혔다, r10)·사다리가 색인 자리를 쓰는 동안은 페이지가 --edge 로 거둔다(0.4 초 전환 — 착지·되돌아옴이 튀지 않게).
   번호 글자는 한 단계 밝게(#d3d9e3) + 두 겹 글자 헤일로 — 헤일로 알파를 낮춘 만큼 글자 쪽이 대비를 진다 */
.index { position: fixed; right: var(--gutter); top: 50%; transform: translateY(-50%); z-index: 6;
  display: grid; gap: 0; padding-right: 16px; opacity: 0; transition: opacity 0.6s; }
html.landed .index { opacity: 1; }
/* 접힌 색인 — 사다리가 색인 자리까지 쓰는 폭(1366–1512 등, 부재 라벨이 선 동안)에는 사라지는 대신 머리띠 오른쪽(현재 사이트 왼쪽)에 가로 한 줄로 선다.
   자리(--fr·--fy)는 페이지가 머리띠 링크에서 잰다. 지금 장만 이름을 단다('02 DACID'). 머리띠 판(z 10) 위 — 색인 딥(z 16)은 아래 규칙이 이긴다 */
html.idx-fold .index { top: var(--fy, 34px); right: var(--fr, 150px); z-index: 11; grid-auto-flow: column; padding-right: 0; }
html.idx-fold .index::before, html.idx-fold .index-fill { display: none; }
html.idx-fold .index a { min-height: 28px; min-width: 28px; padding: 0 6px; gap: 8px; justify-content: center; }
html.idx-fold .index a b { width: auto; }
html.idx-fold .index a:not(.on) b { display: none; }
html.dip .index, html.dip .top { z-index: 16; }   /* 색인 딥 — 장면만 어둠막에 잠기고 머리띠·색인은 그대로 둔다 */
/* 누르는 자리 32 px — 예전엔 10 px 글자에 약 14 px 였다 */
.index a { display: flex; gap: 10px; justify-content: flex-end; align-items: center; min-height: 32px; text-decoration: none;
  text-shadow: 0 0 1px rgba(5, 7, 12, 0.95), 0 0 5px rgba(5, 7, 12, 0.8), 0 0 14px rgba(5, 7, 12, 0.6);
  font-family: var(--font-mono); font-size: var(--ui); letter-spacing: 0.14em; color: #d3d9e3; transition: color 0.3s, text-shadow 0.4s linear; }
/* 책상 구간(페이지가 .bare — 둥근 헤일로를 거둔 동안) — 헤일로 대신 글자 둘레에 한 겹 더(6 px) 받치고, 꺼진 번호를 한 단계 밝힌다(#d3d9e3 → #e2e6ec).
   밝은 나무 상판(색인 뒤 중앙값 휘도 최대 약 99)에서 헤일로 없이 글자·배경 대비가 1024 t 0.58 에서 4.13 이었다 → 4.68(r10 F2) */
.index.bare a { text-shadow: 0 0 1px rgba(5, 7, 12, 0.95), 0 0 5px rgba(5, 7, 12, 0.8), 0 0 6px rgba(5, 7, 12, 0.6), 0 0 14px rgba(5, 7, 12, 0.6); }
.index.bare a:not(.on):not(:hover) { color: #e2e6ec; }
.index a b { width: 3.9em; text-align: left; font-weight: 500; opacity: 0; transform: translateX(6px); transition: opacity 0.3s, transform 0.3s var(--ease-out); white-space: nowrap; }
.index:hover a b, .index a.on b, .index a:focus-visible b { opacity: 1; transform: none; }
.index a.on { color: var(--text-primary); }
.index a:hover { color: var(--accent); }
.index::before { content: ''; position: absolute; right: 0; top: 8px; bottom: 8px; width: 1px; background: rgba(255, 255, 255, 0.14); }
.index-fill { position: absolute; right: 0; top: 8px; width: 1px; height: calc(100% - 16px); background: var(--accent);
  transform-origin: top; transform: scaleY(var(--p, 0)); box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.8); }

/* ── 카피 ── 진행도 구간으로 뜨고 진다. --v(0..1) 는 JS 가 쓴다.
   헤드라인 윗선을 고정 높이(50 % − 0.9em)에 걸고 키커는 위로, 문단은 아래로 쌓는다 — 가운데 정렬(translateY −50 %)이던 때는
   문단 줄 수에 따라 이어지는 카피의 헤드라인이 18 px 씩 오르내렸다. 왼쪽 선은 워드마크·노트와 같은 여백(--gutter) */
.copies { position: fixed; inset: 0; z-index: 5; pointer-events: none; }
.copies::after { content: ''; position: absolute; left: calc(var(--ix, 100vw) - var(--hr, 120px)); top: calc(var(--iy, 50vh) - var(--hr, 120px));
  width: calc(var(--iw, 0px) + 2 * var(--hr, 120px)); height: calc(var(--ih, 0px) + 2 * var(--hr, 120px)); pointer-events: none; opacity: var(--edge, 0); transition: opacity 0.4s linear;
  background: radial-gradient(closest-side, rgba(5, 7, 12, calc(0.42 * var(--dk, 1))) 0, rgba(5, 7, 12, calc(0.42 * var(--dk, 1))) var(--hp, 50%),
    rgba(5, 7, 12, calc(0.34 * var(--dk, 1))) calc(var(--hp, 50%) + (100% - var(--hp, 50%)) * 0.25), rgba(5, 7, 12, calc(0.19 * var(--dk, 1))) calc(var(--hp, 50%) + (100% - var(--hp, 50%)) * 0.5),
    rgba(5, 7, 12, calc(0.06 * var(--dk, 1))) calc(var(--hp, 50%) + (100% - var(--hp, 50%)) * 0.75), rgba(5, 7, 12, 0) 100%); }
.copies::before { content: ''; position: absolute; inset: 0 auto 0 0; width: min(58vw, 900px);
  background: linear-gradient(90deg, rgba(5, 7, 12, calc(0.66 * var(--dk, 1))) 0%, rgba(5, 7, 12, calc(0.38 * var(--dk, 1))) 45%, rgba(5, 7, 12, 0) 100%);
  opacity: var(--scrim, 0); }
/* 폭 821–1279 — 카피가 화면 폭에서 차지하는 몫이 커서(42vw) 글줄 끝이 가림막 꼬리(알파 0.1–0.2) 위의 밝은 기둥·아치에 얹혔다
   (1024: c-ap em p90 2.98 · c-built 키커 3.33 · c-end em 3.24). 가림막을 넓히고 글줄이 끝나는 자리까지 진하게 둔다 — 불투명도(--scrim)는 JS 가 그대로 쓴다 */
@media (min-width: 821px) and (max-width: 1279px) {
  .copies::before { width: min(78vw, 900px);
    background: linear-gradient(90deg, rgba(5, 7, 12, calc(0.8 * var(--dk, 1))) 0%, rgba(5, 7, 12, calc(0.68 * var(--dk, 1))) 40%,
      rgba(5, 7, 12, calc(0.4 * var(--dk, 1))) 66%, rgba(5, 7, 12, 0) 100%); }
}
/* 안 보이는 카피는 불투명도로만 숨긴다(visibility 가 아니라) — 보조기술에 본문 전문이 늘 읽히게. 누르는 자리는 보일 때만(.vis) */
.copy { position: absolute; left: var(--gutter); top: calc(50% - 0.9 * var(--fs-h2)); width: min(max(34em, calc(var(--fs-h2) * 12.5)), 42vw);
  --v: 0; }
.copy > * { opacity: clamp(0, calc(var(--v) * 1.7 - var(--i, 0) * 0.28), 1);
  transform: translateY(calc((1 - clamp(0, calc(var(--v) * 1.7 - var(--i, 0) * 0.28), 1)) * 22px)); }
.copy > :nth-child(1) { --i: 0; } .copy > :nth-child(2) { --i: 1; } .copy > :nth-child(3) { --i: 2; } .copy > :nth-child(4) { --i: 2.6; }
/* 키커는 제목 첫 줄과 함께 선다(r14 P2 U8) — 불투명도가 첫 줄 공개율(1.8v − 0.26 — 아래 .ln)의 1.25 배를 넘지 않는다.
   예전(1.7v)엔 제목 줄이 다 가려진 뒤에도 키커가 0.25 로 남아 '03 · VERIT — 지어진 다음' 만 방 위에 떠 있었다(유령 키커, 퇴장 0.31–0.39 화면).
   이제 첫 줄이 올라서기 시작할 때(v 0.144) 함께 뜨고 첫 줄보다 조금 앞서 v 0.59 에 다 선다(예전과 같은 자리) — 키커 → 제목 → 문단의 계단은 남는다.
   기울기(2.25)는 페이지의 한 프레임 --v 상한(CF_RATE)과 짝 — 빠른 플링에서도 키커 불투명도가 한 프레임 0.25 안 */
.copy > .k { opacity: min(clamp(0, calc(var(--v) * 1.7), 1), clamp(0, calc(var(--v) * 2.25 - 0.325), 1));
  transform: translateY(calc((1 - min(clamp(0, calc(var(--v) * 1.7), 1), clamp(0, calc(var(--v) * 2.25 - 0.325), 1))) * 22px)); }
/* 가운뎃점으로 이은 말('건축·구조')과 끊기면 뜻이 흩어지는 구절('만들 수도'·'그 사이를')을 한 덩어리로 — 줄 첫머리에 가운뎃점·조사 한 어절이 오지 않게 */
.nw { white-space: nowrap; }
/* 장 번호 한 체계 'NN · CHAPTER — 소제목'. 제품 장 첫머리는 로고가 CHAPTER 자리에 선다 */
/* 키커 글자색 — 강조색(#5b8cff = hsl 222° 100 % 68 %)과 같은 색상·채도에 명도만 74 %. 작은 모노 글자(11–13.5 px)라 3D 배경이 조금만 밝아도
   4.5:1 아래로 떨어졌다(r9 병합본: 390 c-diff 4.12 · 1280 c-ap 4.28 · 모든 폭 c-grad 4.82–4.94). 가림막을 넓히지 않고 글자 쪽이 대비를 진다(r9 fixP) */
.copy .k { position: absolute; left: 0; bottom: 100%; margin-bottom: calc(10px + 0.28 * var(--fs-h2)); display: flex; align-items: center; gap: 12px;
  font-family: var(--font-mono); font-size: var(--ui); letter-spacing: 0.2em; color: #7aa2ff; white-space: nowrap; }
.copy .k::before { content: ''; width: 26px; height: 0.5px; background: currentColor; opacity: 0.4; }
.copy .k .ko { letter-spacing: 0.04em; }
.copy .k.brand::before { display: none; }
.copy .k-n { letter-spacing: 0.14em; }
.copy .k-n::after { content: ''; display: inline-block; vertical-align: middle; width: 18px; height: 0.5px; margin-left: 12px; background: currentColor; opacity: 0.4; }
.copy .k-t { letter-spacing: 0.14em; }
.copy .k img { height: calc(var(--ui) * 2.35); width: auto; display: block; }
.copy .k img.verit { height: calc(var(--ui) * 3.45); margin: calc(var(--ui) * -0.55) 0; }
.copy .k-mark { height: calc(var(--ui) * 1.2); width: auto; fill: var(--text-primary); }
.copy h2 { font-size: var(--fs-h2); line-height: 1.22; font-weight: 600; letter-spacing: -0.03em; margin-bottom: calc(0.4 * var(--fs-h2));
  text-wrap: balance; transform: none; }
/* 헤드라인 등장 — 줄마다 가림 상자(.ln) 안에서 아래에서 올라선다. 스크롤로 스크럽된다(시간 연출·상시 애니메이션 없음). 둘째 줄은 반 박자 늦게.
   상자 위아래 0.08em 은 글리프 윗머리·아랫꼬리를 자르지 않을 여유 */
.copy h2 .ln { display: block; width: fit-content; max-width: 100%; overflow: hidden; padding: 0.08em 0; margin: -0.08em 0; }   /* 폭은 글줄만큼 — 블록 폭(카피 전체)이면 글줄 사각형을 재는 쪽(도면 한 장 판·겹침·대비 계측)이 빈 오른쪽까지 잡는다 */
.copy h2 .ln > * { display: block; transform: translateY(calc((1 - clamp(0, calc(var(--v) * 1.8 - var(--d, 0.26)), 1)) * 106%)); }
.copy h2 .ln + .ln > * { --d: 0.42; }
.copy h2 em { font-style: normal; font-weight: 400; color: var(--text-secondary); }
/* r15 P2 D2 — 리포트 창에서 02 카피 본문을 걷는다(페이지가 --pf 0 → 1). 표제란 카드가 그 자리로 올라오기 전(--pf 0.5)에 다 진다. 키커·제목은 남는다 */
#c-diff > p { opacity: calc(clamp(0, calc(var(--v) * 1.7 - var(--i, 0) * 0.28), 1) * clamp(0, calc(1 - var(--pf, 0) * 2), 1)); }
.copy p { font-size: var(--fs-p); line-height: 1.85; color: var(--text-secondary); max-width: 29em; text-wrap: pretty; letter-spacing: -0.005em; }
.cta { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 30px; }
.copy--end.vis .cta { pointer-events: auto; }   /* 안 보이는 동안 투명 버튼이 3D 드래그를 가로채지 않게 */
/* 마감 카피 판 — 아래 띠 배치에서만. 왼쪽 가림막(58–78vw 전체 높이)은 이 배치에서 건물 왼쪽 절반을 누른다 — 대신 카피 글줄 둘레만 덮는다.
   폭·높이는 페이지가 카피 글줄 상자에서 준다(--pw 글줄 오른쪽 + 여유 · --ph 키커 윗변까지 + 여유). 오른쪽·위 가장자리 여유(--pf·--pt) 동안 부드럽게 걷힌다
   (가로는 바깥 판의 마스크, 세로는 안쪽 판의 그라데이션 — 두 마스크가 곱해져 모서리도 둥글게 걷힌다). 안쪽 판은 다른 어둠 층처럼 디더(8bit 등고선 막기) */
.end-plate { display: none; position: absolute; left: 0; bottom: 0; width: var(--pw, 0px); height: var(--ph, 0px); opacity: 0; pointer-events: none; --pf: 120px; --pt: 90px;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - var(--pf)), rgba(0, 0, 0, 0.72) calc(100% - 0.66 * var(--pf)), rgba(0, 0, 0, 0.28) calc(100% - 0.33 * var(--pf)), transparent 100%);
  mask-image: linear-gradient(90deg, #000 calc(100% - var(--pf)), rgba(0, 0, 0, 0.72) calc(100% - 0.66 * var(--pf)), rgba(0, 0, 0, 0.28) calc(100% - 0.33 * var(--pf)), transparent 100%); }
.end-plate::before { content: ''; position: absolute; inset: 0;
  background: linear-gradient(0deg, rgba(5, 7, 12, calc(0.8 * var(--dk, 1))) 0, rgba(5, 7, 12, calc(0.8 * var(--dk, 1))) calc(100% - var(--pt)),
    rgba(5, 7, 12, calc(0.58 * var(--dk, 1))) calc(100% - 0.66 * var(--pt)), rgba(5, 7, 12, calc(0.22 * var(--dk, 1))) calc(100% - 0.33 * var(--pt)), rgba(5, 7, 12, 0) 100%);
  -webkit-mask-image: var(--dither, none); mask-image: var(--dither, none);
  -webkit-mask-size: var(--dpx, 64px) var(--dpx, 64px); mask-size: var(--dpx, 64px) var(--dpx, 64px); }
/* 마감 카피 — 네모난 넓은 화면(821 px 이상 · 화면비 3:2 이하)은 아래 띠에 선다(r10 fxEnd). 옆에 서면 카피와 색인 사이 띠가 0.5W 뿐이라
   마감 건물이 0.26–0.33H 로 줄었다(1024×768 · 1180×820 · 1280×1024). 띠는 두 단 — 왼쪽 키커·헤드라인, 오른쪽 본문·단추(읽는 순서 그대로).
   한 단으로 쌓으면 띠가 화면의 40 % 라 봉우리를 남기려면 카메라를 15 m 까지 낮춰야 했고, 앞 가로수가 정문(1F)을 가렸다.
   바닥은 하단 노트(20 px + 한 줄) 위. 카메라(library.js fitClose)가 같은 식으로 띠 윗변을 계산한다 — 58 px·단추·줄 높이·단 간격을 바꾸면 그쪽 식도 함께 */
@media (min-width: 821px) and (max-aspect-ratio: 3/2) {
  html:not(.fb):not(.no-js) .copy--end { top: auto; bottom: 58px; width: auto; display: grid; grid-template-columns: auto auto; column-gap: clamp(32px, 4vw, 64px); align-items: start; }
  html:not(.fb):not(.no-js) .copy--end h2 { grid-column: 1; grid-row: 1 / span 2; margin-bottom: 0; }
  html:not(.fb):not(.no-js) .copy--end p { grid-column: 2; grid-row: 1; }
  html:not(.fb):not(.no-js) .copy--end .cta { grid-column: 2; grid-row: 2; margin-top: 18px; }
  html:not(.fb):not(.no-js) .end-plate { display: block; }   /* 문서 모드(폴백)는 카피가 문서 흐름이라 판도 띠도 없다 */
}
/* 버튼 — 프로덕션 .btn-primary / .btn-secondary 와 같은 문법(각진 2 px · 12.4 px/600 · 대문자 · 자간 0.085em · 안쪽 하이라이트) */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.55rem; padding: 0.78rem 1.6rem;
  font: 600 12.4px/1.2 var(--font-sans); letter-spacing: 0.085em; text-transform: uppercase; border-radius: 2px; cursor: pointer; text-decoration: none;
  border: 1px solid transparent; transition: background-color 0.2s var(--ease-out), border-color 0.2s var(--ease-out), color 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out), transform 0.2s var(--ease-out); }
.btn-primary { color: #fff; background: var(--accent-solid); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), var(--shadow-sm); }
.btn-primary:hover { background: var(--accent-solid-hover); transform: translateY(-1px); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.26), var(--shadow-accent); }
.btn-secondary { color: var(--text-primary); background: rgba(8, 11, 18, 0.62); border-color: var(--border-medium); }   /* 3D 위라 판을 깐다 — 프로덕션 값(0.015)은 뒤의 벤치·가로등이 버튼 안에 비쳤다 */
.btn-secondary:hover { border-color: rgba(122, 165, 255, 0.42); background: rgba(91, 140, 255, 0.07); }
.btn:hover .btn-arrow { transform: translateX(3px); }

/* DACID 카피 뒤 '도면 한 장' — 평면이 화면을 채우는 동안 밝은 보 위에 글자가 얹혀 읽히지 않았다. 블러 없이 판 한 장.
   모서리의 도면 번호와 가는 상단 룰로 표제란처럼. 자리·크기·불투명도는 페이지가 카피 상자에서 준다 */
.sheet { position: absolute; left: max(0px, calc(46px - var(--gutter))); top: 0; width: 0; height: 0; opacity: 0; pointer-events: none;   /* 시트 왼쪽 변은 화면에서 16 px 이상 — 좁은 폭(1024)에서 3 px 로 붙던 것. 글줄 시작선은 그대로(안쪽 여백이 줄어든다) */
  background: rgba(13, 17, 25, 0.9); border: 1px solid rgba(160, 190, 240, 0.14); border-radius: 3px; }
.sheet::before { content: ''; position: absolute; left: 18px; right: 70px; top: 15px; height: 1px; background: rgba(var(--accent-rgb), 0.45); }
.sheet-no { position: absolute; right: 16px; top: 9px; font: 500 9.5px/1 var(--font-mono); letter-spacing: 0.16em; color: var(--text-tertiary); }

/* 첫 화면 안내 */
.hint { position: absolute; left: 50%; bottom: 44px; transform: translateX(-50%); display: flex; align-items: center; gap: 14px; text-shadow: var(--halo);
  font-family: var(--font-mono); font-size: var(--ui); letter-spacing: 0.16em; color: var(--text-secondary); white-space: nowrap; opacity: 0; }
/* 하단 노트 — 큰 화면(1700+)에서는 머리띠·안내와 함께 --ui 로 자란다(2560 에서 11 px 고정이라 점처럼 작았다). 노트의 판·자리 규칙은 뒤쪽 절 */
@media (min-width: 1700px) { html body .note { font-size: var(--ui); } }
.hint::after { content: ''; position: absolute; left: 50%; top: 100%; margin-top: 12px; width: 1px; height: 22px;
  background: linear-gradient(var(--text-tertiary), transparent); animation: drip 1.8s var(--ease-io) infinite; animation-play-state: paused; }
.hint.on::after { animation-play-state: running; }   /* 안내가 보일 때만 돈다 */
@keyframes drip { 0% { transform: scaleY(0); transform-origin: top; } 50% { transform: scaleY(1); transform-origin: top; }
  51% { transform-origin: bottom; } 100% { transform: scaleY(0); transform-origin: bottom; } }

/* ── DACID 인출선 ── production .callout 과 같은 문법. 글자 크기는 --ui 에서 */
.callouts { position: fixed; inset: 0; z-index: 4; pointer-events: none; }
.leaders { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.leaders polyline { fill: none; stroke: rgba(122, 165, 255, 0.6); stroke-width: 1; stroke-linejoin: round; }
.leaders polyline.moved { stroke: rgba(255, 180, 84, 0.6); }
.leaders polyline.added { stroke: rgba(94, 224, 255, 0.6); }
.leaders polyline.deleted { stroke: rgba(255, 107, 139, 0.6); }
.leaders circle { fill: var(--accent); stroke: rgba(5, 7, 12, 0.9); stroke-width: 1.5; }
.leaders circle.moved { fill: var(--moved); }
.leaders circle.added { fill: var(--added); }
.leaders circle.deleted { fill: var(--deleted); }
.co { position: absolute; left: 0; top: 0; display: grid; font-family: var(--font-mono); font-size: var(--ui); line-height: 1;
  white-space: nowrap; will-change: transform, opacity; }
.co > b, .co > span { display: block; padding: 0.55em 0.82em; background: rgba(8, 11, 18, 0.9); border: 1px solid rgba(160, 190, 240, 0.22); }
.co > b { font-weight: 600; letter-spacing: 0.14em; color: #cfe0ff; border-bottom: 0; border-radius: 3px 3px 0 0; }
.co > span { color: var(--text-secondary); border-radius: 0 0 3px 3px; letter-spacing: 0.03em; }
.co .ko { letter-spacing: 0.02em; }
.co.moved > b { color: #ffd28a; border-color: rgba(255, 180, 84, 0.5); }
.co.added > b { color: #a9efff; border-color: rgba(94, 224, 255, 0.5); }
.co.deleted > b { color: #ffb3c4; border-color: rgba(255, 107, 139, 0.5); }
.co.level > b { color: var(--accent); }
/* 부재 곁의 짧은 라벨 — 좁은 화면, 또는 사다리 자리가 전혀 없을 때 */
.co--mini > span, .co--mini > em { display: none; }
.co--mini > b { border-bottom: 1px solid; border-radius: 3px; margin: 0; font-size: 10px; padding: 4px 6px; }
/* 변경 부재 라벨 — 한 줄(종류 | 위치). 이유 줄은 그 밑에서 Reason 진행에 맞춰 높이가 연속으로 자란다(페이지가 em 높이를 쓴다).
   종류 칸은 글자만큼(max-content), 남는 폭은 위치 칸이 가져간다(r14 P2 U7) — auto auto 는 이유 줄이 종류+위치보다 넓을 때 남는 폭을 두 칸에 나눠
   '⑤ [신설] C2 │빈칸 30 px│ X8/Y4' 처럼 칸막이가 엉뚱한 자리에 섰다(1512 b 129 px ↔ 필요 99 px). 라벨 전체 폭(= 사다리 배치 입력)은 그대로 */
.co.row { grid-template-columns: max-content 1fr; }
.co.row > b { border-bottom-width: 1px; border-bottom-style: solid; border-right-width: 0; border-radius: 3px 0 0 3px; }
.co.row > span { border-radius: 0 3px 3px 0; }
.co.row.cmp > b, .co.row.cmp > span { padding: 0.36em 0.62em; }   /* 넘칠 때 촘촘하게 — 이유 줄이 없는 행부터 */
.co.row.cmp em i { padding: 0.32em 0.62em 0.4em; }
.co.row.why > b { border-radius: 3px 0 0 0; }
.co.row.why > span { border-radius: 0 3px 0 0; }
.co em { display: block; grid-column: 1 / -1; height: 0; overflow: hidden; font-style: normal; opacity: 0; }
.co em i { display: block; font-style: normal; padding: 0.46em 0.82em 0.54em; background: rgba(8, 11, 18, 0.9); border: 1px solid rgba(160, 190, 240, 0.22);
  border-top: 0; border-radius: 0 0 3px 3px; font-family: var(--font-sans); font-size: calc(var(--ui) + 1px); line-height: 1.1; color: #dfe7f5; letter-spacing: 0; }
.co.row.co--mini > b { border-right-width: 1px; border-radius: 3px; }
/* 열 판(r15 D1) — 계단 코어를 피할 수 없는 라벨 열 뒤에 깐다(행 사이 틈으로 코어 해칭이 비치지 않게 — 행이 표가 된다). 행 조각과 틈 조각으로 나뉘어 선다(페이지 platePaint).
   색·알파는 행 바탕(0.9)과 같은 계열 — 겹쳐도 행이 도드라지지 않게 조금 푸르게. 자리·크기·모서리·불투명도는 페이지가 쓴다 */
.co-plate { position: absolute; left: 0; top: 0; width: 0; height: 0; background: rgba(10, 14, 24, 0.9); border-radius: 6px; opacity: 0; pointer-events: none; will-change: transform, opacity; }
/* 번호 핀 — 평면 라벨 · 리포트 카드의 이유 줄 · 검토 목록이 같은 번호로 짝을 이룬다. 축선 번호(테두리만 있는 동그라미)와 헷갈리지 않게 속이 찬 원판 */
.pin { display: inline-block; min-width: 1.46em; height: 1.46em; margin: -0.23em 0.6em -0.23em 0; padding: 0 0.28em; border-radius: 0.73em; vertical-align: middle;
  font: 600 0.86em/1.46em var(--font-mono); font-style: normal; letter-spacing: 0; text-align: center; color: #06080d; background: var(--accent); text-shadow: none; }
.co.moved .pin, .pin.moved { background: var(--moved); }
.co.added .pin, .pin.added { background: var(--added); }
.co.deleted .pin, .pin.deleted { background: var(--deleted); }
/* 이동 치수선 — 옛 축과 새 축 사이(치수보조선 둘 · 45° 틱 · mm). 인출선과 같은 SVG 한 장 */
.leaders .dims path { fill: none; stroke: rgba(255, 200, 130, 0.95); stroke-width: 1.2; stroke-linecap: square; }
.leaders .dims path.halo { stroke: rgba(5, 7, 12, 0.7); stroke-width: 3.4; }   /* 밝은 보·기둥 위를 지날 때 선이 묻히지 않게 */
.leaders .dims text { font-family: var(--font-mono); font-size: calc(var(--ui) * 0.95); font-weight: 500; letter-spacing: 0.02em; fill: #ffd28a;
  paint-order: stroke; stroke: rgba(5, 7, 12, 0.88); stroke-width: 3.2px; stroke-linejoin: round; }
/* 표제 · 리포트 카드 — 정지 컷의 도면 표제(층 구조평면도 · 1:200 · REV.C, 방위표, 투영 축척 막대)가 분석·리포트 단계에서 아래로 자란다.
   계기판과 같은 판(블러 없음). 카피 층 안(가림막 위)에 둔다. 자리·자람·불투명도는 페이지가 준다 — 꺼진 동안은 display 로 뺀다(부모를 숨기면 함께 숨게) */
.dcard { position: absolute; z-index: 1; left: 0; top: 0; width: calc(var(--ui) * 25); padding: 10px 14px 11px; opacity: 0; display: none; pointer-events: none;
  background: rgba(8, 11, 18, 0.9); border: 1px solid var(--plate-edge); border-radius: var(--radius-xs); will-change: transform, opacity;
  font-family: var(--font-mono); font-size: var(--ui); line-height: 1.25; color: var(--text-secondary); }
.dcard.on { display: block; }
.dcard.wide { width: calc(var(--ui) * 30); }
.dcard .ko { letter-spacing: 0.02em; }
.dc-t { display: flex; align-items: center; gap: 10px; }
.dc-n { width: 34px; height: 34px; flex: none; overflow: visible; }
.dc-n circle { fill: none; stroke: rgba(170, 178, 192, 0.5); stroke-width: 1; }
.dc-n path { fill: var(--text-primary); }
.dc-n text { font: 600 7px/1 var(--font-mono); fill: var(--text-secondary); text-anchor: middle; dominant-baseline: central; }
.dc-tt b { display: block; font: 600 calc(var(--ui) * 1.2)/1.2 var(--font-sans); letter-spacing: -0.01em; color: var(--text-primary); }
.dc-tt b .ko { letter-spacing: -0.01em; }
.dc-tt > span { display: block; margin-top: 4px; font-size: calc(var(--ui) * 0.9); letter-spacing: 0.14em; color: var(--text-tertiary); }
.dc-sc { display: flex; align-items: center; gap: 8px; margin: 8px 0 0 44px; font-size: calc(var(--ui) * 0.86); letter-spacing: 0.08em; color: var(--text-tertiary); }
.dc-sc svg { display: block; overflow: visible; }
.dc-sc .f { fill: rgba(223, 231, 245, 0.8); }
.dc-sc .e { fill: none; stroke: rgba(223, 231, 245, 0.8); stroke-width: 1; }
.dc-sec { height: 0; overflow: hidden; opacity: 0; }
.dc-in { padding-top: 9px; }
.dc-in::before { content: ''; display: block; height: 1px; margin-bottom: 9px; background: rgba(255, 255, 255, 0.08); }
.dc-body ol { list-style: none; display: grid; gap: 7px; }
.dc-body li { display: grid; grid-template-columns: auto auto 1fr; align-items: baseline; }
.dc-body li > b { font-weight: 600; letter-spacing: 0.06em; color: #cfe0ff; margin-right: 0.7em; white-space: nowrap; }
.dc-body li > span { font: 400 calc(var(--ui) + 1px)/1.3 var(--font-sans); color: #dfe7f5; letter-spacing: 0; }
.dc-body li.more { display: block; }
.dc-body li.more > span { font-family: var(--font-mono); font-size: calc(var(--ui) * 0.9); color: var(--text-tertiary); }
.dc-sum p + p { margin-top: 7px; }
.dc-k { font-size: calc(var(--ui) * 0.9); letter-spacing: 0.16em; color: var(--accent); }
.dc-cnt { display: flex; flex-wrap: wrap; gap: 6px 16px; color: var(--text-secondary); }
.dc-cnt > span::before { content: ''; display: inline-block; width: 8px; height: 8px; margin-right: 7px; border-radius: 1px; background: currentColor; }
.dc-cnt > .moved::before { background: var(--moved); }
.dc-cnt > .added::before { background: var(--added); }
.dc-cnt > .deleted::before { background: var(--deleted); }
.dc-cnt b, .dc-rv b { font-weight: 600; color: var(--text-primary); }
.dc-pins { margin-left: 10px; }
.dc-pins .pin { margin: -0.23em 0.32em -0.23em 0; }
/* 촘촘한 카드 — 자리가 모자란 화면(좁은 화면 등): 표제 한 줄(축척은 오른쪽), 이유는 한 줄 자막, 요약은 한 줄 */
.dcard.compact { padding: 8px 12px 9px; }
.dcard.compact .dc-t { gap: 8px; }
.dcard.compact .dc-n { width: 24px; height: 24px; }
.dcard.compact .dc-tt { display: flex; align-items: baseline; gap: 10px; white-space: nowrap; }
.dcard.compact .dc-tt > span { margin-top: 0; }
.dcard.compact .dc-sc { position: absolute; right: 12px; top: 15px; margin: 0; }
.dcard.compact .dc-k, .dcard.compact .dc-pins { display: none; }
.dcard.compact .dc-in { padding-top: 7px; }
.dcard.compact .dc-in::before { margin-bottom: 7px; }
.dcard.compact .dc-sum .dc-in { display: flex; flex-wrap: wrap; align-items: center; gap: 0 16px; }
.dcard.compact .dc-sum .dc-in::before { flex-basis: 100%; }
.dcard.compact .dc-sum p + p { margin-top: 0; }
.dcard.compact .dc-body li > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dcard.swap .dc-body { position: absolute; left: 12px; right: 12px; top: 12px; height: auto !important; }
.dcard.swap .dc-body .dc-in { padding-top: 0; }
.dcard.swap .dc-body .dc-in::before { display: none; }
.dcard.cols2 .dc-body ol { grid-template-columns: 1fr 1fr; column-gap: 20px; }
/* 리포트 표제란(r15 P2 D2) — 넓은 화면 리포트 단계: 머리 한 줄(방위표 · 표제 · 축척 막대) + 변경 일람 8행(핀 순) + 꼬리 한 줄.
   행은 한 표 — 열(핀 · 종류 · 부재 · Δ · 이유/위치 · 검토)이 행마다 맞게 subgrid. 촘촘한 판(.cmp)은 종류 칸을 빼고(핀 색이 종류), .nodl 은 Δ·검토 칸도 뺀다(검토는 앰버 테와 꼬리 목록).
   검토 필요 행 — 앰버 테(왼쪽 2 px)와 옅은 바탕, '검토 필요' 칩(--rv 0 → 1, 핀 순으로 차례로 — 페이지가 스크롤로 준다) */
.dcard.rep { padding: 10px 14px 10px; --rh: calc(var(--ui) * 2.1); --rvc: 255, 209, 102; }
.dcard.rep .dc-n { width: 30px; height: 30px; }
.dcard.rep .dc-sc { position: absolute; right: 14px; top: 16px; margin: 0; }
.dcard.rep .dc-k { display: none; }
.dcard.rep .dc-in { padding-top: 7px; }
.dcard.rep .dc-in::before { margin-bottom: 7px; }
/* 낮은 화면(1024–1366 × 720–768)의 촘촘한 판 — 머리 한 줄(방위표 22 px · 표제와 축척 표기를 한 줄로), 칸막이 여백 5 px */
.dcard.rep.tt { padding: 7px 12px 7px; }
.dcard.rep.tt .dc-n { width: 22px; height: 22px; }
.dcard.rep.tt .dc-tt { display: flex; align-items: baseline; gap: 10px; white-space: nowrap; }
.dcard.rep.tt .dc-tt > span { margin-top: 0; }
.dcard.rep.tt .dc-sc { top: 13px; right: 12px; }
.dcard.rep.tt .dc-in { padding-top: 5px; }
.dcard.rep.tt .dc-in::before { margin-bottom: 5px; }
.dcard.rep .dc-body ol { display: grid; grid-template-columns: auto auto auto auto minmax(0, 1fr) auto; column-gap: 0.75em; row-gap: 0; }
.dcard.rep.cmp .dc-body ol { grid-template-columns: auto auto auto minmax(0, 1fr) auto; }
.dcard.rep.cmp.nodl .dc-body ol { grid-template-columns: auto auto minmax(0, 1fr); }
.dcard.rep .dc-body li { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: center; min-height: var(--rh); white-space: nowrap;
  margin: 0 -8px; padding: 0 8px; border-radius: 2px; background: rgba(var(--rvc), calc(0.07 * var(--rv, 0))); box-shadow: inset 2px 0 0 rgba(var(--rvc), calc(0.9 * var(--rv, 0))); }
.dcard.rep .dc-body li .pin { margin: 0; }
.dcard.rep .kd { font-style: normal; font-size: calc(var(--ui) * 0.86); letter-spacing: 0.04em; line-height: 1; padding: 0.24em 0.45em 0.28em; border: 1px solid currentColor; border-radius: 2px; justify-self: start; }
.dcard.rep li.moved .kd { color: #ffd28a; }
.dcard.rep li.added .kd { color: #a9efff; }
.dcard.rep li.deleted .kd { color: #ffb3c4; }
.dcard.rep .dc-body li > b { margin: 0; }
.dcard.rep .dl { font: 400 calc(var(--ui) * 0.95)/1.3 var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: 0.02em; color: #ffd28a; }   /* 모노 — 아래 li > span(산스) 규칙을 받으면 'Δ' 때문에 산스 그리스 문자 글꼴을 새로 받아(글꼴 사건) 미리 구운 배치가 버려졌다 */
.dcard.rep .wy { font: 400 calc(var(--ui) + 1px)/1.3 var(--font-sans); color: #dfe7f5; letter-spacing: 0; overflow: hidden; }
.dcard.rep .wy.tg { font: 400 calc(var(--ui) * 0.95)/1.3 var(--font-mono); letter-spacing: 0.02em; color: var(--text-tertiary); }
.dcard.rep .rvf { font-style: normal; font-size: calc(var(--ui) * 0.82); letter-spacing: 0.02em; line-height: 1; color: rgb(var(--rvc)); opacity: var(--rv, 0); justify-self: end; }
.dcard.rep .rvf:not(:empty) { padding: 0.26em 0.45em 0.3em; border: 1px solid rgba(var(--rvc), 0.6); border-radius: 2px; }
.dcard.rep.cmp .kd, .dcard.rep.nodl .dl, .dcard.rep.nodl .rvf { display: none; }
.dcard.rep .dc-sum .dc-in { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 20px; }
.dcard.rep .dc-sum .dc-in::before { flex-basis: 100%; }
.dcard.rep .dc-sum p + p { margin-top: 0; }
.dcard.rep .dc-cnt b { display: inline-block; min-width: 1ch; text-align: right; font-variant-numeric: tabular-nums; }
/* 자막 줄이 가리키는 라벨 */
.co.hot > b { box-shadow: 0 0 0 1px currentColor, 0 0 14px rgba(255, 255, 255, 0.16); }

.rail { position: relative; z-index: 0; height: 1200vh; pointer-events: none; }

.note { position: fixed; left: var(--gutter); bottom: 20px; z-index: 6; font-family: var(--font-mono); font-size: 11px; text-shadow: var(--halo);
  letter-spacing: 0.06em; color: var(--text-tertiary); pointer-events: none; }
.note b { color: var(--text-secondary); font-weight: 500; }

/* 좁은 화면 단계 막대 — 데스크톱은 계기판 칩이 맡는다 */
.steps { display: none; }
/* 좁은 화면 단계 막대의 받침 — 머리띠 그라데이션이 끝나는 자리라 켜진 칸 이름·칸이 밝은 보 위에 얹혔다(390 에서 배경 p90 177). 화면 폭 전체의 얇은 어두운 띠 */
@media (max-width: 820px) {
  .steps::before { content: ''; position: absolute; left: -20px; right: -20px; top: -12px; bottom: -14px; z-index: -1; pointer-events: none;
    background: linear-gradient(180deg, rgba(5, 7, 12, calc(0.8 * var(--dk, 1))) 0%, rgba(5, 7, 12, calc(0.8 * var(--dk, 1))) 62%, rgba(5, 7, 12, 0) 100%);
    -webkit-mask-image: var(--dither, none); mask-image: var(--dither, none);
    -webkit-mask-size: var(--dpx, 64px) var(--dpx, 64px); mask-size: var(--dpx, 64px) var(--dpx, 64px); }
}

/* ── 좁은 화면 ── 카피는 아래로, 계기판·색인은 접는다 */
@media (max-width: 820px) {
  .hudbox, .index, .copies::after { display: none; }
  .copies::before { inset: auto 0 0 0; width: auto; height: 62vh;
    background: linear-gradient(0deg, rgba(5, 7, 12, calc(0.9 * var(--dk, 1))) 0%, rgba(5, 7, 12, calc(0.6 * var(--dk, 1))) 55%, rgba(5, 7, 12, 0) 100%); }
  .copy { left: 22px; right: 22px; width: auto; top: auto; bottom: 64px; }
  .copy .k { position: static; margin-bottom: 12px; }
  .copy h2 { font-size: 28px; margin-bottom: 12px; }
  .copy p { font-size: 14.5px; }
  .copy br.d { display: none; }
  .hint { bottom: 30px; font-size: 10px; }
  .sheet { display: none; }
  /* 하단 노트 — 좁은 화면은 카피가 아래 띠를 쓰므로 늘 숨기고, (R4a) 장면이 끝난 뒤(마감 머무름 · 푸터 쪽 — 마감 카피 아래 빈 띠) 가운데 아래에 보여 준다(페이지가 html.note-on).
     예전엔 막간 로고가 선 동안 한 번 보였다 */
  .note { left: 20px; right: 20px; bottom: 26px; z-index: 13; text-align: center; opacity: 0; transition: opacity 0.4s; }
  html.note-on .note { opacity: 1; }
  .note .note-long { display: none; }   /* 좁은 화면 노트는 짧게 — '프로토타입 · 데이터는 모두 예시(생성값)입니다' 한 줄 */
  .top { gap: 14px; padding: 18px 20px; }
  .top .tag { display: none; }
  /* 단계 막대 — 머리띠 아래 한 줄: 칸(2 px) · 켜진 칸 이름 */
  .steps { display: flex; position: fixed; top: 54px; left: 20px; right: 20px; z-index: 10; gap: 12px; align-items: center; pointer-events: none;
    opacity: 0; transition: opacity 0.4s; font: 500 11px/1 var(--font-mono); letter-spacing: 0.06em; color: var(--text-secondary); text-shadow: var(--halo); }
  .steps.on { opacity: 1; }
  .steps ol { display: flex; gap: 3px; flex: 1; list-style: none; }
  .steps li { flex: 1; height: 2px; border-radius: 1px; background: rgba(255, 255, 255, 0.2); transition: background 0.3s; }
  .steps li[hidden] { display: none; }
  .steps li.done { background: rgba(var(--accent-rgb), 0.55); }
  .steps li.now { background: var(--accent); box-shadow: 0 0 8px rgba(var(--accent-rgb), 0.8); }
  .steps-lab { flex: none; min-width: 7em; text-align: right; }
}

@media (prefers-reduced-motion: reduce) {
  .boot-mark { animation: none; clip-path: none; }
  .top .tag i, .hint::after { animation: none; }
  .boot { transition-duration: 0.25s, 0s; transition-delay: 0s, 0.25s; }   /* 막이 빨리 걷혀 트리에서도 빨리 빠진다 */
  /* 카피는 떠오르지 않고 불투명도로만(스크롤 스크럽 그대로), 헤드라인 줄 상승도 없다 */
  .copy > *, .copy h2 .ln > *, .copy > .k { transform: none; }   /* .copy > .k — 위 키커 식(r14 P2 U8)이 더 구체적이라 따로 */
  .skip, .legal, .btn, .btn-arrow { transition: none; }
}

/* 인트로 가림막 — 로고가 먼저 서고, 날아가 앉을 때 걷힌다. 색인 딥·움직임 줄이기의 비행 교차에도 쓴다 */
.veil { position: fixed; inset: 0; z-index: 15; background: var(--bg); pointer-events: none; transition: opacity 1.4s var(--ease-io); }
.veil.out { opacity: 0; }
html.no-3d .veil { display: none; }

/* ── 폰 목업 ── 3D 폰 화면 자리에서 시작해 옆으로 비켜선다. 기준 크기 390×844 를 transform 으로만 움직인다.
   어둠막 가운데(--mx·--my)는 비켜선 폰 자리를 페이지가 준다 */
.dim { position: fixed; inset: 0; z-index: 3; pointer-events: none; opacity: 0;
  background: radial-gradient(120% 90% at var(--mx, 66%) var(--my, 50%), rgba(5, 7, 12, calc(0.55 * var(--dk, 1))), rgba(5, 7, 12, calc(0.92 * var(--dk, 1)))); }
.mock { position: fixed; left: 0; top: 0; z-index: 4; margin: 0; width: 390px; height: 844px; transform-origin: 0 0;
  pointer-events: none; visibility: hidden; will-change: transform;
  --bx: 17.2; --by: 9.02; --gx: 8.3; --gy: 8.35; --rb: 66.9; --rg: 58.1; --rs: 48; --rim: #020203; --glass: #000001; --lift: 0; --off: 0; }
/* 폰 틀 — 두께·반경·색은 3D 폰 기하(mm)를 기준 폭 390 px 로 환산한 값을 페이지가 준다(몸체 테 --bx·--by, 유리 테 --gx·--gy).
   들어 올린다고 두꺼워지지 않는다(예전 8.5 → 13 px 는 물리적으로 틀렸고, 인계 순간 외곽선이 6–9 px 튀었다).
   떠오른 정도 --lift(0..1)는 빛만 바꾼다 — 책상 위(0)에선 3D 폰이 그려지는 그대로, 들수록 금속 테가 스탠드 빛을 받고
   그림자는 스탠드 반대쪽(오른쪽 아래)으로 밀린다. 3D 폰이 사라지는 m0 부터 다시 나타나는 m3 까지 틀은 늘 보인다.
   왼쪽 변 안쪽 약 3 px 밝은 띠 — 3D 몸체 베벨이 스탠드 빛을 받아 바깥 2–3 px 가 밝다(단면 채널 합 150→123→137 → 85). 평평한 DOM 테(79)는
   인계 순간 테두리 명암이 바뀌어 보였다(Ua-rim 0.5975 → 0.5985) */
.mock-bezel { position: absolute; left: calc(var(--bx) * -1px); right: calc(var(--bx) * -1px); top: calc(var(--by) * -1px); bottom: calc(var(--by) * -1px);
  border-radius: calc(var(--rb) * 1px); background: var(--rim); opacity: 0;
  box-shadow: inset 4.5px 0 2px -1.5px rgba(255, 255, 255, 0.085), inset 0 0 0 1px rgba(255, 255, 255, calc(0.03 + 0.13 * var(--lift))),
    calc(var(--lift) * 16px) calc(2px + var(--lift) * 46px) 110px calc(var(--lift) * -30px) rgba(0, 0, 0, calc(0.17 + 0.78 * var(--lift))),
    0 0 140px -30px rgba(255, 176, 96, calc(0.28 * var(--lift))); }
.mock-bezel::before { content: ''; position: absolute; inset: 0; border-radius: inherit; opacity: var(--lift);
  background: linear-gradient(155deg, rgba(255, 255, 255, 0.17) 0%, rgba(255, 255, 255, 0.05) 22%, rgba(255, 255, 255, 0) 58%, rgba(255, 255, 255, 0.08) 100%); }
.mock-glass { position: absolute; left: calc(var(--gx) * -1px); right: calc(var(--gx) * -1px); top: calc(var(--gy) * -1px); bottom: calc(var(--gy) * -1px);
  border-radius: calc(var(--rg) * 1px); opacity: 0;
  background: linear-gradient(180deg, rgba(255, 255, 255, calc(0.025 * var(--lift))), rgba(255, 255, 255, 0) 12%), var(--glass); }
/* 화면 바탕(r11 pMock E4) — 유리색 위에 아래 .mock-off 와 같은 반사 띠를 꺼진 정도(--off = 1 − b, 페이지가 준다)만큼. 안쪽은 화면 캔버스가 덮는다.
   둥근 클립 가장자리 한 화소에선 바탕·캔버스·반사 판의 안티에일리어싱이 겹쳐, 바탕이 검정이면 인계 순간 띠 자리 양옆에 어두운 세로 선이 비쳤다(1512@2 Δ>24 211 px → 0) */
.mock-screen { position: absolute; inset: 0; border-radius: calc(var(--rs) * 1px); overflow: hidden;
  background: linear-gradient(180deg, rgba(255, 236, 200, calc(0.070 * var(--off))) 0px, rgba(255, 236, 200, calc(0.104 * var(--off))) 16px, rgba(255, 236, 200, calc(0.139 * var(--off))) 44px,
    rgba(255, 236, 200, calc(0.150 * var(--off))) 74px, rgba(255, 236, 200, calc(0.095 * var(--off))) 110px, rgba(255, 236, 200, calc(0.013 * var(--off))) 136px,
    rgba(255, 236, 200, 0) 144px) 0 0 / 100% 144px no-repeat, var(--glass); }
.mock-view { position: absolute; inset: 0; z-index: 0; transform-origin: 50% 50%; }   /* 자체 쌓임 맥락 — 캔버스 z 가 터치 표시·백라이트 판 위로 새지 않게 */
/* 앱 화면 캔버스 두 장 — 지금 화면·다음 화면. 탭 전환은 한 프레임에 바꾸고, 단어장만 transform 으로 밀려 들어온다(불투명도 교차 없음) */
.mock-view canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; visibility: hidden; }
/* 터치 표시 — 화면 녹화의 '터치 보기' 원(44 pt) */
.mock-tap { position: absolute; z-index: 1; left: 0; top: 0; width: 44px; height: 44px; margin: -22px 0 0 -22px; border-radius: 50%; opacity: 0;
  background: rgba(226, 231, 240, 0.5); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.35), 0 2px 10px rgba(0, 0, 0, 0.3); }
/* 시스템 표시 층(r11 pMock E1) — 상태 막대 · 홈 인디케이터 캔버스(페이지가 .mock-view 다음에 끼우고 자리·높이를 준다). push 중에도 제자리 */
.mock-sys { position: absolute; left: 0; width: 390px; z-index: 0; display: block; pointer-events: none; }
/* 백라이트 — 화면(.mock-view · .mock-sys) 자체의 밝기(filter: brightness)로 켜고 끈다. 예전 꺼진 유리색 덮개 판은 화면과 같은 둥근 클립 가장자리에 겹쳐
   안티에일리어싱이 새어 켜지고 잠길 때 가장자리에 밝은 1 px 선이 비쳤다 — 불투명 덮개는 쓰지 않는다.
   꺼진 유리(r11 pMock E4) — 백라이트가 꺼진 만큼(불투명도 1 − b, 페이지 mockPaint) 책상 위 3D 폰 유리와 같은 그림을 깐다. 인계 순간(b = 0)의 두 그림이 같아야
   3D 반사를 미리 끄지 않고도 한 걸음에 튀지 않는다(r10 은 반사를 꺼서 정수리 hold 에 순흑 판 — 재감사 회귀 #7).
   · 반사 띠 — 정수리 자세(카메라가 폰 바로 위 · 세계 자세는 폭과 무관, 반사는 표시 영역 정규 좌표로 1512@2 · 390m 이 0.5 단계 안에서 같다)에서
     PHONE_ENV 0.5 로 그린 유리를 390×844 로 되짚어 잰 행 평균: 위 18 → 74 px 에서 (37, 35, 30) → 144 px 에서 0. 반투명 따뜻한 회색 7 정지점(r12 fxRoom 재측정 — 오차 ≤ 1.31 단계, 1512@2 · 390m).
     반투명이라 켜지는 화면을 가리지 않고 옅은 비침으로 걷힌다. 빛·PHONE_ENV·카메라 정수리 자세를 바꾸면 다시 잰다
   · 아일랜드(r12 fxRoom) — 3D 아일랜드 메시(19.885 × 5.785 mm · 양 끝 반원 · 중심이 표시 영역 윗변 아래 4.881 mm) × 5.532 px/mm = 앱 상태 막대의 아일랜드
     (library-screen.js drawStatus 110 × 32 · 반지름 16 · 중심 27 px)와 같은 자리·모양. 그래서 띠와 같이(1 − b) 걷히면 되고 따로 일찍 걷지 않는다 —
     예전(중심 57 px)은 앱 아일랜드(27 px)와 자리가 달라 --isl 로 b 0.15 에서 걷었고, 그 전까지 깨어나는 화면에 아일랜드가 둘 보였다(재감사 회귀 #5) */
.mock-off { position: absolute; z-index: 2; inset: 0; opacity: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 236, 200, 0.070) 0px, rgba(255, 236, 200, 0.104) 16px, rgba(255, 236, 200, 0.139) 44px, rgba(255, 236, 200, 0.150) 74px,
    rgba(255, 236, 200, 0.095) 110px, rgba(255, 236, 200, 0.013) 136px, rgba(255, 236, 200, 0) 144px) 0 0 / 100% 144px no-repeat; }
.mock-off::before { content: ''; position: absolute; left: 140px; top: 11px; width: 110px; height: 32px; border-radius: 16px; background: #000; }

/* 좁은 화면 — 앱 장 카피는 화면 아래 26 px(하단 노트 자리 — 노트는 이 동안 숨어 있다)까지 내려 목업에 자리를 준다. 목업 높이 0.61H → 0.64H 가까이 */
@media (max-width: 820px) { .copy--app { bottom: 26px; } }

/* 도면 축선 번호 — 동그라미 안 X1…/Y1…. 자리는 페이지가 화면 가장자리(안전 사각형)에 꽂는다 */
.bubbles { position: fixed; inset: 0; z-index: 3; pointer-events: none; }
.bubbles i { position: absolute; left: 0; top: 0; width: 26px; height: 26px; margin: -13px 0 0 -13px; border-radius: 50%;
  border: 1px solid rgba(122, 165, 255, 0.75); background: rgba(8, 11, 18, 0.78); display: grid; place-items: center;
  font: 500 9.5px/1 var(--font-mono); font-style: normal; letter-spacing: 0.02em; color: #bcd0f5; opacity: 0; }

@media (max-width: 820px) {
  .bubbles { display: none; }
  .dim { background: rgba(5, 7, 12, calc(0.8 * var(--dk, 1))); }
}

/* 하단 노트 — 카메라가 폰 정수리에 내려와 있는 동안(목업 어둠막이 덮기 전·걷힌 뒤) 밝은 공책·노트북 위에 얹혀 읽히지 않았다(2560 대비 1.04).
   그 동안만 0.2 초에 걸쳐 숨긴다(페이지가 html.note-off 를 준다). 좁은 화면은 원래 숨겨 두므로 넓은 화면만 */
@media (min-width: 821px) {
  .note { transition: opacity 0.2s linear, color 0.4s linear; }
  .note b { transition: color 0.4s linear; }
  html.note-off .note { opacity: 0; }
  /* 폰에서 빠져나와 지붕 위 3/4 로 오르는 동안(CAM.exit → 장면 끝)은 노트 자리가 광장 가로등 웅덩이·조명 기둥 위다
     (r9 병합본 배경 중앙값 대비: 2560 4.0–4.1 · 1920 3.85 · 1512 3.65, t 0.92–0.97). 판을 깔지 않고 그 동안만 글자를 한 단계 밝힌다 —
     노트 본문 tertiary → secondary, 강조(예시(생성값))는 primary 로 올려 위계를 지킨다. 페이지가 html.note-hi 를 준다(r9 fixP) */
  html.note-hi .note { color: var(--text-secondary); }
  html.note-hi .note b { color: var(--text-primary); }
  /* 노트 받침(r14 P2 U12) — 글자를 밝혀도 히어로(가로등이 비추는 길)·지붕 위 3/4(광장 가로등)에서 첫 어절 '프로토타입' 뒤가 밝아
     화소별 대비 p10 이 2.06(2560 t 0.02)·2.13(1920)·2.54(1024 t 0.95)까지 떨어졌다. 글자 받침(text-shadow)은 번져 보여, 글줄 둘레에만 옅은 판 한 장.
     가로는 양 끝 여유(--bf) 동안, 세로는 위아래 여유 동안 걷힌다(두 그라데이션이 곱해져 모서리도 둥글다). 노트와 함께 켜진다(html.note-hi · 0.4 초).
     세기 --nba 0.3 — p10 3.0 을 넘기는 가장 옅은 몫에 여유(2560 t 0.02 에서 0.25 는 3.12 · 0.3 은 3.41 — 0.5 는 4.8 까지 눌러 판이 보였다).
     자리·크기(--nbx·--nby·--nbw·--nbh)는 페이지가 노트 글줄 상자에서 준다(library-page.js paint) */
  .note-bed { position: fixed; left: 0; top: 0; z-index: 6; pointer-events: none; opacity: 0; transition: opacity 0.4s linear;
    width: var(--nbw, 0px); height: var(--nbh, 0px); transform: translate(var(--nbx, 0px), var(--nby, 0px)); --bf: 26px; --nba: 0.3;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--bf), #000 calc(100% - var(--bf)), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 var(--bf), #000 calc(100% - var(--bf)), transparent 100%);
    background: linear-gradient(180deg, rgba(5, 7, 12, 0) 0, rgba(5, 7, 12, var(--nba)) 30%, rgba(5, 7, 12, var(--nba)) 70%, rgba(5, 7, 12, 0) 100%); }
  html.note-hi .note-bed { opacity: 1; }
}
.note-bed { display: none; }   /* 좁은 화면(노트는 장면이 끝난 뒤에만)·문서 모드는 받침이 없다 — 넓은 화면(3D)만 아래 줄이 켠다 */
@media (min-width: 821px) { html:not(.fb):not(.no-js) .note-bed { display: block; } }
