/* ==========================================================================
 * motion.css — 리빌 / 상태 클래스 스타일
 * ==========================================================================
 * 여기 있는 규칙은 JS 와 짝을 이루는 "계약"이다. CONTRACT.md §4 가 원문이고
 * 이 파일이 그 구현이다. 규칙 하나를 빼면 해당 인터랙션이 조용히 죽는다.
 *
 * 역할 분담
 *   JS  : 클래스(is-inview / is-loaded / …)를 붙이고, 자식에
 *         --line / --word / --char / --i 인덱스를 심는다.
 *   CSS : 그 클래스와 인덱스를 읽어 실제로 움직인다.
 *
 * 시작 상태를 왜 html.js 안에 가두나
 *   head 인라인 스크립트가 <html class="no-js"> → "js" 로 바꾼다.
 *   JS 가 죽으면 no-js 가 남고, 아래 opacity:0 규칙이 안 걸려서
 *   콘텐츠가 그냥 보인다. 빈 화면이 되는 사고를 막는 안전장치다.
 * ========================================================================== */

/* ======================================================================== *
 * 1. 텍스트 리빌 — .reveal-lines / .reveal-words / .reveal-chars
 * ======================================================================== */

.reveal-lines:where(html.js *),
.reveal-words:where(html.js *),
.reveal-chars:where(html.js *) {
  opacity: 0;
}

.reveal-lines.is-inview,
.reveal-words.is-inview,
.reveal-chars.is-inview {
  opacity: 1;
}

/* --- 단어 단위: 아래에서 밀려 올라온다 --------------------------------- */
.reveal-words .word,
.reveal-chars .char {
  transform: translateY(130%);
  transition: transform var(--dur-reveal-word) var(--ease-out-expo);
  transition-delay: calc(var(--stagger-word) * var(--word, 0));
}

.reveal-chars .char {
  transition-delay: calc(var(--stagger-word) * var(--char, 0));
}

.reveal-words.is-inview .word,
.reveal-chars.is-inview .char {
  transform: translateY(0);
}

/* --- 줄 단위: 살짝 올라오며 흐려짐이 걷힌다 ---------------------------- */
.reveal-lines .line {
  translate: 0 1em;
  opacity: 0;
  transition:
    translate var(--dur-reveal-line) var(--ease-magic),
    opacity var(--dur-reveal-line) var(--ease-magic);
}

.reveal-lines.is-inview .line {
  translate: 0 0;
  opacity: 1;
  transition-delay: calc(var(--line, 0) * var(--stagger-line));
}

/* ======================================================================== *
 * 2. 디바이더 — 왼쪽에서 오른쪽으로 그어진다
 * ======================================================================== */

.divider {
  width: 100%;
  height: var(--divider-height);
  background: var(--color-line);
}

.reveal-divider {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-reveal-line) var(--ease-out-expo);
}

.reveal-divider.is-inview {
  transform: scaleX(1);
}

/* ======================================================================== *
 * 3. 이미지 리빌 — 아래에서 위로 클립이 걷힌다
 * ======================================================================== *
 * is-inview(화면 진입) 와 is-loaded(이미지 로드 완료) 가 **둘 다** 있어야
 * 재생된다. 로드 전에 재생되면 빈 사각형만 열리기 때문이다.
 */

.reveal-image {
  overflow: hidden;
  background: var(--surface-2);
}

.reveal-image img,
.reveal-image video {
  opacity: 0;
}

.reveal-image.is-inview.is-loaded img,
.reveal-image.is-inview.is-loaded video {
  opacity: 1;
  animation: reveal-clip var(--dur-reveal-image) var(--ease-quart) both;
  animation-delay: calc(var(--i, 0) * var(--stagger-image));
}

@keyframes reveal-clip {
  from {
    clip-path: inset(0 0 100%);
    transform: scale(1.1) translateY(-5%);
  }
}

/* 개별 이미지 페이드 — [data-img-fade]
 * (CONTRACT §5.1 의 신규 훅. 기존 Next/Image 방식 img.opacity-0 을 대체한다)
 *
 * JS 계약: mod-reveal.js 가 이 이미지의 load 이벤트에서 **is-loaded** 를 붙인다.
 *          래퍼 쪽에 붙이는 구현이어도 동작하도록 두 경로를 모두 받는다.
 *          error 때는 붙이지 않는 게 원본 동작이므로, 깨진 이미지는 안 보인다.
 */
[data-img-fade]:where(html.js *) {
  opacity: 0;
  transition: opacity var(--dur-image) var(--ease-ui);
}

