/* monday9.com — NOIR v2.0 스타일시트.
   값은 css/noir.css(@designer tokens-v2) 토큰만 사용.
   느와르 3규칙: ① 광원 하나 ② 골드는 선·라벨·소면적(밴드 1개 예외) ③ 움직임은 빛의 변화로.
   스크롤 모션 3종만: 배경 패럴랙스 / 순차 페이드업 / 룰 드로잉. 하이재킹·핀고정·스케일 금지. */

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

ul, ol { list-style: none; }
img { display: block; max-width: 100%; }

a { color: inherit; text-decoration: none; }

/* ---------- 상단 바 ---------- */
.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 40;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-block: var(--sp-2);   /* 좌우는 공통 컨테이너 규칙 */
  background: linear-gradient(180deg, rgba(7, 8, 11, 0.9) 0%, transparent 100%);
}

.wordmark {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  font-weight: 600;
  white-space: nowrap;   /* 390px에서 2행으로 접히는 것 방지 */
}

.topnav { display: flex; gap: var(--sp-4); }

.topnav a {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  color: var(--fg-mute);
  transition: color var(--dur-fast) var(--ease);
}

.topnav a:hover { color: var(--gold); }

/* ---------- 공통 ---------- */
/* 컨테이너 정렬: 좌우 패딩을 "여백 vs 컨테이너 밖 잔여폭" 중 큰 쪽으로 잡는다.
   면(背)은 화면 끝까지 두고 콘텐츠만 중앙으로 모으므로, 풀블리드 띠(FIGURES)와
   중앙 정렬 섹션이 같은 규칙 하나로 공존한다(v2.1 --container: 1280px).
   모바일은 컨테이너가 화면보다 넓어 max()가 --margin을 택하므로 자동 무효. */
.topbar, .sec, .path, .hero, .band {
  padding-inline: max(var(--margin), calc((100% - var(--container)) / 2));
}

/* §X — 본문 섹션만 컨테이너에 1800px 상한을 주고 중앙 정렬한다(사용자 지시).
   ※ 이 1800은 §H-5의 카드 상한을 "폐기"한 게 아니라 .cards → 컨테이너로 "이관"한 것이다.
      숫자는 그대로이고 누가 그 상한을 갖느냐만 바뀌었다 — §H-5를 읽는 사람이 사라진 줄 알고
      .cards에 되살리면 카드가 컨테이너 안에서 다시 좁아진다.
   상한을 카드가 아니라 컨테이너에 두는 이유: 카드만 중앙 정렬하면 라벨·룰이 좌단에 남아
      새 어긋남이 생긴다. 컨테이너를 좁히면 섹션 안의 모든 것이 함께 줄고 함께 중앙에 온다
      (sec-head의 rule-gold·label, cards, books, intro, contact, 그리고 요구하신 "라인"인
       .card 상단 룰과 섹션 구분선까지 별도 선언 없이 따라온다).
   헤더·히어로는 제외한다 — 히어로 구도(§H-1·§M)는 사용자가 화면을 첨부해 고정한 항목이고,
      본문만 옮기면 1957px 이상에서만 기준선이 갈린다(1920 이하는 변화 0).
   min()이 전환점을 스스로 잡으므로 미디어쿼리가 필요 없다. */
.sec, .path, .band {
  padding-inline: max(var(--margin), calc((100% - min(92vw, 1800px)) / 2));
}

.sec { padding-block: var(--sp-8); }

/* §S — 첫 섹션 상단 235px → 72px. §L-3에서 1.4배를 넣은 이유는 "100svh 풀블리드 히어로 바로 아래에
   작은 라벨이 놓여 시각 무게에 눌린다"였는데, §P·§R로 히어로가 92%에서 끝나고 다음 섹션이 이미
   화면에 보인다. 전환이 시각적으로 예고된 상태라 낙차를 여백으로 만들 필요가 없어졌다 —
   판단이 바뀐 게 아니라 전제가 사라진 것이다. */
.hero-track + .sec,
.hero-track + .portrait-band + .sec { padding-top: var(--sp-7); }   /* §S as-built 112px — 235px에서 −52%. 72px 안은 폐기(문서·코드 통일).
   SPEC-C: 인물 밴드(모바일 전용, 데스크톱 display:none)가 DOM상 히어로와 첫 섹션 사이에 끼므로
   — display:none 이어도 인접 형제 결합자는 DOM 기준이다 — 선택자를 확장해 데스크톱 간격을 보존한다.
   C-v2: 히어로가 .hero-track 래퍼(데스크톱 무스타일 = 렌더 불변)에 감싸여 사슬 첫 항이 트랙이 됐다. */

.label {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-mute);
}

.label.gold { color: var(--gold); }

/* 라벨 앞 짧은 골드 룰 — 레퍼런스 시그니처, 등장 시 좌→우 드로잉 */
.rule-gold {
  display: block;
  width: 56px;
  height: var(--rule-mark);
  background: var(--gold);
  margin-bottom: var(--sp-2);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur) var(--ease);
}

.reveal.in .rule-gold { transform: scaleX(1); }

.h2 {
  font-family: var(--font-display);
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  margin-top: var(--sp-2);
}

.h3 {
  font-family: var(--font-display);
  font-size: var(--fs-card);
  font-weight: var(--fw-display);
  line-height: var(--lh-tight);
}

.body { font-size: var(--fs-body); line-height: var(--lh-body); max-width: 64ch; }
.body + .body { margin-top: var(--sp-3); }
.caption { font-size: var(--fs-label); letter-spacing: var(--track-label); text-transform: uppercase; color: var(--fg-dim); margin-top: var(--sp-3); }

/* ---------- 순차 페이드업 (24px / 450ms / stagger 80ms) ---------- */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--dur) var(--ease) var(--d, 0ms), transform var(--dur) var(--ease) var(--d, 0ms);
}

.reveal.in { opacity: 1; transform: none; }

/* ---------- 히어로 ---------- */
.hero {
  position: relative;
  /* §R — 히어로 높이를 인물 렌더 높이에서 "파생"시킨다.
     이전에는 92svh 고정이었는데, 히어로는 뷰포트에 비례하고 인물은 44vw 상한에 걸려 있어
     세로로 긴 창일수록 인물이 히어로를 못 채웠다(1240×1920에서 인물 하단 51.7% vs 히어로 92% = 빈 어둠 40%p).
     두 값이 서로 다른 축을 참조하던 구조 문제이므로, 값이 아니라 관계로 고친다.
     · 가로형: 인물 하단이 92svh보다 아래라 92svh가 선택된다(인물은 화면 밖으로 흘러나간다 — 종전과 동일)
     · 세로형: 인물 하단이 위로 올라오므로 그 값이 선택되어 히어로가 인물과 함께 짧아진다
     하한은 따로 쓰지 않는다 — min-height는 콘텐츠 높이보다 작아질 수 없어 그것이 자동으로 바닥이 된다. */
  --portrait-w: min(max(34.9vw, 64vh), 44vw);              /* §H-1·§H-6① 원식 그대로, 정의 위치만 올림 */
  --portrait-top: calc(11.1vh - 0.0700 * var(--portrait-w));
  --portrait-h: calc(1.4995 * var(--portrait-w));          /* 원본 1067×1600 */
  /* §R — 히어로 하단을 인물에서 파생시킨다. 기준점은 "요소 하단"이고, 페이드 소멸점(요소 89%)이 아니다.
     소멸점을 쓰면 히어로가 4.7%p 더 짧아지는 대신, 콘텐츠 높이가 바닥을 만드는 창(좁고 긴 형태)에서
     페이드가 히어로 하단보다 위에서 끝나 빈 띠가 남는다(실측 1024×1536 2.5%p · 900×1600 5.6%p).
     요소 하단을 쓰고 마스크 소멸점을 히어로 하단에 맞춰 파생시키면 그 초과분을 페이드가 흡수한다.
     1920에서는 인물 하단 102.6vh > 92svh라 92svh가 선택되어 가로형 회귀가 없다. */
  --portrait-bottom: calc(var(--portrait-top) + var(--portrait-h));
  min-height: min(92svh, var(--portrait-bottom));
  display: flex;
  flex-direction: column;
  justify-content: flex-start;              /* v2.5 — 텍스트를 인물 얼굴 높이로 올린다 */
  /* 18.5vh → 16vh. 1366×768처럼 세로가 짧은 화면에서 콘텐츠가 92svh를 넘겨 히어로가 94.1%가 되고
     다음 섹션 노출이 5.9vh로 줄던 것을 8vh로 되돌린다. 데스크톱은 헤드라인(잉크 우단 53.0%)과
     인물(실루엣 좌단 66.4%)이 가로로 13.4%p 떨어져 겹침이 0이라, 밴드가 위로 올라가도 얼굴에 닿지 않는다
     — 얼굴 비침범은 겹침이 있는 모바일 구간의 판정 항목이다(§H-6-5와 같은 구분). 모바일은 28vh 유지. */
  /* §S — 하단 48px → 32px. 이 자리는 배경과 인물이 채우므로 "보이는 간격"이 아니고,
     히어로 높이는 대개 §R 파생식이 정해 이 값과 무관하다. 다만 콘텐츠가 바닥을 만드는 창에서는
     이 값이 곧 히어로 높이라, 줄인 만큼 §R-5의 잔여 빈 영역이 함께 줄어든다.
     전역 --sp-5를 낮추지 않고 히어로 선언에서만 한 단계 내린다(다른 섹션 여백 불변). */
  padding-block: 16vh 24px;   /* §S as-built — 900×1600의 §R-5 잔여가 0으로 닫히는 값. 전역 --sp-5는 불변 */
  overflow: hidden;
  isolation: isolate;
}

