/* 공용 연출 층.
 *
 * 세계 수상 홈페이지를 코드 수준으로 분해해 뽑은 기법만 담는다.
 * 출처는 각 블록 주석에 적었다. 외부 라이브러리는 하나도 쓰지 않는다.
 *
 * 중요 — @property 로 등록하지 않은 커스텀 속성은 보간되지 않는다.
 * 등록 없이 --p 를 mask-image 안에서 애니메이션하면 부드럽게 변하지 않고
 * 0% 에서 100% 로 계단식으로 튄다. 그래서 아래 등록이 먼저 온다.
 */

/* ── @property 등록 ──────────────────────────────────────── */

@property --sf-s {
  syntax: '<length-percentage>';
  inherits: false;
  initial-value: 0px;
}
@property --sf-e {
  syntax: '<length-percentage>';
  inherits: false;
  initial-value: 96px;
}
@property --wipe {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}
@property --curtain {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 100%;
}
@property --pan {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 50%;
}

/* ══ 1. 자동 반전 레이어 ═══════════════════════════════════
   출처: NORMAL IS BORING (Awwwards SOTD)
   화면 전체를 덮는 difference 레이어. 밑으로 사진이 지나가든
   흰 배경이 지나가든 글자가 스스로 반전되어 항상 읽힌다.
   그라디언트로 사진을 어둡게 덮을 필요가 없다. JS 0줄. */

.fx-invert {
  mix-blend-mode: difference;
  color: #fff;
}

.fx-invert-layer {
  position: fixed;
  inset: 0;
  height: 100dvh;
  mix-blend-mode: difference;
  pointer-events: none;
  z-index: 60;
}

.fx-invert-layer > * {
  pointer-events: auto;
  color: #fff;
}

/* difference 는 흰 배경 위 흰 글자를 검게 만든다. 그런데 어중간한
   회색 위에서는 대비가 무너진다. 강제 색 모드에서는 아예 끈다. */
@media (forced-colors: active) {
  .fx-invert,
  .fx-invert-layer {
    mix-blend-mode: normal;
  }
}

/* ══ 2. scroll-fade — 끝에 닿으면 사라지는 가장자리 ════════
   출처: racing.porsche.com
   가로 스크롤 레일의 좌우 끝을 흐리게 하되, 스크롤이 끝에 닿으면
   그쪽 페이드가 저절로 사라진다. JS 0줄.
   1ms 짜리 keyframes 를 scroll(self) 타임라인에 물리고
   animation-range 로 "끝 96px 구간만" 반응하게 만드는 것이 요령이다. */

.fx-scroll-fade {
  mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 var(--sf-s),
    #000 calc(100% - var(--sf-e)),
    transparent 100%
  );
}

@supports (animation-timeline: scroll(self)) {
  .fx-scroll-fade {
    animation: fxFadeStart 1ms linear both, fxFadeEnd 1ms linear both;
    animation-timeline: scroll(self inline), scroll(self inline);
    animation-range: 0 96px, calc(100% - 96px) 100%;
  }
}

@keyframes fxFadeStart {
  from {
    --sf-s: 0px;
  }
  to {
    --sf-s: 96px;
  }
}

@keyframes fxFadeEnd {
  from {
    --sf-e: 96px;
  }
  to {
    --sf-e: 0px;
  }
}

/* ══ 3. 오도미터 숫자 릴 ═══════════════════════════════════
   출처: racing.porsche.com (연도 표시)
   숫자가 슬롯머신처럼 굴러 제자리를 찾는다. 카운트업과 달리
   중간 값이 읽히지 않아 오독이 없다.

   접근성이 핵심이다. 화면에는 0~9 열 개가 다 들어있지만
   스크린리더에는 최종 숫자 하나만 읽혀야 한다. 그래서 릴 전체를
   aria-hidden 으로 감추고 옆에 .sr-only 로 진짜 값을 둔다. */

.fx-odo {
  display: inline-flex;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.fx-odo-d {
  display: inline-block;
  height: 1em;
  line-height: 1;
  overflow: hidden;
  /* overflow:hidden 인 inline-block 의 베이스라인은 박스 아래끝이다.
     그대로 두면 숫자가 옆 글자("원","명")보다 디센더만큼 높이 뜬다.
     그 높이를 내려 베이스라인을 맞춘다. */
  vertical-align: -0.12em;
}

.fx-odo-d > span {
  display: block;
  transform: translateY(calc(-1em * var(--d, 0)));
  transition: transform 1.1s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i, 0) * 60ms);
}