[data-img-fade].is-loaded,
.is-loaded > [data-img-fade],
.is-loaded [data-img-fade] {
  opacity: 1;
}

/* ======================================================================== *
 * 4. SplitText 출력물
 * ======================================================================== *
 * 아래 이름과 구조는 mod-reveal.js 가 SplitText 로 만들어 낸다.
 * 마스크는 글자가 위/아래로 삐져나올 때 잘리게 하는 역할인데,
 * 악센트나 디센더가 잘리면 안 되므로 --bleed 만큼 clip 을 열어 둔다.
 */

.line-mask {
  display: block;
}

.word-mask,
.char-mask,
.line,
.word,
.char {
  display: inline-block;
}

/* 낱말/글자 마스크는 옆 글자와 겹치지 않게 오른쪽으로 조금 벌린 뒤 다시 당긴다 */
.word-mask,
.char-mask {
  --pad: 0.08em;
  padding-right: var(--pad);
  margin-right: calc(var(--pad) * -1);
}

.line-mask,
.word-mask,
.char-mask {
  --bleed: 0.3em;
  clip-path: inset(calc(var(--bleed) * -1) 0);
  overflow: visible !important;
}

/* ======================================================================== *
 * 5. 인뷰 상태 일반
 * ======================================================================== *
 * data-scroll 만 붙은 요소는 클래스 이름 없이도 부드럽게 나타나게 해 둔다.
 * (리빌 클래스가 붙은 요소는 위 규칙이 이미 처리하므로 제외)
 */

/* :not(...) 을 :where() 로 감싸는 게 중요하다. 그냥 :not(.a):not(.b) 로 쓰면
   특정도가 (0,6,0) 까지 올라가서 아래 .is-inview 규칙(0,2,0)을 눌러 버린다.
   그러면 요소가 화면에 들어와도 영영 opacity:0 으로 남는다. */
[data-scroll]:where(html.js *):where(
    :not(.reveal-lines, .reveal-words, .reveal-chars, .reveal-divider, .reveal-image)
  ) {
  opacity: 0;
  transition: opacity var(--dur-reveal-line) var(--ease-out-expo);
}

[data-scroll].is-inview {
  opacity: 1;
}

/* 패럴랙스 대상은 매 프레임 transform 이 바뀐다 */
[data-scroll-speed] {
  will-change: transform;
}

/* ======================================================================== *
 * 6. 연표 호버 디밍
 * ======================================================================== *
 * 한 항목에 마우스를 올리면 나머지가 가라앉는다.
 * :has 를 못 쓰는 브라우저에서는 mod-timeline.js 가 인라인 opacity 로 흉내낸다.
 */

.timeline__item,
[data-timeline-item] {
  transition: opacity var(--dur-medium) var(--ease-magic);
}

@media (hover: hover) and (min-width: 768px) {
  .timeline__grid:has([data-timeline-item]:hover) [data-timeline-item]:not(:hover),
  [data-timeline-grid]:has([data-timeline-item]:hover) [data-timeline-item]:not(:hover) {
    opacity: var(--opacity-dimmed);
  }

  /* 정작 올려놓은 항목은 또렷해야 한다.
     연표 항목은 <a> 라서 base.css 의 일반 링크 호버(opacity .6)가 걸린다.
     그대로 두면 "나머지를 죽여 하나를 띄운다"는 이 연출이 반만 산다. */
  .timeline__item:hover,
  [data-timeline-item]:hover {
    opacity: 1;
  }
}

/* 연도 라벨: 진행선이 지나가면 JS 가 opacity 를 1 로 올린다 */
.timeline__year,
[data-timeline-year],
.timeline__year-mobile,
[data-timeline-item-year-mobile] {
  transition: opacity var(--dur-medium) var(--ease-magic);
}

/* ======================================================================== *
 * 7. 푸터 색 반전 — body.is-footer-inview
 * ======================================================================== *
 * mod-transition.js 가 푸터가 화면 중앙에 닿으면 body 에 붙인다.
 * 전환 시간(0.7s)은 base.css 의 body 규칙이 갖고 있다.
 */

body.is-footer-inview {
  background-color: var(--color-accent);
  color: var(--color-ink-invert);
}

/* 반전 상태에서는 반투명 판도 어두운 쪽으로 뒤집는다 */
body.is-footer-inview {
  --surface-1: var(--surface-1-invert);
  --surface-2: var(--surface-2-invert);
  --surface-3: var(--surface-3-invert);
}

