@charset "UTF-8";
/*
* 텍스트 등장 애니메이션 — 마케터비
* Copyright 마케터비
*
* ====================================================================================================================
* [설계 원칙] JS 가 없으면 아무것도 숨기지 않는다
* --------------------------------------------------------------------------------------------------------------------
* 이 파일의 "숨김" 규칙은 전부 `html.text-anim-ready` 로 시작한다.
* `text-anim-ready` 는 오직 js/text-anim.js 가 스스로 붙이는 클래스다.
*   - js/text-anim.js 가 404 이거나 로드 실패  -> 클래스 없음 -> 숨김 규칙 미적용 -> 텍스트 그대로 보임
*   - js/text-anim.js 가 실행 중 예외          -> 스스로 클래스를 떼어냄  -> 텍스트 그대로 보임
*   - IntersectionObserver 미지원 / 모션최소화 -> 애초에 클래스를 붙이지 않음 -> 텍스트 그대로 보임
* 즉 "CSS 만으로 opacity:0 을 거는" 상황이 존재하지 않는다. 이 규칙을 절대 깨지 말 것.
*
* [모드] body 클래스로 결정 (preferences.html 이 $li->text_anim 값을 읽어 부여)
*   text-anim-fade  : 아래에서 살짝 올라오며 페이드인
*   text-anim-mask  : 마스크 슬라이드 (부모 overflow:hidden + 자식 래퍼 translateY)
*   text-anim-char  : 한 글자씩 순차 등장 (JS 가 span 분리)
*   text-anim-none  : 효과 없음
*
* [실제 효과는 data-text-anim 이 결정한다]
* body 모드 클래스는 "JS 가 무엇을 준비할지"만 정한다. 준비를 마친 요소에는 JS 가
* data-text-anim="fade|mask|char|none" 을 직접 찍고, 아래 효과 규칙은 그 값만 본다.
* 이렇게 해야 char 모드에서 폴백된 요소(긴 문장 / HTML 포함)를 fade 로 되돌리는 것이
* 별도 예외 규칙 없이 자연스럽게 처리된다.
*
* [기존 애니메이션과의 관계]
*   - AOS(data-aos) 와 같은 요소/조상에 겹치면 둘 다 opacity/transform 을 만져 싸운다.
*     -> js/text-anim.js 가 [data-aos] 조상을 가진 요소를 통째로 건너뛴다(양보).
*   - 히어로 슬라이더 캡션은 css/main.css 의 xetHeroUp 이 이미 담당하고 Swiper 슬라이드
*     전환마다 재생되므로, .main-visual / .swiper-slide 내부도 JS 가 건너뛴다.
*
* [토큰] 새 CSS 변수를 만들지 않는다. 이징은 css/main.css 의 xetHeroUp 과 동일한
*        cubic-bezier(0.22, 1, 0.36, 1) 을 그대로 써서 테마 전체의 감각을 맞춘다.
* ==================================================================================================================== */

/* --------------------------------------------------------------------------------------------------------------------
   1. 준비 전 깜빡임 방지
   mask / char 는 JS 가 DOM 을 손봐야 효과가 성립한다. 손보기 전 원문이 잠깐 보였다가
   튀어오르는 것을 막기 위해, "아직 준비되지 않은" 요소만 잠깐 감춘다.
   fade 는 DOM 조작이 없지만 동일한 초기 상태를 위해 같이 묶는다.
   ※ 이 규칙도 html.text-anim-ready 하위이므로 JS 실패 시에는 적용되지 않는다.
   ※ JS 는 건너뛴 요소에도 반드시 data-text-anim="none" 을 찍어 이 규칙에서 빠져나가게 한다.
   -------------------------------------------------------------------------------------------------------------------- */
html.text-anim-ready body.text-anim-fade .js-text-anim:not([data-text-anim]),
html.text-anim-ready body.text-anim-mask .js-text-anim:not([data-text-anim]),
html.text-anim-ready body.text-anim-char .js-text-anim:not([data-text-anim]) {
  opacity: 0;
}

/* text-anim-none : 효과 없음. JS 도 즉시 빠져나가지만, 모드 4종에 CSS 분기를 모두
   명시해 두어 "어떤 모드에서도 감춰지지 않는다"는 것을 코드로 드러낸다. */
