/* ═══════════════════════════════════════════════════════════
   TransNote · 삼성생명 라이프놀로지랩 3기
   단일 다크 테마. 액센트 1개(--ice). 코너 반경 4px 단일 체계.
   ═══════════════════════════════════════════════════════════ */

:root{
  --ink:#0B0B0C;
  --ink-2:#121215;
  /* 패널 불투명도는 대비 안전선이다. 뒤 영상이 밝은 구간(6초 부근 휘도 128)에서도
     본문이 WCAG AA 를 넘기려면 .74 로는 부족해서 .84 로 올렸다. */
  --panel:rgba(10,10,12,.84);
  --warm:#F2EEE7;
  --warm-mute:#A9A29A;      /* --ink 위 7.8:1 */
  --warm-dim:#827D76;       /* --ink 위 4.82:1 (이전 #6F6A65 는 3.68:1 로 AA 미달) */
  --ice:#8FBFE0;
  --line:rgba(242,238,231,.13);

  --r:4px;

  --s1:8px;  --s2:16px; --s3:24px; --s4:40px;
  --s5:64px; --s6:96px; --s7:140px; --s8:200px;

  --serif:'Gowun Batang',serif;
  --sans:'Pretendard Variable',Pretendard,system-ui,sans-serif;
  --ease:cubic-bezier(.16,1,.3,1);
  --gutter:clamp(20px,5vw,80px);

  /* 본문 컨테이너 폭. 1080px 고정이면 넓은 노트북에서 화면의 절반도 안 채운다
     (1600px 에서 57%, 1920px 에서 48%, 2560px 에서 36%). 화면과 함께 자라되
     가독 한계를 넘지 않도록 1440px 에서 멈춘다. */
  --maxw:clamp(1080px,72vw,1440px);

  /* 유동 타입 스케일. 375px 에서 최소, 1440px 에서 기존 값에 도달하고
     그 위로는 1900px 부근까지 계속 자란다. 상한을 1440px 에 묶어두면
     16:9 대형 노트북에서 글씨만 작아 보인다. */
  --t-lede : clamp(15px,   13.24px + .47vw, 22px);
  --t-body : clamp(14px,   12.60px + .38vw, 18.5px);
  --t-label: clamp(12px,   11.50px + .16vw, 14px);
  --t-spec : clamp(15px,   13.20px + .47vw, 20px);
  --t-note : clamp(12.5px, 11.90px + .18vw, 15px);
  --t-dt   : clamp(13.5px, 12.60px + .24vw, 17.5px);
  --t-dd   : clamp(17px,   14.50px + .66vw, 27px);
  --t-sub  : clamp(13px,   12.30px + .19vw, 16.5px);
  --t-link : clamp(13.5px, 12.90px + .16vw, 16.5px);
}

*,*::before,*::after{box-sizing:border-box}
/* 루트에는 overflow 를 걸지 않는다. 가로 넘침은 .film__stage 의 overflow:hidden 이
   이미 막고 있어서 필요가 없고, 루트 overflow 는 스크롤 컨테이너를 바꿔
   스크롤 구동 라이브러리와 엮이기 쉬운 자리라 비워둔다. */
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--ink);color:var(--warm);
  font-family:var(--sans);font-weight:300;font-size:16px;line-height:1.75;
  -webkit-font-smoothing:antialiased;
}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,p,dl,dd,dt,figure{margin:0}
::selection{background:var(--ice);color:var(--ink)}
:focus-visible{outline:2px solid var(--ice);outline-offset:4px;border-radius:var(--r)}

.wrap{width:100%;max-width:var(--maxw);margin-inline:auto;padding-inline:var(--gutter)}

/* ─────────────── ACT 1+2 · 필름 ─────────────── */
/* 스크롤 길이는 JS가 아니라 여기서 정한다. 0~60%가 영상 0~6초. */
.film{position:relative;height:min(700vh,7200px);background:var(--ink)}

.film__stage{
  position:sticky;top:0;overflow:hidden;
  /* svh 미지원 브라우저에서 높이가 무효가 되면 sticky 무대가 접혀버린다 */
  height:100vh;
  height:100svh;
  background:var(--ink) url('poster_00.jpg') center/cover no-repeat;
}
.film__reel{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center;
}
.film__vignette{
  position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(130% 100% at 50% 45%,transparent 42%,rgba(6,6,8,.55) 100%),
    linear-gradient(to bottom,rgba(6,6,8,.34),transparent 26%,transparent 72%,rgba(6,6,8,.42));
}
.film__black{
  position:absolute;inset:0;background:var(--ink);opacity:0;pointer-events:none;
}

/* 로딩 게이트 */
.gate{
  position:absolute;left:var(--gutter);bottom:clamp(28px,6vh,56px);
  display:flex;align-items:center;gap:var(--s2);
  transition:opacity .6s var(--ease);
}
.gate.is-done{opacity:0;pointer-events:none}
.gate__label{
  font-size:12px;letter-spacing:.22em;color:var(--warm-mute);
  text-transform:uppercase;
}
.gate__rail{position:relative;width:clamp(80px,14vw,160px);height:1px;background:var(--line)}
.gate__rail i{position:absolute;inset:0;background:var(--ice);transform-origin:left;transform:scaleX(0)}

/* ─────────────── 서사 카드 ─────────────── */
.cards{position:absolute;inset:0;pointer-events:none}

.card{
  position:absolute;top:50%;
  /* 바닥을 340px 로 뒀더니 701~900px 구간(태블릿, 창을 작게 쓰는 노트북)에서
     좌우 카드+여백 합이 뷰포트에 거의 딱 맞아 여유가 11px 까지 줄었다.
     바닥을 260px 로 낮춰 이 구간을 위한 별도 미디어쿼리 없이 기본 규칙
     하나로 700px 초과 전 구간이 풀리게 한다 — 크기 조정은 모바일에서만. */
  width:clamp(260px,30vw,480px);
  padding:clamp(22px,2.4vw,34px);
  background:var(--panel);
  backdrop-filter:blur(14px) saturate(115%);
  -webkit-backdrop-filter:blur(14px) saturate(115%);
  border:1px solid var(--line);
  border-radius:var(--r);
  opacity:0;visibility:hidden;
  box-shadow:0 30px 70px rgba(0,0,0,.45);
}
.card--l{left:var(--gutter);transform:translateY(-50%)}
.card--r{right:var(--gutter);transform:translateY(-50%)}

.card__cat{
  display:inline-block;margin-bottom:var(--s3);
  padding:5px 12px 6px;
  border:1px solid rgba(143,191,224,.42);border-radius:var(--r);
  font-size:12px;letter-spacing:.2em;color:var(--ice);line-height:1;
}
/* 핵심 용어 옆 영문. 한글보다 한 단계 물러나 보이도록 작게, 옅게, 넓게 */
.en{
  font-family:var(--sans);
  /* 상대 크기만 쓰면 12px 라벨 안에서 8.6px 까지 떨어져 못 읽는다. 하한을 둔다.
     11px 은 본문 최소 권장(12px)에 못 미쳐 스펙 라벨에서 실제로 걸렸다. */
  font-size:max(12px,.72em);font-weight:400;
  letter-spacing:.14em;
  color:var(--ice);
  opacity:.72;
  margin-left:.42em;
  vertical-align:.06em;
  white-space:nowrap;
}
.card__cat .en{font-size:max(12px,.82em);margin-left:.5em;opacity:.85}
.rule dd .en{color:var(--warm-dim);opacity:1}

.card__t{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.35rem,2.1vw,1.95rem);line-height:1.42;
  letter-spacing:-.01em;color:var(--warm);
}
.card__b{
  margin-top:var(--s2);font-size:14.5px;line-height:1.85;color:var(--warm-mute);
}
/* 반투명 패널 위라 --warm-dim 으로는 밝은 장면에서 AA 를 못 넘긴다 */
.card__meta{
  margin-top:var(--s3);padding-top:var(--s2);border-top:1px solid var(--line);
  font-size:12.5px;letter-spacing:.02em;color:var(--warm-mute);
}

/* ─────────────── ACT 3 · 모델 ─────────────── */
.model{position:relative;background:var(--ink);padding-top:clamp(80px,14vh,140px)}

/* 로고: 원본 PNG 를 선명하게 두고, 크롬 재질을 살리는 광택 스윕만 얹는다.
   확대하지 않으므로(496px 원본 > 표시 크기) 흐려질 여지가 없다. */
.mark{
  position:relative;width:clamp(240px,22vw,380px);
  margin:0 auto clamp(14px,2.6vh,28px);
  isolation:isolate;
}
.mark__img{
  display:block;width:100%;height:auto;
  /* 로고 뒤로 아주 옅은 아이스 후광. 글로우가 아니라 접지감을 주는 정도 */
  filter:drop-shadow(0 0 26px rgba(143,191,224,.16));
}
/* 광택 스윕: 로고 실루엣 안에서만 흐르도록 원본을 마스크로 쓴다 */
.mark__sheen{
  position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(105deg,
    transparent 38%,
    rgba(255,255,255,.5) 47%,
    rgba(226,244,255,.72) 50%,
    rgba(255,255,255,.5) 53%,
    transparent 62%);
  background-size:280% 100%;
  mask:url('TransNote_LOGO.png') center/contain no-repeat;
  -webkit-mask:url('TransNote_LOGO.png') center/contain no-repeat;
  mix-blend-mode:screen;
  animation:sheen 7s var(--ease) infinite;
}
@keyframes sheen{
  0%,   62% { background-position:210% 0; opacity:0 }
  68%       { opacity:1 }
  92%       { background-position:-60% 0; opacity:1 }
  100%      { background-position:-60% 0; opacity:0 }
}

.brand{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(2.4rem,6vw,5rem);line-height:1;letter-spacing:.01em;
  text-align:center;color:var(--warm);
}
.lede{
  max-width:30em;margin:var(--s4) auto 0;text-align:center;
  font-size:var(--t-lede);line-height:1.9;color:var(--warm-mute);
}

/* 3규칙: 카드 3개가 아니라 얇은 구분선 목록 */
.rules{
  max-width:clamp(520px,42vw,680px);margin:clamp(56px,8vh,88px) auto 0;
  border-top:1px solid var(--line);
}
.rule{
  display:flex;align-items:baseline;justify-content:space-between;gap:var(--s3);
  padding:18px 2px;border-bottom:1px solid var(--line);
}
.rule dt{font-size:var(--t-dt);letter-spacing:.02em;color:var(--warm-dim)}
.rule dd{font-family:var(--serif);font-size:var(--t-dd);color:var(--warm)}

/* 범위 + 정확도 */
.spec{max-width:clamp(640px,52vw,820px);margin:clamp(48px,7vh,76px) auto 0}
.spec__row{
  display:grid;grid-template-columns:minmax(0,.42fr) minmax(0,1fr);
  gap:var(--s3);align-items:baseline;padding:16px 0;
}
.spec__row + .spec__row{border-top:1px solid var(--line)}
.spec__k{font-size:var(--t-label);letter-spacing:.2em;color:var(--ice)}
.spec__v{font-size:var(--t-spec);line-height:1.7;color:var(--warm-mute)}
.spec__v b{font-family:var(--serif);font-weight:400;font-size:1.5em;color:var(--warm)}
.spec__note{display:block;margin-top:6px;font-size:var(--t-note);color:var(--warm-dim)}

.how{
  max-width:34em;margin:clamp(40px,6vh,64px) auto 0;
  font-size:var(--t-body);line-height:1.9;color:var(--warm-dim);text-align:center;
}

/* ─────────────── 두 개의 문 ─────────────── */
.doors{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(20px,3vw,44px);
  width:100%;max-width:var(--maxw);margin:clamp(84px,12vh,150px) auto 0;
  padding-inline:var(--gutter);
}
.door{display:block;transition:transform .5s var(--ease)}
.door__frame{
  display:block;overflow:hidden;border-radius:var(--r);
  border:1px solid var(--line);background:var(--ink-2);
  aspect-ratio:4/3;
}
.door__frame img{
  width:100%;height:100%;object-fit:cover;
  transition:transform .7s var(--ease),filter .5s var(--ease);
  filter:saturate(.86) brightness(.86);
}
.door:hover{transform:translateY(-4px)}
.door:hover .door__frame img{transform:scale(1.035);filter:saturate(1) brightness(1)}
.door__label{
  display:block;margin-top:var(--s2);
  font-family:var(--serif);font-size:clamp(1.1rem,1.7vw,1.4rem);color:var(--warm);
}
.door__sub{
  display:block;margin-top:4px;
  font-family:var(--sans);font-size:var(--t-sub);color:var(--warm-dim);
}

/* ─────────────── 푸터 ─────────────── */
/* 데스크톱은 왼쪽에 링크(유튜브·깃허브), 오른쪽에 로고(한양대·삼성생명) —
   같은 줄, 양 끝 배치(요청으로 원상복구). 좁은 화면에서만 중앙 정렬로
   두 줄 쌓는다(아래 700px 미디어쿼리). */
