/* ═════ 모션 계약 — 제작 스킬 scripts/motion-contract.css 원문 그대로(수정 금지) · 렌더 차단 시트를 3개로 두려고 한 파일로 합쳤다 ═════ */
/* ============================================================================
   MOTION CONTRACT  —  motion-engine.js 와 반드시 함께 배포한다
   ----------------------------------------------------------------------------
   ★ 이 파일이 없으면 엔진의 [data-motion-done] 계약이 아무 일도 하지 않는다.
     엔진은 "속성만 세팅"할 뿐, "정지=시안" 을 강제하는 것은 이 CSS다.
     엔진만 있고 이 파일이 없으면 다음이 전부 깨진다:

       · 진입 모션의 초기 숨김(opacity 0)이 CSS 로 안 걸려 → 전부 처음부터 보임
         (또는 GSAP from 상태가 도달 불가 트리거에 걸려 → 영구 opacity:0 로 갇힘)
       · 완료 요소의 인라인 transform 잔여물이 안 지워짐
       · prefers-reduced-motion 무시
       · 유동 레이아웃에서 모션 훅이 grid/flex 축소를 막아 가로 오버플로

   ----------------------------------------------------------------------------
   전제 (엔진과의 계약)
     · <html> 에 엔진이 js-motion 클래스를 붙인다. JS 가 죽으면 안 붙는다.
     · 진입 요소는 [data-motion], 마스크 리빌은 [data-motion-mask],
       라인 리빌은 .line-mask > .line, 완료 표식은 [data-motion-done].
     · 초기 상태 커스텀 프로퍼티: --m-o0/--m-x0/--m-y0/--m-s0/--m-r0/--m-clip0
     · 시안 알파는 opacity 가 아니라 color/background-color 의 알파에 둔다
       (RC-2b — GSAP 이 인라인 opacity:1 을 남겨도 시안 알파가 살아남게).
     · 섹션 래퍼 관용 클래스는 .fl-sec (유동 섹션). 화면고정 UI 는 그 밖.

   ★ !important 는 prefers-reduced-motion 블록에서만 쓴다(인라인 GSAP 을 이기려).
     그 외에는 한 줄도 쓰지 않는다.

   @layer 순서: 00-layer.css 에서 ..., motion, dlock; 로 선언되어 있어야 한다.
   ========================================================================== */

/* ==========================================================================
   [MOTION LAYER]  진입/리빌 상태 계약 + 인터랙션 + 접근성
   ========================================================================== */