.hero-media { position: absolute; inset: 0; z-index: -1; }

/* §N-4 — 층 분리. 스크롤 패럴랙스는 이 스택 컨테이너가, 켄번즈 줌은 각 슬라이드가 맡는다.
   둘을 한 요소에 걸면 같은 transform 속성을 다투어 나중에 쓴 쪽이 앞의 것을 지운다.
   스크림(.scrim)과 하단 페이드(::after)는 스택 밖 형제라 패럴랙스에 딸려 움직이지 않는다 —
   같이 움직이면 스택이 아래로 밀릴 때 상단에 스크림 없는 띠가 드러난다. */
.hero-stack { position: absolute; inset: 0; will-change: transform; }

.hero-slide {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* 128% → 100% (§Q). 128%는 스카이라인 원본 하단 21.9%가 "평균 밝기 9.5의 검은 띠"여서
     잘라낸 값이다. 실사 공간 컷의 같은 구간은 앞줄 좌석과 바닥이라 잘라내면 근경을 버리는 것이 되고,
     계단식 강의실이 "내려다보는 공간"으로 읽히지 않는다. 원본을 덜 자르므로 줌 여유도 커진다.
     이 값이 100%이므로 생성 종횡비 = 히어로 종횡비 그대로다(크롭 비율에 1.28을 곱하지 않는다). */
  object-fit: cover;
  object-position: 50% 0;
  filter: var(--img-noir);
  /* §N-3 줌은 애니메이션이 아니라 "상태"다 — 목표 스케일을 클래스로 주고 뗀다.
     전환 순서가 꼬여도 상태가 어긋나지 않는다.
     §N-8 때문에 "보인다(is-visible)"와 "확대 중이다(is-zooming)"를 분리한다 —
     나가는 컷은 가시성만 빠지고 확대는 페이드 내내 계속되어야 한다. */
  opacity: 0;
  transform: scale(1);
  transform-origin: 50% 50%;     /* 좌측 원점은 텍스트 뒤 배경을 크게 바꿔 가독이 흔들린다 */
  /* transform 전이 0s — is-zooming이 빠지는 순간(이미 투명해진 뒤) 되감김 없이 즉시 리셋된다 */
  transition: opacity var(--hero-fade) linear, transform 0s linear;
}

.hero-slide.is-zooming {
  transform: scale(var(--hero-zoom));
  transition: opacity var(--hero-fade) linear, transform var(--hero-zoom-dur) linear;
}

.hero-slide.is-visible { opacity: 1; }

/* 움직이는 장에만 — 동시에 최대 2장(페이드 중 나가는 컷 + 들어오는 컷)이고 그 외에는 1장이다.
   is-visible에만 걸면 페이드 중인 나가는 컷이 아직 보이는 상태에서 레이어를 잃는다. */
.hero-slide.is-visible,
.hero-slide.is-zooming { will-change: transform, opacity; }

/* 광원 하나 — 좌측 스크림 + 하단 페이드(느와르 문법, v1 그라디언트 금지 폐기) */
.hero-media .scrim { position: absolute; inset: 0; background: var(--scrim-hero); }
/* 히어로 한정 완화: 공용 --scrim-bottom(0.95/60%)은 하단을 완전히 검게 마감해
   방금 살린 도시 야경을 다시 지운다. 캐러셀 캡션용 원값은 토큰에 그대로 둔다. */
.hero-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(7, 8, 11, 0.75) 0%, transparent 45%);
}

.hero-body {
  position: relative;
  z-index: 1;          /* v2.2 §F-3 — 헤드라인 블록이 인물보다 앞 */
  max-width: 80%;      /* 2행 유지에 필요한 폭 */
}

/* v2.2b §F-4 — 국소 스크림. 글자가 어깨 위로 올라가도 4.5:1을 확보한다.
   블록 폭 기준으로 깔고 우측으로 감쇠시켜 인물 실루엣과의 경계를 만들지 않는다. */
.hero-body::before {
  content: '';
  position: absolute;
  /* §G-8 — 사각형 폐기, 모서리 없는 타원 방사형.
     사각 그라디언트는 히어로 밖으로 늘려 잘라도 "잘리지 않은 변"이 남아 직선으로 드러난다.
     타원 반경을 박스의 78%/62%로 두면 네 변 어디서도 알파가 0에 도달해 모서리가 존재하지 않고,
     크기가 텍스트 블록에 종속되므로 창이 세로로 길어져도 스크림이 따라 커지지 않는다. */
  /* §G-8 개정 — 모서리 없는 형태를 두 축 분리로 만든다.
     타원 단독은 기준 ①(실루엣 상단 +6%p에서 알파 ≤0.03)과 텍스트 덮기가 양립하지 않는다
     (타원은 밴드 좌우 끝에서 알파가 0으로 떨어져 헤드라인 양끝이 비보호).
     세로는 포락선(양끝 0), 가로는 마스크 페이드 — 네 변 모두 0에 도달하고 모서리가 없다. */
  inset: -4vh calc(var(--sp-6) * -1) -4vh calc(var(--margin) * -1);
  z-index: -1;
  background: linear-gradient(180deg,
    rgba(7, 8, 11, 0) 0%,
    rgba(7, 8, 11, 0) 6%,          /* 실루엣 상단(11.1%)+6%p 아래에서 시작 */
    /* §X — 데스크톱 국소 스크림 제거(0.35 → 0). 전역 0.55는 유지, 모바일 블록은 0.35 그대로
       (모바일은 가로 마스크가 none이라 인물 위까지 덮고 여유가 얇다).

       ※ 근거가 한 번 바뀌었다. 도입 당시(v6.0 선화)는 "텍스트 뒤가 순흑이라 보호 대상이 없다"였는데,
       v8.0에서 소재가 사진으로 바뀌어 그 문장은 더 이상 사실이 아니다.
       현재 근거는 "대비 여유가 하한의 2.7~2.9배라 국소 보호가 불필요하다"이고,
       ★① 데스크톱 국소 0 ← 대비 여유 ← 그레이딩 상한 230.
       상한을 빼면 데스크톱 대비가 8.80 → 7.84 이하로 내려가 이 값(국소 0)의 근거도 함께 무너진다.
       두 장치는 값이 그대로인 채 근거로만 연결돼 있어 코드만 봐서는 보이지 않는다.
       "이제 선화가 아니니 상한 230은 불필요"로 읽지 말 것.
       (모바일 0.35는 이것과 무관한 다른 의존이다 — 아래 모바일 블록의 ★② 참조.) */
    rgba(7, 8, 11, 0) 17%,
    rgba(7, 8, 11, 0) 84%,
    rgba(7, 8, 11, 0) 100%);
  /* §H-6-5 — 데스크톱은 헤드라인(잉크 우단 53.0%)과 인물(실루엣 좌단 58.5%)이 겹치지 않으므로
     인물 위 스크림은 가독 기여가 0이다. 실루엣 좌단 전에 알파 0에 도달시켜 아예 닿지 않게 한다.
     퍼센트 스톱은 박스 폭이 뷰포트마다 달라 흔들리므로 vw로 고정한다. */
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 40vw, transparent 56vw);
  mask-image: linear-gradient(90deg, #000 0, #000 40vw, transparent 56vw);
  pointer-events: none;
}