.foot{
  display:flex;align-items:center;justify-content:space-between;
  flex-wrap:wrap;gap:var(--s4);
  width:100%;max-width:var(--maxw);margin:clamp(90px,13vh,160px) auto 0;
  padding:var(--s4) var(--gutter) clamp(56px,9vh,96px);
  border-top:1px solid var(--line);
}
.foot__links{display:flex;flex-wrap:wrap;gap:var(--s4)}
.foot__links a{
  display:inline-flex;align-items:center;gap:10px;
  min-height:46px;                      /* 26px 이라 터치 최소 크기에 못 미쳤다 */
  font-size:var(--t-link);color:var(--warm-mute);transition:color .3s var(--ease);
}
.foot__links a:hover{color:var(--ice)}

.foot__orgs{display:flex;align-items:center;gap:clamp(18px,2.6vw,32px)}
.foot__hyu{height:46px;width:auto;opacity:.9}
/* 라이프놀로지랩 로고는 파란 배경이 박힌 불투명 이미지라 카드에 담아 앉힌다 */
.foot__lab{
  height:46px;width:auto;border-radius:var(--r);
  padding:0;background:#fff;
}

/* ─────────────── 진입 애니메이션 ─────────────── */
.js [data-rise]{opacity:0}

/* ─────────────── 반응형 ─────────────── */
/* 700px 초과 전 구간(태블릿~대형 모니터)은 별도 미디어쿼리 없이 기본 규칙만으로
   푼다. 크기가 갈리는 지점은 모바일 문턱 하나뿐이어야 유지가 쉽다 — 예전에
   여기 태블릿 전용 구간을 하나 더 뒀었는데, 그건 .card 바닥값을 340px 로
   너무 높게 잡은 탓에 필요해진 땜질이었다. 바닥을 260px 로 낮춰(위 .card 참고)
   제거했다. */

/* 900px 는 "모바일"치고 너무 높은 문턱이었다 — Windows 150%+ 배율을 쓰는 14인치
   노트북은 최대화해도 CSS 유효 폭이 900~960px 근처까지 떨어지고, 여기에 브라우저
   확대(Ctrl+)나 창을 최대화하지 않은 경우까지 겹치면 900px 아래로 내려가
   진짜 노트북인데도 이 아래의 "모바일 전용" 레이아웃(카드 하단 스택, 문 1열,
   푸터 중앙정렬)이 걸렸다. 700px 로 낮췄다 — 700~1000px 구간에서 좌우 카드가
   겹치지 않고 최소 42px 여유를 두는 것을 계산으로 확인했다(요청받은 뒤 검증).
   실제 폰(세로 ≤480px, 흔한 가로모드도 대부분 ≤700px)은 여전히 문제없이 잡힌다. */
@media (max-width:700px){
  .film{height:500vh}   /* 620vh 는 손가락으로 6번 넘게 밀어야 해서 줄였다 */
  .cards{top:auto;bottom:0;height:auto}
  .card{
    position:absolute;left:var(--gutter);right:var(--gutter);
    top:auto;bottom:clamp(28px,7vh,64px);
    width:auto;transform:none;
  }
  .card--l,.card--r{left:var(--gutter);right:var(--gutter);transform:none}
  .doors{grid-template-columns:1fr;gap:var(--s4)}
  /* 좁은 화면에서는 좌우 양 끝 배치가 답답해서 중앙 정렬 두 줄로 쌓는다 */
  .foot{justify-content:center;text-align:center}
  .foot__links{justify-content:center;width:100%}
  .foot__orgs{justify-content:center;width:100%}
}
/* 세로 화면에서는 영상을 정사각으로 잘라 위쪽에 앉힌다.
   화면 전체를 cover 로 채우면 16:9 가 세로 틀에 맞춰 확대돼 가로 26% 만 남고
   저택과 거실이 통째로 사라진다. 반대로 레터박스로 다 보여주면 띠가 화면의
   26% 뿐이라 존재감이 없다. 1:1 은 가로 56% 를 남기면서 화면의 46% 를 채운다.
   두 핵심 프레임(현관문, 피아노)이 정중앙이라 중앙 크롭으로 충분하다.
   아래에 남는 검은 영역이 카드 자리가 되어 대비도 같이 좋아진다. */
@media (max-aspect-ratio:3/4){
  .film__stage{background-image:none}
  .film__reel{
    inset:auto;left:0;right:0;
    top:clamp(36px,6vh,72px);
    width:100%;height:auto;
    aspect-ratio:1/1;
    object-fit:cover;object-position:center;
    border-radius:var(--r);
  }
  /* 영상 바깥이 진짜 검정이라 비네트는 위아래 경계만 살짝 눌러준다 */
  .film__vignette{
    background:linear-gradient(to bottom,rgba(6,6,8,.28),transparent 20%,transparent 64%,rgba(6,6,8,.5));
  }
  .card{bottom:clamp(24px,5vh,52px)}
}

@media (max-width:520px){
  .card{padding:20px}
  .card__t{font-size:1.22rem}
  .card__b{font-size:13.8px}
  .spec__row{grid-template-columns:1fr;gap:6px}
  .rule{gap:var(--s2)}
}

/* ─────────────── 모션 최소화 ───────────────
   레이아웃은 그대로 둔다. 스크럽은 사용자가 스크롤로 직접 끌고 가는 조작이라
   유지하고(main.js 가 지연 보간과 미끄러짐을 뺀다), 여기서는 저절로 움직이는
   것과 호버 전환만 없앤다. 예전처럼 필름을 통째로 해체하면 모션 최소화를 켠
   사용자에게는 페이지가 고장 난 것처럼 보였다. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  .door{transition:none}
  .door__frame img{transition:none}
  *{animation-duration:.01ms!important;animation-iteration-count:1!important}
}

/* ═══════════════════════════════════════════════════════════
   곡 선택 데크 (스택 카루셀)
   원본은 React + motion/react 컴포넌트다. 이 페이지는 GSAP 로 영상을
   스크럽하고 있어서 Motion 을 같이 올리면 같은 프레임을 두고 싸운다.
   그래서 동작만 포인터 이벤트 + transform 으로 이식했다.
   ═══════════════════════════════════════════════════════════ */

/* 오버레이 페이드는 CSS 전환으로 한다. GSAP 으로 하면 rAF 가 멈춘 환경
   (백그라운드 탭 등)에서 opacity 0 상태로 굳어 화면이 안 보인다. */
.deckwrap{
  position:fixed;inset:0;z-index:200;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  opacity:0;
  transition:opacity .4s var(--ease);
}
.deckwrap.is-open{opacity:1}
.deckwrap[hidden]{display:none}
.deckwrap__scrim{
  position:absolute;inset:0;
  background:rgba(6,6,8,.86);
  backdrop-filter:blur(18px) saturate(112%);
  -webkit-backdrop-filter:blur(18px) saturate(112%);
}
.deckwrap__inner{
  position:relative;z-index:1;
  width:100%;max-width:1180px;
  padding:clamp(56px,8vh,88px) var(--gutter) clamp(28px,5vh,48px);
  display:flex;flex-direction:column;align-items:center;
}
/* deckwrap__x(닫기 버튼)·__head(제목)는 마크업에서 뺐다 — 스크림을 눌러도
   닫히므로 X 버튼은 중복이었고, 제목은 카드 목록이 더 넓게 보이도록 없앴다. */

/* ── 데크 ─────────────────────────────────────────────── */
.deck{
  position:relative;width:100%;
  height:clamp(300px,46vh,420px);
  display:flex;align-items:center;justify-content:center;
}
.deck__drag{
  position:absolute;inset:0;z-index:50;
  cursor:pointer;              /* 기본값 — 터치는 media query 로 grab 으로 바꾼다 */
  touch-action:pan-y;         /* 세로 페이지 스크롤은 살려둔다 */
}
/* 마우스(트랙패드 포함)에서는 카드 넘기기가 휠 전용이라 드래그 커서를 보이면
   안 된다. 손가락(coarse pointer)에서만 드래그(=좌우 스와이프)가 실제로
   동작하니 grab/grabbing 커서도 그때만 보여준다. */
@media (pointer:coarse){
  .deck__drag{cursor:grab}
  .deck__drag:active{cursor:grabbing}
}
.deck__drag:focus-visible{outline:2px solid var(--ice);outline-offset:6px}

.pick{
  position:absolute;
  width:clamp(160px,20vw,236px);
  aspect-ratio:2/3;
  border-radius:var(--r);
  overflow:hidden;
  background:var(--ink-2);
  border:1px solid var(--line);
  pointer-events:none;
  will-change:transform;
  box-shadow:0 24px 60px rgba(0,0,0,.55);
}
.pick--shoot{border-style:dashed;border-color:rgba(143,191,224,.42)}
/* 가운데 카드는 scale 로 커지는 것에 더해 그림자도 키워서 진짜로 앞으로
   떠오른 것처럼 보이게 한다(다른 카드는 기본 그림자 그대로). */
.pick.is-center{box-shadow:0 36px 84px rgba(0,0,0,.68)}

.pick__img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;
  filter:saturate(.9) brightness(.82);
}
/* 사진이 아직 없으면 제목만 남기고 조용히 떨어진다 */
.pick.is-noimg .pick__img{display:none}
.pick.is-noimg{
  background:
    radial-gradient(120% 80% at 50% 0%,rgba(143,191,224,.16),transparent 62%),
    linear-gradient(180deg,var(--ink-3),var(--ink));
}
.pick::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(to top,rgba(6,6,8,.9) 0%,rgba(6,6,8,.25) 46%,transparent 78%);
}
/* 중앙에서 멀어질수록 어두워지는 층 */
.pick__dim{position:absolute;inset:0;background:var(--ink);opacity:0}

.pick__badge{
  position:absolute;top:12px;right:12px;z-index:3;
  padding:5px 10px 6px;
  font-family:var(--sans);
  font-size:10.5px;letter-spacing:.18em;line-height:1;
  color:var(--ice);
  background:rgba(10,10,12,.66);
  border:1px solid rgba(143,191,224,.36);border-radius:var(--r);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
.pick__meta{
  position:absolute;z-index:3;left:14px;right:14px;bottom:16px;
  text-align:left;
}
.pick__t{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1rem,1.5vw,1.2rem);line-height:1.35;color:var(--warm);
}
.pick__d{margin-top:5px;font-size:12px;line-height:1.6;color:var(--warm-mute)}
.pick__d:empty{display:none}

/* ── 위치 표시 ─────────────────────────────────────────── */
.deck__dots{
  display:flex;gap:9px;align-items:center;
  margin-top:clamp(22px,4vh,38px);
}
.deck__dots i{
  display:block;width:6px;height:6px;border-radius:50%;
  background:var(--line);
  transition:background .3s var(--ease),width .3s var(--ease);
}
.deck__dots i.is-on{width:20px;border-radius:3px;background:var(--ice)}

@media (max-width:640px){
  .pick{width:clamp(150px,44vw,190px)}
  .deck{height:clamp(280px,42vh,360px)}
}

/* ═══════════════════════════════════════════════════════════
   로그인 · 잠긴 카드
   ═══════════════════════════════════════════════════════════ */

/* 우측 상단 로그인 — 글씨만. 배경/테두리 없이 두되 터치 타깃 44px 은 확보한다.
   z-index 는 일부러 덱(.deckwrap)·튜토리얼(.tutwrap)·연주 화면(.playstage)의
   200보다 낮다(150) — 예전엔 210이라 이 버튼이 그 화면들 "위에" 떠서 체험하기·
   튜토리얼·연주 화면을 열면 목록/컨트롤과 겹쳐 보이는 문제가 있었다. 세 화면
   다 배경이 뷰포트를 불투명하게 덮으므로, 낮추기만 해도 열려 있는 동안은
   자연스럽게 뒤로 가려지고 닫으면 다시 보인다(2026-08-16). */
/* 상단 메뉴(2026-09-30): 언어 전환 + 로그인을 한 줄로. 예전엔 .loginbtn 이 혼자
   position:fixed 였다 — 이제 .topbar 가 자리를 잡고 버튼은 그 안에 흐른다. */
.topbar{position:fixed;top:0;right:clamp(16px,2.6vw,36px);z-index:150;display:flex;align-items:center}
.langsw{display:flex;align-items:center}
.langsw button{
  min-height:44px;padding:0 8px;background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-size:var(--t-link);letter-spacing:.14em;
  color:var(--warm-mute);opacity:.55;transition:color .2s var(--ease),opacity .2s var(--ease);
}
.langsw button[aria-pressed="true"]{color:var(--warm);opacity:1}
.langsw button:hover{color:var(--ice);opacity:1}
/* 영어 모드: 한국어 라벨이 영어로 바뀌면 옆의 영문 부제와 겹친다 */
html[lang="en"] .en--dup{display:none}
/* 영어 모드 첫 페인트에 한국어가 비치지 않게 — i18n.js init()이 치환 후 해제 */
html.i18n-pending body{visibility:hidden}
.loginbtn{
  position:relative;z-index:150;
  min-height:44px;padding:0 clamp(20px,4vw,44px);
  background:none;border:0;cursor:pointer;
  font-family:var(--sans);
  font-size:var(--t-link);font-weight:400;letter-spacing:.18em;
  color:var(--warm-mute);
  text-shadow:0 1px 12px rgba(0,0,0,.75);   /* 밝은 영상 위에서도 읽히게 */
  transition:color .25s var(--ease);
}
.loginbtn:hover{color:var(--ice)}
.loginbtn.is-in{letter-spacing:.04em;color:var(--warm)}