/* ======================================================================== *
 * 8. 모션 최소화
 * ======================================================================== *
 * boot.js 가 prefers-reduced-motion 을 감지해 <html> 에 reduced-motion 을 붙인다.
 * 이 경우 전부 "최종 상태"로 고정한다. 애니메이션이 아니라 결과만 남긴다.
 * 미디어 쿼리도 함께 걸어 JS 가 늦게 뜨는 순간에도 튀지 않게 한다.
 */

html.reduced-motion .reveal-lines,
html.reduced-motion .reveal-words,
html.reduced-motion .reveal-chars {
  opacity: 1 !important;
}

html.reduced-motion .line,
html.reduced-motion .word,
html.reduced-motion .char {
  transform: none !important;
  translate: none !important;
  opacity: 1 !important;
}

html.reduced-motion .reveal-divider {
  transform: scaleX(1) !important;
}

html.reduced-motion .reveal-image img,
html.reduced-motion .reveal-image video,
html.reduced-motion [data-img-fade] {
  opacity: 1 !important;
  animation: none !important;
}

html.reduced-motion [data-scroll] {
  opacity: 1 !important;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .reveal-lines,
  .reveal-words,
  .reveal-chars,
  [data-scroll],
  [data-img-fade] {
    opacity: 1 !important;
  }

  .reveal-lines .line,
  .reveal-words .word,
  .reveal-chars .char {
    transform: none !important;
    translate: none !important;
    opacity: 1 !important;
  }

  .reveal-divider {
    transform: scaleX(1) !important;
  }

  .reveal-image img,
  .reveal-image video {
    opacity: 1 !important;
    animation: none !important;
  }
}

/* mod-reveal.js 가 비상 탈출구로 쓰는 클래스.
   무슨 이유로든 리빌을 통째로 꺼야 할 때 <html> 에 붙인다. */
html.slice-preview .reveal-lines,
html.slice-preview .reveal-words,
html.slice-preview .reveal-chars,
html.slice-preview [data-scroll],
html.slice-preview [data-img-fade] {
  opacity: 1 !important;
}

html.slice-preview .reveal-lines .line,
html.slice-preview .reveal-words .word,
html.slice-preview .reveal-chars .char {
  transform: none !important;
  translate: none !important;
  opacity: 1 !important;
}

html.slice-preview .reveal-divider {
  transform: scaleX(1) !important;
}

html.slice-preview .reveal-image img,
html.slice-preview .reveal-image video {
  opacity: 1 !important;
  animation: none !important;
}

/* 히어로 로고·타이틀은 인트로가 데려온다.
   JS 가 살아 있을 때만 숨겨 둔다 — JS 가 죽으면 그냥 보여야 하니까. */
html.js .hero__logo { opacity: 0; }
/* ⚠ 여기서 transform 을 주면 안 된다.
   GSAP 가 CSS 의 translateY(115%) 를 px 로 환산해 따로 물고 있다가,
   yPercent 애니메이션이 0 으로 끝나도 그 px 이 남는다. 결과적으로 글자가
   마스크 밖(아래)에 머물러 영영 안 보인다. (실측: 끝나고도 translateY 110px)
   시작 상태는 투명도로만 가두고, 이동은 전적으로 GSAP 에 맡긴다. */
html.js .hero__title-line { opacity: 0; }
html.reduced-motion .hero__logo { opacity: 1 !important; }
html.reduced-motion .hero__title-line { opacity: 1 !important; transform: none !important; }

/* 히어로 타이틀을 SplitText 가 단어로 쪼갤 때 쓰는 마스크.
   .word-mask 는 SplitText 가 mask:"words" 로 만들어 준다.
   글자 위아래가 잘리지 않게 세로로 살짝 여유를 주고 같은 만큼 되돌린다. */
.hero__title .word-mask {
  display: inline-block;
  vertical-align: top;
  /* 리빌 시스템의 공용 마스크는 clip-path 로 자르고 위아래로 0.3em 을 흘려보낸다
     (.line-mask,.word-mask 규칙에 overflow:visible !important 가 걸려 있어서
      overflow:hidden 으로는 못 막는다).
     히어로 타이틀은 글자가 위/아래 양쪽에서 들어오므로 그 여유를 좁혀
     시작 위치가 삐져나오지 않게 한다. */
  clip-path: inset(-0.06em 0);
  padding: 0;
  margin: 0;
}
.hero__title .word { display: inline-block; will-change: transform; }

/* 단어로 쪼개진 뒤에는 줄 마스크가 겹쳐 자를 필요가 없다. */
.hero__title-mask:has(.word-mask) { overflow: visible; padding: 0; margin: 0; }