@layer motion {

  /* 1. ★ JS 실패 안전장치 --------------------------------------------------
     기본 상태에서는 아무것도 숨기지 않는다. 모션 알파는 --mo 하나로 한정한다
     (시안 알파는 페인트에 있으므로 여기서 opacity:1 을 무차별 선언하지 않는다). */
  [data-motion] { opacity: var(--mo, 1); }

  /* 엔진이 살아있을 때만(html.js-motion) 초기 숨김 상태를 적용한다.
     개별 transform 속성을 써서 GSAP 인라인 transform 과 경합하지 않는다. */
  html.js-motion [data-motion]:not([data-motion-done]) {
    opacity: var(--m-o0, 0);
    translate: var(--m-x0, 0) var(--m-y0, 0);
    scale: var(--m-s0, 1);
    rotate: var(--m-r0, 0deg);
  }
  /* ★ CSS 절대규칙 — HTML 안에 style 코드를 한 줄도 두지 않는다(발주처 CSS 생성 절대규칙).
     예전에는 "이 호스트는 처음부터 보인다" 를 `style="--m-o0:1"` 인라인으로 적었다.
     같은 일을 **속성 선택자**로 한다: 마크업에는 `data-m-o0` 만 달면 된다. */
  [data-m-o0] { --m-o0: 1; }



  /* ★ LCP 후보는 **절대 숨기지 않는다**.
     `data-motion` 이 붙은 요소는 위 규칙으로 `opacity:0` 에서 시작하는데,
     첫 화면 대표 이미지에 그걸 걸면 브라우저가 그 요소를 늦게 칠하고
     LCP 가 그만큼 밀린다(함정 63·67). `data-motion-visible` 을 달면
     **처음부터 보인 채로** transform 계열 모션만 받는다 —
     scale/translate 는 페인트를 막지 않으므로 첫 프레임부터 그림이 있다. */
  html.js-motion [data-motion][data-motion-visible]:not([data-motion-done]) {
    opacity: var(--mo, 1);
  }

  /* 마스크 리빌용 클리핑 */
  html.js-motion [data-motion-mask]:not([data-motion-done]) {
    clip-path: var(--m-clip0, inset(0 0 100% 0));
  }

  /* ★ 완료 요소 — 인라인 잔여물을 정적 상태로 되돌린다.
     이것이 "도달 불가 트리거로 갇힘" 의 최종 복구선이다.
     엔진이 IntersectionObserver 로 화면 밖 요소에도 data-motion-done 을 찍으면
     여기서 opacity:1 로 복원된다(footer 카피 갇힘의 근본 해결). */
  [data-motion-done] {
    opacity: var(--mo, 1);
    translate: none;
    scale: none;
    rotate: none;
    clip-path: none;
  }

  /* 2. 라인 마스크 리빌 (부모 overflow:clip + 자식 translateY) --------------- */
  .line-mask { overflow: clip; display: block; }

  /* letter-spacing-settle 의 임시 클립 — 완료(=data-motion-done) 즉시 자동 해제된다.
     인라인 스타일로 걸었더니 완료 콜백이 건너뛰어질 때 영구히 남아 글자가 잘렸다. */
  html.js-motion [data-ls-settle]:not([data-motion-done]) { overflow: clip; white-space: nowrap; }
  html.js-motion .line-mask > .line:not([data-motion-done]) { translate: 0 110%; }

  /* 3. CSS 전용 스크롤 리빌 (@supports 게이팅 · 미지원 시 정적 노출) --------- */
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .reveal-css {
        animation: mc-reveal linear both;
        animation-timeline: view();
        animation-range: entry 10% cover 32%;
      }
      @keyframes mc-reveal {
        from { opacity: 0; translate: 0 24px; }
        to   { opacity: 1; translate: 0 0; }
      }
    }
  }

  /* 4. 인터랙션 (호버/프레스) — 항상 속성 명시, transition:all 금지 ---------- */
  .i-lift {
    transition-property: translate;
    transition-duration: var(--dur-fast, .2s);
    transition-timing-function: var(--ease-out-quart, cubic-bezier(.22,1,.36,1));
    transform-origin: bottom center;
  }
  @media (hover: hover) and (pointer: fine) {
    .i-lift:hover { translate: 0 -4px; }
  }
  .i-press {
    transition-property: scale;
    transition-duration: var(--dur-instant, .12s);
    transition-timing-function: var(--ease-out-quart, cubic-bezier(.22,1,.36,1));
  }
  .i-press:active { scale: .97; }

  /* 5. ★ prefers-reduced-motion — 옵션이 아니라 기본 (WCAG 2.3.3) ------------
     "느리게" 가 아니라 "즉시 최종 상태". 인라인 GSAP 을 이겨야 하므로
     이 블록에서만 !important 를 쓴다. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: .01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: .01ms !important;
      scroll-behavior: auto !important;
    }
    html.js-motion [data-motion],
    html.js-motion [data-motion-mask],
    html.js-motion .line-mask > .line {
      opacity: var(--mo, 1) !important;
      translate: none !important;
      scale: none !important;
      rotate: none !important;
      clip-path: none !important;
    }
    [data-parallax], [data-marquee], [data-autoplay] {
      animation: none !important;
      translate: none !important;
    }
  }
  @media (prefers-reduced-motion: no-preference) {
    html:focus-within { scroll-behavior: smooth; }
  }

  /* 6. 성능 훅 — will-change 는 엔진이 애니메이션 직전에만 부여한다 ---------- */
  .will-animate { will-change: transform, opacity; }
}

/* ==========================================================================
   [DLOCK LAYER]  마지막 레이어 — 모션이 디자인/유동배치를 이기지 못하게
   --------------------------------------------------------------------------
   motion 레이어는 components 레이어를 이긴다. 그 힘이 유동 레이아웃을 깨지
   못하도록 마지막에 두 가지 전제를 복원한다. 배치 값을 새로 정하지 않는다.
   ========================================================================== */