html.text-anim-ready body.text-anim-none .js-text-anim {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------------------------------------------------
   2. fade — 아래에서 올라오기
   이동거리 24px. 벤치마킹(prugio-riverfront) 기준으로 과하지 않게 절제한다.
   -------------------------------------------------------------------------------------------------------------------- */
html.text-anim-ready .js-text-anim[data-text-anim="fade"] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1), transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
html.text-anim-ready .js-text-anim[data-text-anim="fade"]:not(.is-text-anim-in) {
  will-change: opacity, transform;
}
html.text-anim-ready .js-text-anim[data-text-anim="fade"].is-text-anim-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* --------------------------------------------------------------------------------------------------------------------
   3. mask — 마스크 슬라이드
   부모가 창(overflow:hidden), 자식 래퍼(.text-anim__mask)가 아래에서 올라와 드러난다.
   ※ overflow:hidden 은 디센더(g, y, 한글 받침 등)를 잘라먹는다.
     padding-bottom 으로 여유를 주고 같은 값의 음수 margin 으로 레이아웃 높이를 되돌린다.
   ※ 자식은 inline-block 이라 부모의 text-align(제목은 center) 을 그대로 따른다.
   -------------------------------------------------------------------------------------------------------------------- */
html.text-anim-ready .js-text-anim[data-text-anim="mask"] {
  display: block;
  overflow: hidden;
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}
html.text-anim-ready .js-text-anim[data-text-anim="mask"] .text-anim__mask {
  display: inline-block;
  max-width: 100%;
  transform: translateY(110%);
  transition: transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
html.text-anim-ready .js-text-anim[data-text-anim="mask"].is-text-anim-in .text-anim__mask {
  transform: none;
  will-change: auto;
}

/* --------------------------------------------------------------------------------------------------------------------
   4. char — 한 글자씩
   구조: [sr 원문(스크린리더 전용)] + [aria-hidden 시각용 래퍼 > .text-anim__word > .text-anim__char]
   ※ 줄바꿈: 공백은 span 으로 감싸지 않고 텍스트 노드 그대로 둔다(줄바꿈 기회 보존).
     단어는 .text-anim__word(inline-block)로 묶어 단어 중간에서 끊기지 않게 한다.
   ※ 글자 지연(transition-delay)은 JS 가 인라인으로 넣는다. 새 CSS 변수를 만들지 않기 위함.
   ※ .text-anim__char 에는 will-change 를 걸지 않는다 — 글자 수만큼 합성 레이어가 생겨 역효과.
   -------------------------------------------------------------------------------------------------------------------- */
html.text-anim-ready .js-text-anim[data-text-anim="char"] .text-anim__word {
  display: inline-block;
}
html.text-anim-ready .js-text-anim[data-text-anim="char"] .text-anim__char {
  display: inline-block;
  opacity: 0;
  transform: translateY(0.45em);
  transition: opacity 0.5s cubic-bezier(0.22, 1, 0.36, 1), transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
html.text-anim-ready .js-text-anim[data-text-anim="char"].is-text-anim-in .text-anim__char {
  opacity: 1;
  transform: none;
}

/* --------------------------------------------------------------------------------------------------------------------
   5. 스크린리더 전용 원문 보관함
   글자를 span 으로 쪼개면 스크린리더가 "분 · 양 · 안 · 내" 처럼 한 글자씩 읽는다.
   시각용 span 은 전부 aria-hidden="true" 로 감추고, 원문은 이 요소에 그대로 남긴다.
   (테마에 기존 sr-only 계열 클래스가 없어 여기서 정의한다. CSS 변수가 아니라 유틸 클래스다.)
   -------------------------------------------------------------------------------------------------------------------- */
.text-anim__sr {
  position: absolute !important;
  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;
}

/* --------------------------------------------------------------------------------------------------------------------
   6. 모션 최소화 선호 사용자 배려
   js/text-anim.js 도 같은 조건에서 아예 실행되지 않지만, 페이지 로드 후 OS 설정이
   바뀌는 경우까지 덮기 위해 CSS 로 한 겹 더 잠근다. (css/main.css 16번 구간과 같은 방침)
   -------------------------------------------------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html .js-text-anim,
  html .js-text-anim .text-anim__mask,
  html .js-text-anim .text-anim__word,
  html .js-text-anim .text-anim__char {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
    will-change: auto !important;
  }
  html .js-text-anim[data-text-anim="mask"] {
    overflow: visible !important;
    padding-bottom: 0 !important;
    margin-bottom: 0 !important;
  }
}
