  /* ════ Unlock Ceremony (초반 컨텐츠 공개 수여식): 별빛 / 옷장 / 창문+열쇠 조각 ════
     정본: claudedocs/unlock_ceremony_redesign.md (2026-07-31 전면 재디자인) · 공개 시점은
     claudedocs/onboarding_reveal_plan.md §3·§4.

     이 화면은 안내 팝업이 아니라 **점등(點燈)의 순간**이다 — 방금의 완주가 새 보물 하나를 깨운다.
     그래서 세로 스택 상자(= 모달 구도)를 버리고 화면을 3존으로 나눈다:
       ① 하늘 존   0~30%   — UI 없음. 빛이 내려오는 길. 킥커 한 줄만(~25%).
       ② 주인공 존 30~56%  — 상품의 초점 중심이 화면 40% 높이. 이 화면 최대 요소.
       ③ 말·버튼 존 56~100% — 이름·설명·쓰임새·보유량, 버튼은 셋 다 같은 자리 고정.
     샤는 무대에서 내려와 **좌하단 모서리 증인**이 된다(chapterVictory chwMascot 문법). 상품과 한
     상자를 나눠 쓰지 않으므로 "코인이 모자처럼 보인다" 계열의 겹침이 구조적으로 생기지 않는다.

     강도 축은 **크기 하나만** 올렸다(연출 헌법 §4.2 — 축을 동시에 올리지 않는다). 컨페티·팡파르·
     전체 플래시는 없다: 이 화면의 상한은 챕터 빅토리보다 조용한 "점등"이다.
     backdrop-filter blur 금지 — 모바일 WebView 성능(styles/app/02-board.css:353). */
  #unlockCeremony {
    position: fixed; inset: 0; z-index: 72; display: none;
  }
  #unlockCeremony.show { display: block; }
  /* 배경 영상 — 덮개(.ucVeil) **아래** 깔린다. 영상은 움직이는 밑그림일 뿐이고, 대비를 만드는 것은
     그 위의 스크림이다(연출 헌법 §4.1: 주변은 채도·밝기를 낮춰 주인공을 돕는다). */
  #unlockCeremony .ucBgVid {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    z-index: 0; pointer-events: none;
  }
  #unlockCeremony .ucVeil {
    position: absolute; inset: 0;
    /* ⚠️ background 단축 속성에서 **색상은 마지막 레이어에만** 올 수 있다. 중간에 var(--scrim)(색)을
       끼우면 선언 전체가 무효가 되어 베일이 통째로 투명해지고, 수여식 글자가 퍼즐 보드 위에 그대로
       겹쳐 읽히지 않는다(2026-07-30 브라우저 검증에서 실제로 발생). 스크림은 색이므로 레이어로 쓰려면
       linear-gradient로 감싼다. */
    /* 챕터 빅토리(08-chapter-victory.css)와 같은 유적 배경 — 수여식도 "챕터를 끝냈다"는 같은
       자리에서 뜨는 화면이라 배경이 다르면 다른 사건처럼 읽힌다. */
    background:
      radial-gradient(120% 90% at 50% 34%, rgba(255,224,122,.14), transparent 58%),
      linear-gradient(rgba(8,14,26,.46), rgba(5,10,20,.72));
    opacity: 0;
    animation: ucVeilIn .6s ease-out both;
  }

  /* ════ ① 하늘 존 ════ 넓은 위 여백은 "빈 공간"이 아니라 빛이 내려오는 길이다. UI를 넣지 않는다.
     유일한 예외가 킥커 한 줄 — 무엇을 끝냈는지 화면이 먼저 말한다(연출 헌법 §2.1 성취 인정).
     목소리는 chapterVictory의 .chwKicker와 같다: 같은 사건(챕터 완주)은 같은 톤으로. */
  #unlockCeremony .ucKicker {
    position: absolute; top: 25%; left: 0; right: 0; z-index: 2;
    text-align: center; opacity: 0;
    color: rgba(255,233,176,.78); font-size: clamp(12px, 3.4vw, 13.5px); font-weight: 700;
    letter-spacing: .07em; text-shadow: 0 1px 3px rgba(30,16,4,.75);
  }
  #unlockCeremony.show .ucKicker { animation: ucRiseIn .70s linear .35s both; }
  /* 화면 전체에 아주 느리게 떠다니는 빛 티끌 — 하늘 존이 죽은 공간으로 읽히지 않게 하는 최소 장치.
     개수·속도·불투명도 모두 낮게 유지한다(시선을 뺏으면 실패). */
  #unlockCeremony .ucMotes { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
  #unlockCeremony .ucMotes i {
    position: absolute; width: 5px; height: 5px; border-radius: 50%;
    background: radial-gradient(closest-side, rgba(255,248,220,.95), rgba(255,224,140,.5) 45%, transparent 72%);
    opacity: 0;
  }
  #unlockCeremony.show .ucMotes i { animation: ucMoteDrift var(--md, 9s) ease-in-out var(--mdl, 0s) infinite; }
  #unlockCeremony .ucMotes i:nth-child(1) { left: 12%; top: 72%; --md: 11s; --mdl: .2s; }
  #unlockCeremony .ucMotes i:nth-child(2) { left: 28%; top: 88%; --md: 9s;  --mdl: 1.4s; }
  #unlockCeremony .ucMotes i:nth-child(3) { left: 46%; top: 80%; --md: 13s; --mdl: .8s; }
  #unlockCeremony .ucMotes i:nth-child(4) { left: 66%; top: 90%; --md: 10s; --mdl: 2.2s; }
  #unlockCeremony .ucMotes i:nth-child(5) { left: 82%; top: 76%; --md: 12s; --mdl: .5s; }
  #unlockCeremony .ucMotes i:nth-child(6) { left: 20%; top: 40%; --md: 14s; --mdl: 3s; }
  #unlockCeremony .ucMotes i:nth-child(7) { left: 78%; top: 34%; --md: 12s; --mdl: 2s; }
  @keyframes ucMoteDrift {
    0%   { opacity: 0;   transform: translateY(0) scale(.7); }
    18%  { opacity: .85; }
    75%  { opacity: .5; }
    100% { opacity: 0;   transform: translateY(-190px) scale(1.05); }
  }

  /* ════ ②③ 무대 ════ 중앙 정렬(= 화면 가운데 앉는 모달 구도)을 버리고 **위에서부터** 흐르게 한다.
     top:30% + 상품 190px 상자 → 초점 중심이 화면 40% 부근(시선의 자연 착지점). */
  #unlockCeremony .ucStage {
    position: absolute; top: 30%; left: 50%; transform: translateX(-50%); z-index: 2;
    width: min(92vw, 380px);
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    text-align: center;
  }
  #unlockCeremony .ucBeat { display: flex; flex-direction: column; align-items: center; gap: 8px; opacity: 0; }
  #unlockCeremony.uc-revealed #ucBeat1 { animation: ucRiseIn .70s linear both; }
  #unlockCeremony.uc-revealed #ucBeat1 .ucDesc,
  #unlockCeremony.uc-revealed #ucBeat1 .ucUsage,
  #unlockCeremony.uc-revealed #ucBeat1 .ucAmount { opacity: 0; }
  #unlockCeremony.uc-settled #ucBeat1 .ucDesc   { animation: ucRiseIn .63s linear .42s both; }
  #unlockCeremony.uc-settled #ucBeat1 .ucUsage  { animation: ucRiseIn .63s linear .56s both; }
  #unlockCeremony.uc-settled #ucBeat1 .ucAmount { animation: ucRiseIn .63s linear .77s both; }

  /* ════ ② 주인공 ════ 화면 점유를 3% → 12~15%로. 크기 축 하나만 올린다. */
  #unlockCeremony .ucObject {
    position: relative;
    width: 190px; height: 190px;
    display: flex; align-items: center; justify-content: center;
  }
  /* 무대 뒤에 까는 아주 옅은 그늘 — 배경이 밝은 유적이라 금빛 후광이 흰 하늘에 묻힌다. 빛을 더
     키우는 대신 **받칠 바닥**을 만든다. 후광(-2)보다 더 뒤(-3). */
  #unlockCeremony .ucObject::before {
    content: ""; position: absolute; z-index: -3; inset: -55%;
    background: radial-gradient(closest-side, rgba(26,14,4,.36), rgba(26,14,4,.17) 52%, transparent 74%);
    opacity: 0;
  }
  #unlockCeremony.uc-revealed .ucObject::before { animation: ucBloomIn .7s ease-out both; }
  /* 뒤에서 도는 후광 — 아주 느리게 돌아 "빛이 살아 있다"만 전한다(속도를 올리면 차분한 톤이 깨진다).
     190px × 178% ≈ 338px ≈ 87vw(390 기준) — 주인공 존을 가로로 채운다. */
  #unlockCeremony .ucRays {
    position: absolute; z-index: -2; width: 178%; height: 178%; border-radius: 50%;
    background: conic-gradient(from 0deg,
      rgba(255,226,138,.34) 0deg, transparent 16deg, rgba(255,226,138,.22) 32deg, transparent 52deg,
      rgba(255,226,138,.32) 72deg, transparent 92deg, rgba(255,226,138,.20) 112deg, transparent 136deg,
      rgba(255,226,138,.34) 160deg, transparent 182deg, rgba(255,226,138,.22) 205deg, transparent 228deg,
      rgba(255,226,138,.30) 252deg, transparent 274deg, rgba(255,226,138,.20) 296deg, transparent 320deg,
      rgba(255,226,138,.32) 340deg, transparent 360deg);
    -webkit-mask-image: radial-gradient(closest-side, transparent 22%, #000 44%, transparent 78%);
            mask-image: radial-gradient(closest-side, transparent 22%, #000 44%, transparent 78%);
    opacity: 0;
  }
  #unlockCeremony.uc-revealed .ucRays { animation: ucRaysIn .8s ease-out both, ucRaysSpin 26s linear .8s infinite; }
  #unlockCeremony .ucBloom {
    position: absolute; z-index: -1; width: 132%; height: 132%; border-radius: 50%;
    background: radial-gradient(closest-side, rgba(255,236,170,.55), rgba(255,214,120,.16) 58%, transparent 74%);
    opacity: 0; transform: scale(.6);
  }
  #unlockCeremony.uc-revealed .ucBloom { animation: ucBloomIn .7s cubic-bezier(.2,1,.3,1) both, ucBloomPulse 3.4s ease-in-out .7s infinite; }
  /* ════ 빛기둥 (강림 경로) ════ 하늘 존이 "빈 공간"이 아니라 길이라는 것을 눈에 보이게 만드는 요소.
     화면 맨 위에서 초점까지 세로로 켜지고(origin top), 상품이 그 길을 타고 내려온다. 착지 프레임에
     스냅아웃 — 빛이 물건에 "부어졌다"로 읽힌다. transform(scaleY)만 쓴다. */
  /* ⚠️ 가운데 정렬은 **margin-left = 폭의 절반**이라 폭과 반드시 같은 식이어야 한다. 2026-08-06까지
     `margin-left: min(-15vw, -59px)`였는데, 음수에서 min()은 **더 작은(=더 왼쪽) 쪽**을 고르므로
     폭이 118px 상한에 걸린 뒤에도 여백만 계속 커졌다 — 아이패드 프로 10.5"(834px) 실측에서 빛기둥이
     상품 중심에서 **67.7px 왼쪽으로** 빗나갔다(폭 118인데 여백 −125.1). 좁은 화면에서는 두 값이
     거의 같아 안 드러났다. 같은 날 좌하단 증인(.ucWitness)에서 고친 것과 **같은 부류의 실수**다.
     그래서 폭을 변수로 두고 여백을 거기서 파생시킨다 — 이러면 다시 어긋날 수가 없다. */
  #unlockCeremony .ucColumn {
    --uc-col-w: min(30vw, 118px);
    position: absolute; z-index: -2; left: 50%; bottom: 50%;
    width: var(--uc-col-w); height: 62vh; margin-left: calc(var(--uc-col-w) / -2);
    transform-origin: 50% 0%; transform: scaleY(0);
    background: linear-gradient(180deg, rgba(255,238,180,0) 0%, rgba(255,238,180,.34) 26%, rgba(255,244,205,.5) 74%, rgba(255,244,205,.14) 100%);
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
    opacity: 0; pointer-events: none;
  }
  #unlockCeremony.uc-prize-in .ucColumn { animation: ucColumnIn .35s ease-out both; }
  #unlockCeremony.uc-land .ucColumn { animation: ucColumnOut .3s ease-in both; }
  @keyframes ucColumnIn  { from { opacity: 0; transform: scaleY(0); } to { opacity: 1; transform: scaleY(1); } }
  @keyframes ucColumnOut { from { opacity: 1; transform: scaleY(1); } to { opacity: 0; transform: scaleY(1.06); } }

  /* ════ 창문 전용: 제네릭 스테인드글라스 ════ 특정 챕터 그림을 쓰지 않는다(§8-3). 보석색 유리판이
     4분할로 앉은 창문 — shardAssembly가 이 배경을 쐐기로 잘라 날려 보낸다. */
  #unlockCeremony .ucGlassCell {
    position: absolute; z-index: 2; width: 168px; height: 168px;
    display: flex; align-items: center; justify-content: center;
  }
  #unlockCeremony .ucGlassCell[hidden] { display: none; }
  /* 장미창 실물 1장. **방사 대칭**이라 shardAssembly의 쐐기 절단(중심에서 뻗는 삼각형)과 정확히 맞물린다 —
     사각 격자 창이면 모서리 쐐기가 어색해진다. 특정 챕터 그림이 아니라 추상 문양인 것도 의도다:
     18판 시점 플레이어의 창문은 미완이라 실물 작품을 보이면 그 자리에서 미달 통보가 된다(§8-3).
     ⚠️ FX가 이 요소의 computed backgroundImage/Size/Position을 그대로 읽어 쐐기에 복사한다 —
     background를 단축이 아닌 다른 형태로 바꾸면 조각에 그림이 안 실린다. */
  #unlockCeremony .ucGlass {
    width: 100%; height: 100%;
    background: url('../../assets/gallery/ceremony_window.webp') center / contain no-repeat;
    filter: drop-shadow(0 6px 14px rgba(30,16,4,.45));
  }

  /* 🪦 문/빛의 장막(uc-door · uc-light · uc-gate) — 2026-08-02 제거. 되살리지 말 것.
     옷장은 나무 문이 내려와 갈라지고 그 빛 속에서 상품이 나왔고(2026-08-01), 별빛은 같은 동사를 빛의
     장막으로 공유했다. 경첩 회전·예비 동작까지 넣어 보강했지만 **오너가 걷어내기로 결정** — 세 수여식은
     이제 하나의 동사만 쓴다: **위에서 내려온다.** 상품이 화면 밖에서 강림해 착지하는 것(ucPrizeDrop)이
     이 화면의 유일한 절정이고, 장치가 하나 줄어 그 절정이 더 또렷해진다.
     상품 크기(253px)는 남긴다 — 문 때문에 키웠던 값이지만 그 자체로 주인공이 커서 좋다(오너 2026-08-01). */
  /* ⚠️ 2026-08-02 사고 기록: 문/장막 CSS를 통째로 지울 때 **사이에 끼어 있던 이 규칙들까지 함께
     날아갔다**(상품의 위치·낙하 애니메이션·창문 아이콘 숨김). 결과: 별빛 수여식에서 창문용 4분할
     아이콘이 코인 옆에 새어 나왔고 상품이 흐름에 끼어 앉았다 — 브라우저로 띄워 봐서 잡았다.
     블록 삭제는 "시작~끝 선택자" 기준으로 자르지 말고 **지울 규칙을 하나씩 확인**할 것. */
  #unlockCeremony .ucPrize {
    position: absolute; z-index: 2; width: 158px; height: 158px; object-fit: contain;
    filter: drop-shadow(0 10px 16px rgba(60,34,4,.5));
    opacity: 0;
  }
  #unlockCeremony .ucPrize[hidden] { display: none; }
  /* 상품은 화면 **밖에서**(-60vh) 강림해 착지한다 — 이 화면의 유일한 절정. 2026-08-02에 문/장막을
     걷어내면서 세 수여식이 전부 이 하나의 동사만 쓴다(오너 결정). */
  #unlockCeremony.uc-prize-in .ucPrize { animation: ucPrizeDrop .65s cubic-bezier(.24,1.3,.4,1) both, ucPrizeIdle 3.2s ease-in-out .75s infinite; }
  #unlockCeremony .ucIcon {
    width: 132px; height: 132px; color: var(--gold); fill: none; stroke: currentColor; stroke-width: 1.25;
    stroke-linecap: round; stroke-linejoin: round;
    filter: drop-shadow(0 8px 14px rgba(40,22,4,.5));
  }
  #unlockCeremony .ucIcon[hidden] { display: none; }
  /* 253 → 190px (오너 2026-08-02 "너무 크다"). 문을 걷어내며 문 때문에 키웠던 값도 함께 내렸다. */
  #unlockCeremony.uc-big .ucPrize { width: 190px; height: 190px; }
  /* 상자도 같이 키운다 — .ucPrize는 absolute라 혼자 키우면 아래 이름이 밀려나지 않아 이름 위를 덮는다
     (2026-08-01 실측). 후광·바닥 그늘은 상자 기준 비율이라 함께 커진다. */
  #unlockCeremony.uc-big .ucObject { width: 224px; height: 224px; }

  #unlockCeremony .ucRing {
    position: absolute; z-index: 1; width: 120px; height: 120px; border-radius: 50%;
    border: 2px solid rgba(255,232,160,.85); opacity: 0;
  }
  #unlockCeremony.uc-land .ucRing { animation: ucRingOut .68s ease-out both; }
  #unlockCeremony .ucSparks { position: absolute; z-index: 3; inset: -12%; pointer-events: none; }
  #unlockCeremony .ucSparks i {
    position: absolute; width: 9px; height: 9px; opacity: 0;
    background: radial-gradient(closest-side, #fff8dc, rgba(255,224,130,.9) 45%, transparent 70%);
  }
  #unlockCeremony .ucSparks i:nth-child(1) { left: 10%; top: 22%; --sd: 0s;    --sx: -34px; --sy: -30px; }
  #unlockCeremony .ucSparks i:nth-child(2) { left: 84%; top: 18%; --sd: .06s;  --sx:  38px; --sy: -26px; }
  #unlockCeremony .ucSparks i:nth-child(3) { left: 50%; top: 2%;  --sd: .12s;  --sx:   4px; --sy: -42px; }
  #unlockCeremony .ucSparks i:nth-child(4) { left: 4%;  top: 70%; --sd: .04s;  --sx: -42px; --sy:  22px; }
  #unlockCeremony .ucSparks i:nth-child(5) { left: 90%; top: 66%; --sd: .1s;   --sx:  42px; --sy:  18px; }
  #unlockCeremony .ucSparks i:nth-child(6) { left: 46%; top: 92%; --sd: .16s;  --sx:  -4px; --sy:  38px; }
  #unlockCeremony .ucSparks i:nth-child(7) { left: 24%; top: 6%;  --sd: .08s;  --sx: -22px; --sy: -36px; }
  #unlockCeremony .ucSparks i:nth-child(8) { left: 72%; top: 88%; --sd: .14s;  --sx:  26px; --sy:  32px; }
  #unlockCeremony.uc-land .ucSparks i { animation: ucSparkPop .78s ease-out var(--sd) both; }
  /* 지평선 빛줄 — 착지 높이에서 화면 양끝으로 한 번 퍼진다. 예전의 대각 스윕을 대체한다: 스윕은
     무대를 가로질러 "지나가는" 빛이라 착지의 인과를 만들지 못했고, 이건 착지 지점에서 **뻗어
     나가는** 빛이라 원인(착지)과 결과(퍼짐)가 한 프레임에 붙는다. */
  #unlockCeremony .ucHorizon {
    position: absolute; z-index: 2; top: 40%; left: 50%; width: 100vw; height: 2px;
    transform: translate(-50%, -50%) scaleX(0);
    background: linear-gradient(90deg, transparent, rgba(255,244,205,.9) 50%, transparent);
    opacity: 0; pointer-events: none;
  }
  #unlockCeremony.uc-land .ucHorizon { animation: ucHorizonSpread .62s cubic-bezier(.2,.85,.3,1) both; }

  /* ════ ③ 말 존 ════ 어두운 유적 배경 위 글자 — 밝은 캔버스 기준의 --text/--muted는 묻힌다. */
  #unlockCeremony .ucName {
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400; font-size: clamp(26px, 7.4vw, 32px);
    color: var(--gold); -webkit-text-stroke: 1px var(--gold-deep); paint-order: stroke fill;
    text-shadow: 0 2px 0 var(--gold-deep), 0 3px 8px rgba(20,10,0,.5);
  }
  /* 이름은 착지의 반동으로 **찍힌다**(스탬프). 페이드로 올라오면 절정과 정산이 끊긴다. */
  #unlockCeremony.uc-settled #ucBeat1 .ucName { animation: ucNameStamp .56s linear both; }
  #unlockCeremony .ucDesc, #unlockCeremony .ucUsage {
    display: block; max-width: 300px; color: rgba(255,250,240,.92); font-size: clamp(15px, 3.6vw, 16px);
    font-weight: 600; line-height: 1.45; text-shadow: 0 1px 3px rgba(0,0,0,.65);
    /* 한국어가 어절 중간에서 잘리면("열려 / 요") 문장이 사고처럼 읽힌다. balance는 두 줄 길이를 고르게
       나눠 외톨이 글자를 없앤다(미지원 브라우저는 keep-all만 적용돼도 충분). */
    word-break: keep-all; overflow-wrap: break-word; text-wrap: balance;
  }
  /* ⚠️ 짝 규칙 필수 — 위에서 작성자가 `display:block`을 줬으므로 브라우저 기본 `[hidden]{display:none}`이
     진다. 이게 없으면 창문 수여식에서 비운 설명 줄이 **빈 줄로 남아** 무언극과 쓰임새 사이가 벌어진다
     (CLAUDE.md에 박힌 그 함정 — noDrags·outing에서 두 번 났다). */
  #unlockCeremony .ucDesc[hidden] { display: none; }
  /* ── 창문 사슬 도식 (오너 2026-08-02) ────────────────────────────────────────────────────────
     네 칸을 한 줄에: 별 셋 → 조각 → 열쇠 → 창문. 순서대로 불이 들어온다. 문장 넷을 대신하는 자리라
     칸마다 **한 단어 라벨**만 둔다(문장이 아니라 이름표라 읽는 비용이 다르고, 두 이름을 지킬 수 있다).
     ⚠️ 1차 시안은 [별 셋 → 열쇠]만 두고 창문은 위의 실물을 반짝이게 했는데, 사슬의 끝이 줄 밖에 있어
     관계가 안 읽혔다(오너 판정). 끝까지 한 줄에 넣는 것이 이 도식의 핵심이다. */
  #unlockCeremony .ucDemo {
    display: flex; align-items: flex-start; justify-content: center; gap: clamp(4px, 1.4vw, 7px);
    margin: 4px 0 2px; opacity: 0; transform: translateY(6px);
    transition: opacity .3s ease, transform .3s ease;
  }
  #unlockCeremony .ucDemo[hidden] { display: none; }
  #unlockCeremony .ucDemo.show { opacity: 1; transform: none; }
  #unlockCeremony .ucDemoStep {
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    opacity: .34; filter: grayscale(.45);
    transition: opacity .22s ease, filter .22s ease, transform .22s ease;
  }
  #unlockCeremony .ucDemoStep.on { opacity: 1; filter: none; transform: scale(1.04); }
  #unlockCeremony .ucDemoArt { display: inline-flex; align-items: center; justify-content: center; min-height: 32px; }
  #unlockCeremony .ucDemoCap {
    font-size: clamp(9.5px, 2.6vw, 11px); font-weight: 700; letter-spacing: .02em;
    color: rgba(255,236,186,.82); text-shadow: 0 1px 2px rgba(30,16,4,.7); white-space: nowrap;
  }
  /* ⚠️ 스프라이트 아이콘은 `<path fill="currentColor">`라 **글자색을 따른다** — svg에 fill을 주면
     path의 표시 속성이 이겨서 무시되고 어두운 본문색으로 칠해진다(2026-08-02 브라우저 실측: 별 셋이
     검게 나왔다). 이 스프라이트를 쓰는 곳에서는 fill이 아니라 color를 바꾼다. */
  #unlockCeremony .ucDemoStars { gap: 1px; }
  #unlockCeremony .ucDemoStars .ico { width: 15px; height: 15px; color: var(--gold, #f6c83c); }
  #unlockCeremony .ucDemoStep.on .ucDemoStars .ico { filter: drop-shadow(0 0 5px rgba(255,214,110,.7)); }
  /* 마지막 칸은 **위에 떠 있는 그 창문의 축소판**이어야 한다 — 처음엔 스프라이트의 4분할 아이콘을 썼는데
     주인공이 둥근 스테인드글라스라 서로 다른 물건처럼 보였다(2026-08-02 브라우저 실측). 같은 문법의
     원형 유리(쐐기 + 납테 + 중앙 불꽃)를 CSS로 그린다 — 새 아트 0장. */
  #unlockCeremony .ucDemoWindow {
    width: 22px; height: 22px; border-radius: 50%;
    background:
      radial-gradient(closest-side, #ffd35a 0 26%, rgba(255,211,90,0) 30%),
      conic-gradient(#d8483f 0 45deg, #e8a33a 45deg 90deg, #4f8fd0 90deg 135deg, #7a4fc0 135deg 180deg,
                     #d8483f 180deg 225deg, #e8a33a 225deg 270deg, #4f8fd0 270deg 315deg, #7a4fc0 315deg 360deg);
    box-shadow: inset 0 0 0 2px rgba(122,84,28,.92), 0 0 0 1px rgba(255,226,150,.45);
  }
  #unlockCeremony .ucDemoStep.on .ucDemoWindow { box-shadow: inset 0 0 0 2px rgba(122,84,28,.92), 0 0 8px 2px rgba(255,214,110,.8); }
  /* One shard is the repeatable reward; the adjacent complete key is shown only as the N/N outcome. */
  #unlockCeremony .ucDemoShard {
    width: 9px; height: 15px; border-radius: 2px; background: #f6c83c;
    box-shadow: inset 0 -2px 3px rgba(150,96,10,.5);
  }
  #unlockCeremony .ucDemoStep.on .ucDemoShard { box-shadow: inset 0 -2px 3px rgba(150,96,10,.5), 0 0 7px rgba(255,214,110,.75); }
  #unlockCeremony .ucDemoKey { min-width: 42px; }
  /* 화살표는 글자(→)가 아니라 선이다 — 어느 언어에서도 똑같이 읽히고 줄바꿈에 끌려가지 않는다. */
  #unlockCeremony .ucDemoArrow {
    position: relative; width: clamp(11px, 3.2vw, 16px); height: 2px; border-radius: 2px; margin-top: 15px;
    background: rgba(255,226,150,.28); transition: background .22s ease;
  }
  #unlockCeremony .ucDemoArrow::after {
    content: ""; position: absolute; right: -1px; top: 50%;
    width: 5px; height: 5px; border-top: 2px solid rgba(255,226,150,.28);
    border-right: 2px solid rgba(255,226,150,.28); transform: translateY(-50%) rotate(45deg);
    transition: border-color .22s ease;
  }
  #unlockCeremony .ucDemoArrow.on { background: rgba(255,226,150,.85); }
  #unlockCeremony .ucDemoArrow.on::after { border-top-color: rgba(255,226,150,.85); border-right-color: rgba(255,226,150,.85); }
  /* 마지막 칸에 불이 들어오는 순간, 위에 떠 있는 **그 창문**도 한 번 반짝인다 — 도식의 끝과 화면의
     주인공이 같은 물건이라는 것이 이 한 박자로 붙는다. */
  #unlockCeremony.uc-demo-done #ucObject1 .ucGlassCell { animation: ucDemoGlassPulse .7s ease-out both; }
  @keyframes ucDemoGlassPulse {
    0%   { filter: none; transform: scale(1); }
    38%  { filter: brightness(1.5) drop-shadow(0 0 16px rgba(255,226,150,.85)); transform: scale(1.06); }
    100% { filter: none; transform: scale(1); }
  }
  /* ── 자수정 사슬 전용 칸 (오너 승인 2026-08-02) — 나머지(.ucDemoStep/.ucDemoArrow/.ucDemoCap)는 창문과 공유 */
  #unlockCeremony .ucDemoBonus .ico { width: 25px; height: 25px; color: #ffe6a3; }
  #unlockCeremony .ucDemoGem i { display: block; width: 24px; height: 24px; border-radius: 50%;
    border: 1.5px solid #e9d8ff;
    background:
      radial-gradient(circle at 38% 30%, rgba(255,250,255,.95) 0 12%, transparent 22%),
      radial-gradient(circle at 50% 56%, #c79bf3 0 32%, #9a55e0 66%, #5f2ea0 100%);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.5), inset 0 -2px 3px rgba(60,24,110,.42), 0 0 6px rgba(154,85,224,.5); }
  #unlockCeremony .ucDemoStep.on .ucDemoBonus .ico { filter: drop-shadow(0 0 7px rgba(255,226,150,.8)); }
  #unlockCeremony .ucDemoStep.on .ucDemoGem i { box-shadow: inset 0 1px 0 rgba(255,255,255,.5), inset 0 -2px 3px rgba(60,24,110,.42), 0 0 11px 2px rgba(154,85,224,.85); }
  /* 마지막 칸은 나머지보다 살짝 크다 — 액자가 있는 그림이라 같은 치수면 실루엣이 더 작게 읽힌다. */
  #unlockCeremony .ucDemoRobe img { width: 32px; height: 32px; object-fit: contain; }
  #unlockCeremony .ucDemoStep.on .ucDemoRobe img { filter: drop-shadow(0 0 7px rgba(255,226,150,.7)); }
  /* 자수정 보석 알 — 맵 노드 소켓(.eliteSocket.filled)을 그대로 확대한 것. 색·하이라이트 위치·테두리가
     같아야 "맵에서 본 그 보석"으로 읽힌다(오너 2026-08-02: "게임에서 쓰는 것과 너무 다르다").
     상품이 <img>가 아니라 이 요소이므로 낙하 키프레임을 여기에 건다 — 세 수여식의 유일한 동사가
     "위에서 내려온다"인데 자수정만 그냥 나타나면 통일이 깨진다. */
  /* 크기 132 → 104px (오너 2026-08-05 "너무 커서 귀엽고 예쁘지 않아"). 두 가지가 함께 잘못돼 있었다:
     ⑴ **임팩트 링(.ucRing 120px)보다 자수정이 더 컸다.** 링은 scale(.38)→(2.3)으로 퍼지는데(45.6→276px)
        앞의 절반이 132px 알 **뒤에** 숨어, 절정의 파문이 늦게 시작하는 것처럼 보였다. 104px면 링이
        「받침」처럼 둘러싸 보석이 그 안에 앉은 것으로 읽힌다.
     ⑵ 이 알은 크기를 **아이콘(.ucIcon 132px)에서 빌려왔는데**, 아이콘은 선화(stroke 1.25·fill none)라
        같은 치수여도 잉크가 거의 없다. 꽉 찬 원을 같은 크기로 두면 훨씬 무겁게 읽힌다 — 무대를 삼키고
        바로 아래 「자수정」 글자에 닿을 만큼. 채워진 도형은 선화보다 작아야 무게가 맞는다.
     ⚠️ **둥근 실루엣은 건드리지 말 것** — 각진 #i-gem 스프라이트를 쓰면 맵 노드 소켓에서 본 보석과
     다른 물건으로 읽힌다(오너 2026-08-02, 위 unlockCeremony.js 주석). 아래 컷면은 실루엣을 바꾸지 않고
     **안쪽에만** 넣는다. */
  #unlockCeremony .ucGemJewel {
    position: absolute; z-index: 2; width: 104px; height: 104px; border-radius: 50%;
    border: 4px solid #e9d8ff;
    isolation: isolate;                 /* 아래 컷면의 blend를 이 알 안에 가둔다(뒤의 배경 영상과 섞이면 안 된다) */
    background:
      radial-gradient(circle at 38% 30%, rgba(255,250,255,.95) 0 12%, transparent 24%),
      radial-gradient(circle at 50% 56%, #c79bf3 0 32%, #9a55e0 66%, #5f2ea0 100%);
    box-shadow:
      inset 0 4px 0 rgba(255,255,255,.45), inset 0 -10px 16px rgba(60,24,110,.45),
      0 8px 16px rgba(30,10,60,.5), 0 0 26px rgba(154,85,224,.6);
    opacity: 0;
  }
  /* 컷면 — 맵에서는 19px이라 안 보이던 결점이 수여식에서 5배로 커지며 드러났다: 면이 하나도 없어서
     **보석이 아니라 보라색 구슬**로 보였다. 방사형 명암을 얹어 「깎인 돌」로 읽히게 한다. 실루엣은
     그대로 원이므로 맵 소켓과 같은 물건으로 남는다. soft-light라 바탕 보라의 색조는 유지된다. */
  #unlockCeremony .ucGemJewel::before {
    content: ""; position: absolute; inset: 0; border-radius: 50%; mix-blend-mode: soft-light;
    background: conic-gradient(from 12deg,
      rgba(255,255,255,.20)   0deg  26deg, rgba(90,40,150,.24)  26deg  62deg,
      rgba(255,255,255,.11)  62deg  96deg, rgba(90,40,150,.26)  96deg 140deg,
      rgba(255,255,255,.17) 140deg 178deg, rgba(90,40,150,.22) 178deg 220deg,
      rgba(255,255,255,.09) 220deg 262deg, rgba(90,40,150,.28) 262deg 306deg,
      rgba(255,255,255,.15) 306deg 340deg, rgba(90,40,150,.20) 340deg 360deg);
  }
  #unlockCeremony .ucGemJewel[hidden] { display: none; }

  /* ── 펫 「칸노」 수여식 — 유일하게 낙하하지 않는 상품 (pet_recruit_spec §3-3) ─────────────────────
     기존 넷은 물건이라 위에서 내려오는 것이 맞지만, **살아 있는 것이 하늘에서 떨어지면 물건이 된다.**
     길이(T_PRIZE 1150 + T_FALL 650)와 착지 임팩트는 그대로 두고 **경로만 수평**으로 바꾼다 —
     연출 헌법 §4.3(출발점 = 원인, 도착점 = 결과).
     ⚠️ 그림이 전부 머리를 왼쪽에 두고 그려져 있어 **오른쪽에서 왼쪽으로** 걸어온다(§5-1). 좌우 반전
     금지 — 반전하면 앉기 포즈와 방향이 어긋난다.
     ⚠️ 걷기 4프레임은 `steps(1)`로 배경만 갈아 끼운다. **JS로 프레임을 돌리지 않는다** — 길이를 두
     곳에 적으면 한쪽만 고쳐도 아무 검사에 안 걸리고 조용히 어긋난다(fx/animClass.js 주석과 같은 이유). */
  #unlockCeremony .ucPetSprite {
    position: absolute; left: 50%; bottom: 0; width: 190px; height: 161px;
    margin-left: -95px;   /* transform은 걷기 애니가 쓰므로 가운데 정렬은 마진으로 (덮어쓰기 함정) */
    background: url('../../assets/companion/kanno/walk-1.webp') center bottom / contain no-repeat;
    filter: drop-shadow(0 6px 10px rgba(8,20,40,.5));
  }
  #unlockCeremony .ucPetSprite[hidden] { display: none; }
  #unlockCeremony.uc-prize-in .ucPetSprite {
    /* 프레임 간격 = .52s ÷ 4 = **130ms** (오너가 GIF로 고른 값). 한 바퀴가 걷기 길이(.65s)보다 짧아
       도중에 다시 돌지만, 순환이라 이음매가 없다. 착지에서 .uc-land가 animation을 통째로 갈아 끼운다. */
    animation: ucPetWalkIn .65s linear both, ucPetStep .52s steps(1) infinite;
  }
  /* 착지 = 앉으며 앞발을 든다. 걷기 애니를 멈추고 그림을 갈아 끼운다(이 화면의 유일한 절정). */
  #unlockCeremony.uc-land .ucPetSprite {
    animation: ucPetSit .34s cubic-bezier(.24,1.5,.4,1) both;
    background-image: url('../../assets/companion/kanno/sit-wave.webp');
  }
  /* 걸어오는 경로 — 오른쪽 화면 밖에서 가운데로. 이징은 linear다(걸음은 등속이고, 감속을 얹으면
     발이 미끄러지는 것으로 읽힌다). 멈추는 쫀득함은 아래 ucPetSit의 스쿼시가 맡는다. */
  @keyframes ucPetWalkIn {
    from { opacity: 0; transform: translateX(62vw); }
    8%   { opacity: 1; }
    to   { opacity: 1; transform: translateX(0); }
  }
  @keyframes ucPetStep {
    0%   { background-image: url('../../assets/companion/kanno/walk-1.webp'); }
    25%  { background-image: url('../../assets/companion/kanno/walk-2.webp'); }
    50%  { background-image: url('../../assets/companion/kanno/walk-3.webp'); }
    75%  { background-image: url('../../assets/companion/kanno/walk-4.webp'); }
  }
  /* 부피 보존 스쿼시(연출 헌법 §4.6 ①) — 앉는 순간 세로로 눌리고 가로로 퍼졌다가 제자리.
     이동도 회전도 없다(④ 자리를 잡은 뒤에는 흔들지 않는다). */
  @keyframes ucPetSit {
    0%   { transform: scaleX(1) scaleY(1); }
    38%  { transform: scaleX(1.07) scaleY(.93); }
    70%  { transform: scaleX(.98) scaleY(1.02); }
    100% { transform: scaleX(1) scaleY(1); }
  }
  html[data-motion="reduced"] #unlockCeremony .ucPetSprite { animation: none !important; opacity: 1; transform: none; }
  html[data-motion="reduced"] #unlockCeremony.uc-land .ucPetSprite { background-image: url('../../assets/companion/kanno/sit-wave.webp'); }
  #unlockCeremony.uc-prize-in .ucGemJewel { animation: ucPrizeDrop .65s cubic-bezier(.24,1.3,.4,1) both, ucPrizeIdle 3.2s ease-in-out .75s infinite; }
  html[data-motion="reduced"] #unlockCeremony .ucDemo { transition: none; opacity: 1; transform: none; }
  html[data-motion="reduced"] #unlockCeremony.uc-demo-done #ucObject1 .ucGlassCell { animation: none; }
  #unlockCeremony .ucAmount { display: flex; flex-direction: column; align-items: center; gap: 2px; margin-top: 4px; }
  #unlockCeremony .ucAmount[hidden] { display: none; }
  #unlockCeremony .ucAmountLabel { font-size: 12.5px; font-weight: 700; letter-spacing: .04em; color: rgba(255,233,176,.78); text-shadow: 0 1px 2px rgba(0,0,0,.55); }
  #unlockCeremony .ucAmountVal {
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400; font-size: 26px; line-height: 1;
    color: var(--gold); -webkit-text-stroke: 1.1px var(--gold-deep); paint-order: stroke fill;
    text-shadow: 0 2px 0 var(--gold-deep), 0 3px 6px rgba(60,34,4,.4);
  }
  /* 코인이 한 알 도착할 때마다 숫자가 한 번 눌렸다 튄다 (오너 지시 2026-08-07).
     ⚠️ **부피 보존 스쿼시&스트레치**다 — 균일 scale()은 커졌다 작아진 것으로만 보이고 탄성이 안
     읽힌다(연출 헌법 §4.6 ①, 옛 착지 연출 72개가 전부 그 실수였다). 이동·회전 0(§4.6 ④).
     ⚠️ 길이를 JS에 적지 않는다 — unlockCeremony.js는 `playClass()`로 걸고, 그 안전망 타이머는
     이 duration을 getComputedStyle로 **읽는다**. 여기만 고치면 된다(§4.6의 타이머 복제 함정). */
  #unlockCeremony .ucAmountVal.ucAmountTick { animation: ucAmountTick .26s ease-in-out; }
  @keyframes ucAmountTick {
    0%   { transform: scaleX(1.10) scaleY(.90); animation-timing-function: ease-in-out; }
    46%  { transform: scaleX(.96)  scaleY(1.06); animation-timing-function: ease-in-out; }
    100% { transform: scale(1); }
  }

  /* window 전용 2nd beat(열쇠 조각) — 1st beat가 자리 잡은 뒤에만 같은 문법으로 이어 등장한다.
     조건 문장은 여기 한 곳뿐이고, 이 비트에는 임팩트·링·햅틱이 없다(절정은 이미 지나갔다). */
  /* 창문 수여식만 2단이라 그대로 두면 열쇠 조각이 **버튼을 파고든다**(390×844 실측: 2단 하단 779px
     vs 버튼 상단 689px). 재디자인 §3의 "창문 후퇴"를 그대로 쓴다 — 2단이 붙는 순간 무대가 위로
     물러나고 1단 상품이 줄어, 열쇠가 앉을 자리가 생긴다. 버튼 위치는 절대 움직이지 않는다(세 수여식
     공통 고정이 곧 "또 하나 열렸다"의 학습 단서). */
  #unlockCeremony .ucStage { transition: top .45s cubic-bezier(.2,.9,.3,1); }
  #unlockCeremony #ucBeat1 .ucObject { transition: transform .45s cubic-bezier(.2,.9,.3,1), margin .45s ease; }
  html[data-motion="reduced"] #unlockCeremony .ucStage,
  html[data-motion="reduced"] #unlockCeremony #ucBeat1 .ucObject { transition: none !important; }

  /* ════ 증인 ════ 샤는 무대 밖 좌하단 모서리에서 절정 뒤에 합류한다(chapterVictory chwMascot과
     같은 역할·같은 타이밍). 상품·숫자를 가리지 않고, 버튼과 대각으로 균형을 잡는다. */
  /* ⚠️ 크기·위치는 **버튼과의 관계**가 정한다. 둘 다 화면 하단에 있고 버튼은 가운데 정렬이라,
     샤가 설 수 있는 폭은 "버튼 왼쪽 끝까지" 하나뿐이다: `(100vw - 버튼폭) / 2`.
     이전 값(34vw + 버튼 72vw)은 그 폭을 **넘겼다** — 오너 실기기(2026-07-31)에서 버튼이 샤의
     어깨·팔을 가로질러 잘라, 뒤에 선 구도가 아니라 사고처럼 보였다. 원인은 샤 이미지가 세로로
     길다는 것(745×1097 = 1:1.47): 가로 34vw면 세로는 50vw가 되어 버튼 높이(약 16vw)를 훌쩍 넘는다.
     그래서 **세로로는 비켜설 수 없고 가로로만 비켜설 수 있다.**
     지켜야 할 부등식 (모든 화면폭에서 성립):
         샤 오른쪽 끝(width + left) ≤ 버튼 왼쪽 끝((100vw - 버튼폭)/2) − 여유 1vw
     현재 값: 22vw − 5vw = 17vw  ≤  (100 − 64)/2 = 18vw − 1vw = 17vw  ✅ (버튼폭은 아래 .ucBtn)
     둘 중 **하나만 고치면 반드시 다시 겹친다** — 샤를 키우려면 버튼을 같은 만큼 좁혀야 한다.
     left가 음수인 것은 의도다: 화면 밖으로 살짝 걸쳐 "무대 밖에서 지켜본다"를 만든다(다 보이면
     무대 위 인물이 된다). 단 얼굴이 잘리지 않는 선(전체 폭의 ~25%)까지만.
     ⚠️ **그 "~25%"는 폭과 left의 단위가 같을 때만 지켜진다** (2026-08-06 오너 보고: 아이패드 프로
     10.5"에서 샤가 절반쯤 잘렸다). 옛 값은 `width: min(22vw, 100px)` + `left: -5vw`였는데, 폭은
     100px에서 **상한에 걸려 멈추는 반면** 밀어내는 양은 vw라 화면이 넓어질수록 계속 커진다:
        390px(아이폰)  폭 85.8 · 밀어냄 19.5 → 잘림 22.7%   ← 의도값
        834px(아이패드 세로) 폭 100(상한) · 밀어냄 41.7 → 잘림 41.7%
       1112px(아이패드 가로) 폭 100(상한) · 밀어냄 55.6 → 잘림 55.6%  ← 절반 넘게
     그래서 left를 **제 폭의 비율**로 묶는다(--uc-witness-w). 22vw 대비 5vw = 0.227이라 좁은 화면의
     그림은 1픽셀도 안 바뀌고, 넓은 화면에서만 밀어냄이 폭을 따라 멈춘다.
     위 부등식도 그대로 성립한다(834px: 오른쪽 끝 77px ≤ 버튼 왼쪽 264px). */
  #unlockCeremony .ucWitness {
    --uc-witness-w: min(22vw, 100px);
    position: absolute; z-index: 3; bottom: 0;
    /* 화면폭이 아니라 **제 폭**의 5/22만 무대 밖으로. 5/22는 옛 값(left −5vw ÷ width 22vw)을 그대로
       옮긴 비율이라 반올림 오차가 없다 — 좁은 화면에서는 계산 결과가 옛 값과 **완전히 동일**하고,
       따라서 위 「버튼과 겹치지 않는다」 부등식도 1픽셀도 손해 보지 않는다. */
    left: calc(var(--uc-witness-w) * -5 / 22);
    width: var(--uc-witness-w); height: auto; object-fit: contain;
    filter: drop-shadow(0 8px 14px rgba(60,34,4,.42));
    opacity: 0; pointer-events: none;
  }
  #unlockCeremony .ucWitness[hidden] { display: none; }
  #unlockCeremony.uc-land .ucWitness { animation: ucWitnessIn .87s linear .21s both; }

  /* ════ 버튼 ════ 세 수여식 모두 **같은 자리**(하단 고정). 콘텐츠 양(별빛=보유량, 창문=2비트)에
     따라 위치가 출렁이지 않는다 — 세 번 반복되는 화면이라 위치 학습이 곧 "또 하나 열렸다" 학습이다.
     재질은 chapterVictory의 btn_green과 통일한다(전면 축하 화면 가족).
     ⚠️ 폭 72vw → 64vw(300 → 290px 상한), 2026-07-31: 좌하단 증인(.ucWitness)이 설 자리를 만들기 위해
     양옆을 각각 4vw씩 돌려줬다. 위 부등식의 짝이라 **폭을 다시 넓히려면 샤를 같이 줄여야 한다.**
     상한 290px는 chapterVictory 버튼(max-width 260px)과 같은 가족 안이라 커 보임을 잃지 않는다.
     **위치(bottom)는 건드리지 않는다** — 세 수여식이 같은 자리에 버튼을 두는 것이 "또 하나 열렸다"의
     학습 단서다. 겹침을 세로로 풀려는 시도(버튼 올리기)는 무대 하단 텍스트와 다시 충돌한다. */
  #unlockCeremony .ucBtn {
    position: absolute; z-index: 4; left: 50%;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 8%);
    width: min(64vw, 290px); aspect-ratio: 467 / 146; padding: 0 8% 3%;
    border: none; border-radius: 0; box-shadow: none;
    background: url('../../assets/common/btn_green.png') center / 100% 100% no-repeat;
    display: flex; align-items: center; justify-content: center;
    color: #fff8e9; font-size: clamp(17px, 5vw, 21px); font-weight: 900; letter-spacing: .3px;
    text-shadow: 0 2px 0 #2c7a1f, 0 3px 5px rgba(18,55,8,.5);
    opacity: 0; transform: translateX(-50%); transition: filter .12s;
  }
  #unlockCeremony .ucBtn[hidden] { display: none; }
  /* 등장(.59s) 뒤부터 **idle 넛지**가 이어진다 — 타이틀 PLAY 버튼과 같은 리듬(연출 정본 §4.6).
     이 화면은 버튼이 하나뿐이고 플레이어가 눌러야 다음으로 가므로, "여기를 누르세요"를 말하는 것이
     정확히 이 넛지의 역할이다. PLAY와 같은 5.8s 주기라 두 화면이 같은 호흡으로 읽힌다.
     ⚠️ `btnNudge`를 **그대로 쓰면 안 된다** — 그건 순수 scale이라 이 버튼의 `translateX(-50%)`
     중앙정렬을 덮어써 버튼이 왼쪽으로 반쪽 밀린다. 아래 전용 키프레임이 매 프레임 정렬을 들고 간다. */
  #unlockCeremony .ucBtn:not([hidden]) { animation: ucBtnIn .59s linear both, ucBtnNudge 5.8s ease-in-out .59s infinite; }
  @keyframes ucBtnNudge {
    /* 값·리듬은 04-screens-title.css의 `btnNudge`와 동일 — 정렬 translate만 얹었다. */
    0%, 63%, 100% { transform: translateX(-50%) scale(1); }
    66%  { transform: translateX(-50%) scaleX(1.06) scaleY(0.92); }
    73%  { transform: translateX(-50%) scaleX(0.96) scaleY(1.07); }
    80%  { transform: translateX(-50%) scaleX(1.02) scaleY(0.98); }
    87%  { transform: translateX(-50%) scaleX(0.99) scaleY(1.01); }
    93%  { transform: translateX(-50%) scale(1); }
  }
  /* 감속 모드: 넛지만 끈다(등장은 남긴다 — 버튼이 갑자기 나타나면 어디서 왔는지 모른다). */
  html[data-motion="reduced"] #unlockCeremony .ucBtn:not([hidden]) { animation: ucBtnIn .59s linear both; }
  #unlockCeremony .ucBtn:active { filter: brightness(.95); }

  @keyframes ucVeilIn   { from { opacity: 0; } to { opacity: 1; } }
  /* 쫀득 물리(정본 §4.6) — 텍스트·패널도 몸이 있는 것으로 취급하되, 진폭은 얌전하게(굵기 왜곡 방지). */
  @keyframes ucRiseIn {
    0%   { opacity: 0; transform: translateY(10px) scaleX(.97) scaleY(1.06); animation-timing-function: ease-in-out; }
    58%  { opacity: 1; transform: translateY(0) scaleX(1.07) scaleY(.95); animation-timing-function: ease-in-out; }
    82%  { transform: translateY(0) scaleX(.98) scaleY(1.02); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }
  /* ⚠️ 버튼은 translateX(-50%)로 가로 중앙을 잡으므로 키프레임에서 그 값을 **반드시 보존**한다.
     scale/translateY만 쓴 키프레임을 걸면 중앙정렬이 사라져 왼쪽으로 밀린다(이 저장소의 반복 함정). */
  @keyframes ucBtnIn {
    0%   { opacity: 0; transform: translateX(-50%) translateY(18px) scaleX(.95) scaleY(1.09); animation-timing-function: ease-in-out; }
    58%  { opacity: 1; transform: translateX(-50%) translateY(0)    scaleX(1.10) scaleY(.94); animation-timing-function: ease-in-out; }
    82%  { transform: translateX(-50%) translateY(0) scaleX(.97) scaleY(1.02); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
  }
  @keyframes ucRaysIn   { from { opacity: 0; transform: scale(.82); } to { opacity: 1; transform: scale(1); } }
  @keyframes ucRaysSpin { from { transform: rotate(0deg) scale(1); } to { transform: rotate(360deg) scale(1); } }
  @keyframes ucBloomIn  { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: scale(1); } }
  @keyframes ucBloomPulse { 0%,100% { opacity: .85; transform: scale(1); } 50% { opacity: 1; transform: scale(1.07); } }
  @keyframes ucPrizeDrop { from { opacity: 0; transform: translateY(-60vh) scale(.62); } to { opacity: 1; transform: translateY(0) scale(1); } }
  @keyframes ucPrizeIdle { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
  @keyframes ucRingOut  { from { opacity: .95; transform: scale(.38); } to { opacity: 0; transform: scale(2.3); } }
  @keyframes ucSparkPop { 0% { opacity: 0; transform: translate(0,0) scale(.4); } 35% { opacity: 1; transform: translate(calc(var(--sx) * .6), calc(var(--sy) * .6)) scale(1.15); } 100% { opacity: 0; transform: translate(var(--sx), var(--sy)) scale(.5); } }
  @keyframes ucHorizonSpread { 0% { opacity: 0; transform: translate(-50%,-50%) scaleX(0); } 22% { opacity: .95; } 100% { opacity: 0; transform: translate(-50%,-50%) scaleX(1); } }
  @keyframes ucNameStamp {
    0%   { opacity: 0; transform: scale(.6); animation-timing-function: ease-in-out; }
    55%  { opacity: 1; transform: scaleX(1.05) scaleY(1.17); animation-timing-function: ease-in-out; }
    78%  { transform: scaleX(1.09) scaleY(.93); animation-timing-function: ease-in-out; }
    92%  { transform: scaleX(.98) scaleY(1.02); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: scale(1); }
  }
  @keyframes ucWitnessIn {
    0%   { opacity: 0; transform: translateY(24px) scaleX(.90) scaleY(1.12); animation-timing-function: ease-in-out; }
    55%  { opacity: 1; transform: translateY(-4px) scaleX(1.10) scaleY(.94); animation-timing-function: ease-in-out; }
    80%  { transform: translateY(0) scaleX(.97) scaleY(1.03); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }

  /* 감속 모드: 회전·맥동·낙하·충격파·반짝임·빛줄·스탬프를 전부 끄고 결과만 보여준다. 3존 구도라
     정적 포스터로도 의미가 온전하다(스택 구도 대비 이 안의 부가 이점).
     ⚠️ 버튼은 transform:none을 주면 안 된다 — 가로 중앙정렬이 transform에 실려 있다. */
  html[data-motion="reduced"] #unlockCeremony .ucVeil,
  html[data-motion="reduced"] #unlockCeremony .ucKicker,
  html[data-motion="reduced"] #unlockCeremony .ucBeat,
  html[data-motion="reduced"] #unlockCeremony .ucBeat *,
  html[data-motion="reduced"] #unlockCeremony .ucWitness,
  html[data-motion="reduced"] #unlockCeremony .ucBtn:not([hidden]) {
    animation: none !important; opacity: 1 !important;
  }
  html[data-motion="reduced"] #unlockCeremony .ucBeat *,
  html[data-motion="reduced"] #unlockCeremony .ucWitness { transform: none !important; }
  html[data-motion="reduced"] #unlockCeremony .ucBtn { transform: translateX(-50%) !important; }
  html[data-motion="reduced"] #unlockCeremony .ucRing,
  html[data-motion="reduced"] #unlockCeremony .ucSparks,
  html[data-motion="reduced"] #unlockCeremony .ucHorizon,
  html[data-motion="reduced"] #unlockCeremony .ucColumn,
  html[data-motion="reduced"] #unlockCeremony .ucMotes { display: none !important; }

  /* ════ #winChapterBanner: "가벼운" 챕터 완주(6판형, 새 개념 0개) — #win 카드 안의 배너 슬롯.
     #win .card는 구운 프레임 이미지 위에 콘텐츠를 고정 %로 절대배치하는 계약을 따른다
     (styles/app/03-modal-win-effects.css 상단 주석 참조). 이 파일은 그 계약 위에 새 슬롯 하나만 얹는다 —
     기존 03 파일은 건드리지 않는다. game/curriculum.js chapterFinishMode() === "light"일 때만 다른
     에이전트가 텍스트/표시를 채운다. 시작은 숨김. */
  #win .card > #winChapterBanner {
    position: absolute; left: 0; right: 0; margin: 0 auto; top: 70%;
    z-index: 6; text-align: center; pointer-events: none;
  }
  #win .card > #winChapterBanner[hidden] { display: none; }
  #winChapterBanner .wcbText {
    display: inline-block; padding: 6px 16px; border-radius: 999px;
    font-family: 'Jua', system-ui, sans-serif; font-weight: 400;
    font-size: clamp(14px, 3.4vw, 16px); letter-spacing: .02em;
    color: var(--gold); background: rgba(46,39,24,.32);
    border: 1px solid rgba(255,224,122,.4);
    text-shadow: 0 1px 2px rgba(40,20,4,.55);
    animation: wcbIn .70s linear .28s both;
  }
  @keyframes wcbIn {
    0%   { opacity: 0; transform: translateY(8px) scaleX(.96) scaleY(1.08); animation-timing-function: ease-in-out; }
    60%  { opacity: 1; transform: translateY(0) scaleX(1.08) scaleY(.95); animation-timing-function: ease-in-out; }
    100% { opacity: 1; transform: translateY(0) scale(1); }
  }
  html[data-motion="reduced"] #winChapterBanner .wcbText { animation: none; }