.hero-title {
  /* 정장 하이라이트(최악 1.9:1) 위를 획이 스칠 때의 보험. 느와르에서 그림자는 문법이고,
     3대 금지 대상인 "UI 장식용 소프트 섀도"와는 용도가 다르다. */
  text-shadow: 0 2px 12px rgba(7, 8, 11, 0.35);
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: var(--fw-display);
  line-height: var(--lh-hero);
  letter-spacing: var(--track-display);
  margin: var(--sp-3) 0 var(--sp-3);
}

/* §J — 서브는 본문이 아니라 데크(deck). 헤드라인 대비 1:4.7~4.8 위계를 유지한다.
   헤드라인 상한이 해제돼 있으므로(§H-4) 서브에도 상한을 두지 않는다 — 한쪽만 걸리면
   2560에서 위계가 1:6.1로 되돌아간다. 폭은 우단이 56vw에서 멈추도록 52vw(좌단 4vw + 52 = 56).
   그 선은 국소 스크림이 0에 도달하는 선이자 실루엣 좌단(58.5%)보다 2.5%p 왼쪽이다. */
.hero-sub {
  /* §J-8 — 색상은 히어로 서브 전용(전역 --fg-mute 불변: PROFILE·카드 본문까지 밝아진다).
     섀도는 보조가 아니라 필수 — 색상 단독은 미달 4.0%, 섀도 단독 6.4%, 조합에서만 0%. */
  color: #D5D9DD;
  /* §J-12 발동 — 히어로 90svh로 서브 밴드가 45.4~70.4%까지 넓어지며 인물의 밝은 구간이 더 들어와
     모바일 최악이 4.52:1(기준 4.5, 여유 0.02)이 됐다. 0.45면 원본 상한이 266이라
     인물 밝기와 무관하게 안전해진다. 헤드라인은 0.35 유지 — 미달 여유가 이미 크다. */
  text-shadow: 0 2px 12px rgba(7, 8, 11, 0.45);
  font-size: max(1.1875rem, 1.6vw);   /* §J 확정 — 모바일 하한 19px(현행 15.2px 대비 +25%) */
  max-width: 52vw;
}

/* ---------- THE PATH 캐러셀 (클릭 + 화살표 + 키보드 / 크로스페이드) ---------- */
.path { padding-block: var(--sp-8); }

.stage { margin-top: var(--sp-5); }

/* 캡션·화살표의 위치 기준. 데스크톱에선 둘 다 absolute라 프레임 높이 = 이미지 높이. */
.stage-frame { position: relative; }

.stage-media {
  position: relative;
  aspect-ratio: 16 / 9;   /* 원본 자산 비율(1280×720) — 아래 프레임 규칙과 단일 출처 */
  background: var(--bg-raise);
  overflow: hidden;
}

.slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur) var(--ease-veil); /* 크로스페이드 450ms — 이동·스케일 금지 */
}

.slide.active { opacity: 1; }

.slide img { width: 100%; height: 100%; object-fit: cover; filter: var(--img-noir); }

.slide-placeholder {
  width: 100%; height: 100%;
  background: linear-gradient(135deg, #14171D 0%, #0C0E12 60%, #07080B 100%);
}

.stage-veil { position: absolute; inset: 0; background: var(--scrim-bottom); pointer-events: none; }

.stage-caption {
  position: absolute;
  left: var(--sp-5);
  bottom: var(--sp-5);
  right: var(--sp-8);
}

.slide-year {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  color: var(--gold);
}

.slide-title {
  font-family: var(--font-display);
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--track-display);
  margin: var(--sp-1) 0;
}

.slide-caption { color: var(--fg-mute); max-width: 56ch; }

.stage-controls { display: contents; }   /* 데스크톱: 래퍼는 레이아웃에 관여하지 않음 */

.nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 48px; height: 48px;
  background: rgba(7, 8, 11, 0.55);
  border: var(--rule-hair) solid var(--rule);
  border-radius: var(--radius);   /* v2.1 — 직각 */
  color: var(--fg);
  font: inherit;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

/* v2.1 — 좌측은 아웃라인, 우측만 주홍 면 + 잉크 화살표(레퍼런스 문법) */
.nav.prev { left: var(--sp-3); }
.nav.prev:hover { background: rgba(7, 8, 11, 0.85); border-color: var(--gold); color: var(--gold); }

.nav.next {
  right: var(--sp-3);
  background: var(--gold);
  border-color: var(--gold);
  color: var(--on-gold);
}

.nav.next:hover { background: var(--gold-bright); border-color: var(--gold-bright); }

.thumbs {
  display: flex;
  gap: var(--gutter);
  margin-top: var(--sp-3);
  overflow-x: auto;
  scrollbar-width: none;
}

.thumbs::-webkit-scrollbar { display: none; }

.thumb {
  flex: 1 1 0;      /* v2.1 QA — 컨테이너 폭을 슬라이드 수만큼 등분해 8개가 모두 들어온다 */
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
  opacity: 0.5;
  transition: opacity var(--dur) var(--ease); /* 크기 변화 금지 — opacity·보더만 */
}

.thumb.active { opacity: 1; }

.thumb img, .thumb-placeholder {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  filter: var(--img-noir);
  border: var(--rule-hair) solid transparent;
  background: var(--bg-raise);
}

.thumb.active img, .thumb.active .thumb-placeholder { border-color: var(--gold); }

.thumb-year {
  display: block;
  margin-top: 6px;
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  color: var(--fg-dim);
}

.thumb.active .thumb-year { color: var(--fg); }

/* ---------- FIGURES 밴드 (v2.1 §E-1: 주홍 풀블리드 유지 + 높이로 통제) ----------
   색을 빼는 대신 면적을 줄인다 — 주홍은 골드보다 전진감이 강해
   같은 높이(112px)면 "빛의 띠"가 아니라 덩어리가 된다. */
.band {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;   /* v2.1 QA — 5등분 셀은 잉크가 셀 좌측에 몰린다. 항목을 양끝까지 분산 */
  align-items: center;
  gap: var(--sp-2) var(--gutter);
  min-height: var(--sp-6); /* v2.1 — 112px → 72px 축소 */
  background: var(--gold);
  color: var(--on-gold); /* 주홍 면 위 텍스트는 잉크색 필수(5.42:1 / 흰색 3.21:1 미달) */
  padding-block: var(--sp-1);   /* 면은 풀블리드, 안쪽 수치만 컨테이너 정렬 */
}

.band-num {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-card);   /* v2.1 — 밴드 72px 축소에 맞춰 한 단 하향 */
  font-weight: var(--fw-display);
  line-height: 1;
}

.band-label {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  opacity: 0.75;
}

/* ---------- START HERE 4카드 ---------- */
.cards {
  display: grid;
  /* minmax(0, 1fr) — `1fr`은 `minmax(auto, 1fr)`이라 트랙 최솟값이 **min-content**다.
     769~789px 구간에서 카드 4열의 min-content 합이 컨테이너를 9px 넘어 **페이지 가로 스크롤**을
     만들었다(모바일 1열 규칙이 768에서 풀리는 바로 위 구간). 0을 최솟값으로 주면 트랙이 줄고
     텍스트가 정상 줄바꿈된다 — 넘치지 않는 폭에서는 렌더 결과가 완전히 같다. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gutter);
  margin-top: var(--sp-6);
}

.card {
  background: var(--bg-raise);
  border-top: var(--rule-mark) solid var(--gold);
  padding: var(--sp-4);
  min-height: 220px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.card-idx { font-size: var(--fs-label); letter-spacing: var(--track-label); color: var(--gold); }
/* 사용자 지시로 shortNote 4건이 비어(2026-08-27) 현재 렌더되지 않는다 — 규칙은 유지한다(@designer 확정).
   ⚠️ 되살릴 때: margin-top:auto 가 남아 있으므로 데이터 1줄 복원만으로 다시 하단 앵커로 동작한다.
   조판 기준(@designer): min-height 220px 유지 / 본문 상단 정렬 + 잔여는 여백으로 수용(빈 스페이서·
   margin-top:auto 본문 이전 금지) / 데스크톱 행 내 패널 균등 유지(align-items:start 금지) —
   "본문 종료선이 카드마다 다른 것은 텍스트의 사실이고, 균등해야 하는 것은 패널이다." */
.card-note { margin-top: auto; font-size: var(--fs-label); letter-spacing: var(--track-label); color: var(--fg-dim); }

/* ---------- 2컬럼 소개 ---------- */
.intro {
  display: grid;
  grid-template-columns: 4fr 8fr;
  gap: var(--gutter);
  background: var(--bg-deep);
}

.titles { margin-top: var(--sp-5); border-top: var(--rule-hair) solid var(--rule); }

.titles li {
  padding: var(--sp-2) 0;
  border-bottom: var(--rule-hair) solid var(--rule);
  font-size: var(--fs-small);
  color: var(--fg-mute);
}

.titles li:first-child { color: var(--fg); }

/* ---------- 저서 (원색 유지 — 느와르 예외, 골드 밴드와 같은 화면 금지) ---------- */
/* §Y-8 — 메타 5행이 붙으면서 1열 리스트가 2×2 격자가 된다(@designer).
   4열로 가면 셀 폭이 423px인데 그 아래 메타 5행이 쌓여 셀 하나가 800px 높이가 되고,
   한 줄이어도 섹션이 화면 한 장을 넘는다. max-width는 두지 않는다 — §X처럼 컨테이너가 폭을 정한다. */
.books {
  margin-top: var(--sp-5);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--sp-6);
  row-gap: var(--sp-7);
}