/* 잠긴 카드는 자물쇠 느낌으로 한 겹 눌러둔다 */
.pick--locked .pick__img{filter:saturate(.5) brightness(.55)}
.pick--locked .pick__badge{color:var(--warm-mute);border-color:var(--line)}
.pick--locked.is-unlocked .pick__img{filter:saturate(.9) brightness(.82)}
.pick--locked.is-unlocked .pick__badge{color:var(--ice);border-color:rgba(143,191,224,.36)}

.deck__gate{
  margin-top:var(--s3);
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;justify-content:center;
  font-size:var(--t-link);color:var(--warm-mute);
}
.deck__gate[hidden]{display:none}
.deck__gate-btn{
  min-height:44px;padding:0 18px;
  background:none;border:1px solid rgba(143,191,224,.42);border-radius:var(--r);
  color:var(--ice);cursor:pointer;
  font-family:var(--sans);font-size:var(--t-link);letter-spacing:.06em;
  transition:background .25s var(--ease),color .25s var(--ease);
}
.deck__gate-btn:hover{background:rgba(143,191,224,.12)}

/* ═══════════════════════════════════════════════════════════
   난이도 별 · 액션 카드 · 흰 설명창
   ═══════════════════════════════════════════════════════════ */

/* 별은 문자로 찍지 않고 그라디언트 폭으로 그린다 — 3.5 같은 반별을 정확히
   표현할 수 있고, 이모지 폰트에 따라 모양이 달라지는 문제도 없다. */
