/* ═════════ 셈톡 /login — 로그인 A안 v3 «두 점의 만남» (login-stage.css · D-886, 사용자 결정 2026-10-02) ═════════
   로고의 두 점(파랑 #2E6BFF = 의뢰인, 초록 #0FAE6D = 세무사)이 칼럼 위 무대의 캐릭터가 된다.
   출발점: 요청 패키지 docs/requests/2026-10-02-login-a-v3/login-stage.css(+ login-footer-override.css).
   패키지와 다른 점(사용자 결정 2026-10-02 · 계획 §0·§4):
     · 앱 웹뷰 판정은 서버 `.app-embed`(D-692) 하나다 — 패키지의 대안 켜기(클래스·질의문자열)는 걷었다.
     · 페이지 단위 규칙(푸터 숨김·앱 임베드 셸 접기)은 :has() 대신 body 클래스 `.page-login`으로 건다
       (layout.html이 로그인 플래그 hideTopBrand일 때 단다 — 옛 브라우저에서도 같은 화면).
     · 앱 임베드 /login은 푸터를 **전부** 숨긴다(약관 줄은 칼럼에 있다 — 사용자 결정).
   v4 말풍선(D-887, 요청 docs/requests/2026-10-02-login-a-v4-bubble/REQUEST-UPDATE.md):
     캐릭터 몸통만 원 → 말풍선(원 몸통 + 안쪽 아래 작은 뾰족 꼬리, 인라인 SVG — login.html). 바뀐 규칙은 «v4» 표시가 붙은
     .lg-body · .lg-bubble · .lg-bub-* · 얼굴 자리 7줄과 합치기·모션 줄임의 해당 줄뿐 — 상자 치수·간격·꼬리표·버튼·타이밍(520ms)은 v3 그대로.
     ⚠ v4 패키지 CSS에도 .is-app 규칙이 있지만 옮기지 않았다(위 첫 항목 — 앱 판정은 .app-embed 하나).
     🔴 SVG 조각의 기준점은 transform-box: view-box + px transform-origin(viewBox 단위)이다 — %·키워드로 바꾸면 iOS Safari에서 어긋난다.

   🔴 이 파일은 templates/login.html의 <head>에서만 싣는다(layout-dialect가 shell.css **뒤**로 합친다).
      페이지 단위 규칙이 들어 있어서 전역 번들(app.css)에 합치지 마라 — 합치더라도 셸·푸터 규칙은 .page-login이 막는다.
   🔴 클래스는 모두 .lg-* 네임스페이스. 운영 클래스(.login-page·.login-col·.login-brand·.login-sns·
      .login-btn·.login-legal)는 이름을 바꾸지 않고 **.lg-page 안에서만** 덧씌운다 — 버튼 모양(52/50px·모서리 16·
      간격 8, D-165·D-704)과 락업 잉크(--brand-ink 44/36, D-885)는 app.css 그대로다. 여기서 다시 칠하지 마라.
   ⚠ 움직이는 속성은 transform·opacity뿐(design.md §7). 색은 토큰(var(--*)) 우선, 캐릭터 고유색만 리터럴.
   시험: LoginStageContractTest(CSS 계약) · ShellFooterContractTest(푸터 마크업·shell.css는 그대로) · LoginTypeScaleContractTest(글자 값).
   글자(타이포그래피 C1 · D-890, 요청 docs/requests/2026-10-02-typography/REQUEST.md §3-3·§3-4):
     헤드라인 = title2(20/28/600/0 · ≥768 22/30) · 상태 알약 = label2(14/20/500/+.01em) · 꼬리표 = caption2(12/500/+.02em, 행간은 알약 높이).
     버튼 label1·약관 caption2는 app.css(.login-btn·.login-legal)에 있다. 값은 리터럴 px·em — 토큰(typography.css)은 C2에서 들어온다.
     🔴 700 이상 굵기·소수점 크기·고정 px 음수 자간을 다시 넣지 마라(§3-4 «하지 말 것»). 무대 수치(--lg-*)는 이 작업에서 손대지 않았다.

   세로 리듬(8px 격자) — 기준 화면(데스크톱 1280×800 / 폰 390×844)에서 시안(reference/)과 같다:
     [머리 여백 40 — 점프 최고점 자리, 무대 상자 안]
     캐릭터 88 / 72 → 꼬리표 간격 20 / 16 → 꼬리표 24 / 22   = 무대 바닥
     → 40 / 32 → 락업(잉크 44 / 36) → 20 / 16 → 헤드라인(30 / 28 — title2 행간, C1 전엔 32 / 30) → 12 → 상태 알약 32
     → 32 / 28 → 버튼 4개(운영 52 / 50) → 16 → 약관 줄
   v3: 위 숫자를 모두 «배율 단위» --lg-u(기준 화면 = 1px)에 곱한다.
     · 큰 화면: 1440×900을 넘으면 커져서 1920×1080(또는 높이 1000)에서 1.18배 = 캐릭터 104px에서 멈춘다.
     · 낮은 화면: 데스크톱은 높이 760 아래, 폰은 높이 720 아래에서 줄어든다(최저 0.62 / 0.64배).
     · 버튼·락업·글자 크기는 배율을 타지 않는다(운영 규칙 그대로 · 칼럼 536px 그대로).
     · 그래도 안 들어가는 높이(가로 폰 등)는 겹치지 않고 세로로 스크롤된다 — 고정 높이가 없다.
       ⚠ 가로 폰(844×390)에서 셸이 사이드바를 그리는 것은 셸 전체 문제라 이번 범위 밖이다(사용자 결정 2026-10-02). */