/* 셀 = 커버(좌, 200px) + 제목·메타(우). 커버는 두 행에 걸치고 메타는 제목 아래 같은 열에 선다.
   img를 두 행에 걸쳐야 제목이 짧은 항목에서도 커버 상단이 제목 상단과 맞는다. */
/* §Z-1 — 커버 폭 상한은 **절대값 260px**이다(@designer, 44%·33% 비율안 폐기).
   제약이 원본 픽셀 수라 상한도 픽셀이어야 한다 — 비율은 뷰포트를 따라 계속 커져
   어디선가 반드시 원본을 넘는다(33%는 2560에서 293px로 한계선 초과).
   260px × DPR 2 = 520px ≤ 책 3권 -800 원본 568px. 어느 뷰포트·어느 DPR에서도 임계가 없다.
   align-items는 start다 — center로 두면 메타가 커버보다 짧아 라벨 첫 행이 커버 상단보다
   내려가고, 네 항목의 라벨 열이 가로로 안 맞아 "한 표로 읽힌다"가 무너진다(@designer). */
.books li {
  display: grid;
  grid-template-columns: min(260px, 40%) minmax(0, 1fr);
  column-gap: var(--sp-4);
  row-gap: var(--sp-3);
  align-items: start;
}
.books li > img,
.books li .book-link > img { grid-column: 1; grid-row: 1 / span 2; }
.book-body { grid-column: 2; grid-row: 1; }
.book-meta { grid-column: 2; grid-row: 2; }

.books img { width: 100%; box-shadow: var(--noir-shadow); }

/* 메타 — 라벨 열 폭이 네 항목에 **공통**이라야 목록이 "네 개의 표"가 아니라 "한 표"로 읽힌다.
   행 수는 달라도 된다(책 5행 / 논문 4행) — 지켜야 할 것은 라벨 열의 폭과 정렬이다(@designer).
   상세는 헤어라인으로 나누지만 목록은 간격으로 나눈다 — 4항목 × 5행이면 헤어라인 20개가 시끄럽다. */
.book-meta { margin: 0; }
.book-meta > div {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  gap: var(--sp-2);
  padding-block: 3px;
}
.book-meta dt {
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--fg-dim);
  padding-block-start: 0.15em;
}
.book-meta dd {
  margin: 0;
  font-size: var(--fs-small);
  color: var(--fg-mute);
  overflow-wrap: anywhere;
}

/* 항목 전체가 링크다(§Y-1) — a는 display:contents라 그리드 상자를 만들지 않는다.
   메타가 붙어 클릭 영역이 커지는 건 이득이다(@designer). */
.book-link { display: contents; color: inherit; text-decoration: none; }
.books li:has(.book-link) { cursor: pointer; }
.books li:has(.book-link:hover) .book-title { color: var(--gold); }
.books li .book-title { transition: color var(--dur-fast) var(--ease); }
/* 논문 배지는 제목 위 한 줄 — 종류가 다르다는 유일한 표시다(캡션이 사라진 자리) */
.books li.is-paper .paper-badge { display: inline-block; margin-block-end: var(--sp-1); }

.book-title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-card);
  font-weight: var(--fw-display);
  letter-spacing: 0.02em;
}

.book-sub { display: block; margin-top: 4px; font-size: var(--fs-small); color: var(--fg-mute); }

/* ---------- 연락 ---------- */
.contact { background: var(--bg-deep); }

.email {
  display: inline-block;
  margin: var(--sp-3) 0 var(--sp-5);
  font-family: var(--font-display);
  font-size: var(--fs-section);
  font-weight: var(--fw-display);
  letter-spacing: var(--track-display);
  transition: color var(--dur-fast) var(--ease);
}

.email:hover { color: var(--gold-bright); }

.links { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-4); border-top: var(--rule-hair) solid var(--rule); padding-top: var(--sp-4); }
.links li .label { display: block; margin-bottom: 4px; }
.links a:hover { color: var(--gold); }
.links .arrow { display: inline-block; transition: transform var(--dur-fast) var(--ease); }
.links a:hover .arrow { transform: translateX(4px); }

/* ---------- BOOKS — KCI 논문 행 (§Y-1) ----------
   책 3권과 나란히 놓이되 종류가 다르다: 표지 그리드가 아니라 전폭 한 행이고,
   골드는 배지 한 곳에만 쓴다(느와르 3규칙 — 골드는 선·라벨·소면적). */
.paper {
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: var(--rule-hair) solid var(--rule);
}

.paper-badge {
  display: inline-block;
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  color: var(--gold);
  border: var(--rule-hair) solid var(--gold);
  padding: 3px 8px;
  margin-bottom: var(--sp-2);
}

.paper-title {
  font-family: var(--font-display);
  font-size: var(--fs-card);
  font-weight: var(--fw-display);
  line-height: var(--lh-tight);
  max-width: 42ch;   /* 읽기 길이 — .body의 64ch보다 짧게(세리프 제목) */
}

.paper-meta {
  margin-top: var(--sp-1);
  font-size: var(--fs-small);
  color: var(--fg-mute);
}

.colophon { margin-top: var(--sp-6); font-size: var(--fs-label); letter-spacing: var(--track-label); color: var(--fg-dim); }


/* ---------- 히어로 인물 누끼 (§5.0 층 구조: 배경 → 스크림 → 인물 → 타이포) ---------- */
/* v2.2 §F-2 — 전신 100vh 풀블리드 폐기, 1:1 상반신으로 축소.
   1:1 크롭이라 높이가 곧 폭이므로 32vw 상한이 "인물 폭 ≤ 화면 34%" 기준을 보장한다. */
/* v2.5 §F-8 — bust(1:1) 폐기, 전신(2:3) 복귀 + 상단 정렬.
   bust로는 높이를 키우면 폭이 같이 커져(1:1) 인물이 화면을 지배한다.
   전신은 같은 높이에서 폭이 2/3라 "세로를 채우되 폭은 40% 이하"가 성립한다.
   머리 상단이 이미지의 3.2% 지점이므로 top 6vh면 머리가 화면 8%대에서 시작한다. */
