/* 배경 4개 레이어. L0 베이스 그라디언트, L1 오로라 오브,
   L2·L3 별(canvas). 전부 pointer-events: none — 배경은 조작 대상이 아니다. */

.bg {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}

/* L0 — 위로 갈수록 보랏빛. 움직이지 않는다. */
.bg__base {
  position: absolute;
  inset: -10%;
  background:
    radial-gradient(120% 80% at 50% -10%, var(--void-high) 0%, transparent 60%),
    linear-gradient(180deg, #0C0838 0%, var(--void) 55%, #05041C 100%);
}

/* L0.5 — 아득히 먼 은하와 성운.
   오브(L1)보다 뒤에 깔려 오로라 빛이 그 위를 지나간다. 그래서 더 멀리
   있는 것으로 읽힌다.

   이 레이어는 **거의 움직이지 않는다.** 별은 중심으로 흘러들지만 은하는
   제자리에서 아주 느리게 돌기만 한다. 손에 닿지 않는다는 것이 이
   레이어의 전부라, 다가오는 순간 그 인상이 깨진다. 시차 계수도 가장
   작다(parallax.js의 deepSky). */
.bg__deep {
  position: absolute;
  inset: -8%;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
  transition: transform var(--dur-slide) var(--ease-slide);
  will-change: transform;
}

.galaxy, .nebula {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
}

/* 나선 은하 — 기울어진 렌즈. 밝은 핵에서 바깥으로 급히 어두워진다.
   납작한 타원 그라디언트 두 겹이 핵과 원반을 만든다. */
/* 오브(오로라)는 화면 절반을 덮는 거대한 빛무리다. 은하가 그것과
   구분되려면 정반대로 가야 한다 — 작고, 핵이 또렷하고, 가장자리가
   급히 죽어야 한다. 크고 부드럽게 만들면 오브가 하나 더 늘어난 것으로만
   보인다. 그래서 blur를 최소로 두고 밝기 대비를 세게 준다. */
.galaxy--spiral {
  width: 15vw; height: 3.6vw;
  left: 7%; top: 21%;
  background:
    /* 원반 — 납작하게 퍼지는 팔 */
    radial-gradient(ellipse 50% 100% at 50% 50%, rgba(178, 166, 255, 0.42) 0%, rgba(150, 140, 235, 0.14) 55%, transparent 78%),
    /* 핵 — 작고 밝은 중심. 이 대비가 은하로 읽히게 만든다 */
    radial-gradient(ellipse 17% 62% at 50% 50%, rgba(255, 250, 235, 0.95) 0%, rgba(255, 238, 205, 0.5) 30%, transparent 68%);
  filter: blur(4px);
  opacity: 0.6;
  animation: deep-turn-1 240s linear infinite;
}

/* 타원 은하 — 방향 없는 둥근 빛무리. 나선보다 작고 더 멀다. */
.galaxy--elliptical {
  width: 5.4vw; height: 4vw;
  right: 17%; top: 15%;
  background: radial-gradient(ellipse at 50% 50%, rgba(236, 244, 255, 0.85) 0%, rgba(158, 178, 255, 0.3) 34%, transparent 70%);
  filter: blur(3px);
  opacity: 0.5;
  animation: deep-breathe 190s ease-in-out infinite alternate;
}

/* 성운 — 은하보다 훨씬 크고 흐릿한 구름. 형태가 읽히면 안 된다. */
.nebula {
  width: 36vw; height: 24vw;
  right: 3%; bottom: 14%;
  border-radius: 46% 54% 58% 42% / 52% 44% 56% 48%;
  background:
    radial-gradient(ellipse at 38% 44%, rgba(205, 160, 255, 0.26) 0%, transparent 60%),
    radial-gradient(ellipse at 64% 62%, rgba(120, 178, 255, 0.18) 0%, transparent 56%);
  filter: blur(44px);
  opacity: 0.5;
  animation: deep-turn-2 320s linear infinite;
}

/* 주기를 발표 시간(약 7시간)보다 짧게 두되 서로 나누어떨어지지 않게 한다.
   회전만 하고 위치는 바뀌지 않는다 — 다가오지 않아야 하기 때문이다. */
@keyframes deep-turn-1 {
  from { transform: rotate(-24deg) scale(1); }
  to   { transform: rotate(336deg) scale(1); }
}
@keyframes deep-turn-2 {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
@keyframes deep-breathe {
  from { transform: scale(0.94); opacity: 0.28; }
  to   { transform: scale(1.06); opacity: 0.4; }
}

/* L1 — 시차로 움직이는 컨테이너. 오프셋은 JS가 --px/--py에 넣는다. */
.bg__aurora {
  position: absolute;
  inset: -20%;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
  transition: transform var(--dur-slide) var(--ease-slide);
  will-change: transform;
}

.orb {
  position: absolute;
  border-radius: 50%;
  will-change: transform, opacity;
  /* 슬라이드별 강조(networking, closing)가 opacity를 흔든다.
     filter는 일부러 transition에서 뺐다 — filter를 애니메이션하면 이미
     크게 블러 처리된 레이어를 매 프레임 재합성해야 해서 발표 노트북에서
     프레임이 떨어진다. 더 나쁜 것은 이 비용이 background.js가 재는
     캔버스 루프 밖, 컴포지터에서 발생한다는 점이다 — 여기서 한 번 멈추면
     별 배경이 절반만 그려지거나 완전히 멈춘 채로 되돌아오지 않는다.
     그래서 filter는 전환 없이 즉시 스냅하고, opacity만 부드럽게 바뀐다. */
  transition: opacity 1200ms ease;
}

/* 주기가 서로 나누어떨어지지 않는다. 38·51·67·83초의 최소공배수는
   실질적으로 발표 시간을 넘으므로 같은 그림이 반복되지 않는다.
   transform과 opacity만 애니메이션한다. width나 filter를 흔들면
   매 프레임 재계산이 일어나 프로젝터에서 프레임이 떨어진다. */
.orb--1 {
  width: 46vw; height: 46vw; left: 4%; top: -6%;
  background: radial-gradient(circle, var(--glow-orchid) 0%, transparent 70%);
  filter: blur(120px); opacity: 0.55;
  animation: drift-1 38s ease-in-out infinite alternate;
}
.orb--2 {
  width: 40vw; height: 40vw; right: -4%; top: 10%;
  background: radial-gradient(circle, var(--glow-violet) 0%, transparent 70%);
  filter: blur(130px); opacity: 0.55;
  animation: drift-2 51s ease-in-out infinite alternate;
}
.orb--3 {
  width: 34vw; height: 34vw; left: 24%; bottom: -12%;
  background: radial-gradient(circle, var(--glow-cyan) 0%, transparent 72%);
  filter: blur(100px); opacity: 0.32;
  animation: drift-3 67s ease-in-out infinite alternate;
}
.orb--4 {
  width: 30vw; height: 30vw; right: 18%; bottom: 4%;
  background: radial-gradient(circle, var(--glow-rose) 0%, transparent 70%);
  filter: blur(90px); opacity: 0.4;
  animation: drift-4 83s ease-in-out infinite alternate;
}

@keyframes drift-1 {
  from { transform: translate3d(0, 0, 0) scale(1); opacity: 0.5; }
  to   { transform: translate3d(7vw, 5vh, 0) scale(1.18); opacity: 0.62; }
}
@keyframes drift-2 {
  from { transform: translate3d(0, 0, 0) scale(1.1); opacity: 0.58; }
  to   { transform: translate3d(-6vw, 8vh, 0) scale(0.92); opacity: 0.42; }
}
@keyframes drift-3 {
  from { transform: translate3d(0, 0, 0) scale(0.95); opacity: 0.28; }
  to   { transform: translate3d(9vw, -6vh, 0) scale(1.22); opacity: 0.4; }
}
@keyframes drift-4 {
  from { transform: translate3d(0, 0, 0) scale(1.05); opacity: 0.36; }
  to   { transform: translate3d(-8vw, -4vh, 0) scale(0.9); opacity: 0.48; }
}

/* L2·L3 — 별. 컨테이너 둘이 서로 다른 계수로 움직인다. */
.bg__stars {
  position: absolute;
  inset: 0;
  transform: translate3d(var(--px, 0px), var(--py, 0px), 0);
  transition: transform var(--dur-slide) var(--ease-slide);
  will-change: transform;
}

.bg__stars canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* 슬라이드별 배경 강조. main.js가 body[data-slide]를 갱신한다.
   네트워킹 파티는 하루 중 유일한 쉬는 구간이라 채도를 올리고,
   클로징은 수미상관으로 배경을 밝힌다(콘텐츠 문서 §7-1, §7-4).
   opacity에는 !important가 필요하다: 각 .orb--n의 drift-* 키프레임이
   opacity를 계속 애니메이션하고 있고, 실행 중인 애니메이션 값은
   일반 우선순위의 저작 규칙(선택자 우선순위와 무관하게)을 항상 이긴다.
   따라서 상태 기반(data-slide)의 opacity 재정의가 실제로 반영되려면
   !important로 애니메이션보다 우선순위를 높이는 것이 유일한 방법이다.
   filter는 키프레임이 건드리지 않으므로 이 규칙만으로도 이미 이기며,
   여기에 !important를 붙이는 것은 불필요한 잡음이다. */
body[data-slide="networking"] .orb { filter: blur(80px) saturate(1.7); opacity: 0.72 !important; }
body[data-slide="closing"] .orb    { filter: blur(110px) brightness(1.35); opacity: 0.7 !important; }

/* 배경 끄기 — 진행자 노트 창의 버튼이 body에 이 클래스를 붙인다. */
body.bg-off .bg__deep,
body.bg-off .bg__aurora,
body.bg-off .bg__stars { display: none; }

/* 리사이즈로 --px/--py가 바뀌는 순간에는 전환을 꺼서 스냅시킨다.
   #deck과 같은 이유다 — main.js의 render(isResize)가 이 구간에만
   body에 is-resizing을 붙인다. */
body.is-resizing .bg__deep,
body.is-resizing .bg__aurora,
body.is-resizing .bg__stars {
  transition: none;
}

/* prefers-reduced-motion을 일부러 보지 않는다 (2026-08-03 결정).
   이 덱은 개인이 브라우징하는 페이지가 아니라 발표자가 통제하는 무대
   장비에서 한 번 재생되는 화면이다. 회사 지급 노트북은 이 설정이 켜져
   있는 경우가 흔한데, 존중하면 오로라와 별이 아무 표시 없이 정지한 채로
   떠서 이 덱의 signature가 통째로 사라진다.
   되돌리려면 이 파일과 src/background.js, styles/slides.css,
   styles/progress.css 네 곳을 함께 되돌린다 — 배경만 살아 있고 전환은
   죽어 있는 중간 상태가 가장 이상하다.
   과부하 대비는 남아 있다: background.js의 자동 강등과 진행자 노트 창의
   "배경 끄기" 버튼(body.bg-off) 두 가지다. */