@layer dlock {

  /* 그리드/플렉스 아이템은 콘텐츠보다 작아질 수 있어야 한다.
     모션 훅이 붙은 노드의 min-*-size:auto 때문에 트랙이 밀려 가로 오버플로가
     나는 것을 막는다(유동형에서 가장 비싼 사고). */
  .fl-sec :is([data-motion], [data-motion-hover], [data-motion-touch]) {
    min-inline-size: 0;
  }

  /* .line-mask(overflow:clip) 는 폭을 부모에게서 받아야 한다.
     안 그러면 콘텐츠 폭으로 커져 래퍼 상한을 넘는다. */
  .fl-sec .line-mask { max-inline-size: 100%; }

  /* --------------------------------------------------------------------------
     [선택 · 폰트별] locl 글리프 전사
     Noto Sans CJK KR 처럼 <html lang="ko"> 에서 KOR locl lookup 이 문장부호를
     한국어 전용 글리프로 치환하는 폰트를 쓰면, Figma 렌더와 글자 모양이 달라진다
     (특히 가운뎃점 · / 공백 advance). 그런 폰트일 때만 아래 주석을 해제한다.
     Pretendard / Poppins / Inter 는 해당 lookup 이 없어 불필요하다.
     .fl-sec, .fl-sec * { font-feature-settings: "locl" 0; }
     -------------------------------------------------------------------------- */
}

/* ═════ // 모션 계약 ═════ */

/* motion — Motion Director 소유(D3). 초기 상태는 .js 가 있을 때만 건다(JS 꺼짐 = 시안 최종 상태) */
/* 등장 3종의 초기 상태(JS 가 살아 있을 때만) — 진행은 main.js(GSAP)가 몰고, 끝나면 is-in 이 최종 그림을 고정한다 */
.js [data-motion="fade-up"] { opacity: 0; transform: translate3d(0, 40px, 0); }
.js [data-motion="mask-up"] { opacity: 0; transform: translate3d(0, 60px, 0); clip-path: inset(0 0 100% 0); }
.js [data-motion="clip-up"] { clip-path: inset(100% 0 0 0); }
.js [data-motion="clip-up"] img { transform: scale(1.12); }
.js [data-motion].is-in { opacity: 1; transform: none; clip-path: none; }
.js [data-motion="clip-up"].is-in img { transform: none; }

/* hero — 슬라이드 교차 · 켄번스 · 제목 줄 등장 */
.js .hero__slide { transition: opacity 1.4s ease, visibility 0s 1.4s; }
.js .hero__slide.is-active { transition: opacity 1.4s ease; }
/* 켄번스는 활성 슬라이드에서만 도는 키프레임 — 대기 슬라이드에 배율 잔여값을 남기지 않는다(정착 기하 = 레이아웃) */
.js .hero__slide.is-active .hero__bg { animation: ken-burns 7s linear both; }
@keyframes ken-burns {
  from { transform: scale(1.08); }
  to { transform: scale(1); }
}
/* 제목 줄은 키프레임으로 올린다 — 비활성 제목에 opacity 0 을 남기지 않는다(정지 그림 = 시안) */
.js .hero__title.is-active .hero__line { animation: hero-line-in 1.1s var(--ease-out) both; }
.js .hero__title.is-active .hero__line + .hero__line { animation-delay: .12s; }
@keyframes hero-line-in {
  from { opacity: 0; transform: translate3d(0, 60%, 0); }
  to { opacity: 1; transform: none; }
}
.js .hero__logo { opacity: 0; transform: translate3d(0, 20px, 0); transition: opacity 1s var(--ease-out) .2s, transform 1s var(--ease-out) .2s; }
.js .is-ready .hero__logo { opacity: 1; transform: none; }
.hero__scroll-bar span { animation: scroll-drip 2.2s cubic-bezier(.65, 0, .35, 1) infinite; }
@keyframes scroll-drip {
  0% { transform: translateY(-100%); }
  60%, 100% { transform: translateY(200%); }
}


/* portfolio — 카드 전환 */
.js .product { transition: transform .9s var(--ease-out), opacity .6s ease; }
.js .product__text { transition: opacity .5s ease; }
.js .product:not(.is-active) .product__text { transition: opacity .3s ease, visibility 0s .3s; }
.js .product:not(.is-active) { cursor: pointer; }

@media (prefers-reduced-motion: reduce) {
  .js [data-motion], .js [data-motion] img { opacity: 1 !important; transform: none !important; clip-path: none !important; transition: none !important; }
  .js .hero__slide.is-active .hero__bg { animation: none; }
  .js .hero__logo { transform: none !important; transition: none !important; }
  /* 자동 전환이 꺼지므로 대기 슬라이드는 영영 보이지 않는다 — 렌더 트리에서 뺀다 */
  .hero__slide:not(.is-active), .hero__title:not(.is-active) { display: none; }
  .js .hero__title.is-active .hero__line { animation: none; }
  .js .hero__logo { opacity: 1; }
  .hero__scroll-bar span { animation: none; }
}