.hero-portrait {
  /* §H-1 — 기준축은 vw(목표값이 전부 폭 기준). 머리 위치만 vh라 그 차이를 보정한다.
     §H-2 하한 64vh — vw 단독이면 16:10 창에서 인물이 짧아져 허벅지 단면이 화면 안에 노출된다.
     right 식은 우측 여백이 아니라 실루엣 "좌단"을 58.5%에 고정한다(헤드라인과의 간극 불변). */
  /* §H-6① — 64vh 하한이 세로형에서 폭주한다(981×1114에서 64vh = 72.7vw → 실루엣 우단 105%).
     44vw 상한을 겹쳐 어떤 비율에서도 화면 밖으로 잘리지 않게 한다.
     §R — --portrait-w·--portrait-top은 .hero에서 정의해 상속받는다. 히어로 높이가 이 값들에서
     파생되므로 정의가 두 곳에 갈리면 두 값이 어긋난다(같은 식을 두 번 쓰지 않는다). */
  position: absolute;
  width: var(--portrait-w);
  height: auto;
  /* §M(@designer·@main 확정) — 우측 여백 과다로 인물을 8vw 오른쪽 평행이동(실루엣 우단 81.8% → 89.8%).
     7vw(우단 88.8%/간극 10.4%p) 안이 나왔다가 "간극은 빈 공간이 아니라 배경이 보이는 구간"이라는
     판단으로 8vw·간극 13.5%p 현행 유지로 확정됐다. 1%p 차이로 다시 만지지 않는다.
     41.5%(좌단 앵커)나 --portrait-w는 건드리지 않는다 — 그 값을 만지면 인물 크기가 함께 변한다. */
  right: calc(41.5% - 0.809 * var(--portrait-w) - 8vw);
  top: var(--portrait-top);
  bottom: auto;
  z-index: 0;                                     /* 배경(-1)보다 앞, 헤드라인(1)보다 뒤 */
  display: flex;
  align-items: flex-start;
  /* §H-6② — 하단 크롭 단면 상시 마스크. 조건 분기가 필요 없다:
     가로형은 인물 하단이 102.6%라 페이드 구간이 화면 밖(무변화), 세로형에서만 작동한다. */
  /* §P-3·§R — 소멸 지점을 "히어로 하단"에 맞춰 파생시킨다. 고정 89%는 히어로가 92svh일 때만 맞는 값이고,
     히어로가 인물이나 콘텐츠에 따라 줄면 다리가 화면 안에서 먼저 사라져 같은 빈 띠가 다시 생긴다.
     아래 식은 요소 상단부터 히어로 하단까지의 실제 길이라 어느 경우에도 히어로 하단에서 알파가 0이 된다.
     페이드 길이 8vh 고정 — 더 길게 잡으면 다리가 더 일찍 지워진다. */
  --fade-end: calc(min(92svh, var(--portrait-bottom)) - var(--portrait-top));
  -webkit-mask-image: linear-gradient(180deg, #000 calc(var(--fade-end) - 8vh), transparent var(--fade-end));
  mask-image: linear-gradient(180deg, #000 calc(var(--fade-end) - 8vh), transparent var(--fade-end));
}

.hero-portrait img {
  width: 100%;
  height: auto;
  object-fit: contain;
  object-position: top right;
  filter: var(--img-noir-soft);
}

/* ---------- 히어로 버튼 (v2.1: 주 = 주홍 면 + 잉크 텍스트 / 보조 = 아웃라인, 직각) ---------- */
.hero-actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-4); flex-wrap: wrap; }