/* 쉼표·소수점은 굴리지 않는다 */
.fx-odo-sep {
  display: inline-block;
}

@media (prefers-reduced-motion: reduce) {
  .fx-odo-d > span {
    transition: none;
  }
}

/* ══ 4. 줄 단위 밝기 스크럽 ════════════════════════════════
   출처: NORMAL IS BORING
   문단이 스크롤에 맞춰 한 줄씩 켜진다. 원본은 GSAP ScrollTrigger
   scrub 1:1 이지만 animation-timeline: view() 로 같은 결과를 낸다.
   글자 단위 split 보다 긴 글에 어울린다. */

.fx-lines .fx-line {
  display: block;
  opacity: 0.22;
}

@supports (animation-timeline: view()) {
  .fx-lines .fx-line {
    animation: fxLineOn linear both;
    animation-timeline: view();
    animation-range: entry 25% cover 55%;
  }
}

@keyframes fxLineOn {
  to {
    opacity: 1;
  }
}

@supports not (animation-timeline: view()) {
  .fx-lines .fx-line {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-lines .fx-line {
    animation: none;
    opacity: 1;
  }
}

/* ══ 5. clip-path 커튼 ═════════════════════════════════════
   출처: racing.porsche.com 프리로더
   아래에서 위로 걷히는 커튼. 사진이나 섹션 등장에 쓴다. */

.fx-curtain {
  clip-path: inset(0 0 var(--curtain) 0);
}

@supports (animation-timeline: view()) {
  .fx-curtain {
    animation: fxCurtain linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 85%;
  }
}

@keyframes fxCurtain {
  from {
    --curtain: 100%;
  }
  to {
    --curtain: 0%;
  }
}

@supports not (animation-timeline: view()) {
  .fx-curtain {
    --curtain: 0%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-curtain {
    animation: none;
    --curtain: 0%;
  }
}

/* ══ 6. 원형 확장 와이프 ═══════════════════════════════════
   출처: IZANAMI (Awwwards SOTD)
   원본은 WebGL 프래그먼트 셰이더로 고리(annulus) 마스크를 만든다:
     edgeMask = smoothstep(p+e, p, d) * smoothstep(p+2e, p+e, d)
   CSS radial-gradient 마스크로 같은 인상을 낸다. 셰이더 없이. */

.fx-wipe {
  mask-image: radial-gradient(
    circle at 50% 55%,
    #000 var(--wipe),
    transparent calc(var(--wipe) + 14%)
  );
}

@supports (animation-timeline: view()) {
  .fx-wipe {
    animation: fxWipe linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 45%;
  }
}

@keyframes fxWipe {
  from {
    --wipe: 0%;
  }
  to {
    --wipe: 100%;
  }
}

@supports not (animation-timeline: view()) {
  .fx-wipe {
    --wipe: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-wipe {
    animation: none;
    --wipe: 100%;
  }
}

/* ══ 7. 스택형 sticky ══════════════════════════════════════
   출처: IZANAMI (CSS 안에 sticky 57회)
   형제 섹션을 sticky 로 나열하면 카드가 겹겹이 쌓인다.
   ::after 로 불투명 배경을 깔지 않으면 아래 섹션이 비쳐 지저분해진다. */

.fx-stack > * {
  position: sticky;
  top: var(--top-offset, 0px);
}

.fx-stack > *:not(:first-child) {
  border-top: 1px solid var(--line-dark, rgba(255, 255, 255, 0.14));
}

/* ══ 8. 이음매 없는 마퀴 ═══════════════════════════════════
   출처: racing.porsche.com
   콘텐츠를 정확히 2벌 넣고 -50% 까지 민다. -50% 가 딱 1벌
   분량이라 되돌아가는 순간이 보이지 않는다. */

.fx-marquee {
  overflow: hidden;
  -webkit-user-select: none;
  user-select: none;
}

.fx-marquee-in {
  display: flex;
  width: max-content;
  animation: fxMarquee var(--speed, 48s) linear infinite;
}

.fx-marquee:hover .fx-marquee-in {
  animation-play-state: paused;
}

@keyframes fxMarquee {
  to {
    transform: translate3d(-50%, 0, 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-marquee-in {
    animation: none;
  }
}

/* ══ 9. object-position 패럴랙스 ═══════════════════════════
   출처: IZANAMI (UV 창만 미는 트릭의 CSS 등가물)
   사진 자체는 제자리에 있고 보이는 창만 위아래로 움직인다.
   요소가 이동하지 않으므로 레이아웃 시프트가 0 이다. */

.fx-pan-wrap {
  overflow: clip;
}

.fx-pan {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% var(--pan);
}

@supports (animation-timeline: view()) {
  .fx-pan {
    animation: fxPan linear both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
}

@keyframes fxPan {
  from {
    --pan: 28%;
  }
  to {
    --pan: 72%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fx-pan {
    animation: none;
    --pan: 50%;
  }
}

/* ══ 10. content-visibility ════════════════════════════════
   출처: racing.porsche.com (8개 요소에 적용)
   화면 밖 섹션의 렌더링을 통째로 건너뛴다. CSS 한 줄짜리 최대 가성비.
   contain-intrinsic-size 를 같이 주지 않으면 스크롤바가 요동친다.
   auto 키워드를 붙이면 한 번 렌더된 뒤 실제 높이를 기억한다. */

.fx-defer {
  content-visibility: auto;
  contain-intrinsic-size: auto 600px;
}

/* ══ 11. LQIP 블러업 ═══════════════════════════════════════
   출처: racing.porsche.com (Cloudinary 대체)
   20px 폭 썸네일을 base64 로 인라인해 배경에 깔고, 진짜 사진이
   로드되면 위에서 페이드인한다. scale(1.08) 은 blur 가장자리가
   비쳐 보이는 것을 막는다. 포르쉐도 같은 값을 쓴다. */

.fx-lqip {
  position: relative;
  overflow: hidden;
  background-size: cover;
  background-position: center;
}

.fx-lqip::before {
  content: '';
  position: absolute;
  inset: 0;
  background: inherit;
  filter: blur(12px);
  transform: scale(1.08);
}

.fx-lqip > img {
  position: relative;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.5s ease;
}

.fx-lqip > img.loaded,
.fx-lqip > img[data-loaded] {
  opacity: 1;
}

/* ══ 12. 오버슈트 커서 ═════════════════════════════════════
   출처: NORMAL IS BORING
   이징에 오버슈트가 있어 링크 위에서 톡 튄다.
   기존 커서의 이징만 이 값으로 바꾸면 인상이 완전히 달라진다. */

.fx-cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: currentColor;
  pointer-events: none;
  z-index: 10000;
  mix-blend-mode: difference;
  transition:
    width 0.33s cubic-bezier(0.34, 1.56, 0.64, 1),
    height 0.33s cubic-bezier(0.34, 1.56, 0.64, 1),
    opacity 0.2s linear;
}

.fx-cursor.on {
  width: 2.75rem;
  height: 2.75rem;
}

@media (pointer: coarse), (prefers-reduced-motion: reduce) {
  .fx-cursor {
    display: none;
  }
}

/* ══ 13. 유령 자동완성 ═════════════════════════════════════
   출처: pentagram.com/archive
   입력창 뒤에 회색 완성어를 깔고 진짜 input 을 투명하게 겹친다.
   "Show me ___" 처럼 고정 접두어를 붙여 검색이 아니라
   문장 완성으로 읽히게 만든다. */

.fx-ask {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  position: relative;
  border-radius: 999px;
  padding: 0.875rem 1.25rem;
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  transition: background 0.2s linear, height 0.3s linear;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .fx-ask {
    background: var(--paper, #fff);
  }
}

.fx-ask-prefix {
  flex-shrink: 0;
  align-self: stretch;
  display: flex;
  align-items: center;
  -webkit-user-select: none;
  user-select: none;
  opacity: 0.55;
}

.fx-ask-field {
  position: relative;
  flex: 1;
  min-width: 0;
}

.fx-ask-ghost {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0.42;
  -webkit-user-select: none;
  user-select: none;
}

.fx-ask-field > input {
  position: relative;
  z-index: 1;
  width: 100%;
  background: transparent;
  border: 0;
  outline-offset: 4px;
  font: inherit;
  color: inherit;
}

/* ══ 14. sr-only ═══════════════════════════════════════════
   오도미터·아이콘 버튼·유령 자동완성이 전부 필요로 한다. */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