/* ── 배율 단위와 치수 ── */
.login-page.lg-page {
  /* 위로: 폭 1440→1920, 높이 900→1000 중 작은 쪽을 따라 1 → 1.1818 (88 → 104px) */
  --lg-up: clamp(1px, min(1px + (100vw - 1440px) * .000379, 1px + (100vh - 900px) * .0018182), 1.1818px);
  /* 아래로: 높이 760 아래부터 줄인다(v2의 «낮은 데스크톱» 계단 80px@760 대신 연속 곡선) */
  --lg-down: clamp(.62px, 1px - (760px - 100vh) * .00227, 1px);
  --lg-u: calc(var(--lg-up) + var(--lg-down) - 1px);      /* 둘 중 하나는 늘 1px이다 */
  --lg-d: clamp(60px, calc(88 * var(--lg-u)), 104px);       /* 캐릭터 지름 */
  --lg-off: calc(var(--lg-d) * 72 / 88);                    /* 중심축 ↔ 무대 가운데 */
  --lg-head: calc(40 * var(--lg-u));                        /* 머리 여백(점프 자리) */
  --lg-hop: calc(30 * var(--lg-u));                         /* 카카오 점프 높이 (머리 여백 안에 들어간다) */
  --lg-tag-gap: calc(20 * var(--lg-u));
  --lg-tag-h: 24px;
  --lg-g-intro: calc(40 * var(--lg-u));                     /* 무대 바닥 → 락업 */
  --lg-g-title: calc(20 * var(--lg-u));                     /* 락업 → 헤드라인 */
  --lg-g-pill: calc(12 * var(--lg-u));                      /* 헤드라인 → 상태 알약 */
  --lg-g-sns: calc(32 * var(--lg-u));                       /* 알약 → 첫 버튼 */
  --lg-g-legal: calc(16 * var(--lg-u));                     /* 끝 버튼 → 약관 */
  --lg-bleed: var(--shell-pad, 24px);                       /* 배경이 본문 좌우 패딩까지 번지는 폭 */
  --lg-topbar: var(--topbar-h, 68px);
  --lg-safe-t: 0px; --lg-safe-b: 0px;

  /* 운영 .login-page를 덮는다: 패딩·높이만. 흰 바탕·가운데 맞춤(flex)은 운영 규칙 그대로.
     «보이는 내용»(캐릭터 머리 ~ 약관)을 상단바 아래 남는 화면의 가운데에 둔다 — v2처럼 칼럼을 머리 여백만큼
     위로 당긴다(.login-col margin-top 음수). 다만 v2는 짧은 화면에서 무대가 본문 위 패딩을 넘어 상단바 밑까지
     파고들어 점프가 잘렸다(320×568 실측: 점프 최고점이 상단바 아래 −14px).
     v3: «점프 최고점 + 2px»(--lg-hopsafe)에서 본문 위 패딩을 뺀 만큼만 위 패딩으로 받쳐 준다(아래도 같은 양 →
     가운데 그대로). 들어가는 화면에서는 v2와 같은 자리, 안 들어가는 화면에서는 페이지가 늘어 스크롤된다. */
  --lg-hopsafe: calc(var(--lg-hop) + var(--lg-d) * .06 + 2px);         /* 점프 높이 + 늘어남 6% + 여유 */
  --lg-guard: max(0px, var(--lg-hopsafe) - var(--main-pad-t, 24px));
  position: relative; isolation: isolate;
  padding: calc(var(--lg-safe-t) + var(--lg-guard)) 0 calc(var(--main-pad-t, 24px) + var(--lg-guard) + var(--lg-safe-b));
  /* ⚠ 최소 높이 = 셸의 «한 화면»(--main-fill = 100vh − 상단바 − 본문 위 패딩, D-736)과 같은 셈을 dvh로 한 번 더 한다
       (모바일 주소창을 뺀 실제 높이 — 계획 §4 «100dvh, 100vh 폴백»). 숫자를 손으로 적지 않고 셸 변수만 읽는다. */
  min-height: calc(100vh - var(--lg-topbar) - var(--main-pad-t, 24px));
  min-height: calc(100dvh - var(--lg-topbar) - var(--main-pad-t, 24px));   /* 모바일 주소창을 뺀 실제 높이 */
  color-scheme: light;                                       /* 운영은 다크 모드가 없다 — OS가 어두워도 밝게 */
  -webkit-tap-highlight-color: transparent;
}

.lg-page .login-col { margin-top: calc(-1 * var(--lg-head)); }

/* ── 배경: 옅은 빛 두 덩이(모서리) + 무대 뒤 흰 후광. 큰 화면에서는 빛도 함께 커진다(vmax) ──
   바닥은 48px 안개로 끊는다 — 예전엔 페이지 바닥에서 빛이 칼같이 잘려 스크롤하면 가로 경계가 보였다.
   (안개는 페이지 바닥 «아래»에만 걸려서 첫 화면 그림은 그대로다) */