.btn {
  display: inline-block;
  padding: 14px 28px;
  border-radius: var(--radius);   /* v2.1 — 필형 폐기, 직각 사각형 */
  font-size: var(--fs-label);
  letter-spacing: var(--track-label);
  border: var(--rule-hair) solid transparent;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.btn-gold { background: var(--gold); color: var(--on-gold); }
.btn-gold:hover { background: var(--gold-bright); }

.btn-outline { border-color: var(--rule-strong); color: var(--fg); }
.btn-outline:hover { border-color: var(--gold); color: var(--gold); }

/* ---------- ⑥ 캐러셀 슬라이드 프레임 (@designer QA P1) ----------
   높이만 제한하면 폭은 컨테이너 전폭이라 프레임이 3~5:1로 늘어나고,
   object-fit: cover가 16:9 원본의 상하를 잘라 소실점 구도·광원이 사라진다.
   → 원본 비율(16:9)을 유지한 채 "높이 상한에서 역산한 폭"을 명시해 중앙 정렬한다.
   폭을 명시해야 height = width * 9/16 로 결정되어 레터박스도 크롭도 생기지 않는다. */
.stage-frame {
  width: min(100%, calc(min(62vh, 560px) * 16 / 9));
  margin-inline: auto;
}

.stage-media {
  aspect-ratio: 16 / 9;
  height: auto;
  width: 100%;
}

/* 진행 바 (@designer QA P2) — 스펙 §5 인디케이터 3종 중 누락분.
   썸네일 스트립 아래 헤어라인 위를 골드가 채워 나간다. */
.progress {
  position: relative;
  height: var(--rule-hair);
  margin-top: var(--sp-3);
  background: var(--rule);
}

.progress-bar {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  background: var(--gold);
  transition: width var(--dur) var(--ease);
}

/* SPEC-C §2 — 인물 밴드는 모바일 전용. 데스크톱은 히어로 우측 인물(§H·§R)이 그대로이므로 숨긴다. */
.portrait-band { display: none; }

/* C-v2 §7.1 — 크로스페이드 레이어(좌측 스크림·인물·좌측 텍스트)는 html.scrollfx 에서만 존재.
   데스크톱·reduce 는 여기서 숨겨져 v1 렌더가 그대로다(§7.4-1·§9.5: 검증된 상태가 폴백). */
.hero-fade-scrim,
.hero-left-text,
.hero-portrait-layer { display: none; }

/* ============================================================
   모바일 v2.4 (@designer §G) — 원칙: 겹침 0.
   데스크톱의 "겹쳐서 만드는 깊이감" 문법을 전부 해제하고 세로 스택 + 명시적 간격으로 간다.
   좁은 폭에서 겹침은 깊이가 아니라 사고다.
   ============================================================ */
@media (max-width: 768px) {
  /* --- 히어로: 순수 타이포 무대 (SPEC-C §1, C안 레이어 분리) ---
     인물은 아래 인물 밴드로 분리 — §G-5/§G-6(텍스트를 내려 얼굴을 피함)의 존재 이유가 소멸해
     28vh 패딩·90svh를 해제한다. 상단 공백은 0이 아니라 소량 유지(사용자 원문 "조금만 보여서"):
     max() 하한 64px는 낮은 가로형 화면에서 topbar(44px)와의 충돌 방지. */
  .hero { min-height: 0; justify-content: flex-start; padding-block: max(9vh, 64px) 40px; }
  .hero-body { max-width: 100%; }
  /* 배경 확대 불필요 — @desginer-info 검증: 지면선 아래는 원본에 디테일이 없어(99.3%가 밝기 20 미만)
     CSS 확대든 크롭이든 검은 띠가 남는다. 진짜 원인은 인물 하단 하드컷이므로 페이드로 해결한다. */
  /* 컷별 가로 크롭 위치 — 모바일 전용. data.js의 posMobile이 인라인 --pos-mobile로 들어오고,
     값이 없는 컷은 기본값 50% 0(중앙)이 그대로 쓰인다. */
  .hero-slide { height: 100%; object-position: var(--pos-mobile, 50% 0); }
  /* 패럴랙스만 비활성 — 스택에 걸어야 한다. 슬라이드에 transform:none을 걸면 켄번즈 줌까지 죽는다. */
  .hero-stack { transform: none !important; }
  /* SPEC-C §1b — 모바일 인물은 히어로에서 인물 밴드(.portrait-band)로 이동.
     여기 있던 v2.6b~§V 블록(translateX 16.3%·58svh 상한·하단 마스크·폭 기준 렌더)의 판단 이력은
     docs/review/hero-mobile/SPEC-C.md §1b 참조. 데스크톱 .hero-portrait(§H·§R)는 불변. */
  .hero-portrait { display: none; }

  /* SPEC-C §1b — ★② 인물 위 글자 보호 스크림(0.35) 제거: 전제("인물 위에 텍스트")가
     C안(인물 밴드 분리)으로 소멸했다. §4 대비 재측정 통과가 해제 조건이며, 미달 슬라이드가
     생기면 --scrim-hero 강화가 아니라 이 밴드를 약화값으로 되살리는 쪽이 국소적이다.
     ★②의 의존 경고("인물 교체 시 배경 전량 재측정")는 히어로에 인물을 되돌리는 날 부활한다 —
     이력·수치·재도입 조건은 SPEC-C §1b·§4 및 이 파일 v2.7b 이력(§G-8) 참조. */
  .hero-body::before { display: none; }

  .hero-actions .btn { padding: 12px 20px; }
  /* SPEC-C §1 — 서브 섀도 0.35 통일은 §4 실측 미통과로 기각: 컷3의 서브 밴드 최대 픽셀 148에서
     0.35는 4.23:1(기준 4.5 미달), 0.45는 5.2:1. §J-12의 0.45는 인물만이 아니라 이 배경 컷
     때문에도 필요했던 값이다 — 베이스 0.45를 그대로 상속한다(선언 없음 = 상속).
     0.35 재도전 조건: heroSlides 교체 등으로 §4 컷 전수 재측정 통과 시. */
  .hero-sub { max-width: 100%; }   /* §J — 전폭 사용(인물 소멸) */
  /* §J-9 — 모바일은 데이터 개행을 무시하고 자동 랩(헤드라인과 동일 원칙).
     37자로 쪼개 명시 개행하면 조각 낭비가 누적돼 9행이 되고, 자동 랩이면 7행에서 끝난다. */
  .hero-sub br { display: none; }

  /* --- C-v2 §7 스크롤 크로스페이드 (html.scrollfx 에서만 — reduce 는 아래 v1 밴드가 무대) --- */
  /* 트랙: 래퍼 높이가 페이드 구간(--fade-track)을 소진한다 — preventDefault 없는 "멈춤" 체감. */
  html.scrollfx .hero-track {
    --fade-track: 90svh; /* §7.2 — 스와이프 한 번에 페이드 완결("한 번 밀면 한 장면") */
    --fade-p: 0;
    height: calc(100svh + var(--fade-track));
  }
  html.scrollfx .hero {
    position: sticky;
    top: 0;
    height: 100svh;
    min-height: 0;
    overflow: hidden;
    /* C-v2.5 §14 단일 소스 — 인물 폭과 '머리 top'을 여기서 한 번만 정의하고 인물·스탠자 A·B 가
       모두 이것을 참조한다. 종전 A 앵커는 39svh 고정값이었는데 390×844 에서 우연히 머리 top(329)과
       일치해 조용히 통과했고, 높이 구속 레짐(폴드·가로모드)에서는 머리가 88px 로 내려오는데
       A 는 39svh 에 남아 106~185px 아래로 떠 §13/§14.3 ①(A 상단 = 머리 상단)을 위반했다.
       인물 크기식과 같은 병 — 값이 아니라 관계로 고친다. 1.4995 = 원본 종횡비(1067×1600). */
    --fx-head-clear: max(88px, 12svh);
    --fx-portrait-w: min(88%, calc((100svh - var(--fx-head-clear)) / 1.4995));
    /* 머리 top = 스테이지 높이 − 인물 높이 (인물이 bottom:0 앵커). 인물 높이는 두 구속의 min 이므로
       머리 top 은 max 로 뒤집힌다 — 폭 구속이면 100svh−131.96vw, 높이 구속이면 클리어런스 자신.
       131.96vw = 원본 종횡비 1.4995 × 인물 폭 88% (매직 넘버 아님) —
       에셋(1067×1600) 또는 폭 비율 변경 시 이 값도 함께 갱신할 것.
       calc 에서 100%(폭 기준)를 높이 계산에 쓸 수 없어 vw 로 표기한다(스테이지 폭 = 뷰포트 폭). */
    --fx-head-top: max(var(--fx-head-clear), calc(100svh - 131.96vw));
  }
  /* 텍스트 아웃: p 0→0.45 — 위로 24px 떠나며 소멸. 매핑은 전부 선형(이징 금지 §7.2).
     transition: none 필수 — .reveal 의 등장 트랜지션이 상속되면 스크롤 매핑에 이징이 얹혀
     손과 화면이 어긋난다(실측: p 0.2 시점 opacity 0.65 ≠ 매핑값 0.556, 역스크롤 비대칭).
     부수효과: scrollfx 에서 hero-body 의 로드 등장 페이드는 소멸한다 — 매핑이 p=0 에서
     opacity 1 을 강제하므로 어차피 공존 불가(정착 상태 시각은 동일, §7.5-1 판정 기준). */
  html.scrollfx .hero-body {
    opacity: clamp(0, 1 - var(--fade-p) / 0.45, 1);
    transform: translateY(calc(-24px * clamp(0, var(--fade-p) / 0.45, 1)));
    transition: none;
  }
  html.scrollfx .hero-track.txt-off .hero-body { pointer-events: none; } /* opacity<0.2 오클릭 방지 */
  html.scrollfx .hero-track.txt-hidden .hero-body { visibility: hidden; } /* 홀드 구간 — 탭 순서 제거 */
  /* C-v2.1 §9.1 — 전면 딤 행 삭제(사용자 지시 ①: "배경 골목 유지"). 재도입 조건·이력은 SPEC-C §9.1.
     대신 좌측 가로 그라디언트 스크림: 데스크톱 --scrim-hero 문법(90deg 좌진→우옅) 승계 —
     배경을 죽이지 않으면서 좌측 텍스트 존만 가라앉힌다. 구간은 인물과 동일(0.35→0.85). */
  html.scrollfx .hero-fade-scrim {
    display: block;
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg, rgba(7, 8, 11, 0.55) 0%, rgba(7, 8, 11, 0.3) 55%, rgba(7, 8, 11, 0) 100%);
    opacity: clamp(0, (var(--fade-p) - 0.35) / 0.5, 1);
    pointer-events: none;
  }
  /* C-v2.1 §9.1·§9.4 — 좌측 동반 텍스트: 헤드라인과 같은 문구가 절반 위계(22~26px)로 돌아온다.
     인 구간 0.50→0.85 — 헤드라인 아웃(0→0.45) '완료 후' 시작해 동일 문구 이중상 회피(§9.2, 0.05 갭).
     세로 중심 ≈45svh(얼굴 라인과 수평 균형), 폭 ≤55%, 데이터 개행 존중(§J-9 br 숨김의 예외). */
  /* C-v2.4 §13 — 두 앵커에 두 클러스터: 스탠자 A(4행)=머리 높이, B(2행)=주머니 손 높이.
     갭은 명시값이 아니라 두 앵커의 '결과값'(§13-2 하드코딩 금지) — 갭이 인물 몸통을 비워 보여주는
     창이 된다. 페이드 매핑(§9.1 구간)은 컨테이너에 한 번만. */
  html.scrollfx .hero-left-text {
    display: block;
    position: absolute;
    inset: 0;
    margin: 0;
    font-family: var(--font-display);
    font-size: clamp(1.25rem, 5.2vw, 1.375rem); /* §12.1 상향분 유지 — 상한은 행별 예산(§12.2-1) */
    font-weight: var(--fw-display);
    line-height: 1.8; /* §13-2 그룹 내 행간 — 조밀한 행 결속(종전 2.7 균일 기둥 폐지) */
    color: var(--fg);
    pointer-events: none;
    opacity: clamp(0, (var(--fade-p) - 0.5) / 0.35, 1);
    transform: translateY(calc(12px * (1 - clamp(0, (var(--fade-p) - 0.5) / 0.35, 1))));
  }
  html.scrollfx .hero-left-text .hlt {
    position: absolute;
    left: var(--margin);
    max-width: 55%;
    margin: 0;
  }
  html.scrollfx .hero-left-text .hlt-a {
    /* §14 — 머리 top 연동(종전 39svh 고정). 폰 세로에서는 두 값이 같아 렌더 불변이고,
       높이 구속 레짐에서만 A 가 머리를 따라 올라간다(§13/§14.3 ① 앵커 복원). */
    top: var(--fx-head-top);
  }
  /* C-v2.5 §14.3 앵커 스팬 가드 — 인물이 작아지면 머리→주머니 스팬(0.60 × 인물높이)이 줄어
     A(4행 146px) + 최소 갭(73px) + B(2행 73px) = 292px 를 밑도는 구간이 생긴다(360×800 이 이미 285px).
     앵커 우선순위: ① A 상단 = 머리 > ② 최소 갭 73px > ③ B 하단 = 주머니 존.
     스팬 부족 시 ③을 포기하고 B 를 A 아래 최소 갭 위치에 둔다 — 갭을 줄이거나(§13.3 시인성 하한 위반)
     A 를 올리는(머리 정렬 파괴) 처리는 금지. 엔진이 실측 후 .fx-span-short 를 부여한다. */
  html.scrollfx .hero-left-text .hlt-b {
    bottom: 22svh; /* 하단 앵커: B 하단 = 78svh — 주머니 존(77~82svh) 안, §12.4 종지(손 옆) 유지 */
  }
  html.scrollfx .hero-left-text.fx-span-short .hlt-b {
    bottom: auto;                       /* ③ 포기 — 주머니 존에서 떼어낸다 */
    top: calc(var(--fx-head-top) + var(--fx-b-top)); /* A 상단(머리) + A 높이 + 최소 갭 (엔진 실측 주입) */
  }
  /* 인물 인: p 0.35→0.85 — 아래에서 16px 올라오며 등장. 구도는 v1 밴드 승계. */
  html.scrollfx .hero-portrait-layer {
    display: block;
    position: absolute;
    inset: 0;
    margin: 0;
    pointer-events: none;
    opacity: clamp(0, (var(--fade-p) - 0.35) / 0.5, 1);
    transform: translateY(calc(16px * (1 - clamp(0, (var(--fade-p) - 0.35) / 0.5, 1))));
  }
  html.scrollfx .hero-portrait-layer img {
    position: absolute;
    bottom: 0;
    /* C-v2.1 §9.4 — 사용자 지시 ②: v1 −6% → −16%(탐색 −12~−16 중 상단). 실렌더 판정:
       얼굴 중심 276px(>260 ✓)·텍스트 밴드 높이의 실루엣 좌단 56.5%(≥55% ✓).
       '실루엣 좌단'은 텍스트 세로 밴드(y≈340~420)와 겹치는 높이 기준 — 전 높이 최솟값은
       하반신(다리)이 잡혀 어떤 right 값으로도 55%를 못 넘는다(원본 알파 실측 0.187w vs 밴드 0.325w). */
    right: -16%;
    /* C-v2.5 §14.1 — 폭·높이 동시 제약(데스크톱 §R 문법 승계).
       종전 `width: 88%` 단독은 인물 높이를 스테이지 '폭'에 비례시켰는데(높이 = 1.4995 × 폭 × 0.88),
       스테이지 높이는 100svh(높이 비례)라 두 축이 다른 변수를 참조했다 — 종횡비가 1에 가까워질수록
       인물이 스테이지를 초과하고 bottom:0 앵커라 초과분이 전부 위로 밀려 머리부터 잘렸다
       (실측: 672폭부터 topbar 침범, 768×700에서 313px 잘림 — 갤럭시 폴드 펼침이 이 구간).
       max-height 로 풀지 않는다: width 지정 + max-height 는 대체 요소의 종횡비를 깨뜨린다.
       1.4995 = 원본 종횡비 — 에셋(portrait-full-noir-1600.webp, 원본 1067×1600) 교체 시 함께 갱신할 것.
       클리어런스를 비례항으로 두는 이유: 고정 px면 짧은 뷰포트에서 머리가 topbar 에 밀착한다.
       88px = topbar 44 + 최소 숨 44. 폰 세로(390×844 등)는 폭 구속이 그대로 이겨 렌더 불변. */
    width: var(--fx-portrait-w); /* §14 단일 소스 (.hero 에서 정의) */
    height: auto;
    filter: grayscale(1);
    -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
  }
  /* 캡션 인: p 0.70→0.95 — 인물이 충분히 선 뒤 서명처럼. 레이어 불투명도와 곱으로 적용되며
     0.85 이후 레이어가 1이므로 0.95 도달점은 표와 일치한다. 문구는 data.js(§3 보류 상태) 그대로. */
  html.scrollfx .hero-portrait-layer .pb-cap {
    position: absolute;
    left: var(--margin);
    bottom: 28px;
    color: var(--gold);
    font-size: var(--fs-label);
    letter-spacing: var(--track-label);
    text-transform: uppercase;
    font-weight: 600;
    opacity: clamp(0, (var(--fade-p) - 0.70) / 0.25, 1);
  }
  /* C-v2 §4-2 발동 — ★② 밴드 스크림을 scrollfx 한정으로 복원(원래 0.35 그라디언트 그대로, 새 값 없음).
     사유: 스테이지가 555px→100svh 로 커지며 슬라이드 크롭이 바뀌어 컷3의 밝은 구간(픽셀 156)이
     CTA 아래로 들어와 2.47:1(기준 4.5) 미달 — §4-2 처방대로 컷별 예외 없이 전 컷 균일 복원.
     .hero-body 의 자식이므로 텍스트 레이어와 함께 페이드 아웃된다(인물 무대는 자체 딤이 담당).
     reduce 폴백·데스크톱은 이 규칙 밖 — v1 상태(제거) 그대로. */
  html.scrollfx .hero-body::before {
    display: block;
    /* 스톱 재산정 (알파 0.35 불변, 기하만 C-v2 실측 기준): v1 스톱(75%→100% 페이드)은 v1 레이아웃
       (§G-8 수치 탐색) 기준이라, C-v2 에서 CTA 가 밴드 박스의 87.1~96.4%에 놓여 페이드 꼬리에 걸렸다
       (실효 알파 ~0.1 → 컷3 CTA 3.19:1 미달). 하단 inset 을 −6vh 로 늘려 CTA 하단이 ~90%로 들어오게
       하고 플래토를 92%까지 유지 — 페이드는 CTA 아래 잉크 없는 구간에서만 일어난다. */
    inset: -2vh calc(var(--margin) * -1) -6vh calc(var(--margin) * -1);
    background: linear-gradient(180deg,
      rgba(7, 8, 11, 0) 0%,
      rgba(7, 8, 11, 0) 10%,
      rgba(7, 8, 11, 0.35) 22%,
      rgba(7, 8, 11, 0.35) 92%,
      rgba(7, 8, 11, 0) 100%);
    -webkit-mask-image: none;
    mask-image: none;
  }

  /* v1 밴드는 scrollfx 에서 숨김 — reduce·비지원 환경의 폴백 무대로 마크업·스타일 보존 (§7.1) */
  html.scrollfx .portrait-band { display: none; }

  /* --- 인물 밴드 (SPEC-C §2) — 순검은 무대의 독무대 --- */
  .portrait-band {
    display: block;
    position: relative;
    height: 74svh; /* small viewport 고정 — iOS 주소창 수축과 무관, sticky/리스너 없음 */
    overflow: hidden;
    background: var(--bg-deep);
    /* 상단 경계 = 노와르 헤어라인 — 기존 사용례(§: rule-hair × gold) 승계, 새 값 없음 */
    border-top: var(--rule-hair) solid var(--gold);
  }
  .portrait-band img {
    position: absolute;
    bottom: 0;
    right: -6%;
    width: 88%;
    height: auto;
    filter: grayscale(1);
    /* 다리 단면을 어둠에 담금 — 기존 히어로·profpage와 같은 페이드 문법 */
    -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
    mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
  }
  /* 캡션 — 문구는 data.js identity.portraitCaption (사용자 확인 대기, 빈 값 = 미렌더).
     색은 --gold(#FF3B00, 다크 대비 5.41:1) — 목업의 #c8a96b는 임시색, 신규 색 도입 금지(SPEC-C §0). */
  .portrait-band .pb-cap {
    position: absolute;
    left: var(--margin);
    bottom: 28px;
    color: var(--gold);
    font-size: var(--fs-label);
    letter-spacing: var(--track-label);
    text-transform: uppercase;
    font-weight: 600;
  }

  /* --- 캐러셀: 오버레이 전면 해제, 세로 스택 --- */
  .stage-frame { width: 100%; }
  .stage-media {
    width: 100%;
    height: auto;                                /* vh 금지 — 주소창 변화로 비율이 깨진다 */
    aspect-ratio: 16 / 9;
  }
  .stage-veil { display: none; }                 /* 캡션이 이미지 밖으로 나가므로 베일 불필요 */
  .stage-caption {
    position: static;                            /* 오버레이 → 이미지 아래 정적 배치 */
    margin-top: var(--sp-3);
  }
  .slide-title { font-size: clamp(1.5rem, 7vw, 2rem); }
  .slide-caption { font-size: var(--fs-small); }

  .nav {                                          /* 이미지 위 absolute → 하단 컨트롤 행 */
    position: static;
    transform: none;
    width: 48px; height: 48px;                    /* 터치 타깃 44px 이상 */
  }
  .stage-controls { display: flex; gap: var(--sp-2); margin-top: var(--sp-3); }   /* display:contents 해제 → 실제 행 */

  /* 썸네일: 가로 스크롤 → 4열 × 2행 그리드(8장이 모두 보여야 인덱스가 성립) */
  .thumbs {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--sp-2);
    overflow: visible;
  }
  .thumb { flex: none; width: 100%; }
  .thumb-year { font-size: 0.625rem; letter-spacing: 0.12em; }

  /* --- 나머지 섹션 --- */
  .sec { padding-block: var(--sp-6); }
  /* §S — 모바일 72px → 48px. 데스크톱과 폭을 다르게 잡는다: 모바일은 히어로가 이미 짧고
     라벨까지의 실거리가 데스크톱보다 짧아 같은 비율로 줄이면 붙어 보인다. */
  .hero-track + .sec { padding-top: var(--sp-5); } /* C-v2: scrollfx 에서 밴드가 display:none — 트랙 직후 섹션 */
  /* SPEC-C — reduce 폴백에서는 히어로와 첫 섹션 사이에 인물 밴드가 끼므로 §S 간격 규칙을 밴드 뒤로 확장 */
  .portrait-band + .sec { padding-top: var(--sp-5); }
  .path { padding-block: var(--sp-6); }
  .band {                                         /* 2열 × 3행, 높이 auto */
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    min-height: 0;
    padding-block: var(--sp-4);
    row-gap: var(--sp-3);
  }
  .cards { grid-template-columns: 1fr; }
  .intro { grid-template-columns: 1fr; }
  /* §Y-8 모바일 as-built (C-v2.5 개정) — 1열, **제목·부제 전폭 상단 → 표지(좌)+메타(우) 2열**.
     종전 주석의 근거("메타만 전폭으로 내려 라벨/값 2열이 눌리지 않게")는 **라벨 생략으로 무효화**됐다:
     라벨이 없으면 메타가 값 1열이라 좁은 폭에서도 성립하므로, 제목이 상단으로 올라가고 메타가
     표지 옆에 선다. §G-2("3권이 나란해야")도 이 구조에선 여전히 미적용(1열 유지) — 항목당 세로
     블록이 크고, 3열로 나누면 표지·값이 동시에 접힌다. 마크업 순서는 불변(그리드 배치만 이동)이라
     데스크톱 렌더는 픽셀 단위로 영향이 없다. */
  .books { grid-template-columns: minmax(0, 1fr); column-gap: 0; row-gap: var(--sp-6); }
  /* 표지 폭 46% — 제목이 상단으로 빠져 표지가 열을 독점하므로 40%에서 상향(@designer 확정).
     §Z-1 픽셀 상한 260px은 유지한다: 비율만 쓰면 768에서 커버가 353px이 되고 DPR-2에서 706px이라
     원본(568px) 업스케일이 발생한다 — 비율안으로 되돌리지 않는 근거가 여기에도 그대로 적용된다. */
  .books li { grid-template-columns: min(260px, 46%) minmax(0, 1fr); column-gap: var(--sp-3); }
  .book-body { grid-column: 1 / -1; grid-row: 1; }   /* 제목·부제(·배지) 전폭 상단 */
  .books li > img,
  .books li .book-link > img { grid-column: 1; grid-row: 2; }
  .book-meta {
    grid-column: 2;
    grid-row: 2;
    /* 목표 화면 실측: 첫 값이 표지 상단선에 정렬된다(@designer §3 정밀화).
       중앙/하단 정렬은 2열의 시작선을 어긋나게 해 "하나의 표"가 아니라 두 덩어리로 읽힌다. */
    align-self: start;
  }
  /* 라벨 생략(사용자 확정) — 값만 보인다. display:none·visibility:hidden 은 접근성 트리에서도
     제거되므로 쓰지 않고, clip 방식 시각적 숨김으로 dt 를 트리에 남긴다("602"가 비시각 사용자에게
     PAGES 로 읽힌다). dl/dt/dd 구조가 이미 있어 aria-label 추가는 불필요. 데스크톱은 라벨 표시 유지. */
  .book-meta dt {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  /* 라벨 열이 사라진 자리 — 값 1열 + 행 피치 확대(라벨 리듬이 없어진 자리를 행간이 메운다).
     최종 기준은 수치가 아니라 열 균형(메타 높이 = 표지 높이 ±15%, §5 판정). */
  /* 행 피치 32px — 목표 스크린샷 실측 환산치(이미지 행 피치 49px × 스케일 0.658), @designer §3.
     실측 분해: 값 fs 14px × lh 1.5 = 21px + padding-block 5.5px×2 = 32px. */
  .book-meta > div {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding-block: 5.5px;
  }
  /* 값 조판: 360 폭에서 메타 열이 149px까지 좁아져 최장값(SERIES "Part 3 · Understanding AI")이
     2행으로 접힌다 — 라벨 리듬이 사라진 자리를 행간으로 메우는 의도가 랩으로 깨지므로,
     캡션 계열의 트래킹을 살짝 죄어(-0.01em) 폭을 흡수한다(크기·색·서체 불변). */
  /* 값 조판: 360 폭에서 메타 열이 149px까지 좁아져 최장값(SERIES "Part 3 · Understanding AI")이
     2행으로 접힌다 — 라벨 리듬이 사라진 자리를 행간으로 메우는 의도가 랩으로 깨지므로,
     캡션 계열의 트래킹을 살짝 죄어(-0.01em) 폭을 흡수한다(크기·색·서체 불변). */
  .book-meta dd { line-height: 1.5; letter-spacing: -0.01em; }
  .book-title { font-size: var(--fs-card); }
  .email { font-size: clamp(1.125rem, 5.6vw, 1.75rem); word-break: break-all; }  /* 거대 타이포 금지 */
  .links { grid-template-columns: 1fr; }
  /* 네비 축소 오버라이드 폐기(SPEC-NAV-MOBILE §1, 2026-09-01) — 768 이하는 햄버거 오버레이가 받는다.
     축소는 5항목을 억지로 밀어넣던 타협이었고, 햄버거 전환의 목적이 그 타협을 없애는 것이다. */
  /* 터치 타깃 44px — 작은 라벨 링크는 글자 크기 대신 패딩으로 확보한다 */
  .topnav a, .links a, .email { display: inline-block; min-height: 44px; line-height: 44px; }
  .links li .label { margin-bottom: 0; }
  .topbar { padding-block: 0; }
}