.pick__stars{
  margin-top:6px;
  font-size:13px;line-height:1;letter-spacing:.12em;
  --fill:0%;
  /* 노란색(요청) — #FFC94D, 다크 배경 위 11.78:1(요구 3:1 이상, 넉넉함) */
  background:linear-gradient(90deg,#FFC94D var(--fill),rgba(242,238,231,.22) var(--fill));
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  width:max-content;
}

/* 연주 카드가 아닌 것(촬영·보관함)은 빛이 사선으로 훑고 지나간다 */
.pick--action::before{
  content:'';position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(105deg,
    transparent 40%,
    rgba(143,191,224,.28) 48%,
    rgba(226,244,255,.42) 50%,
    rgba(143,191,224,.28) 52%,
    transparent 60%);
  background-size:260% 100%;
  animation:pickSweep 5.5s var(--ease) infinite;
}
@keyframes pickSweep{
  0%,   58% { background-position:200% 0; opacity:0 }
  64%       { opacity:1 }
  88%       { background-position:-60% 0; opacity:1 }
  100%      { background-position:-60% 0; opacity:0 }
}

/* ── 흰 설명창 ─────────────────────────────────────────── */
.sheetcard{
  /* 예전엔 position:absolute 로 카드 위에 겹쳐 띄웠다. 그러면 z-index 를 아무리
     올려도 "카드가 화면을 다 먹어서 설명창이 안 보인다"는 문제가 남는다.
     흐름 안에 넣어 flex 가 설명창-카드 순서로 세로 배치하게 한다. */
  position:relative;z-index:2;
  width:min(520px,86vw);
  margin-bottom:clamp(20px,3vh,34px);
  padding:clamp(22px,2.8vw,34px);
  background:#FBF9F5;color:#1B2430;
  border-radius:var(--r);
  box-shadow:0 40px 90px rgba(0,0,0,.6);
  opacity:0;transform:translateY(12px) scale(.98);
  transition:opacity .35s var(--ease),transform .35s var(--ease);
}
.sheetcard[hidden]{display:none}
.sheetcard.is-open{opacity:1;transform:translateY(0) scale(1)}

.sheetcard__x{
  position:absolute;top:12px;right:12px;
  width:46px;height:46px;display:grid;place-items:center;
  background:none;border:0;border-radius:var(--r);
  color:#6E6A66;cursor:pointer;
  transition:color .2s var(--ease),background .2s var(--ease);
}
.sheetcard__x:hover{color:#1B2430;background:rgba(27,36,48,.06)}

.sheetcard__kicker{
  font-size:12.5px;letter-spacing:.2em;color:#0B5C9E;margin-bottom:10px;
}
.sheetcard__t{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.5rem,2.6vw,2rem);line-height:1.3;color:#1B2430;
}
.sheetcard__stars{
  margin-top:10px;font-size:15px;letter-spacing:.14em;line-height:1;
  --fill:0%;
  /* 노란색(요청) — #8A6100, 흰 판(#FBF9F5) 위 5.27:1. 밝은 노랑을 그대로 쓰면
     흰 배경에서 1.4:1 밖에 안 나와서(실측) 짙은 골드로 낮췄다 — 같은 계열,
     이 배경에서 읽히는 톤. */
  background:linear-gradient(90deg,#8A6100 var(--fill),rgba(27,36,48,.18) var(--fill));
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  width:max-content;
}
.sheetcard__body{
  margin-top:var(--s3);
  font-size:15px;line-height:1.85;color:#4A4842;
  white-space:pre-line;
}
.sheetcard__body:empty{display:none}

.sheetcard__actions{
  display:flex;flex-wrap:wrap;gap:10px;margin-top:var(--s4);
}
/* 모바일 촬영 카드의 권장 문구 — 버튼 줄 아래 한 줄 전체 폭으로 작게(2026-09-14) */
.sheetcard__note{flex-basis:100%;margin-top:2px;font-size:12.5px;line-height:1.6;color:#8A8680}
.sheetbtn{
  min-height:46px;padding:0 20px;   /* 44px 로 두면 반올림으로 43px 이 나온다 */
  border-radius:var(--r);border:1px solid rgba(27,36,48,.18);
  background:none;color:#1B2430;cursor:pointer;
  font-family:var(--sans);font-size:14.5px;letter-spacing:.02em;
  transition:background .2s var(--ease),border-color .2s var(--ease),color .2s var(--ease);
}
.sheetbtn:hover:not(:disabled){background:rgba(27,36,48,.06)}
/* 잠긴 버튼(예: 전자 피아노가 있어야 하는 곡) — 왜 못 누르는지는 본문이 설명한다 */
.sheetbtn:disabled{opacity:.4;cursor:not-allowed}
.sheetbtn--primary{
  background:#0B5C9E;border-color:#0B5C9E;color:#fff;   /* 흰 글자 대비 6.9:1 */
}
.sheetbtn--primary:hover:not(:disabled){background:#0A4E8A;border-color:#0A4E8A}

/* 연주 화면의 "플레이"(초록) — 실제 채점이 시작되는 자리라 듣기(파랑)·다른
   버튼과 확실히 구분돼야 한다는 요청(2026-08-15)으로 별도 색을 준다.
   #1B8A4A(더 밝은 초록)는 흰 글자 대비 4.39:1 로 기준(4.5:1) 미달이라
   한 단계 더 어둡게 잡았다. */
.sheetbtn--start{
  background:#166F3B;border-color:#166F3B;color:#fff;   /* 흰 글자 대비 6.2:1 */
}
.sheetbtn--start:hover:not(:disabled){background:#125A30;border-color:#125A30}

@media (prefers-reduced-motion:reduce){
  .pick--action::before{animation:none;opacity:0}
}

/* 글자 팝아웃 — 각 글자가 아래에서 밀려 올라온다. 넘치는 부분을 잘라야
   "아래에서 솟는" 느낌이 나므로 부모에 overflow:hidden 을 준다. */
[data-pop]{overflow:hidden}
.pop__ch{display:inline-block;will-change:transform}


/* 설명창이 열려 있는 동안의 데크 배치.
   설명창이 흐름 위쪽을 차지하므로 카드는 저절로 아래로 내려간다. 다만 카드는
   .deck 박스 안에서 position:absolute 로 떠 있어서 박스 높이만 줄여도 크기가
   안 줄고 오히려 위아래로 삐져나온다. 카드 폭(=aspect-ratio 로 높이까지 결정)
   자체를 줄여야 흰 판이 주인공이 된다. */
.deckwrap.has-sheet .pick{width:clamp(120px,13vw,168px)}
.deckwrap.has-sheet .deck{height:clamp(240px,36vh,272px)}
.deckwrap.has-sheet .deck__dots{margin-top:clamp(14px,2vh,22px)}

/* 모바일은 세로가 더 빡빡하다. 미디어쿼리 뒤에 와야 위 규칙을 덮을 수 있다. */
@media (max-width:640px){
  .deckwrap.has-sheet .pick{width:clamp(104px,30vw,132px)}
  .deckwrap.has-sheet .deck{height:clamp(170px,26vh,210px)}
}

/* ═══════════════════════════════════════════════════════════
   튜토리얼 · 이미지 좌측 고정 + 설명 2단 구성

   예전엔 카드 5장을 겹쳐 쌓고 넘길 때마다 스택이 움직이는 연출이었는데,
   캐러셀 느낌을 없애고 활성 카드의 이미지 하나만 좌측 자리에 그대로 보여주는
   방식으로 바꿨다(2026-08-17). 이미지 없는 카드(규칙1·마무리)는 그 자리가
   빈 채로 남는다 — 규칙1은 대신 아래 실습 건반이 그 역할을 한다.

   좌우 넘기는 버튼(.tutnav)은 본문 길이가 카드마다 달라도 항상 같은 자리에
   있어야 한다("좌우 넘기는 버튼의 위치가 고정되어야 함") — .tutstage를
   align-items:stretch로 두 열의 높이를 이미지 열(aspect-ratio로 고정된 높이)에
   맞추고, .tutcopy를 세로 flex로 만들어 .tutnav에 margin-top:auto를 줬다.
   본문이 짧든 길든 남는 공간을 nav 위쪽이 전부 흡수하므로 nav는 항상 열
   바닥에 붙는다. */
.tutwrap{
  position:fixed;inset:0;z-index:200;overflow-y:auto;
  /* align-items:center 였을 때는 .tutwrap__inner 전체(.tutstage + 그 아래
     실습 건반)를 세로로 가운데 앉혔는데, 건반 높이가 규칙마다 다르다
     (규칙1 큰 라벨 건반 ~290px, 규칙2~4 표준 건반 ~180px, 마무리는 없음) —
     그 차이만큼 .tutwrap__inner 전체 높이가 바뀌고, "가운데 정렬"이 그때마다
     .tutstage(그 안의 좌우 넘기는 버튼 포함)를 다른 화면 높이로 밀어 올리거나
     내렸다(2026-08-17, .tutcopy 높이를 고정했는데도 버튼이 계속 움직인 원인).
     flex-start로 위에 고정하면 아래 건반이 얼마나 크든 .tutstage의 화면상
     위치는 항상 같다. */
  display:flex;align-items:flex-start;justify-content:center;
  opacity:0;
  transition:opacity .4s var(--ease);
  /* 건반이 큰 카드(규칙1)만 세로 스크롤이 필요해질 수 있는데, 스크롤바가
     생겼다 없어졌다 하면 그만큼 가운데 정렬 폭이 바뀌어 좌우 넘기는 버튼이
     몇 px씩 옆으로 흔들렸다. 항상 자리를 남겨 둔다. */
  scrollbar-gutter:stable;
}
.tutwrap.is-open{opacity:1}
.tutwrap[hidden]{display:none}
.tutwrap__scrim{
  position:fixed;inset:0;
  background:rgba(6,6,8,.88);
  backdrop-filter:blur(18px) saturate(112%);
  -webkit-backdrop-filter:blur(18px) saturate(112%);
}
.tutwrap__inner{
  position:relative;z-index:1;
  width:100%;max-width:1080px;
  padding:clamp(28px,5vh,64px) var(--gutter);
}

/* ── 2단 무대 ─────────────────────────────────────────── */
.tutstage{
  display:grid;
  grid-template-columns:minmax(0,0.92fr) minmax(0,1fr);
  gap:clamp(28px,5vw,72px);
  align-items:stretch;   /* 두 열 높이를 맞춰야 오른쪽 nav 위치가 카드마다 안 흔들린다 */
}

/* ── 왼쪽: 활성 규칙의 이미지 한 장(캐러셀 폐기, 2026-08-17) ──────────── */
.tutimgbox{
  position:relative;
  aspect-ratio:1/1;
  width:100%;
  border-radius:18px;
  overflow:hidden;
  background:linear-gradient(180deg,var(--ink-3),var(--ink-2));
  border:1px solid var(--line);
  box-shadow:0 30px 70px rgba(0,0,0,.55);
  display:grid;place-items:center;
  cursor:pointer;
}
/* object-fit:contain — cover였을 때 776×220처럼 가로로 긴 원본(규칙2 등)이
   정사각형 박스에서 위아래가 잘렸다("이미지가 잘리지 않게 배치"). contain은
   비율을 지키며 전체를 다 보여주는 대신 남는 자리는 레터박스로 남는다. */
.tutimg{max-width:100%;max-height:100%;object-fit:contain}
.tutimg[hidden]{display:none}

/* ── 오른쪽: 설명 ─────────────────────────────────────── */
/* min-height:0 이 핵심이다 — 없으면 grid 아이템은 내용(본문 글자 수)만큼
   최소 높이를 요구해서, 본문이 긴 카드에서 이 열이 .tutimgbox보다 커지고
   그만큼 행(grid row) 전체가 늘어나 버린다(= nav 위치가 카드마다 흔들림).
   0으로 두면 행 높이는 .tutimgbox의 aspect-ratio 하나로만 정해지고, 이 열은
   그 높이로 늘어나기만(align-items:stretch) 한다 — 넘치는 본문은 아래
   overflow-y로 받는다. */
.tutcopy{min-width:0;min-height:0;display:flex;flex-direction:column}
/* min-height:0 을 여기도 줘야 flex 자식(swap)이 내용만큼 커지며 .tutnav를
   밀어내지 않는다 — 넘치면 이 안에서만 스크롤된다. */
.tutcopy__swap{min-height:0;overflow-y:auto}
.tutcopy__swap{
  transition:opacity .22s var(--ease),transform .22s var(--ease);
}
.tutcopy__swap.is-swapping{opacity:0;transform:translateY(8px)}
.tutcopy__kicker{
  font-family:var(--sans);font-size:var(--t-label);letter-spacing:.24em;
  text-transform:uppercase;color:var(--ice);
}
.tutcopy__t{
  margin-top:12px;
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.45rem,3.2vw,2.3rem);line-height:1.32;letter-spacing:-.01em;
  color:var(--warm);
}
.tutcopy__t:empty{display:none}
.tutcopy__body{
  margin-top:var(--s3);
  font-size:var(--t-lede);line-height:1.9;color:var(--warm-mute);
}
.tutcopy__body:empty{display:none}

/* ── 화살표 + 점 ──────────────────────────────────────── */
.tutnav{
  /* auto — 본문 길이와 무관하게 항상 .tutcopy 바닥에 붙는다(위 .tutstage 주석 참고) */
  margin-top:auto;
  padding-top:clamp(26px,4vh,44px);
  display:flex;align-items:center;gap:clamp(14px,2.4vw,24px);
}
.tutnav__arrow{
  width:52px;height:52px;flex:none;
  display:grid;place-items:center;
  border-radius:50%;border:1px solid var(--line);
  background:rgba(255,255,255,.05);
  color:var(--warm);cursor:pointer;
  transition:color .25s var(--ease),border-color .25s var(--ease),
             background .25s var(--ease);
}
.tutnav__arrow:hover{color:var(--ice);border-color:var(--ice);background:rgba(143,191,224,.12)}
.tutnav__dots{display:flex;gap:9px;align-items:center;margin-left:auto}
.tutnav__dots i{
  display:block;width:6px;height:6px;border-radius:50%;
  background:var(--line);
  transition:background .3s var(--ease),width .3s var(--ease);
}
.tutnav__dots i.is-on{width:20px;border-radius:3px;background:var(--ice)}

/* ── 규칙별 실습 건반 ─────────────────────────────────────
   2단 무대(.tutstage) 아래에 가로로 걸친다. 규칙1·2 는 한 옥타브뿐이라 좁고,
   규칙3·4 는 전 옥타브라 넓어서 좌우로 스크롤된다 — 어느 쪽이든 가운데 정렬. */
.tutpiano{
  margin-top:clamp(20px,3.4vh,36px);
  display:flex;flex-direction:column;align-items:center;gap:10px;
}
.tutpiano[hidden]{display:none}
.tutpiano__hint{
  font-family:var(--sans);font-size:13.5px;line-height:1.6;
  color:var(--warm-mute);text-align:center;
}
.tutpiano__slot{max-width:100%}
/* 규칙1 의 큰 라벨 건반(renderLabeledOctave)은 원래 크기가 700px 라 좁은 화면에서
   넘친다 — 넘칠 때만 좌우로 스크롤되게 두고, 세로는 잘리지 않게 한다. */
.tutpiano__slot{overflow-x:auto;overflow-y:hidden;scrollbar-width:thin}
/* buildPiano 용 래퍼. pannable 이면 piano.js 가 여기에 손잡이를 붙이고
   overflow-x 를 직접 켠다(인라인 스타일) — 그 폭만 여기서 정한다. */
.tutpiano__wrap{width:min(672px,100%)}
/* 안내 문구 + 퀴즈 ? 버튼을 한 줄에 */
.tutpiano__bar{display:flex;align-items:center;justify-content:center;gap:12px}
.tutpiano__help{
  width:40px;height:40px;border-radius:50%;
  background:#0076CE;color:#fff;border:0;cursor:pointer;
  font-family:var(--sans);font-size:18px;font-weight:600;line-height:1;
  box-shadow:0 6px 18px rgba(0,118,206,.35);
}
.tutpiano__help[hidden]{display:none}
.tutpiano__help:disabled{opacity:.35;cursor:default;box-shadow:none}

/* ── 마무리 퀴즈(2026-09-14) — 이미지 자리에 한 음짜리 악보 ────────────
   악보 글자색은 흰 배경 기준 잉크색이라(연주 화면 패널과 같은 이유) 어두운
   이미지 박스 위에 바로 그리면 안 보인다 — 밝은 유리 패널을 깔고 그 위에 그린다. */
.tutquiz{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:18px}
.tutquiz[hidden]{display:none}
.tutquiz__progress{font-family:var(--sans);font-size:13.5px;letter-spacing:.14em;color:var(--ice)}
.tutquiz__note{
  width:100%;flex:1;min-height:0;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.88);border-radius:14px;
}
.tutquiz__note[hidden]{display:none}   /* display:flex 가 [hidden] 을 덮어쓰므로 명시 */
.tutquiz__note svg{max-width:100%;max-height:100%;height:auto}
.tutquiz__done{display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center}
.tutquiz__done[hidden]{display:none}
.tutquiz__done-t{font-family:var(--serif);font-size:clamp(1.6rem,3vw,2.2rem);color:var(--warm)}
.tutquiz__done-s{font-family:var(--sans);font-size:14px;color:var(--warm-mute)}
.tutquiz__done-acts{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:6px}
/* .sheetbtn 기본형은 밝은 카드용(어두운 글자)이라 어두운 이미지 박스 위에선 안 보인다 —
   닫기 버튼(.tutwrap__x)과 같은 어두운 배경용 외곽선 버튼으로 바꾼다 */
.tutquiz__done-acts .sheetbtn:not(.sheetbtn--primary){background:none;border:1px solid var(--line);color:var(--warm)}

/* 모바일은 연주 화면과 마찬가지로 건반을 아예 없앤다(.playstage__pianowrap과
   같은 원칙, 2026-08-17) — 손가락 터치로는 화음·순서 판정을 보기 어렵고,
   세로 공간도 튜토리얼 이미지·설명에 더 필요하다. */
@media (max-width:640px){
  .tutpiano{display:none}
}

/* 닫기 — 스크림을 눌러도 닫히지만, 전체화면에 가까운 화면이라 명시적 출구도 둔다 */
.tutwrap__x{
  position:fixed;top:clamp(14px,2.6vh,26px);right:var(--gutter);z-index:2;
  min-height:46px;padding:0 18px;
  background:none;border:1px solid var(--line);border-radius:var(--r);
  color:var(--warm-mute);cursor:pointer;
  font-family:var(--sans);font-size:14.5px;
  transition:color .25s var(--ease),border-color .25s var(--ease);
}
.tutwrap__x:hover{color:var(--ice);border-color:var(--ice)}

/* 가로로 눕힌 태블릿(2단 무대 폭인데 높이가 짧음, 예: 1152×720): 정사각형 이미지
   박스(435px)가 뷰포트를 다 먹어 아래 실습 건반(224px)이 화면 밖으로 밀렸다
   (2026-09-14). 박스 높이를 "뷰포트 − 건반 블록·여백(320px)"으로 제한하고 폭은
   그 높이를 따라가게 한다(정사각형 유지, justify-self:center 라야 grid가 폭을
   강제로 늘리지 않는다). 데스크톱처럼 높이가 넉넉하면 기존 값 그대로. */
@media (min-width:821px) and (max-height:900px){
  .tutimgbox{
    justify-self:center;width:auto;
    height:clamp(220px,calc(100vh - 320px),460px);
    height:clamp(220px,calc(100dvh - 320px),460px);
  }
  .tutwrap__inner{padding-block:clamp(14px,2.4vh,28px)}
  .tutpiano{margin-top:clamp(12px,2vh,24px)}
}

@media (max-width:820px){
  /* 세로로 쌓는다 — 이미지가 위, 설명이 아래. 이때는 두 열이 아니라 위아래로
     쌓이므로 tutorial.js의 syncCopyHeight()(옆 열끼리 높이를 맞추는 용도)가
     여기 적용되면 안 된다 — JS도 이 폭에서는 건드리지 않고 넘어간다. */
  .tutstage{grid-template-columns:1fr;gap:clamp(22px,4vh,36px)}
  .tutimgbox{width:min(320px,72vw);margin-inline:auto}
  .tutcopy{height:auto !important;text-align:center}
  .tutnav{justify-content:center}
  .tutnav__dots{margin-left:0}
}

@media (prefers-reduced-motion:reduce){
  .tutcopy__swap{transition:none}
}

/* ═══════════════════════════════════════════════════════════
   연주 화면(플레이 스테이지)

   피아노 방 사진을 어둡게 눌러 블러한 배경 위에, 유리 패널 두 장(위=오른손,
   아래=왼손)과 하단 건반을 얹는다.

   세로 예산이 이 화면의 핵심 제약이다. 악보 SVG 원본은 보표 하나당 202px
   (18 + 56x3 + 8x2)라 두 보표면 414px, 여기에 헤더/컨트롤/건반(148px)을
   더하면 720p 노트북에서 830px 로 110px 넘친다. 그래서 악보 영역만
   flex:1 + min-height:0 으로 두고, SVG 는 viewBox 가 있으니 height:100% 로
   비율 유지 축소시킨다 — 벡터라 줄여도 흐려지지 않는다.
   ═══════════════════════════════════════════════════════════ */
.playstage{
  position:fixed;inset:0;z-index:200;overflow:hidden;
  opacity:0;
  transition:opacity .45s var(--ease);
}
.playstage.is-open{opacity:1}
.playstage[hidden]{display:none}

/* 배경 — img 에 filter 를 한 번만 먹인다. 화면에 스크롤이 없어서 매 프레임
   다시 계산되지 않고, 유리 패널의 backdrop-filter 와 겹쳐도 부담이 적다. */
.playstage__bg{position:absolute;inset:0;background:var(--ink)}
.playstage__bg img{
  width:100%;height:100%;object-fit:cover;
  filter:blur(26px) brightness(.30) saturate(.85);
  transform:scale(1.08);          /* 블러 가장자리가 비는 걸 막는다 */
}
/* 사진만으로는 밝은 창문 쪽이 아직 떠서 글자를 방해한다. 한 겹 더 눌러준다. */
.playstage__bg::after{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(6,6,8,.72),rgba(6,6,8,.58) 42%,rgba(6,6,8,.80));
}

.playstage__inner{
  position:relative;z-index:1;
  height:100%;
  width:100%;max-width:1180px;margin:0 auto;
  padding:clamp(10px,1.8vh,24px) var(--gutter) clamp(8px,1.4vh,18px);
  display:flex;flex-direction:column;gap:clamp(7px,1.2vh,16px);
}

/* ── 머리말 ─────────────────────────────────────────────── */
.playstage__head{
  display:flex;align-items:center;justify-content:space-between;
  gap:clamp(12px,2vw,28px);flex:none;flex-wrap:wrap;
}
.playstage__id{flex:none}
.playstage__t{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.1rem,1.1rem + 1.2vh,1.7rem);line-height:1.2;letter-spacing:-.01em;
  color:var(--warm);
}
.playstage__stars{
  margin-top:4px;font-size:14px;letter-spacing:.16em;line-height:1;
  background:linear-gradient(90deg,#FFC94D var(--fill,0%),rgba(242,238,231,.22) var(--fill,0%));
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.playstage__stars:empty{display:none}
.playstage__back{
  flex:none;min-height:46px;padding:0 18px;
  background:none;border:1px solid var(--line);border-radius:var(--r);
  color:var(--warm-mute);cursor:pointer;
  font-family:var(--sans);font-size:14.5px;letter-spacing:.02em;
  transition:color .25s var(--ease),border-color .25s var(--ease);
}
.playstage__back:hover{color:var(--ice);border-color:var(--ice)}

/* ── 유리 ───────────────────────────────────────────────── */
/* .deckwrap__scrim 이 이미 쓰고 있는 blur+saturate 문법을 그대로 이어간다 */
.glass{
  background:rgba(255,255,255,.055);
  border:1px solid rgba(255,255,255,.14);
  border-radius:var(--r);
  backdrop-filter:blur(13px) saturate(118%);
  -webkit-backdrop-filter:blur(13px) saturate(118%);
  box-shadow:0 18px 46px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.12);
}

/* 악보 패널만 "밝은" 유리다.
   처음엔 건반과 같은 어두운 유리로 갔다가 실측에서 걸렸다 — notation.js 는
   단음을 흰 박스가 아니라 밑줄+글자로 그리고, 그 글자색이 흰 배경 기준으로
   맞춰진 짙은 잉크(#0C4269 등)라 어두운 유리 위에서 1.43:1 로 사실상 안 보였다.
   패널을 밝은 프로스티드 유리로 두면 유리 질감(투과+블러)은 그대로 살면서
   notation.js 에 이미 검증돼 있는 대비값을 하나도 다시 만들 필요가 없다. */
.glass--light{
  background:rgba(255,255,255,.86);
  border-color:rgba(255,255,255,.55);
  box-shadow:0 18px 46px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.65);
}

/* ── 악보 영역 ──────────────────────────────────────────── */
.playstage__scores{
  flex:1;min-height:0;
  display:flex;flex-direction:column;gap:clamp(8px,1.4vh,14px);
  /* 아래 min-height 아래로는 더 안 줄어드니, 화면이 아주 낮으면 여기서 스크롤된다 */
  overflow-y:auto;
}
/* 높이 하한이 곧 글자 크기 하한이다.
   악보 SVG 원본은 202px 이고 그 안 음이름이 14px 로 그려진다. 패널을 무한정
   줄이면 SVG 도 같이 줄어드는데, 처음 짰을 때 720p 에서 0.56 배까지 눌려
   음이름이 7.8px 로 렌더링됐다 — 흰건반 A~G / 검은건반 1~5 를 읽는 게 이
   표기법의 핵심인데 그걸 못 읽으면 화면이 무의미하다. 패널 하한 198px
   (= SVG 158px, 약 0.78배, 음이름 약 11px)을 둬서 그 아래로는 줄지 않게 한다.
   위로도 원본 크기를 넘겨 키우지 않는다(벡터라 깨지진 않지만 과하게 커 보인다). */
.playstage__panel{
  flex:1 1 auto;
  min-height:198px;max-height:252px;
  padding:clamp(6px,1.1vh,12px) clamp(10px,1.4vw,16px);
  display:flex;flex-direction:column;gap:4px;
}
.playstage__panel[hidden]{display:none}

.playstage__hand{
  flex:none;
  font-family:var(--sans);font-size:12px;letter-spacing:.2em;
  color:#0B5C9E;                        /* 오른손 — 밝은 유리 위 5.3:1 */
}
.playstage__hand--bass{color:#864A16}   /* 왼손 — 같은 배경에서 5.3:1 */
/* .en 은 기본이 다크 테마용(--ice + opacity .72)이라 밝은 유리 패널 위에서
   1.46:1 로 사실상 안 보였다 — 악보 패널을 밝은 유리로 바꾸면서 부모 색만 고치고
   중첩된 .en 을 놓친 회귀. 부모 색을 그대로 물려받게 한다. */
.playstage__hand .en{color:inherit;opacity:.68;letter-spacing:.14em}

.playstage__staff{
  flex:1;min-height:0;
  overflow-x:auto;overflow-y:hidden;
  scrollbar-width:thin;
  /* 한 마디만 그릴 때 악보가 왼쪽에 몰리지 않게 가운데로 앉힌다
     (renderNotation 에 fitContent:true 를 줘서 SVG 폭이 내용 폭이 됐다).
     내용이 컨테이너보다 넓어지면 flex-start 처럼 동작하며 그대로 스크롤된다. */
  display:flex;align-items:center;justify-content:center;
}
/* SVG 에 viewBox 가 있어서 높이만 맞추면 폭은 비율대로 따라온다 */
/* flex 자식이라 기본 flex-shrink:1 이 걸려, 넓은 악보가 컨테이너 폭에 맞게
   압축되고 스크롤이 생기지 않았다(viewBox 2668px 이 1018px 로 눌림).
   flex:none 으로 원래 폭을 지키게 해야 흐름 모드에서 좌우로 흐른다. */
.playstage__staff svg{height:100%;width:auto;max-width:none;display:block;flex:none}
.playstage__staff::-webkit-scrollbar{height:6px}
.playstage__staff::-webkit-scrollbar-thumb{
  background:rgba(27,36,48,.28);border-radius:3px;
}

/* ── 컨트롤 ─────────────────────────────────────────────── */
.playstage__controls{
  flex:none;
  display:flex;align-items:center;gap:clamp(12px,1.8vw,22px);flex-wrap:wrap;
  flex:1;justify-content:flex-end;min-height:46px;
}
/* 지금 어느 단계인지만 보여준다 — webpage 의 설명 문구 두 줄은 유리 위에서
   글자만 빽빽해져서 뺐다. 색만으로 구분하지 않도록 번호도 같이 찍는다. */
.playstage__steps{
  display:flex;align-items:center;gap:clamp(10px,1.6vw,18px);
  list-style:none;counter-reset:pstep;
}
.playstage__steps li{
  counter-increment:pstep;
  display:flex;align-items:center;gap:8px;
  font-size:13.5px;letter-spacing:.02em;color:var(--warm-dim);
  transition:color .3s var(--ease);
}
.playstage__steps li::before{
  content:counter(pstep);
  display:grid;place-items:center;
  width:22px;height:22px;border-radius:50%;
  border:1px solid var(--line);
  font-size:11.5px;line-height:1;
  transition:border-color .3s var(--ease),background .3s var(--ease),color .3s var(--ease);
}
.playstage__steps li.is-on{color:var(--warm)}
.playstage__steps li.is-on::before{
  border-color:var(--ice);background:var(--ice);color:#06131D;
}

/* ── 건반 ───────────────────────────────────────────────── */
.playstage__keys{
  flex:none;
  padding:clamp(4px,.8vh,10px) clamp(10px,1.4vw,16px) clamp(6px,1vh,10px);
  display:flex;flex-direction:column;align-items:center;gap:5px;
}
/* 폭이 곧 보이는 옥타브 수다. 흰건반 하나가 48px 이므로
   데스크톱 2옥타브 = 14 x 48 = 672px, 모바일 1옥타브 = 7 x 48 = 336px.
   (모바일에서 2옥타브를 그대로 넣으면 건반 폭이 24px 로 줄어 터치 최소
   44px 에 한참 못 미친다 — 손가락으로는 정확히 못 누른다.) */
.playstage__pianowrap{
  width:min(672px,100%);
  zoom:var(--pz,1);
  overflow-x:auto;overflow-y:hidden;
  border-radius:0 0 6px 6px;
}
.playstage__midi{
  display:flex;align-items:center;gap:9px;
  font-size:12.5px;line-height:1.5;color:var(--warm-mute);text-align:center;
}
.playstage__midi-dot{
  flex:none;width:7px;height:7px;border-radius:50%;background:var(--warm-dim);
  transition:background .3s var(--ease);
}
/* 상태색은 강조색 하나로 모은다 — 연결됨은 --ice, 아닌 건 회색.
   초록/주황 상태색까지 쓰면 화면에 의미 있는 색(손: 주황·파랑)과 섞여
   "이 색이 무슨 뜻인지"가 흐려진다. 손 색은 규칙 4 가 가르치는 내용이라 유지. */
.playstage__midi-dot[data-state="ok"]{background:var(--ice)}
.playstage__midi-dot[data-state="off"]{background:var(--warm-dim)}

/* piano.js 가 pannable 모드에서 직접 붙이는 요소들 — 건반 위에서 드래그하면
   음이 눌리므로, 건반이 없는 별도 손잡이 바에서만 끌게 돼 있다. */
.piano-wrapper-pannable::-webkit-scrollbar{display:none}
/* 건반 바로 위의 "잡아서 좌우로 미는" 띠. 원래 14px 이라 마우스로 겨냥하기도
   힘들고 터치 최소 44px 에도 한참 못 미쳤다. 건반을 직접 끌면 음이 눌려버리므로
   (piano.js 가 임계값 전까지는 press 로 취급) 이 띠를 넉넉히 키우는 게
   스크롤 범위를 넓히는 가장 확실한 방법이다. */
/* 손잡이가 일반 블록 요소로 스크롤 콘텐츠 안에 있으면, 폭이 뷰포트(672px)로
   고정된 채 피아노(2496px, 88건반 전체)와 함께 스크롤돼 버린다 — 그러면
   A0~G2(약 672px 어치)를 넘어가는 순간 손잡이 자체가 화면 밖으로 사라져서
   그 이후 구간은 잡을 수 없었다("범위가 A0~G2까지만 존재"). position:sticky로
   뷰포트 왼쪽에 고정해 스크롤 위치와 무관하게 항상 잡을 수 있게 한다 —
   이러면 88건반 전 구간을 다 오갈 수 있다. */
.piano-drag-handle{
  position:sticky;left:0;
  height:44px;display:grid;place-items:center;cursor:grab;
  touch-action:none;                 /* 여기서는 브라우저 기본 제스처를 막고 직접 민다 */
  border-radius:6px 6px 0 0;
  background:linear-gradient(180deg,rgba(255,255,255,.10),rgba(255,255,255,.03));
  transition:background .2s var(--ease);
}
.piano-drag-handle:hover{background:linear-gradient(180deg,rgba(255,255,255,.16),rgba(255,255,255,.05))}
.piano-drag-handle:active{cursor:grabbing}
/* 잡는 곳이라는 걸 알리는 그립 자국 — 가로로 길게 둬야 "좌우로 민다"로 읽힌다 */
.piano-drag-handle span{
  display:block;width:72px;height:4px;border-radius:2px;
  background:rgba(255,255,255,.34);
  box-shadow:0 6px 0 -1px rgba(255,255,255,.18);
}

/* 세로가 빠듯한 노트북(720p 등)에서 악보를 1:1 로 띄우기 위해 건반만 줄인다.
   마우스는 정밀 포인팅이라 34px 건반도 누르는 데 지장이 없다. */
/* 건반 음이름 — 규칙 1("흰 건반은 영어, 검은 건반은 숫자")을 화면에서 확인하는
   유일한 수단이라 본문 최소 크기(12px)를 지킨다. */
.key-label{font-size:12px}
.bk .key-label{font-size:11px}

@media (pointer:fine) and (max-height:820px){
  .playstage__pianowrap{--pz:.72}
  /* zoom 은 글자까지 같이 줄인다(12px -> 8.6px). 라벨만 1/zoom 으로 역보정해서
     화면에는 원래 크기 그대로 그려지게 한다. */
  .playstage__pianowrap .key-label{font-size:16.7px}
  .playstage__pianowrap .bk .key-label{font-size:15.3px}
}

@media (max-width:640px){
  /* 모바일은 한 옥타브만. 손가락이라 위 zoom 규칙은 적용되지 않고, 흰건반은
     48px 원래 크기 그대로 남아 터치 최소 44px 를 넘긴다.
     흰건반 7개 = 336px 가 실제로 들어가려면 좌우 여백을 줄여야 한다 —
     기본 gutter(20px)+패널 padding 으로는 315px 만 남아 한 건반이 잘렸다. */
  .playstage__inner{padding-inline:12px}
  .playstage__keys{padding-left:6px;padding-right:6px}
  .playstage__pianowrap{width:min(336px,100%)}

  /* 세로가 빠듯해 악보가 하한까지 눌리면 음이름이 10.6px 까지 떨어진다.
     모바일에서는 세로 스크롤을 허용하는 대신 하한을 올려 12px 를 지킨다 —
     읽히지 않는 악보보다 조금 스크롤하는 쪽이 낫다. */
  .playstage__panel{min-height:212px}

  .playstage__steps li{font-size:12.5px}
  .playstage__head{gap:var(--s2)}

  /* 모바일은 화면 건반을 아예 없앤다 — 예시곡까지 포함 전체(2026-08-16 확장).
     화면 터치로는 화음·양손 동시 입력을 정확히 볼 수 없어서 채점 자체가
     MIDI 유선 연결 기준으로 바뀌었고(위 화음 규칙 참고), 흐름 모드는 원래도
     악보 드래그 제스처와 건반 손잡이 제스처가 충돌했다. MIDI 연결 상태 문구
     (.playstage__midi)는 유선 연결 여부를 확인할 수 있어야 하므로 남겨두고,
     건반 자체(.playstage__pianowrap)만 지운다 — .playstage__keys 를 통째로
     지우면 그 문구까지 같이 사라진다. */
  .playstage__pianowrap{display:none}

  /* 모바일은 듣기만 남기고 손모드·속도·플레이(테스트 시작) 버튼을 없앤다
     (2026-08-17, "오직 듣기 버튼만 활성화") — 위 건반 삭제와 같은 이유로,
     화면 터치로는 실제 테스트를 제대로 볼 수 없어서 아예 그 진입로를 막고
     듣기(감상)만 남긴다. #playStart는 연습모드의 "테스트" 버튼과 같은
     요소를 재사용하므로, 연습모드(.is-practice)에서는 그대로 둔다 — 거기는
     건반 자체가 없어 터치만으로 완결되는 별개 게임이라 이 제약이 안 맞는다. */
  .playstage__hands,
  .playstage__speed{display:none !important}
  .playstage:not(.is-practice) #playStart{display:none !important}
}

@media (prefers-reduced-motion:reduce){
  .playstage{transition:none}
  .playstage__steps li,
  .playstage__steps li::before{transition:none}
}

/* ── 연주 화면: 손 선택 · 마디 표시 · 결과 ─────────────────── */
.playstage__hands{display:flex;gap:0;border:1px solid var(--line);border-radius:var(--r);overflow:hidden}
.playstage__hands[hidden]{display:none}
.playstage__hands button{
  min-height:46px;padding:0 clamp(12px,1.6vw,20px);
  background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-size:14px;letter-spacing:.02em;
  color:var(--warm-mute);
  transition:background .2s var(--ease),color .2s var(--ease);
}
.playstage__hands button + button{border-left:1px solid var(--line)}
.playstage__hands button:hover:not(:disabled){color:var(--warm);background:rgba(255,255,255,.06)}
.playstage__hands button.is-on{background:var(--ice);color:#06131D}
.playstage__hands button:disabled{opacity:.34;cursor:not-allowed}

/* 마디 표시는 절대배치 — 세로 예산을 한 픽셀도 쓰지 않는다 */
.playstage__scores{position:relative}
.playstage__measure{
  /* 오른손 패널의 "오른손 Treble" 라벨과 같은 줄, 반대쪽 끝에 앉힌다 —
     절대배치라 세로 예산을 쓰지 않으면서도 경계에 걸쳐 보이지 않는다. */
  position:absolute;top:7px;right:10px;z-index:3;
  padding:3px 10px 4px;
  background:rgba(6,6,8,.72);border:1px solid rgba(255,255,255,.16);
  border-radius:var(--r);
  font-family:var(--sans);font-size:12px;letter-spacing:.1em;
  color:var(--warm);
}
.playstage__measure[hidden]{display:none}

/* 오른손만 모드의 왼손 패널 — 참고용이라 한 단계 물러난다 */
.playstage__panel.is-passive{opacity:.55}

/* ── 결과 ─────────────────────────────────────────────── */
.playstage__result{
  position:absolute;inset:0;z-index:10;
  display:grid;place-items:center;
  padding:var(--gutter);
  background:rgba(6,6,8,.62);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  opacity:0;transition:opacity .3s var(--ease);
}
.playstage__result.is-open{opacity:1}
.playstage__result[hidden]{display:none}
.playstage__resultcard{
  width:min(420px,100%);
  padding:clamp(26px,3.4vw,40px);
  text-align:center;color:#1B2430;
}
.playstage__resultkicker{
  font-family:var(--sans);font-size:12.5px;letter-spacing:.2em;color:#0B5C9E;
}
.playstage__score{margin-top:var(--s2);display:flex;align-items:baseline;justify-content:center;gap:6px}
.playstage__score b{
  font-family:var(--serif);font-weight:400;font-size:clamp(3rem,8vw,4.4rem);line-height:1;
  color:#1B2430;
}
.playstage__score span{font-size:15px;color:#4A4842}
.playstage__resultline{margin-top:var(--s2);font-size:14.5px;line-height:1.7;color:#4A4842}
.playstage__resultacts{
  margin-top:var(--s3);display:flex;gap:10px;justify-content:center;flex-wrap:wrap;
}
/* 부스 모드 이름 입력(2026-09-14) — 결과 카드 안, 점수 아래·버튼 위. 입력칸 16px는
   iOS가 포커스 시 화면을 확대하는 기준(16px 미만)을 피하기 위한 값. */
.playstage__rankask{margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid rgba(27,36,48,.12)}
.playstage__rankask-t{font-size:14.5px;color:#4A4842}
.playstage__rankask-in{
  margin-top:var(--s2);width:min(260px,100%);height:44px;padding:0 14px;
  border:1px solid rgba(27,36,48,.22);border-radius:var(--r);
  background:#fff;color:#1B2430;font:inherit;font-size:16px;text-align:center;
}
.playstage__rankask-in:focus{outline:2px solid #0B5C9E;outline-offset:1px}
.playstage__rankask-acts{margin-top:var(--s2);display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
.playstage__rankask-msg{margin-top:var(--s2);font-size:13.5px;color:#0B5C9E}
.playstage__rankask-msg:empty{display:none}

@media (max-width:640px){
  .playstage__hands button{font-size:13px;padding:0 12px}
}

@media (prefers-reduced-motion:reduce){
  .playstage__result{transition:none}
  .playstage__hands button{transition:none}
}

/* ═══════════════════════════════════════════════════════════
   악보 스캐너 · 대기 · 결과

   scanner_example.png 구조 — 라이브 영상 위에 어두운 마스크와 가이드 박스를
   캔버스로 그리고(scan.js), 위에는 제목 줄, 아래에는 갤러리·셔터·전환을 둔다.
   줌 슬라이더는 뺐다(iOS 사파리 미지원이라 기기에 따라 죽은 UI 가 된다).
   ═══════════════════════════════════════════════════════════ */
.scanwrap{
  position:fixed;inset:0;z-index:220;
  background:var(--ink);
  opacity:0;transition:opacity .32s var(--ease);
}
.scanwrap.is-open{opacity:1}
.scanwrap[hidden]{display:none}

.scan{position:absolute;inset:0;display:flex;flex-direction:column}
.scan[hidden]{display:none}

/* ── ① 촬영 ─────────────────────────────────────────────── */
.scan__stage{position:absolute;inset:0;background:#000;overflow:hidden}
.scan__video{width:100%;height:100%;object-fit:contain;display:block}
.scan__guide{position:absolute;inset:0;pointer-events:none}

.scan__head{
  position:relative;z-index:2;
  display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  padding:clamp(12px,2.4vh,22px) var(--gutter);
}
.scan__title{
  font-family:var(--serif);font-size:clamp(1rem,2.2vw,1.3rem);color:var(--warm);
  text-shadow:0 1px 12px rgba(0,0,0,.8);
}
.scan__icon{
  width:46px;height:46px;flex:none;
  display:grid;place-items:center;border-radius:50%;
  background:rgba(6,6,8,.55);border:1px solid rgba(255,255,255,.18);
  color:var(--warm);cursor:pointer;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.scan__icon:hover{color:var(--ice);border-color:var(--ice)}
.scan__icon--ghost{background:none;border:0;pointer-events:none}

.scan__bottom{
  position:relative;z-index:2;margin-top:auto;
  display:flex;flex-direction:column;align-items:center;gap:clamp(10px,1.8vh,18px);
  padding:clamp(14px,2.4vh,24px) var(--gutter) clamp(20px,4vh,40px);
  background:linear-gradient(180deg,transparent,rgba(6,6,8,.72) 38%);
}
.scan__hint{
  font-size:13.5px;color:var(--warm);text-align:center;
  text-shadow:0 1px 10px rgba(0,0,0,.9);
}
/* 모바일 갤러리 모드 권장 문구(2026-09-14) — 힌트 아래 작게 */
.scan__note{
  margin-top:4px;font-size:12px;line-height:1.55;color:var(--warm-mute);text-align:center;
  text-shadow:0 1px 10px rgba(0,0,0,.9);
}
.scan__note[hidden]{display:none}

.scan__modes{
  display:flex;border:1px solid rgba(255,255,255,.22);border-radius:var(--r);
  overflow:hidden;background:rgba(6,6,8,.55);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
}
.scan__modes button{
  min-height:44px;padding:0 clamp(14px,2.4vw,22px);
  background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-size:13.5px;color:var(--warm-mute);
  transition:background .2s var(--ease),color .2s var(--ease);
}
.scan__modes button + button{border-left:1px solid rgba(255,255,255,.18)}
.scan__modes button.is-on{background:var(--ice);color:#06131D}

.scan__controls{
  display:flex;align-items:center;justify-content:center;
  gap:clamp(28px,7vw,64px);
}
.scan__side{
  width:52px;height:52px;flex:none;
  display:grid;place-items:center;border-radius:50%;
  background:rgba(6,6,8,.55);border:1px solid rgba(255,255,255,.2);
  color:var(--warm);cursor:pointer;
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.scan__side:hover{color:var(--ice);border-color:var(--ice)}
.scan__side[hidden]{display:none}
.scan__shutter[hidden]{display:none}
/* 셔터 — 예제처럼 가운데가 크고 링이 한 겹 돈다 */
.scan__shutter{
  width:74px;height:74px;flex:none;border-radius:50%;
  display:grid;place-items:center;
  background:rgba(6,6,8,.5);border:2px solid rgba(255,255,255,.85);
  cursor:pointer;padding:0;
  transition:transform .12s var(--ease);
}
.scan__shutter:active{transform:scale(.94)}
.scan__shutter span{
  display:block;width:56px;height:56px;border-radius:50%;
  background:var(--warm);
  transition:background .2s var(--ease);
}
.scan__shutter:hover span{background:var(--ice)}

.scan__error{
  position:absolute;left:var(--gutter);right:var(--gutter);
  bottom:calc(clamp(20px,4vh,40px) + 190px);z-index:3;
  padding:12px 16px;border-radius:var(--r);
  background:rgba(120,26,26,.9);border:1px solid rgba(255,140,140,.5);
  color:#FFE3E3;font-size:13.5px;line-height:1.6;text-align:center;
}
.scan__error[hidden]{display:none}

/* ── ② 대기 ─────────────────────────────────────────────
   프롬프트의 픽셀 그리드 로더를 바닐라로 옮겼다. 3x3 칸이 체브런 파면으로
   밝아지고, 라벨은 좌우로 흐르고, 경과 시간은 고정폭 숫자로 센다. */
.scan--wait{align-items:center;justify-content:center;background:var(--ink)}
.scanwait{display:flex;flex-direction:column;align-items:center;gap:var(--s2);text-align:center;padding:var(--gutter)}
.pixgrid{
  display:grid;grid-template-columns:repeat(3,6px);gap:2px;
}
.pixgrid span{
  width:6px;height:6px;border-radius:1px;background:var(--warm);
  opacity:.12;
  animation:pixel-on 650ms ease-in-out infinite;
}
@keyframes pixel-on{
  0%,100%{opacity:.12}
  50%{opacity:1}
}
.scanwait__label{
  font-size:15px;font-weight:400;
  background:linear-gradient(90deg,var(--warm-dim) 35%,var(--warm) 50%,var(--warm-dim) 65%);
  background-size:200% 100%;
  -webkit-background-clip:text;background-clip:text;color:transparent;
  animation:shimmer-text 1.4s linear infinite;
}
@keyframes shimmer-text{
  0%{background-position:200% 0}
  100%{background-position:-200% 0}
}
.scanwait__time{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:13px;color:var(--warm-mute);font-variant-numeric:tabular-nums;
}
.scanwait__note{margin-top:var(--s2);font-size:12.5px;color:var(--warm-dim);max-width:26em;line-height:1.7}

/* ── ③ 결과 / ④ 실패 ────────────────────────────────────── */
.scan--result,.scan--fail{
  overflow-y:auto;background:var(--ink);
  align-items:center;justify-content:center;
}
.scanres{
  width:100%;max-width:1100px;margin:auto;
  padding:clamp(20px,4vh,44px) var(--gutter);
  display:flex;flex-direction:column;gap:clamp(16px,2.6vh,26px);
}
.scanres__head{text-align:center}
.scanres__kicker{
  font-family:var(--sans);font-size:var(--t-label);letter-spacing:.22em;
  text-transform:uppercase;color:var(--ice);
}
/* "인식 실패"라는 문구가 이미 상태를 말하므로 색으로 또 외치지 않는다 */
.scanres__kicker--warn{color:var(--warm-mute)}
.scanres__t{
  margin-top:10px;
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.3rem,3vw,1.9rem);line-height:1.3;color:var(--warm);
}
.scanres__tip{margin-top:var(--s2);font-size:14px;line-height:1.8;color:var(--warm-mute)}

/* 찍은 사진과 변환된 악보를 나란히 — 제대로 읽혔는지 바로 대조할 수 있게 */
.scanres__body{
  display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1fr);
  gap:clamp(16px,2.6vw,32px);align-items:start;
}
.scanres__shot{margin:0;display:flex;flex-direction:column;gap:8px}
.scanres__shot img{
  width:100%;border-radius:var(--r);border:1px solid var(--line);
  background:var(--ink-2);
}
.scanres__shot figcaption{font-size:12.5px;letter-spacing:.02em;color:var(--warm-dim);text-align:center}
.scanres__score{display:flex;flex-direction:column;gap:10px;min-width:0}
.scanres__panel{
  padding:clamp(8px,1.4vh,14px) clamp(10px,1.4vw,16px);
  display:flex;flex-direction:column;gap:4px;
  height:clamp(150px,20vh,200px);
}
.scanres__panel[hidden]{display:none}

.scanres__acts{display:flex;gap:10px;justify-content:center;flex-wrap:wrap}

/* 진행요원용 우회로 — 관람객 눈에는 거의 안 보이게 */
.scanres__fallback{
  align-self:center;margin-top:var(--s2);
  background:none;border:0;cursor:pointer;
  font-size:11.5px;color:var(--warm-dim);opacity:.14;
  transition:opacity .2s var(--ease);
}
.scanres__fallback:hover,.scanres__fallback:focus-visible{opacity:.8}

@media (max-width:820px){
  .scanres__body{grid-template-columns:1fr}
}

@media (prefers-reduced-motion:reduce){
  .scanwrap{transition:none}
  .pixgrid span{animation:none;opacity:.4}
  .scanwait__label{animation:none;color:var(--warm);-webkit-text-fill-color:currentColor}
}

/* ═══════════════════════════════════════════════════════════
   로그인 — 우측 상단 LOGIN 과 악보 모음집 잠금이 같은 창을 연다
   ═══════════════════════════════════════════════════════════ */
.loginwrap{
  position:fixed;inset:0;z-index:240;
  display:grid;place-items:center;padding:var(--gutter);
  opacity:0;transition:opacity .3s var(--ease);
}
.loginwrap.is-open{opacity:1}
.loginwrap[hidden]{display:none}
.loginwrap__scrim{
  position:absolute;inset:0;
  background:rgba(6,6,8,.8);
  backdrop-filter:blur(14px) saturate(110%);
  -webkit-backdrop-filter:blur(14px) saturate(110%);
}
/* 다크 카드(참고: modern_login_signup_example.png) — 촬영 저장 창(.savecard)은
   밝은 유리 그대로 두고, 클래스를 공유하는 __x/__t/__err 만 .logincard 안에서
   덮어써서 서로 안 건드리게 한다(__body/__note/__mark/구글 버튼은 로그인
   전용이라 직접 고쳤다). */
.logincard{
  position:relative;z-index:1;
  width:min(400px,100%);
  padding:clamp(34px,4.4vw,46px) clamp(24px,3.4vw,38px) clamp(24px,3vw,32px);
  text-align:center;color:var(--warm);
}
.logincard__x{
  position:absolute;top:10px;right:10px;
  width:46px;height:46px;display:grid;place-items:center;
  background:none;border:0;border-radius:var(--r);
  color:#6E6A66;cursor:pointer;
  transition:color .2s var(--ease),background .2s var(--ease);
}
.logincard__x:hover{color:#1B2430;background:rgba(27,36,48,.06)}
.logincard .logincard__x{color:var(--warm-mute)}
.logincard .logincard__x:hover{color:var(--warm);background:rgba(255,255,255,.08)}

/* 원형 배지 — 로고를 어두운 배경 위에 두면 크롬 재질의 광택·음영이 흰 유리
   위보다 뚜렷하게 대비된다. 은은한 아이스 톤 후광을 배지 안쪽에 얹었다. */
/* 브랜드 로고가 너무 작다는 지적(2026-08-15)으로 배지·마크를 한 단계 키웠다 —
   64px/34px 였던 걸 84px/52px 로. 배지가 카드 폭(min 400px)의 21% 를 넘지
   않게 clamp 해서 좁은 화면에서 과하게 커지지 않는다. */
.logincard__badge{
  margin:0 auto;width:clamp(72px,20vw,84px);height:clamp(72px,20vw,84px);
  display:grid;place-items:center;border-radius:50%;
  border:1px solid var(--line);
  background:radial-gradient(120% 120% at 50% 0%,rgba(143,191,224,.16),transparent 70%);
}
.logincard__mark{width:clamp(44px,12vw,52px);height:auto;display:block}

.logincard__t{
  margin-top:8px;
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.4rem,3vw,1.8rem);line-height:1.3;color:#1B2430;
}
.logincard .logincard__t{margin-top:var(--s3);color:var(--warm)}

.logincard__body{
  margin-top:var(--s2);
  font-size:14.5px;line-height:1.8;color:var(--warm-mute);
}
/* 구글 버튼 — 어두운 카드 위라 공식 다크 변형(테두리+어두운 배경, 컬러 G 로고 유지)을 쓴다 */
.loginbtn-google{
  margin-top:var(--s3);
  width:100%;min-height:52px;
  display:inline-flex;align-items:center;justify-content:center;gap:12px;
  background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.22);border-radius:var(--r);
  color:var(--warm);cursor:pointer;
  font-family:var(--sans);font-size:15px;font-weight:400;
  transition:background .2s var(--ease),border-color .2s var(--ease);
}
.loginbtn-google:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.42)}
.logincard__err{
  margin-top:var(--s2);padding:10px 14px;border-radius:var(--r);
  background:rgba(160,30,30,.1);border:1px solid rgba(160,30,30,.3);
  font-size:13px;line-height:1.6;color:#8A1D1D;
}
.logincard__err[hidden]{display:none}
/* 어두운 카드 위에서는 진한 적색 글자(#8A1D1D)가 거의 안 보인다 — 밝은 톤으로 */
.logincard .logincard__err{
  background:rgba(224,90,90,.16);border-color:rgba(224,90,90,.42);color:#FFCFCF;
}
/* --warm-dim 은 순수 --ink(4.82:1) 기준으로 맞춘 색이다. 유리 오버레이가
   배경을 살짝 밝혀서 실제로는 3.86:1 까지 떨어졌다 — 여유가 더 큰 --warm-mute 로. */
.logincard__note{margin-top:var(--s3);font-size:12.5px;line-height:1.7;color:var(--warm-mute)}

/* 로그인 뒤 우측 상단 버튼에 프로필 사진을 같이 띄운다 */
.loginbtn__avatar{
  width:24px;height:24px;border-radius:50%;object-fit:cover;
  border:1px solid var(--line);vertical-align:-7px;margin-right:8px;
}

@media (prefers-reduced-motion:reduce){
  .loginwrap{transition:none}
}

/* ═══════════════════════════════════════════════════════════
   순위표 — 곡 카드를 고르면 데크 왼쪽에 뜬다(순위표.png 구성)

   데크는 이미 화면을 꽉 쓰고 있어서, 순위표를 흐름에 넣으면 카드가 밀린다.
   왼쪽에 절대배치로 띄우고 좁은 화면에서는 통째로 숨긴다(카드 고르기가 우선).
   ═══════════════════════════════════════════════════════════ */
.rank{
  position:absolute;left:var(--gutter);top:50%;transform:translateY(-50%);
  z-index:5;
  width:clamp(240px,22vw,300px);
  max-height:82vh;overflow-y:auto;
  padding:clamp(16px,1.8vw,22px);
  background:rgba(10,10,12,.72);
  border:1px solid var(--line);border-radius:var(--r);
  backdrop-filter:blur(14px) saturate(112%);
  -webkit-backdrop-filter:blur(14px) saturate(112%);
  opacity:0;transition:opacity .35s var(--ease);
}
.rank.is-on{opacity:1}
.rank[hidden]{display:none}

.rank__t{font-family:var(--serif);font-size:1.15rem;color:var(--warm)}
.rank__song{margin-top:4px;font-size:12.5px;color:var(--warm-mute)}

/* ── 시상대: 1위를 가운데 높게 ─────────────────────────── */
.rank__podium{
  margin-top:var(--s3);
  display:grid;grid-template-columns:repeat(3,1fr);gap:8px;align-items:end;
}
.rank__pod{display:flex;flex-direction:column;align-items:center;gap:6px;min-width:0}
.rank__avatar{
  width:44px;height:44px;border-radius:50%;object-fit:cover;
  background:var(--ink-3);border:1px solid var(--line);
  display:grid;place-items:center;
  font-family:var(--sans);font-size:16px;color:var(--warm-mute);
}
.rank__name{
  font-size:11.5px;color:var(--warm);max-width:100%;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.rank__pts{font-size:12px;color:var(--ice);font-variant-numeric:tabular-nums}
.rank__bar{
  width:100%;border-radius:var(--r) var(--r) 0 0;
  display:grid;place-items:center;
  font-family:var(--serif);font-size:15px;color:var(--warm);
  /* 등수는 높이로도 읽히지만 숫자를 같이 넣어 색·높이에만 기대지 않는다 */
}
/* 금·은·동.
   높이와 숫자가 이미 등수를 말하고 있으므로 색은 보강이다(색만으로 전달하지
   않는다 — DESIGN_DIRECTION 의 color-not-only 원칙).

   --ink(#0B0B0C) 위 대비를 계산해서 고른 값이다:
     금 #E3B94F  10.6:1   은 #C8CDD4  12.3:1   동 #CE8E4E  7.1:1
   막대 안 숫자는 반투명 막대에 겹치므로 한 단계 밝은 톤을 따로 둔다
   (금 기준 막대 위 5.9:1). */
:root{
  --gold:#E3B94F;   --gold-lit:#F2D98B;
  --silver:#C8CDD4; --silver-lit:#E8EBEF;
  --bronze:#CE8E4E; --bronze-lit:#EDBE8E;
}

.rank__pod--1 .rank__bar{height:62px;background:rgba(227,185,79,.38);color:var(--gold-lit)}
.rank__pod--2 .rank__bar{height:44px;background:rgba(200,205,212,.30);color:var(--silver-lit)}
.rank__pod--3 .rank__bar{height:34px;background:rgba(206,142,78,.32);color:var(--bronze-lit)}

/* 포디움 점수도 등수 색을 따라간다(기본은 --ice 였다) */
.rank__pod--1 .rank__pts{color:var(--gold)}
.rank__pod--2 .rank__pts{color:var(--silver)}
.rank__pod--3 .rank__pts{color:var(--bronze)}

/* ── 순위 리스트 ──────────────────────────────────────── */
.rank__list{margin-top:var(--s3);list-style:none;display:flex;flex-direction:column;gap:2px}
.rank__row{
  display:flex;align-items:center;gap:10px;
  padding:9px 10px;border-radius:var(--r);
  font-size:13px;color:var(--warm);
}
.rank__row + .rank__row{border-top:1px solid var(--line)}
.rank__row.is-me{border:1px solid var(--ice);background:rgba(143,191,224,.1)}
.rank__no{width:16px;flex:none;text-align:center;color:var(--warm-mute);font-variant-numeric:tabular-nums}
/* 리스트의 1·2·3 등 숫자도 같은 금·은·동으로. rank.js 가 등수 클래스를 붙이지
   않으므로 순서로 집는다 — 리스트는 항상 1등부터 순서대로 그려진다. */
.rank__list .rank__row:nth-child(1) .rank__no{color:var(--gold);font-weight:500}
.rank__list .rank__row:nth-child(2) .rank__no{color:var(--silver);font-weight:500}
.rank__list .rank__row:nth-child(3) .rank__no{color:var(--bronze);font-weight:500}
.rank__row .rank__avatar{width:28px;height:28px;font-size:12px}
.rank__who{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rank__score{flex:none;font-variant-numeric:tabular-nums;color:var(--warm)}

.rank__empty{margin-top:var(--s3);font-size:12.5px;line-height:1.8;color:var(--warm-dim);text-align:center}
.rank__empty[hidden]{display:none}
.rank__mine{
  margin-top:var(--s3);padding-top:var(--s2);border-top:1px solid var(--line);
  font-size:12.5px;color:var(--warm-mute);text-align:center;
}
.rank__mine[hidden]{display:none}

/* 순위표가 뜨면 데크를 오른쪽으로 밀어 자리를 만든다 — 안 그러면 카드와 겹친다
   (1280px 에서 40px 겹치는 걸 실측). 순위표 폭 + 여백만큼만 민다. */
.deckwrap.has-rank .deckwrap__inner{
  /* padding 은 레이아웃 속성이라 전환을 걸지 않는다(리플로우를 매 프레임 유발) */
  padding-left:calc(var(--gutter) + clamp(240px,22vw,300px) + clamp(20px,2.4vw,40px));
}

/* 좁은 화면에서는 카드 고르기가 우선이라 순위표를 숨기고 밀기도 되돌린다 */
@media (max-width:1100px){
  .rank{display:none}
  .deckwrap.has-rank .deckwrap__inner{padding-left:var(--gutter)}
}

@media (prefers-reduced-motion:reduce){
  .rank{transition:none}
}

/* ═══════════════════════════════════════════════════════════
   악보 모음집 — 테이블(폴더) 목록 → 그 안의 악보 목록
   ═══════════════════════════════════════════════════════════ */
.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;
}

.libwrap{
  position:fixed;inset:0;z-index:230;overflow-y:auto;
  opacity:0;transition:opacity .35s var(--ease);
}
.libwrap.is-open{opacity:1}
.libwrap[hidden]{display:none}
.libwrap__scrim{
  position:fixed;inset:0;
  background:rgba(6,6,8,.9);
  backdrop-filter:blur(16px) saturate(112%);
  -webkit-backdrop-filter:blur(16px) saturate(112%);
}
.libwrap__inner{
  position:relative;z-index:1;
  width:100%;max-width:1000px;margin:0 auto;
  padding:clamp(24px,5vh,56px) var(--gutter);
}

.lib__head{
  display:flex;align-items:flex-start;justify-content:space-between;
  gap:var(--s3);flex-wrap:wrap;
  padding-bottom:var(--s3);border-bottom:1px solid var(--line);
}
.lib__kicker{font-family:var(--sans);font-size:12px;letter-spacing:.22em;color:var(--ice)}
.lib__t{margin-top:8px;font-family:var(--serif);font-weight:400;font-size:clamp(1.4rem,3vw,2rem);color:var(--warm)}
.lib__headacts{display:flex;gap:10px;flex-wrap:wrap}
/* .sheetbtn 은 밝은 유리 판 위를 전제로 한 잉크색(#1B2430)이다. 모음집 헤더는
   어두운 배경이라 그대로 쓰면 1.26:1 로 안 보인다 — 여기서만 밝은 글자로. */
.lib__headacts .sheetbtn{
  color:var(--warm);border-color:var(--line);
}
.lib__headacts .sheetbtn:hover{color:var(--ice);border-color:var(--ice);background:rgba(143,191,224,.1)}

/* ── 테이블 카드 — 왼쪽 정사각 이미지 + 가운데 이름 ────── */
.lib__grid{
  margin-top:var(--s4);
  display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--s2);
}
.libcard{
  display:flex;align-items:center;gap:var(--s2);
  padding:12px;text-align:left;
  background:rgba(255,255,255,.04);
  border:1px solid var(--line);border-radius:var(--r);
  color:var(--warm);cursor:pointer;
  transition:background .2s var(--ease),border-color .2s var(--ease);
}
.libcard:hover{background:rgba(255,255,255,.09);border-color:var(--ice)}
.libcard__img{
  width:64px;height:64px;flex:none;border-radius:var(--r);object-fit:cover;
  background:var(--ink-3);
}
.libcard__body{min-width:0;flex:1}
.libcard__name{
  font-family:var(--serif);font-size:1.05rem;line-height:1.35;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.libcard__meta{margin-top:4px;font-size:12.5px;color:var(--warm-mute)}

.lib__empty{
  margin-top:var(--s5);text-align:center;
  font-size:14.5px;line-height:1.9;color:var(--warm-mute);
}
.lib__empty[hidden]{display:none}
.lib__empty b{color:var(--warm);font-weight:400}

/* ── 악보 목록(표) ────────────────────────────────────── */
.lib__scores{margin-top:var(--s4)}
.lib__scores[hidden]{display:none}
.lib__table{width:100%;border-collapse:collapse;font-size:14px}
.lib__table th{
  padding:10px 12px;text-align:left;
  font-family:var(--sans);font-size:12px;font-weight:400;letter-spacing:.14em;
  color:var(--warm-mute);border-bottom:1px solid var(--line);
}
.lib__table td{padding:12px;border-bottom:1px solid var(--line);color:var(--warm)}
.lib__table tbody tr{transition:background .2s var(--ease)}
.lib__table tbody tr:hover{background:rgba(255,255,255,.05)}
.lib__num{font-variant-numeric:tabular-nums;color:var(--warm-mute)}
.lib__playbtn{
  min-height:44px;padding:0 16px;
  background:none;border:1px solid var(--line);border-radius:var(--r);
  color:var(--warm);cursor:pointer;font-family:var(--sans);font-size:13.5px;
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.lib__playbtn:hover{color:var(--ice);border-color:var(--ice)}

/* ── 저장 창 ──────────────────────────────────────────── */
.savewrap{
  position:fixed;inset:0;z-index:250;
  display:grid;place-items:center;padding:var(--gutter);
  opacity:0;transition:opacity .3s var(--ease);
}
.savewrap.is-open{opacity:1}
.savewrap[hidden]{display:none}
.savewrap__scrim{position:absolute;inset:0;background:rgba(6,6,8,.82);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)}
.savecard{
  position:relative;z-index:1;
  width:min(420px,100%);max-height:86vh;overflow-y:auto;
  padding:clamp(26px,3.4vw,36px);
  text-align:center;color:#1B2430;
}
.savecard__label{
  display:block;margin-top:var(--s3);margin-bottom:8px;
  font-family:var(--sans);font-size:12.5px;letter-spacing:.06em;color:#4A4842;text-align:left;
}
.savecard__input{
  width:100%;min-height:46px;padding:0 14px;
  background:#fff;border:1px solid rgba(27,36,48,.24);border-radius:var(--r);
  font-family:var(--sans);font-size:14.5px;color:#1B2430;
}
.savecard__input:focus-visible{outline:2px solid #0B5C9E;outline-offset:1px}
.savecard__tables{display:flex;flex-direction:column;gap:6px}
.savecard__tables:empty::before{
  content:'아직 모음집이 없어요 — 아래에서 새로 만들어 주세요';
  font-size:12.5px;color:#6E6A66;
}
.savecard__pick{
  display:flex;align-items:center;gap:10px;
  min-height:46px;padding:0 12px;text-align:left;
  background:none;border:1px solid rgba(27,36,48,.2);border-radius:var(--r);
  color:#1B2430;cursor:pointer;font-family:var(--sans);font-size:14px;
  transition:background .2s var(--ease),border-color .2s var(--ease);
}
.savecard__pick:hover{background:rgba(27,36,48,.05)}
.savecard__pick.is-on{border-color:#0B5C9E;background:rgba(11,92,158,.1)}
.savecard__acts{margin-top:var(--s3);display:flex;gap:10px;justify-content:center;flex-wrap:wrap}

@media (prefers-reduced-motion:reduce){
  .libwrap,.savewrap{transition:none}
}

/* ═══════════════════════════════════════════════════════════
   확인 창(로그아웃 등) — 되돌리기 어려운 동작 앞에 한 번 되묻는다
   ═══════════════════════════════════════════════════════════ */
.confirmwrap{
  position:fixed;inset:0;z-index:260;
  display:grid;place-items:center;padding:var(--gutter);
  opacity:0;transition:opacity .25s var(--ease);
}
.confirmwrap.is-open{opacity:1}
.confirmwrap[hidden]{display:none}
.confirmwrap__scrim{
  position:absolute;inset:0;background:rgba(6,6,8,.82);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
}
.confirmcard{
  position:relative;z-index:1;
  width:min(380px,100%);
  padding:clamp(26px,3.4vw,36px);
  text-align:center;color:var(--warm);
}
.confirmcard__t{
  font-family:var(--serif);font-weight:400;
  font-size:clamp(1.2rem,2.6vw,1.5rem);line-height:1.35;color:var(--warm);
}
.confirmcard__body{
  margin-top:var(--s2);
  font-size:14px;line-height:1.8;color:var(--warm-mute);
}
.confirmcard__acts{
  margin-top:var(--s3);display:flex;gap:10px;justify-content:center;flex-wrap:wrap;
}
/* .sheetbtn 은 밝은 판 전용 잉크색이라 어두운 카드 위에서는 안 보인다 */
.confirmcard__acts .sheetbtn{color:var(--warm);border-color:var(--line)}
.confirmcard__acts .sheetbtn:hover{color:var(--ice);border-color:var(--ice);background:rgba(143,191,224,.1)}
.confirmcard__acts .sheetbtn--primary{background:var(--ice);border-color:var(--ice);color:#06131D}
.confirmcard__acts .sheetbtn--primary:hover{background:#A5CDE8;border-color:#A5CDE8;color:#06131D}

@media (prefers-reduced-motion:reduce){
  .confirmwrap{transition:none}
}

/* ── 연주 화면: 빠르기 · 마디 넘기기 ────────────────────── */
.playstage__speed{
  display:flex;border:1px solid var(--line);border-radius:var(--r);overflow:hidden;
}
.playstage__speed[hidden]{display:none}
.playstage__speed button{
  min-height:46px;padding:0 clamp(10px,1.4vw,16px);
  background:none;border:0;cursor:pointer;
  font-family:var(--sans);font-size:13.5px;color:var(--warm-mute);
  transition:background .2s var(--ease),color .2s var(--ease);
}
.playstage__speed button + button{border-left:1px solid var(--line)}
.playstage__speed button:hover:not(:disabled){color:var(--warm);background:rgba(255,255,255,.06)}
.playstage__speed button.is-on{background:var(--ice);color:#06131D}
/* 플레이가 시작되면 빠르기를 잠근다 — 곡 중간에 속도가 바뀌면 이미 계산해 둔
   음표별 타이밍(빨간선 기준)이 전부 어긋난다(2026-08-16). */
.playstage__speed button:disabled{opacity:.34;cursor:not-allowed}

/* ── 카운트다운 ─────────────────────────────────────────────
   플레이(초록)를 누른 직후 3·2·1 을 보여주고 나서 실제 테스트(빨간선 스크롤)를
   시작한다 — 곡이 시작하자마자 첫 음을 놓치지 않도록 준비 시간을 준다. */
.playstage__countdown{
  position:absolute;inset:0;z-index:20;
  display:grid;place-items:center;
  background:rgba(6,6,8,.6);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
}
.playstage__countdown[hidden]{display:none}
.playstage__countdown span{
  font-family:var(--serif);font-weight:400;font-size:clamp(4rem,16vw,7rem);
  color:var(--warm);text-shadow:0 4px 30px rgba(0,0,0,.6);
  animation:playCountBeat .82s var(--ease);
}
@keyframes playCountBeat{
  0%{opacity:0;transform:scale(.6)}
  30%{opacity:1;transform:scale(1.08)}
  100%{opacity:1;transform:scale(1)}
}
@media (prefers-reduced-motion:reduce){
  .playstage__countdown span{animation:none}
}

/* ── 빨간 세로선 ────────────────────────────────────────────
   지금 눌러야 하는 음의 위치를 알려주는 고정 기준선. 오른손·왼손 두 패널을
   가로질러 하나로 이어져야 해서(.playstage__scores 가 두 패널을 감싸는
   부모라 position:relative), 패널 각각이 아니라 이 부모에 절대배치로 얹는다.
   악보 쪽이 실제 시간에 맞춰 오른쪽에서 왼쪽으로 흐르고, 이 선은 화면에
   고정돼 움직이지 않는다. */
.playstage__redline{
  position:absolute;top:0;bottom:0;z-index:4;
  width:3px;background:#E5484D;
  box-shadow:0 0 8px rgba(229,72,77,.7);
  pointer-events:none;
}
.playstage__redline[hidden]{display:none}

.playstage__mnav{display:flex;gap:6px}
.playstage__mnav[hidden]{display:none}
.playstage__mnav button{
  width:46px;height:46px;display:grid;place-items:center;
  background:none;border:1px solid var(--line);border-radius:var(--r);
  color:var(--warm-mute);cursor:pointer;
  transition:color .2s var(--ease),border-color .2s var(--ease);
}
.playstage__mnav button:hover:not(:disabled){color:var(--ice);border-color:var(--ice)}
.playstage__mnav button:disabled{opacity:.32;cursor:not-allowed}

/* ── 흐르는 악보(촬영·모음집 곡) ─────────────────────────
   마디로 끊지 않고 인식한 악보 전체를 한 줄로 이어 그린다. 연주가 진행되면
   지금 칠 음이 가운데 오도록 부드럽게 흘려보내고, 사용자가 직접 잡고 좌우로
   훑어볼 수도 있다. */
.playstage__staff--flow{
  justify-content:flex-start;         /* 전체 악보라 가운데 정렬이 아니라 스크롤 */
  cursor:grab;
  /* scroll-behavior:smooth 를 쓰면 부드럽긴 하지만, 스크롤이 애니메이션으로
     처리돼 진행 상황에 따라 즉시 따라붙지 못하고 밀린다. JS 에서 매 음마다
     위치를 다시 잡으므로 auto 로 두고, 부드러움은 아래 전환으로 준다. */
  scroll-behavior:auto;
}
.playstage__staff--flow:active{cursor:grabbing}
.playstage__staff--flow.is-dragging{cursor:grabbing;scroll-behavior:auto}
@media (prefers-reduced-motion:reduce){
  .playstage__staff--flow{scroll-behavior:auto}
}

/* 예시곡 실시간 테스트(빨간선) 전용 — 레이아웃은 --flow 와 같지만(마디로 안
   끊고 이어 그림) 스크롤을 시간이 직접 미는 동안이라 사용자가 손대면 안 된다.
   그래서 "잡는 손" 커서(grab)는 안 준다(2026-08-16). */
.playstage__staff--timed{
  justify-content:flex-start;
  scroll-behavior:auto;
}

/* 어두운 배경 위의 보조 버튼(팀원 UI 수정본, 2026-08-15 반영).
   .sheetbtn 은 밝은 카드(.sheetcard #FBF9F5, .savecard, .playstage__resultcard)
   기준으로 만들어져서 글자가 #1B2430 이다. 그런데 촬영 결과/실패 화면은
   background:var(--ink) 라 검정 위 검정이 되어 "저장하기·다시 촬영·닫기" 가
   사실상 보이지 않았다(연주하기만 --primary 라 파란 배경에 흰 글자로 보였다).

   그 두 화면에서만 잉크를 뒤집는다. 밝은 카드 쪽은 그대로 둔다.
   대비: 글자 #F2EEE7 on #0B0B0C = 18.1:1, 테두리 = 3.48:1 (비텍스트 최소 3:1 충족) */
.scan--result .sheetbtn,
.scan--fail .sheetbtn{
  color:var(--warm);
  border-color:rgba(242,238,231,.4);
}
.scan--result .sheetbtn:hover:not(:disabled),
.scan--fail .sheetbtn:hover:not(:disabled){
  background:rgba(242,238,231,.1);
  border-color:rgba(242,238,231,.62);
}
/* --primary(연주하기)는 파란 배경에 흰 글자라 어두운 화면에서도 그대로 읽힌다.
   위 규칙이 색을 덮어쓰지 않도록 되돌려 준다. */
.scan--result .sheetbtn--primary,
.scan--fail .sheetbtn--primary{
  color:#fff;
  border-color:#0B5C9E;
}

/* ═══════════════════════════════════════════════════════════
   모바일 가로 모드 방지 (2026-08-17)

   레이아웃 전부가 세로(휴대폰 세로 폭) 기준으로 짜여 있어서 — 연주 화면 건반,
   튜토리얼 2단, 촬영 가이드 박스 등 — 가로로 눕히면 다 깨진다. HTML을 안
   건드리고 순수 CSS로, 화면 전체를 덮는 안내 문구로 막는다.

   pointer:coarse + hover:none 조합으로 "터치 기기"만 겨냥한다 — 데스크톱에서
   창을 옆으로 좁고 낮게 만든 경우까지 막으면 안 되므로. max-height:600px는
   일반적인 폰의 가로 모드 높이(보통 400px 안팎)는 걸리고, 태블릿 가로 모드
   (보통 700px+)는 안 걸리게 잡은 값이다. */
@media (orientation:landscape) and (max-height:600px) and (pointer:coarse) and (hover:none) {
  body::before{
    content:'📱 세로 모드로 사용해주세요';
    position:fixed;inset:0;z-index:99999;
    display:flex;align-items:center;justify-content:center;
    background:#0B0B0C;color:#F2EEE7;
    font-family:var(--sans);font-size:18px;line-height:1.6;text-align:center;
    padding:40px;white-space:pre-line;
  }
  body > *:not(script):not(style){display:none !important}
}