.lg-bg { --lg-fade: 48px; position: absolute; z-index: -1; pointer-events: none; overflow: hidden;
  inset: calc(-1 * var(--main-pad-t, 24px)) calc(-1 * var(--lg-bleed)) calc(-1 * var(--lg-fade));
  -webkit-mask-image: linear-gradient(#000 calc(100% - var(--lg-fade)), transparent);
          mask-image: linear-gradient(#000 calc(100% - var(--lg-fade)), transparent); }
.lg-glow { position: absolute; border-radius: 50%; }
.lg-glow.g1 { --s: clamp(760px, 48vmax, 1200px); width: var(--s); height: var(--s); top: calc(var(--s) * -420 / 760); right: calc(var(--s) * -360 / 760);
  background: radial-gradient(circle, rgba(49,130,246,.13) 0, rgba(49,130,246,.05) 38%, rgba(49,130,246,0) 68%); animation: lg-drift 18s ease-in-out infinite alternate; }
.lg-glow.g2 { --s: clamp(700px, 44vmax, 1100px); width: var(--s); height: var(--s); bottom: calc(var(--s) * -400 / 700); left: calc(var(--s) * -360 / 700);
  background: radial-gradient(circle, rgba(49,130,246,.09) 0, rgba(49,130,246,.035) 40%, rgba(49,130,246,0) 68%); animation: lg-drift 22s ease-in-out -6s infinite alternate-reverse; }
.lg-glow.g3 { --w: calc(760 * var(--lg-u)); width: var(--w); height: calc(520 * var(--lg-u)); top: 0; left: 50%; margin-left: calc(var(--w) / -2);
  background: radial-gradient(closest-side, #fff 0, #fff 55%, rgba(255,255,255,0)); }
@keyframes lg-drift { from { transform: translate3d(0,0,0); } to { transform: translate3d(-40px, 30px, 0); } }

/* ── 무대 ── 상자 = 머리 여백 + 캐릭터 + 꼬리표. 상자 바닥 = 꼬리표 바닥 */
.lg-stage { position: relative; max-width: 440px; margin: 0 auto;
  height: calc(var(--lg-head) + var(--lg-d) + var(--lg-tag-gap) + var(--lg-tag-h)); }
.lg-char { position: absolute; top: var(--lg-head); width: var(--lg-d); height: var(--lg-d); left: calc(50% - var(--lg-d) / 2); }
.lg-blue  { margin-left: calc(-1 * var(--lg-off)); --c1: #6E9BFF; --c2: #2E6BFF; --c3: #1F55D6; --cs: rgba(46,107,255,.42); --flat: #2E6BFF; }
.lg-green { margin-left: var(--lg-off);           --c1: #45D296; --c2: #0FAE6D; --c3: #0A8F58; --cs: rgba(15,174,109,.40); --flat: #0FAE6D; }

.lg-move { position: absolute; inset: 0; transform-origin: 50% 50%;
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0) scale(var(--ms, 1));
  transition: transform .72s cubic-bezier(.34, 1.22, .64, 1); }
.lg-hop, .lg-breath { position: absolute; inset: 0; transform-origin: 50% 100%; }
.lg-hop { transition: transform .5s cubic-bezier(.34, 1.56, .64, 1); }
.lg-breath { animation: lg-breath 3s ease-in-out infinite; }
.lg-green .lg-breath { animation-delay: -1.5s; }
@keyframes lg-breath { 0%, 100% { transform: scale(1, 1); } 50% { transform: scale(1.018, 1.03); } }

/* ── v4 몸통 = 말풍선 (인라인 SVG .lg-bubble, viewBox 100 = 캐릭터 상자 d) — 모양 탐색 초안(concept-a-shape-bubble)과 같은 모양 ──
   모양: 원(지름 92, 중심 49·53.5 — v3 원과 같은 «동그란» 몸통) + 안쪽 아래 작은 뾰족 꼬리(꼭짓점 둥글기 3.2, 이음 필렛 6, 꼭짓점 아래 끝 ≈98.2 · 몸통 원 바닥 99.5).
         초안 _src/shapes/geom.py bubble()을 원호·직선으로 정확히 옮김(초안과 외곽선 차이 < 0.03). 파랑 = 오른쪽 아래, 초록 = 왼쪽 아래(좌우 반전).
   칠: v3 원과 같은 입체감 — 바탕 방사형 그라데이션(빛 왼쪽 위, 같은 세 색) + 안쪽 그늘(오른쪽 아래) + 안쪽 빛(왼쪽 위) + 윗면 하이라이트.
       v3의 inset box-shadow 두 겹을 같은 값의 방사형 그라데이션으로 옮겼다 → 꼬리까지 이어지고, 블러 필터가 없다(운영·Flutter 그대로).
   색 바닥 그림자는 v3와 같은 box-shadow를 몸통 원 자리(::before)에 둔다(CSS — SVG 필터 아님). */
.lg-body { position: absolute; inset: 0; }
.lg-body::before { content: ""; position: absolute; left: 3%; top: 7.5%; width: 92%; height: 92%; border-radius: 50%;
  box-shadow: 0 16px 26px -16px var(--cs); transform-origin: 50% 50%; transition: transform .3s ease; }
.lg-green .lg-body::before { left: 5%; }
.lg-bubble { position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible; }
.lg-bubble .lg-s1 { stop-color: var(--c1); } .lg-bubble .lg-s2 { stop-color: var(--c2); } .lg-bubble .lg-s3 { stop-color: var(--c3); }
.lg-bubble .lg-bub-shape, .lg-bubble .lg-bub-tailwrap, .lg-bubble .lg-bub-tail { transform-box: view-box; }
.lg-bub-shape { transform-origin: 49px 53.5px; transition: transform .3s ease; }
.lg-green .lg-bub-shape { transform-origin: 51px 53.5px; }
/* 꼬리: 숨을 들이쉴 때(몸통이 위로 늘 때) 살짝 움츠렸다 내쉬면 돌아온다. lg-breath와 같은 3s · 같은 위상(초록 −1.5s).
   접히는 점 = 몸통 원 안(74, 76) — 움츠려도·접혀도 말풍선 밖으로 튀어나오지 않는다(기하 검사).
   애플 반응 때는 몸통과 함께 멈춰 위상이 계속 맞다(아래 «애플: 자세를 곧게», D-887) */
.lg-bub-tailwrap { transform-origin: 74px 76px; animation: lg-tail 3s ease-in-out infinite; }
.lg-green .lg-bub-tailwrap { transform-origin: 26px 76px; animation-delay: -1.5s; }
@keyframes lg-tail { 0%, 100% { transform: scale(1); } 50% { transform: scale(.93); } }
.lg-bub-tail { transform-origin: 74px 76px; transition: transform .32s cubic-bezier(.34, 1.4, .64, 1) .28s; }   /* 되돌아올 때(데모 리셋·bfcache) 톡 튀어나온다 */
.lg-green .lg-bub-tail { transform-origin: 26px 76px; }
.lg-bub-hl { transition: opacity .4s ease; }
.lg-bub-flat { fill: var(--flat); opacity: 0; transition: opacity .35s ease; }
/* v4 얼굴 자리 = 초안과 같음: 얼굴 중심 (49, 51) — 눈 위 41% · 볼 위 59%, 눈 ±13% · 볼 ±25%(v3와 같은 간격). 초록은 좌우 반전 */
.lg-blue .lg-eye, .lg-green .lg-eye { top: 41%; }
.lg-blue .lg-eye.l { left: calc(36% - var(--ew) / 2); } .lg-blue .lg-eye.r { left: calc(62% - var(--ew) / 2); }
.lg-green .lg-eye.l { left: calc(38% - var(--ew) / 2); } .lg-green .lg-eye.r { left: calc(64% - var(--ew) / 2); }
.lg-blue .lg-blush, .lg-green .lg-blush { top: 59%; }
.lg-blue .lg-blush.l { left: calc(24% - var(--bw) / 2); } .lg-blue .lg-blush.r { left: calc(74% - var(--bw) / 2); }
.lg-green .lg-blush.l { left: calc(26% - var(--bw) / 2); } .lg-green .lg-blush.r { left: calc(76% - var(--bw) / 2); }
.lg-blue .lg-shadow, .lg-green .lg-shadow { left: 22%; right: 22%; }   /* 바닥 그림자: 몸통 원(92%)에 맞춰 조금 좁게(초안과 같음) */

.lg-shadow { position: absolute; left: 20%; right: 20%; top: calc(100% + 3px); height: 9px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(25,31,40,.15), rgba(25,31,40,0)); transition: opacity .3s ease; }

/* 얼굴 = 눈 둘 + 볼. JS가 매 프레임 transform만 쓴다(얼굴 묶음 최대 6px + 눈동자 3px).
   will-change는 이 둘(캐릭터당 얼굴 하나)에만 — 레이어를 아낀다. */
.lg-face { position: absolute; inset: 0; transition: opacity .25s ease; will-change: transform; }
.lg-eye { --ew: calc(var(--lg-d) * .116); position: absolute; top: 40%; width: var(--ew); height: calc(var(--lg-d) * .15); transform-origin: 50% 60%; transition: transform .07s ease; }
.lg-eye.l { left: calc(37% - var(--ew) / 2); } .lg-eye.r { left: calc(63% - var(--ew) / 2); }
.lg-eye.lg-blink { transform: scaleY(.1); }
.lg-pupil { position: absolute; inset: 0; border-radius: 50%; background: #0E1726; transition: opacity .15s ease; }
.lg-pupil::after { content: ""; position: absolute; left: 19%; top: 16%; width: 31%; height: 24%; border-radius: 50%; background: rgba(255,255,255,.9); }
.lg-happy { position: absolute; left: -22%; top: 13%; width: 144%; height: 80%; opacity: 0; fill: none; stroke: #0E1726; stroke-width: 2.7; stroke-linecap: round; transition: opacity .15s ease; overflow: visible; }
.lg-blush { --bw: calc(var(--lg-d) * .144); position: absolute; top: 58%; width: var(--bw); height: calc(var(--lg-d) * .077); border-radius: 50%; background: #FF9DBB; opacity: .45; filter: blur(1px); transition: opacity .25s ease; }
.lg-blush.l { left: calc(25% - var(--bw) / 2); } .lg-blush.r { left: calc(75% - var(--bw) / 2); }
.lg-green .lg-blush { background: #FFD3C9; opacity: .55; }
.lg-stage[data-hover="kakao"] .lg-green .lg-blush { opacity: .95; }

/* 꼬리표 «의뢰인»·«세무사» — 같은 알약, 각 캐릭터 중심축 아래 같은 높이.
   글자 = caption2(12/500/+.02em · 최소 크기 12, 폰에서도 11.5로 줄이지 않는다) — 행간은 알약 높이(--lg-tag-h)로 세로 가운데. D-890 */
.lg-tag { position: absolute; left: 50%; top: calc(100% + var(--lg-tag-gap)); transform: translateX(-50%); white-space: nowrap;
  box-sizing: border-box; min-width: 56px; height: var(--lg-tag-h); padding: 0 10px; border-radius: var(--r-full, 999px); text-align: center;
  font-size: 12px; line-height: var(--lg-tag-h); font-weight: 500; letter-spacing: .02em; transition: opacity .3s ease, transform .3s ease; }
.lg-blue .lg-tag { background: #EDF4FF; color: #1B64DA; box-shadow: inset 0 0 0 1px rgba(46,107,255,.10); }
.lg-green .lg-tag { background: #EAF8F1; color: #08804F; box-shadow: inset 0 0 0 1px rgba(15,174,109,.12); }

/* ── 버튼별 반응 (마우스 hover · 키보드 포커스 · 터치 누르는 순간 — 셋 다 JS가 data-hover로 건다) ── */
/* 카카오: 통통 뛰기 + 눈이 ^^ */
.lg-stage[data-hover="kakao"] .lg-hop { animation: lg-hop 1.3s cubic-bezier(.3,.7,.4,1) infinite; }
.lg-stage[data-hover="kakao"] .lg-green .lg-hop { animation-delay: .1s; }
.lg-stage[data-hover="kakao"] .lg-shadow { animation: lg-hop-sh 1.3s cubic-bezier(.3,.7,.4,1) infinite; }
.lg-stage[data-hover="kakao"] .lg-green .lg-shadow { animation-delay: .1s; }
.lg-stage[data-hover="kakao"] .lg-pupil, .lg-stage.lg-merging .lg-pupil { opacity: 0; }
.lg-stage[data-hover="kakao"] .lg-happy, .lg-stage.lg-merging .lg-happy { opacity: 1; }
.lg-stage[data-hover="kakao"] .lg-blush { opacity: .9; }
@keyframes lg-hop {
  0%   { transform: translateY(0) scale(1, 1); }
  10%  { transform: translateY(0) scale(1.08, .9); }
  30%  { transform: translateY(calc(-1 * var(--lg-hop))) scale(.95, 1.06); }
  46%  { transform: translateY(0) scale(1.07, .93); }
  56%  { transform: translateY(calc(-7 / 30 * var(--lg-hop))) scale(.99, 1.01); }
  66%  { transform: translateY(0) scale(1.015, .985); }
  74%, 100% { transform: translateY(0) scale(1, 1); }
}
@keyframes lg-hop-sh {
  0%, 10% { transform: scale(1); opacity: 1; }
  30% { transform: scale(.62); opacity: .45; }
  46% { transform: scale(1.06); opacity: 1; }
  56% { transform: scale(.92); opacity: .85; }
  66%, 100% { transform: scale(1); opacity: 1; }
}
/* 네이버: 둘이 서로 기울고, 사이에 초록 반짝 */
.lg-stage[data-hover="naver"] .lg-blue .lg-hop  { transform: translateX(7px) rotate(6deg); }
.lg-stage[data-hover="naver"] .lg-green .lg-hop { transform: translateX(-7px) rotate(-6deg); }
.lg-spark { --sz: clamp(36px, calc(var(--lg-d) * .806), 58px);
  position: absolute; left: 50%; top: calc(var(--lg-head) + var(--lg-d) * .5 - var(--sz) / 2); width: var(--sz); height: var(--sz); margin-left: calc(var(--sz) / -2);
  opacity: 0; transform: scale(.2) rotate(-40deg); pointer-events: none; overflow: visible; }
.lg-spark .sp-star { fill: var(--naver, #03C75A); }
.lg-spark .sp-dot { fill: var(--naver, #03C75A); opacity: .7; transform-box: fill-box; transform-origin: center; }
.lg-stage[data-hover="naver"] .lg-spark { animation: lg-spark-in .55s cubic-bezier(.34,1.56,.64,1) forwards, lg-twinkle 1.4s ease-in-out .55s infinite; }
.lg-stage[data-hover="naver"] .lg-spark .sp-dot { animation: lg-dot 1.4s ease-in-out infinite; }
@keyframes lg-spark-in { to { opacity: 1; transform: scale(1) rotate(0); } }
@keyframes lg-twinkle { 0%, 100% { opacity: 1; transform: scale(1) rotate(0); } 50% { opacity: .85; transform: scale(.82) rotate(18deg); } }
@keyframes lg-dot { 0%, 100% { opacity: .2; transform: scale(.6); } 50% { opacity: .9; transform: scale(1.1); } }
/* 구글: 고개 갸웃 */
.lg-stage[data-hover="google"] .lg-blue .lg-hop  { transform: rotate(-11deg); }
.lg-stage[data-hover="google"] .lg-green .lg-hop { transform: rotate(-11deg); transition-delay: .06s; }
/* 애플: 자세를 곧게 */
.lg-stage[data-hover="apple"] .lg-hop { transform: translateY(-6px) scale(.97, 1.05); }
.lg-stage[data-hover="apple"] .lg-green .lg-hop { transition-delay: .05s; }
.lg-stage[data-hover="apple"] .lg-breath,
.lg-stage[data-hover="apple"] .lg-bub-tailwrap { animation-play-state: paused; }
/* 꼬리 숨쉬기도 함께 멈춘다(D-887 · 사용자 결정 2026-10-02 — 이 한 줄만 v4 패키지와 다르다). animation-play-state는 상속되지
   않아 .lg-breath만 멈추면 꼬리(lg-tail)가 계속 돌고, 손을 떼면 몸통이 내쉴 때 꼬리가 움츠리는 엇박자가 다음 합치기까지 남는다.
   Flutter는 꼬리가 숨 값에서 나와 원래 같이 멈춘다. .lg-bub-tail(전환으로 접힘)이 아니라 애니메이션이 걸린 tailwrap이어야 먹는다 */
.lg-stage[data-hover="apple"] .lg-shadow { opacity: .7; }

/* ── 누름: 두 점이 모여 로고 배치(파랑 왼쪽 위 · 초록 오른쪽 아래)가 된다 ──
   운영에서는 이 애니메이션이 data-lg-nav-delay(기본 520ms) 동안 돌고 그다음 진짜 OAuth 주소로 이동한다.
   이동이 시작돼도 다음 문서가 그려지기 전까지 화면은 계속 움직인다(링·납작해짐은 그 사이에 마무리된다). */
.lg-stage.lg-merging .lg-hop { animation: none; transform: none; }
.lg-stage.lg-merging .lg-breath { animation: none; }
.lg-stage.lg-merging .lg-shadow, .lg-stage.lg-merging .lg-tag { opacity: 0; }
.lg-stage.lg-merging .lg-tag { transform: translateX(-50%) translateY(-6px); }
.lg-stage.lg-merging .lg-face { opacity: 0; transition: opacity .3s ease .42s; }
/* v4 말풍선 → 로고 점(원): ① 꼬리가 몸통 안으로 접혀 들어가고(0~.24s) ② 몸통 원이 지름 92 → 100(= d), 중심 → (50, 50)으로 부풀고(.08~.44s)
   ③ 그다음은 v3와 같다 — 하이라이트 .4s 뒤 사라짐 · .45s 뒤 단색(.lg-bub-flat = v3 ::after). 끝 상태 = v3와 같은 지름 d의 단색 원 */
.lg-stage.lg-merging .lg-bub-tailwrap { animation: none; }
.lg-stage.lg-merging .lg-bub-tail { transform: scale(0); transition: transform .24s cubic-bezier(.5, 0, .9, .45); }
.lg-stage.lg-merging .lg-bub-shape { transform: translate(1px, -3.5px) scale(1.0870); transition: transform .36s cubic-bezier(.34, 1.25, .64, 1) .08s; }
.lg-stage.lg-merging .lg-green .lg-bub-shape { transform: translate(-1px, -3.5px) scale(1.0870); }
.lg-stage.lg-merging .lg-body::before { transform: translate(1.087%, -3.804%) scale(1.0870); transition: transform .36s cubic-bezier(.34, 1.25, .64, 1) .08s; }
.lg-stage.lg-merging .lg-green .lg-body::before { transform: translate(-1.087%, -3.804%) scale(1.0870); }
.lg-stage.lg-merging .lg-bub-hl { opacity: 0; transition-delay: .4s; }
.lg-stage.lg-merging .lg-bub-flat { opacity: 1; transition-delay: .45s; }
.lg-ring { --rs: calc(var(--lg-d) * 168 / 88); position: absolute; left: 50%; top: var(--ring-y, 84px); width: var(--rs); height: var(--rs);
  margin: calc(var(--rs) / -2) 0 0 calc(var(--rs) / -2); border-radius: 50%;
  border: 1.5px solid rgba(49,130,246,.45); opacity: 0; transform: scale(.4); pointer-events: none; }
.lg-ring::after { content: ""; position: absolute; inset: 14px; border-radius: 50%; background: radial-gradient(closest-side, rgba(49,130,246,.16), rgba(49,130,246,0)); }
.lg-stage.lg-merging .lg-ring { animation: lg-ring 1s cubic-bezier(.2,.7,.2,1) .62s both; }
@keyframes lg-ring { 0% { opacity: 0; transform: scale(.45); } 25% { opacity: 1; } 100% { opacity: 0; transform: scale(1.25); } }

/* ── 소개 묶음 = 락업 + 헤드라인 + 상태 알약 ── */
.lg-intro { margin-top: var(--lg-g-intro); }
/* 헤드라인 = title2 토큰: 모바일 20/28/600, 데스크톱(≥768) 22/30. D-890
   (C1 전: 22/32/700/−.6px · 폰 20/30/−.5px · ≤340 19/28 — 세 단을 토큰 두 단으로 줄였다. 320에서도 두 줄 고르게(text-wrap: balance))
   자간 = --ls-18(17~20px 곡선 +.01em) — 타이포 점검 2026-10-03 F15(옛 리터럴 0 → 토큰, 차이 0.2px).
   크기·행간·굵기도 var()로 적는다(F15 검증 지적 — 옛 리터럴 20px/28px/600 + 이 파일의 @media (min-width: 768px) 22/30 줄).
   데스크톱 단은 typography.css :root가 같은 768 분기에서 --fs-title2/--lh-title2를 22/30으로 바꾸므로 이 파일에 따로 두지 않는다(픽셀 같음). */
.lg-title { margin: var(--lg-g-title) 0 0; font-size: var(--fs-title2); line-height: var(--lh-title2); font-weight: var(--fw-semibold); letter-spacing: var(--ls-18);
  color: var(--text-primary); word-break: keep-all; text-wrap: balance; }   /* 좁은 폰에서 두 줄이면 고르게 나눈다 */
/* 상태 알약 = label2(14/20/500/+.01em) — 32px 알약 안 flex 가운데(행간 20 < 32). caption1(13/400)보다 label2를 고른 이유:
   알약은 «칩»(§3-3 label2 쓰임)이고 C1 전 굵기(500)·회색 글자(grey700)와 같은 무게로 읽힌다. 가장 긴 문구(«네이버 아이디로 이어서 시작해요»)도
   320 칼럼(288px)에 들어간다(실측 — LoginTypeScaleContractTest 주석). D-890 */
.lg-status { display: flex; align-items: center; gap: 8px; width: fit-content; max-width: 100%; height: 32px; margin: var(--lg-g-pill) auto 0; padding: 0 14px 0 12px;
  box-sizing: border-box; border-radius: var(--r-full, 999px); background: var(--grey100, #F2F4F6);
  font-size: 14px; line-height: 20px; font-weight: 500; color: var(--grey700, #4E5968); letter-spacing: .01em; white-space: nowrap; }
.lg-status-ic { position: relative; width: 13px; height: 12px; flex: 0 0 auto; }
.lg-status-ic i { position: absolute; width: 6px; height: 6px; border-radius: 50%; }
.lg-status-ic i:first-child { left: 0; top: 0; background: #2E6BFF; }
.lg-status-ic i:last-child { left: 6.5px; top: 5.5px; background: #0FAE6D; }
.lg-status.lg-typing .lg-status-ic i { animation: lg-ic .6s ease-in-out infinite alternate; }
.lg-status.lg-typing .lg-status-ic i:last-child { animation-delay: .3s; }
@keyframes lg-ic { to { transform: scale(.6); opacity: .6; } }
.lg-caret { flex: 0 0 auto; width: 1.5px; height: 14px; margin-left: -6px; background: var(--primary, #3182F6); border-radius: 1px; animation: lg-caret 1s steps(1) infinite; }
.lg-status.lg-typing .lg-caret { animation: none; }
@keyframes lg-caret { 50% { opacity: 0; } }

/* ── 운영 버튼·약관(.login-*)은 그대로 — 간격과 누름 피드백만 ── */
.lg-page .login-sns { margin-top: var(--lg-g-sns); }
.lg-page .login-legal { margin-top: var(--lg-g-legal); text-wrap: balance; }   /* 한 줄일 땐 변화 없음 · 좁은 폰에서 «봐요.»만 떨어지지 않게 */
.lg-page .login-btn { touch-action: manipulation; -webkit-tap-highlight-color: transparent; -webkit-touch-callout: none;
  transition: background .15s ease, border-color .15s ease, transform .18s cubic-bezier(.2,.7,.2,1), opacity .25s ease; }
.lg-page .login-btn:active, .lg-page .login-btn.lg-pressed { transform: scale(.985); }
.lg-page .login-btn .lg-label { display: inline-flex; align-items: center; gap: 8px; }
.lg-spin { width: 15px; height: 15px; box-sizing: border-box; border-radius: 50%;
  border: 2px solid currentColor; border-right-color: transparent; opacity: .55; animation: lg-spin .8s linear infinite; }
@keyframes lg-spin { to { transform: rotate(360deg); } }
.lg-page .login-btn.lg-busy { cursor: progress; }
.lg-page .login-btn.lg-busy .lg-label { animation: lg-in .28s ease both; }
@keyframes lg-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.lg-page .login-sns.lg-locked .login-btn:not(.lg-pressed) { opacity: .4; pointer-events: none; }
.lg-page .login-sns.lg-locked .login-btn.lg-pressed { pointer-events: none; }

/* ── 폰·태블릿 세로(≤768 — 운영 셸이 드로어로 바뀌는 폭) ── */
@media (max-width: 768px) {
  .login-page.lg-page {
    /* 폰 기준 390×844 = 1px. 높이 720 아래부터 줄인다(667 → 0.87 · 568 → 0.64) */
    --lg-u: clamp(.64px, 1px - (720px - 100vh) * .0024, 1px);
    --lg-d: clamp(52px, calc(72 * var(--lg-u)), 72px);   /* 320×568에서도 52px 아래로는 안 줄인다 */
    --lg-off: calc(var(--lg-d) * 58 / 72);
    --lg-tag-gap: calc(16 * var(--lg-u));
    --lg-tag-h: 22px;
    --lg-g-intro: calc(32 * var(--lg-u));
    --lg-g-title: calc(16 * var(--lg-u));
    --lg-g-sns: calc(28 * var(--lg-u));
    --lg-bleed: 16px;
  }
  .lg-glow.g3 { --w: 520px; height: 420px; }
  .lg-tag { min-width: 52px; padding: 0 9px; }   /* 글자는 caption2 12px 그대로(C1 전 11.5px — 최소 크기 아래라 걷었다) */
  /* 헤드라인은 위 기본 규칙(title2 모바일 20/28)이 그대로 — 폰 전용 크기 줄을 두지 않는다 */
}
/* 아주 좁은 폰(≤340, 예: 320×568 SE 1세대): 알약 좌우 여백만 한 단 줄인다.
   C1 전엔 헤드라인 19px·알약 12.5px로 줄였다 — 토큰 밖 크기라 걷었다. 20/28 헤드라인은 320에서 두 줄로 고르게 나뉜다(실측). */
@media (max-width: 340px) {
  .lg-status { padding: 0 12px 0 10px; }
}

/* ── 푸터 «말»(D-710)의 질문 순환 숨기기 — /login에서만 ──────────────────────────
   로그인 지면은 무대(캐릭터)가 이미 움직인다 — 화면 아래 푸터의 질문 글줄(.sf-talk, 16s 순환)과 답줄(.sf-ans)이
   같이 돌면 시선이 둘로 갈린다. 락업·태그라인·링크 3열·약관 줄은 그대로 둔다.
   display:none이라 낱말 애니메이션(sf-word)도 함께 멈춘다. ⚠ shell.css·푸터 마크업은 고치지 않는다(ShellFooterContractTest).
   🔴 :has()로 걸지 않는다 — body 클래스 .page-login(layout.html, 로그인 플래그 hideTopBrand)이 스위치다. */
.page-login .shell-footer .sf-talk,
.page-login .shell-footer .sf-ans { display: none; }

/* ── 앱 인앱 웹뷰(임베드) /login ────────────────────────────────────────────────
   판정은 서버 한 곳 — `/login?app=1` → GlobalModelAdvice.appEmbed → body.app-embed(D-692).
   shell.css가 상단바·사이드바를 접고, 여기서 /login의 남은 셸을 정리한다.
   🔴 전부 `.app-embed.page-login`으로 좁힌다 — 다른 임베드 지면(14곳)의 셸·푸터는 shell.css 그대로다.
   ⚠ 안전 영역 env(safe-area-inset-*)는 viewport-fit=cover일 때만 0이 아니다 — 지금은 적용하지 않으므로 0이고,
     웹뷰는 앱바 아래 Scaffold 안이라 필요도 없다(사용자 결정 2026-10-02). 남겨 둬도 해가 없다. */
.app-embed.page-login .shell { min-height: 100vh; min-height: 100dvh; }
/* 태블릿 폭 웹뷰에서 사이드바 자리(216px)가 비지 않게 · 푸터가 없으니 본문 아래 패딩도 걷는다 */
.app-embed.page-login .main { padding-bottom: 0; margin-left: 0 !important; }
/* 푸터 전체를 숨긴다(사용자 결정 2026-10-02) — 약관 링크는 칼럼의 약관 줄에 있다. 다른 임베드 지면은 링크 열을 남긴다. */
.app-embed.page-login .main > .shell-footer { display: none; }
.app-embed.page-login .login-page.lg-page {
  --lg-topbar: 0px; --lg-bleed: 0px;
  --lg-safe-t: env(safe-area-inset-top, 0px); --lg-safe-b: env(safe-area-inset-bottom, 0px);
  padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px));
}
/* 푸터가 없으니 배경 바닥 안개(아래로 48px 넘침)를 걷는다 — 남기면 그만큼 빈 세로 스크롤이 생긴다(패키지 실측 892 > 844) */
.app-embed.page-login .lg-bg { --lg-fade: 0px; }

/* ── 탭이 숨었거나 무대가 화면 밖이면 JS가 .lg-paused를 단다 — 장식의 CSS 애니메이션도 멈춘다 ──
   🔴 장식(배경 빛 .lg-bg · 무대 .lg-stage · 상태 알약 .lg-status)에만 건다. 패키지처럼 .lg-page 아래 «전부»를 멈추면
      가로 폰(844×390)에서 4번째 버튼까지 스크롤해 무대가 화면 밖일 때 누른 버튼 글자의 lg-in(fill both, from opacity 0)이
      0%에서 서 버려 «연결 중…»과 스피너가 안 보였다(실측). 버튼 묶음(.login-sns)은 늘 돈다. */
.lg-page.lg-paused :is(.lg-bg, .lg-stage, .lg-status) *,
.lg-page.lg-paused :is(.lg-bg, .lg-stage, .lg-status) *::before,
.lg-page.lg-paused :is(.lg-bg, .lg-stage, .lg-status) *::after { animation-play-state: paused !important; }

/* ── 모션 줄임: 정지 화면(숨쉬기·깜빡임·추적·반응·합치기 없음). 버튼은 누르는 즉시 이동한다(JS) ── */
@media (prefers-reduced-motion: reduce) {
  .lg-glow, .lg-breath, .lg-caret, .lg-status-ic i, .lg-spark, .lg-spark .sp-dot, .lg-ring, .lg-bub-tailwrap { animation: none !important; }
  .lg-stage[data-hover] .lg-hop, .lg-stage[data-hover] .lg-shadow { animation: none !important; transform: none !important; }
  .lg-stage[data-hover] .lg-pupil { opacity: 1 !important; } .lg-stage[data-hover] .lg-happy { opacity: 0 !important; }
  .lg-move, .lg-hop, .lg-tag, .lg-face, .lg-body::before, .lg-bub-shape, .lg-bub-tail, .lg-bub-hl, .lg-bub-flat { transition: none !important; }
  .lg-face { will-change: auto; }
  .lg-spin { animation: none; border-right-color: currentColor; opacity: .35; }
  .lg-page .login-btn.lg-busy .lg-label { animation: none; }
  .lg-page .login-btn { transition: background .15s ease, border-color .15s ease; }
}