/* 태블릿(600~768) — 인물이 화면 우측 끝에 밀착해 가운데가 비어 보인다는 사용자 지적 반영.
   좌측으로 당기면 390·430과 같은 구조(얼굴은 텍스트 위, 정장만 헤드라인 뒤)가 이 폭에서도 성립한다.
   ※ 모바일 블록 뒤에 두어야 우선순위가 유지된다. */
@media (min-width: 600px) and (max-width: 768px) {
  /* §G-7 — 20%. 16%는 겹침 1.4%p라 폰트 폴백(Times New Roman에서 잉크 우단 51.4%)에서
     겹침이 음수로 뒤집힌다. 20%면 폴백 하한에서도 3.8%p가 남는다. */
  .hero-portrait { right: 20%; }
  /* §P는 폰(≤600) 조건에서 세운 값이다. 이 구간에 그대로 적용하면 폭 기준 크기가 폭발한다 —
     768폭에서 인물 높이가 112vh가 되어 머리가 화면 위로 잘리고 실루엣이 48.3% → 64.7%로 커진다.
     이 구간만 이전 기하(84vh 고정 + 레터박싱)를 유지한다. 높이 축소는 @designer 판단 대기. */
  .hero { min-height: 100svh; }
  .hero-portrait { width: auto; height: 84vh; }
  .hero-portrait img { width: auto; height: 100%; }
}

/* v3.0 데스크톱 구도 고정 — 히어로 텍스트만 컨테이너 중앙 정렬에서 좌측 마진 기준으로 바꾼다.
   중앙 정렬이면 넓은 화면에서 잉크 우단이 53.8% → 66.7%로 밀려 레퍼런스 구도가 깨진다.
   폰트 상한(9.3rem)도 해제해야 2560px에서 잉크 우단이 40.3%로 무너지지 않는다. */
@media (min-width: 769px) {
  /* §H-3 최종 — 전역 컨테이너 92vw, 히어로 예외 폐기.
     헤더·히어로·캐러셀·PRACTICE 네 기준선이 계산이 아니라 구조적으로 0px 일치한다.
     (1280 안은 검토·폐기: 2560에서 콘텐츠 2355px·카드 589px 우려가 있었으나 §H-5 카드 상한으로 해소,
      대신 1280 안에서는 섹션−히어로 좌단이 2560에서 568px 튄다.) */
  .hero-title { font-size: max(2.75rem, 7.6vw); }
}

/* §H-5 현재 유효 — 92vw에서는 카드 그리드만 무한히 늘어나므로 고유 상한을 준다.
   캐러셀 996px · PROFILE 64ch · 저서 780px과 같은 패턴. 중앙 정렬 금지(좌단은 컨테이너에 붙임).
   실측: 2560에서 그리드 1800px · 카드 432px(1920의 424px과 사실상 동일). */
.cards { max-width: none; }   /* §X — 상한은 컨테이너로 이관됐다(위 .sec 규칙). 여기에 되살리지 말 것 */




/* 위 minmax(0,1fr)의 짝 — 트랙이 최장 단어보다 좁아질 수 있는 구간에서 단어가 셀을 밀지 않게 한다.
   일반 폭에서는 단어가 트랙보다 짧아 아무 효과가 없다. */
.card .h3, .card .body, .card .card-note { overflow-wrap: break-word; }
