/* ───────────────────────────────────────────
   소설역(小說驛) — 일상에서 소설 세계로 환승하는 정거장
   먹빛 어둠 + 촛불 호박색 + 한지 종이색
   ─────────────────────────────────────────── */

:root {
  --bg: #0e1116;
  --bg-2: #141923;
  --raised: #1a2029;
  --line: #29313e;
  --paper: #ece4d4;
  --paper-dim: #b3aa96;
  --faint: #938a76;   /* 본문배경(--bg) 5.53:1 · 패널배경(--raised) 4.79:1 — 둘 다 WCAG AA(4.5) 통과 */
  --amber: #e8a948;
  --amber-soft: #f3c578;
  --amber-dim: rgba(232, 169, 72, 0.16);
  --stamp: #c0504a;
  --ok: #6cba86;      /* 번역 결과 '전체성공' 배지 — 먹빛·호박 팔레트에 어울리는 옥색 초록(amber·stamp와 3색 구분 · --raised 대비 ≈7:1 AA) */
  --serif: "Gowun Batang", "Nanum Myeongjo", serif;
  --sans: "Gowun Dodum", "Malgun Gothic", sans-serif;
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* 보기(외형) 설정 — 본문 글자(.page)·미리보기·쪽번호(#p-counter)가 소비하고, 배경은 듣기 모드 = 본문 카드(.player-body ·
     #r-view 자체는 배경 없음) · 읽기 모드 = #player-screen 전체 + 상단 바 색까지 → 화면 전체가 책 한 장(사용자 결정).
     기본값 = 현재 디자인 그대로. app.js(applyAppearance)가 사용자 설정으로 덮어쓴다.
     책장·dock·시트 패널·듣기 모드의 상단 바는 미소비 → 다크 유지. */
  --reader-font: var(--serif);
  --reader-size: 18.5px;
  --reader-bg: linear-gradient(165deg, rgba(236, 228, 212, .055), rgba(236, 228, 212, .02));
  --reader-fg: var(--paper);
  --reader-fg-dim: var(--paper-dim);
  --reader-hl: var(--amber-dim);
  /* 리더 화면(#player-screen) 최대 폭 — 글자 스타일 '본문 폭'(개선요청 #7 · PC에서 양옆이 비는 문제). 기본 = 보통 560px
     (기존 화면 그대로). app.js(applyAppearance)가 넓게·최대일 때만 덮어쓴다. #r-view 자체 치수(padding·border·margin)는
     건드리지 않고 컨테이너 폭만 바꾼다(★5 — 두 모드가 같은 컨테이너라 기하 동일). 독(.player-dock)도 같은 값을 따른다. */
  --reader-maxw: 560px;

  /* 타이포 스케일 (SSOT) — 본문 3단 + 행간 + 굵기 위계. 캡션 13px = 가독성 바닥(이보다 작은 글자 금지).
     --lh-read 는 리더 본문 행간 — #r-measure-breaks 가 복사해 페이지네이션을 계산하므로(app.js) 현재값 2.02
     박제: 바꾸면 모든 책 재페이지네이션 + READ_BREAKS_CACHE_KEY 무효화 필요(사용자 결정 2026-06-19 · 줄바꿈 규칙
     word-break/overflow-wrap 변경도 동일 — #r-flow 규칙 참조). */
  --fs-body: 15px;
  --fs-label: 14px;
  --fs-caption: 13px;
  --lh-ui: 1.6;
  --lh-read: 2.02;
  --fw-title: 700;
  --fw-strong: 600;
  --fw-body: 400;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--sans);
  line-height: var(--lh-ui);   /* normal(≈1.2) 이던 UI 텍스트 일괄 교정 — 리더/미리보기는 자체 행간이라 미침투 */
  /* 어절 보존 줄바꿈을 UI 전체 기본값으로(UX T-21 — 시트·토스트·안내 문구가 "필요/해요"처럼 어절 중간에서 갈렸다).
     리더 본문 규칙(#r-flow·#r-measure-breaks·.leaf-flow)과 같은 값이고 그쪽은 명시 규칙을 그대로 둔다(계산값 불변 →
     READ_BREAKS_CACHE_KEY 무관). overflow-wrap = 공백 없는 긴 토큰(URL·키)만 글자 단위 비상 줄바꿈. */
  word-break: keep-all; overflow-wrap: break-word;
  color: var(--paper);
  background:
    radial-gradient(120% 50% at 50% -8%, #1d2535 0%, transparent 60%),
    linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 46%);
  background-attachment: fixed;
  min-height: 100dvh;
  overflow-x: hidden;
  overscroll-behavior-y: contain; /* 읽기 모드 스와이프 중 당겨서-새로고침 오발동 방지 */
}

/* 한지 결 — 미세한 노이즈 */
.noise {
  position: fixed; inset: 0; z-index: 40;
  pointer-events: none; opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

button { font-family: inherit; color: inherit; cursor: pointer; }

.ghost-btn {
  position: relative; /* .alert 배지 점의 기준 */
  background: none; border: 1px solid var(--line);
  border-radius: 999px; padding: 8px 14px;
  /* 터치 표준 44px — 시트 텍스트 알약(닫기·찾기·추가 등)을 포함한 전 알약에 적용. 시각 폭은 그대로고
     세로만 44로 차오른다(inline-flex 중앙정렬). 초슬림 상단 바만 예외(.player-head 아래에서 해제). */
  min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-label); color: var(--paper-dim);
  transition: color .2s, border-color .2s, background .2s;
}
/* 사용량 경고 배지 — 설정 시트를 열 때까지 점 표시 */
.ghost-btn.alert::after {
  content: ""; position: absolute; top: 2px; right: 6px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--stamp);
  box-shadow: 0 0 5px rgba(192, 80, 74, .8);
}
/* 공지 '새 소식' 점(UX T-17) — 안 읽은 공지가 있으면 공지 버튼에. 경고(붉은 .alert)와 구분되게 호박색. */
.ghost-btn.unread::after {
  content: ""; position: absolute; top: 4px; right: 5px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 5px rgba(232, 169, 72, .7);
}
.ghost-btn:active { color: var(--amber); border-color: var(--amber); background: var(--amber-dim); }
.ghost-btn.on { color: var(--amber); border-color: var(--amber); }
/* 비활성 표시(클릭은 가능 — 핸들러가 안내 토스트를 띄운다. disabled 속성은 click이 안 떠 안내 불가). */
.ghost-btn.is-off { opacity: .4; }

/* ── 터치 히트영역 (P0 접근성) — 시각 크기는 유지, 탭 영역만 ≥44px ──
   헤더 알약은 실제로 키우고(여유 있음), 초슬림 상단 바·작은 아이콘 버튼은 투명 ::before 로
   탭 영역만 넓힌다(상단 바 초슬림 불변식·아이콘 시각 크기 보존). */
.shelf-head .ghost-btn {
  min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  white-space: nowrap; flex: 0 0 auto;   /* 두 글자 라벨이 세로로 꺾이지 않게 — 모자라면 브랜드 쪽(배지)이 접힌다 */
}
/* 좁은 폰(360px급): 브랜드 + 3버튼(공지·번역·설정)이 한 줄에 들어가도록 버튼 좌우 여백·간격만 줄인다
   (390px 이상은 기존 치수 그대로 · 세로 탭 영역 44px 유지). */
@media (max-width: 380px) {
  .shelf-head .ghost-btn { padding-left: 10px; padding-right: 10px; }
  .shelf-head .head-actions { gap: 6px; }
}
/* 초슬림 상단 바: 세로 탭 영역만 44px — 버튼 간격이 2px(.head-actions)라 가로로 늘리면 겹친다 */
.player-head .ghost-btn::before {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: 44px;
}
/* 작은 아이콘 버튼: 44×44 — 이웃과 충분히 떨어져 있어 히트영역이 겹치지 않는다 */
.toc-step, .thist-nav, .eye-btn, .size-step, .thist-del, .thist-dl { position: relative; }
.toc-step::before, .thist-nav::before, .eye-btn::before,
.size-step::before, .thist-del::before, .thist-dl::before {
  content: ""; position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 100%; height: 100%;
  min-width: 44px; min-height: 44px;
}

.danger-btn {
  background: var(--stamp); border: none; border-radius: 999px;
  padding: 10px 22px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-label); color: #fff;
}

/* ───────── 책장 화면 ───────── */

#shelf-screen {
  max-width: 560px; margin: 0 auto;
  padding: calc(18px + var(--safe-top)) 18px calc(48px + var(--safe-bottom));
}

.shelf-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}

.brand { display: flex; align-items: center; gap: 12px; }

/* 낙관(落款) 도장 */
.stamp {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  background: linear-gradient(150deg, #d05c52, var(--stamp));
  color: #f6ede0; font-family: var(--serif); font-weight: 700; font-size: 22px;
  border-radius: 8px;
  box-shadow: inset 0 0 0 2px rgba(246, 237, 224, .55), inset 0 0 0 4px var(--stamp),
              0 4px 14px rgba(192, 80, 74, .25);
  transform: rotate(-3deg);
}

.brand-text h1 {
  margin: 0; font-family: var(--serif); font-weight: var(--fw-title);
  font-size: 24px; letter-spacing: 6px; line-height: 1.1;
}
.brand-text p {
  margin: 2px 0 0; font-size: var(--fs-caption); letter-spacing: 3px; color: var(--faint);
}

.shelf-hint {
  margin: 18px 2px 26px; font-size: var(--fs-caption); color: var(--faint);
  border-left: 2px solid var(--amber); padding-left: 10px;
}

/* 책장: 줄(선반) 단위 */
.shelf-row {
  display: flex; gap: 13px; align-items: flex-end;
  padding: 0 6px 0;
  position: relative;
  margin-bottom: 34px;
}
.shelf-row::after {
  /* 나무 선반 */
  content: ""; position: absolute; left: -8px; right: -8px; bottom: -12px;
  height: 12px; border-radius: 2px;
  background: linear-gradient(180deg, #33291c 0%, #1f1812 55%, #120e0a 100%);
  box-shadow: 0 10px 18px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(236, 228, 212, .07);
}

/* 책등 */
.spine {
  position: relative;
  width: 74px; height: var(--h, 190px);
  border: none; border-radius: 3px 6px 6px 3px;
  background:
    linear-gradient(90deg, rgba(0,0,0,.38) 0%, rgba(255,255,255,.09) 7%,
      rgba(255,255,255,0) 22%, rgba(0,0,0,.18) 92%, rgba(0,0,0,.42) 100%),
    var(--c, #2e4057);
  box-shadow: 0 7px 14px rgba(0, 0, 0, .45);
  display: flex; flex-direction: column; align-items: center;
  padding: 14px 0 10px;
  opacity: 0;
  animation: rise .5s cubic-bezier(.2, .7, .3, 1) forwards;
  animation-delay: calc(var(--i, 0) * 60ms);
  transition: transform .18s ease;
  /* 길게 누르기(삭제)의 전제 — 빼면 모바일 길게 누르기가 텍스트 선택/콜아웃에 먹힌다 */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.spine:active { transform: translateY(-6px); }

@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}

.s-title {
  writing-mode: vertical-rl;
  font-family: var(--serif); font-weight: var(--fw-title);
  font-size: 15px; letter-spacing: 2px;
  line-height: 1.45;
  color: rgba(246, 240, 228, .92);
  max-height: calc(var(--h, 190px) - 64px);
  max-width: 46px; /* 긴 제목은 둘째 세로줄로 — 그보다 길면 뒷부분 생략 */
  /* 전역 어절 보존(body keep-all · T-21)의 예외 — 좁은 세로 2열은 글자 단위로 채워야 한다. keep-all이면 공백마다
     세로줄이 갈려 짧은 제목도 3열로 넘쳐(46px 밖) 잘렸다. */
  word-break: normal;
  overflow: hidden;
  /* 잘리는 긴 제목 끝을 하드 컷 대신 부드러운 페이드로 — 깨져 보이지 않게(P1-5). 짧은 제목엔 무영향. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 16px), transparent);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .4);
}

/* 진행도: 책등 왼쪽의 호박색 가름끈 */
.s-band {
  position: absolute; left: 7px; bottom: 8px;
  width: 3px; height: calc((var(--h, 190px) - 50px) * var(--p, 0) / 100);
  background: linear-gradient(180deg, var(--amber-soft), var(--amber));
  border-radius: 2px;
  box-shadow: 0 0 6px rgba(232, 169, 72, .5);
}

.s-percent {
  position: absolute; bottom: 9px; right: 8px;
  /* 표지 배지라 13px(캡션 바닥) 미만 의도적 예외 — 10→12px 로만 상향(더 키우면 책등 레이아웃이 깨진다) */
  font-size: 12px; color: rgba(246, 240, 228, .75);
  letter-spacing: .5px;
}

/* 책 추가 슬롯 */
.spine.add {
  background: none;
  border: 1.5px dashed var(--line);
  box-shadow: none;
  color: var(--faint);
  justify-content: center; gap: 10px;
  animation-name: rise;
}
.spine.add .plus { font-size: 26px; line-height: 1; color: var(--amber); }
/* 가로 라벨(T-17) — 세로쓰기에선 '책 추가'의 공백이 한 칸을 먹어 '책 / 추 / 가'로 고르지 않게 갈라졌다 */
.spine.add .add-label {
  white-space: nowrap; letter-spacing: 1px; font-size: var(--fs-caption);
}
.spine.add:active { border-color: var(--amber); }

/* 책등 + 데스크톱 ⋯ 버튼 묶음 — ⋯는 책등 버튼 안에 넣을 수 없어(중첩 버튼) 형제로 겹쳐 둔다 */
.spine-wrap { position: relative; flex: 0 0 auto; }
.spine-more { display: none; }
/* 마우스 기기에서만 — 평소엔 흐리게 늘 보이고(안내 문구 «⋯ 메뉴»를 찾을 수 있게 · 개선요청 #6) hover·키보드 포커스 때
   진해진다(터치 기기는 그리지 않음 — 길게 누르기가 삭제 경로 · ★10) */
@media (hover: hover) and (pointer: fine) {
  .spine-more {
    display: grid; place-items: center;
    position: absolute; top: -9px; right: -9px; z-index: 2;
    width: 30px; height: 30px; padding: 0;
    border: 1px solid var(--line); border-radius: 50%;
    background: var(--raised); color: var(--paper);
    font-size: 16px; line-height: 1;
    box-shadow: 0 3px 10px rgba(0, 0, 0, .5);
    opacity: .45; transition: opacity .15s, color .15s, border-color .15s;
  }
  .spine-wrap:hover .spine-more,
  .spine-wrap:focus-within .spine-more { opacity: 1; }
  .spine-more:hover, .spine-more:focus-visible { color: var(--amber); border-color: var(--amber); outline: none; }
}

/* 빈 책장 소개(T-07) — 책이 한 권이라도 있으면 렌더하지 않는다 */
.shelf-empty {
  text-align: center; color: var(--paper-dim); font-size: var(--fs-label);
  padding: 4px 0 30px;
}
.shelf-intro-title {
  margin: 0 0 14px; font-family: var(--serif); font-weight: var(--fw-title);
  font-size: 20px; line-height: 1.4; color: var(--paper); letter-spacing: .5px;
}
/* 빈 책장 소개 3단계 — 단계마다 작은 움직이는 그림(인라인 SVG + CSS 애니메이션 · 의존성 0).
   한 주기(--cycle)를 셋으로 나눠 ①→②→③ 순서로 차례차례 다시 조립 — 지금 재생 중인 단계의 타일 테두리가 호박빛으로 켜진다.
   애니메이션을 끄면(prefers-reduced-motion) 각 그림의 기본값 = 완성 장면(책이 꽂힘 · 펼친 책+소리 · 번역본이 내려받기함 위)이 정지 표시된다.
   ★ ③ 그림은 '번역 → 내려받기'에서 끝난다 — 번역본이 책장으로 저절로 들어가는 듯한 장면 금지(루트 원칙 #1). */
.shelf-steps {
  list-style: none; counter-reset: step;
  margin: 0 auto 22px; padding: 0; max-width: 340px;
  text-align: left; line-height: 1.6;
  --cycle: 9s;
}
.shelf-steps li {
  counter-increment: step;
  position: relative;
  display: flex; gap: 14px; align-items: center;
  padding: 7px 0;
  animation: step-in .5s cubic-bezier(.2, .7, .3, 1) both;
}
.shelf-steps li:nth-child(1) { --d: 0s; }
.shelf-steps li:nth-child(2) { --d: calc(var(--cycle) / 3); animation-delay: .12s; }
.shelf-steps li:nth-child(3) { --d: calc(var(--cycle) / 3 * 2); animation-delay: .24s; }
/* 번호 = 그림 타일 왼쪽 위 배지(목록 순서는 <ol> 그대로) */
.shelf-steps li::before {
  content: counter(step);
  position: absolute; left: -7px; top: 1px; z-index: 1;
  width: 21px; height: 21px;
  display: grid; place-items: center;
  border: 1px solid var(--amber); border-radius: 50%;
  background: var(--bg);
  font-size: var(--fs-caption); line-height: 1; color: var(--amber);
}
.step-art {
  flex: 0 0 56px; width: 56px; height: 56px;
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--raised);
  overflow: visible;
  animation: step-focus var(--cycle) var(--d) ease-in-out infinite both;
}
.step-art * { transform-box: fill-box; }
.step-art .a-stage { animation: art-stage var(--cycle) var(--d) linear infinite both; }

/* 공통 색 */
.step-art .ink { fill: var(--paper); }
.step-art .ink-dim { fill: var(--line); stroke: var(--faint); stroke-width: .8; }
.step-art .on-ink { fill: var(--bg); }
.step-art .hair { fill: none; stroke: var(--faint); stroke-width: 1.4; stroke-linecap: round; }
.step-art .glow { fill: none; stroke: var(--amber); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.step-art .amber { fill: var(--amber); }
.step-art .glyph { font-family: var(--sans); font-weight: 700; text-anchor: middle; }

/* ① txt 파일이 내려와 책장 빈칸에 호박빛 책으로 꽂힌다 */
.art-shelf .a-doc { opacity: 0; transform-origin: center bottom; animation: shelf-doc var(--cycle) var(--d) ease-in infinite both; }
.art-shelf .a-new { transform-origin: center bottom; animation: shelf-new var(--cycle) var(--d) ease-out infinite both; }

/* ② 책이 펼쳐지고, 읽는 줄 하이라이트가 내려가며 소리가 퍼진다(듣기 모드의 문장 강조와 같은 그림) */
.art-read .a-pl { transform-origin: right center; animation: read-page var(--cycle) var(--d) ease-out infinite both; }
.art-read .a-pr { transform-origin: left center; animation: read-page var(--cycle) var(--d) ease-out infinite both; }
.art-read .a-hl { animation: read-hl var(--cycle) var(--d) steps(1, end) infinite both; }
.art-read .a-w1 { animation: read-wave var(--cycle) var(--d) ease-out infinite both; }
.art-read .a-w2 { animation: read-wave2 var(--cycle) var(--d) ease-out infinite both; }

/* ③ 원문(中)이 뒤집혀 번역본(가)이 되고, 화살표가 내려받기함으로 떨어진다 — 여기서 끝(책장 연결 없음) */
.art-trans .a-src { opacity: 0; transform: scaleX(0); transform-origin: center; animation: trans-src var(--cycle) var(--d) ease-in infinite both; }
.art-trans .a-dst { transform-origin: center; animation: trans-dst var(--cycle) var(--d) ease-out infinite both; }
.art-trans .a-arrow { animation: trans-arrow var(--cycle) var(--d) cubic-bezier(.3, 0, .4, 1.4) infinite both; }
.art-trans .a-tray { animation: trans-tray var(--cycle) var(--d) ease-out infinite both; }

@keyframes step-in { from { opacity: 0; transform: translateY(8px); } }
@keyframes step-focus {
  0%, 36%, 100% { border-color: var(--line); box-shadow: none; }
  6%, 32% { border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-dim); }
}
/* 주기 = 0%(완성 장면) → 살짝 사라짐(3~6%) → 그 사이 처음 장면으로 바꿔(4%) → 다시 떠올라 조립(9~38%) → 완성 장면으로 대기.
   0%가 완성 장면이라 차례를 기다리는 단계(지연 중)도 빈칸이 아니라 완성 그림을 보여 준다. */
@keyframes art-stage { 0% { opacity: 1; } 3%, 6% { opacity: 0; } 9%, 100% { opacity: 1; } }

@keyframes shelf-doc {
  0%, 3.9% { opacity: 0; transform: translateY(19px) scale(.5, .7); }
  4%, 7% { opacity: 0; transform: translateY(-4px); }
  12%, 17% { opacity: 1; transform: translateY(0); }
  24% { opacity: 1; transform: translateY(17px) scale(.62, .8); }
  26.5%, 100% { opacity: 0; transform: translateY(19px) scale(.5, .7); }
}
@keyframes shelf-new {
  0%, 3.9% { opacity: 1; transform: scaleY(1); }
  4%, 24% { opacity: 0; transform: scaleY(.3); }
  28% { opacity: 1; transform: scaleY(1.08); }
  31.5%, 100% { opacity: 1; transform: scaleY(1); }
}

@keyframes read-page {
  0%, 3.9% { transform: scaleX(1); }
  4%, 11% { transform: scaleX(.08); }
  19%, 100% { transform: scaleX(1); }
}
@keyframes read-hl {
  0%, 3.9% { opacity: 1; transform: translateY(12px); }
  4%, 19% { opacity: 0; transform: translateY(0); }
  19.5% { opacity: 1; transform: translateY(0); }
  24% { opacity: 1; transform: translateY(6px); }
  28.5%, 100% { opacity: 1; transform: translateY(12px); }
}
@keyframes read-wave {
  0%, 3.9% { opacity: 1; transform: translateY(0); }
  4%, 20% { opacity: 0; transform: translateY(3px); }
  24% { opacity: 1; transform: translateY(0); }
  27.5% { opacity: .45; }
  31% { opacity: 1; }
  34.5% { opacity: .45; }
  38%, 100% { opacity: 1; transform: translateY(0); }
}
@keyframes read-wave2 {
  0%, 3.9% { opacity: 1; transform: translateY(0); }
  4%, 22.5% { opacity: 0; transform: translateY(3px); }
  26.5% { opacity: 1; transform: translateY(0); }
  30% { opacity: .45; }
  33.5% { opacity: 1; }
  37% { opacity: .45; }
  40.5%, 100% { opacity: 1; transform: translateY(0); }
}

@keyframes trans-src {
  0%, 3.9% { opacity: 0; transform: scaleX(0); }
  4%, 14.5% { opacity: 1; transform: scaleX(1); }
  19% { opacity: 1; transform: scaleX(0); }
  19.5%, 100% { opacity: 0; transform: scaleX(0); }
}
@keyframes trans-dst {
  0%, 3.9% { transform: scaleX(1); }
  4%, 19% { transform: scaleX(0); }
  23.5%, 100% { transform: scaleX(1); }
}
@keyframes trans-arrow {
  0%, 3.9% { opacity: 1; transform: translateY(0); }
  4%, 24% { opacity: 0; transform: translateY(-7px); }
  29%, 100% { opacity: 1; transform: translateY(0); }
}
@keyframes trans-tray {
  0%, 3.9% { stroke: var(--amber); transform: translateY(0); }
  4%, 28% { stroke: var(--faint); transform: translateY(0); }
  30% { stroke: var(--amber); transform: translateY(1.5px); }
  33.5%, 100% { stroke: var(--amber); transform: translateY(0); }
}

/* 시트(번역·공지 등)가 책장을 덮는 동안엔 멈춘다 — 안 보이는 그림에 배터리를 쓰지 않는다.
   (탭이 백그라운드·화면 끔이면 브라우저가 렌더를 멈추고, 책이 생기면 소개 블록 자체가 사라진다) */
body:has(.sheet:not([hidden])) .shelf-steps li,
body:has(.sheet:not([hidden])) .shelf-steps .step-art,
body:has(.sheet:not([hidden])) .shelf-steps .step-art * { animation-play-state: paused; }

/* 움직임 줄이기 = 전부 정지 — 각 그림의 기본값(완성 장면)이 보인다 */
@media (prefers-reduced-motion: reduce) {
  .shelf-steps li, .shelf-steps .step-art, .shelf-steps .step-art * { animation: none; }
}
.shelf-empty-actions {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.shelf-drop-note { margin: 14px 0 0; font-size: var(--fs-caption); color: var(--faint); }
/* 빈 책장의 1차 CTA(P1-4) — 번역으로 안내. 호박빛 채움으로 위계를 준다(.toc-go 와 같은 톤).
   .btn-primary = 같은 룩의 범용 1차 버튼(번역 시작 등 · UX T-11). */
.shelf-empty-cta, .btn-primary {
  border: none; border-radius: 999px;
  padding: 12px 22px;
  font-family: inherit; font-size: var(--fs-label); font-weight: var(--fw-strong);
  background: radial-gradient(120% 120% at 30% 25%, var(--amber-soft), var(--amber) 58%, #c9882e);
  color: #241a07;
  box-shadow: 0 6px 18px rgba(232, 169, 72, .25);
  transition: transform .12s ease;
}
.shelf-empty-cta:active, .btn-primary:active { transform: scale(.97); }
/* 빈 책장 소개의 1차 버튼 = 책 추가(T-07) — 크게 · 번역하러 가기는 보조(.ghost-btn) */
.shelf-empty .shelf-empty-cta {
  min-width: 200px; min-height: 48px; padding: 12px 28px;
  font-size: var(--fs-body); font-weight: var(--fw-title);
}

/* 드래그 앤 드롭 등록 중 표시(데스크톱) — 화면 전체 점선 테두리. 드롭을 가로채지 않게 pointer-events 없음 */
#shelf-screen.drop-on::after {
  content: "여기에 놓으면 책장에 꽂혀요";
  position: fixed; inset: 12px; z-index: 25;
  display: grid; place-items: center;
  border: 2px dashed var(--amber); border-radius: 18px;
  background: rgba(14, 17, 22, .78);
  font-size: var(--fs-body); font-weight: var(--fw-strong); color: var(--amber-soft);
  pointer-events: none;
}

/* 책 메뉴(데스크톱 ⋯·우클릭) — .sheet를 재사용(ESC·포커스 공통 동작)하되 스크림 없이 커서 옆 작은 팝오버.
   id 특이도로 데스크톱 가운데 모달 규칙(@media .sheet/.sheet-panel)을 이긴다. 위치는 app.js(openBookMenu)가 지정. */
#book-menu { display: block; background: transparent; padding: 0; animation: none; }
#book-menu .sheet-panel {
  position: absolute; width: auto; min-width: 176px; max-width: none; max-height: none; margin: 0;
  padding: 6px; gap: 2px;
  border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .55);
  animation: fade .12s ease;
}
#book-menu .menu-item { padding: 10px 12px; }
#book-menu .menu-item:hover, #book-menu .menu-item:focus-visible { background: var(--amber-dim); outline: none; }
#book-menu .menu-item.danger { color: #ec8f88; }
#book-menu .menu-item.danger .menu-ic { color: inherit; }

/* ───────── 플레이어 화면 ───────── */

#player-screen {
  position: fixed; inset: 0; z-index: 10;
  display: flex; flex-direction: column;
  max-width: var(--reader-maxw); margin: 0 auto; /* 본문 폭 옵션 — 상단 바·본문·쪽번호가 함께 넓어진다 */
  padding: calc(12px + var(--safe-top)) 18px calc(14px + var(--safe-bottom));
  background:
    radial-gradient(130% 46% at 50% -6%, #232c3e 0%, transparent 62%),
    linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 50%);
  animation: slide-up .32s cubic-bezier(.2, .7, .3, 1);
}
@keyframes slide-up {
  from { opacity: 0; transform: translateY(26px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* 초슬림 한 줄 바 — 본문을 가리지 않게 작고 흐릿한 "투명 아이콘"들 */
.player-head {
  display: flex; align-items: center; gap: 6px;
  min-height: 34px;
  margin-bottom: 4px;
}
.player-head .ghost-btn {
  border-color: transparent;
  padding: 5px 9px; font-size: var(--fs-label);
  min-height: 0; /* 상단 바 초슬림 불변식 — 44px 해제, 대신 ::before 가 세로 탭영역만 44로 넓힌다(아래) */
}
.head-actions { display: flex; gap: 8px; }
.player-head .head-actions { gap: 2px; }

/* 듣기 본문 카드(UX T-17) — 카드 배경은 #r-view가 아니라 이 상자가 칠한다: 음수 마진 + 같은 크기 패딩으로 화면 여백
   쪽으로 10px(아래 8px) 넓혀 칠하되 콘텐츠 상자(= #r-view 폭·높이·위치)는 그대로다. #r-view 좌우 padding 0을 유지한 채
   글자가 카드 가장자리에 붙지 않는다(★5 — #r-view 박스 치수 불변 · 두 모드 모두 같은 마진/패딩이라 기하 동일).
   카드는 쪽번호(#p-counter)까지 감싼다(책 한 장의 쪽번호). 읽기 모드는 ::before가 화면 전체를 칠하므로 배경만 끈다. */
.player-body {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  margin: 0 -10px -8px; padding: 0 10px 8px;
  border-radius: 12px;
  background: var(--reader-bg); /* 듣기: 어두운 화면 위 카드 — 밝은 프리셋(종이·세피아)의 어두운 글자도 읽힌다 */
}
#player-screen.mode-read .player-body { background: none; }

/* 면주(상단 제목) — 작고 흐릿하게, 길면 말줄임 */
#p-title {
  flex: 1; min-width: 0; margin: 0;
  text-align: center;
  font-family: var(--serif); font-weight: var(--fw-title); font-size: var(--fs-label);
  letter-spacing: .5px;
  color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 쪽번호 풍 진행률 — 본문 아래 중앙 */
#p-counter {
  margin: 8px 0 2px; text-align: center;
  font-size: var(--fs-caption); color: var(--faint); letter-spacing: 1px;
}
/* 쪽번호에 회차 라벨(«제1200화 · 1234 / 5678 페이지 · 99%»)이 붙어 좁은 폰(360px)에서 두 줄로 접히거나 넘치지 않게 —
   한 줄 고정·넘치면 말줄임(본문 박스 높이 불변 = 두 모드 페이지 경계 불변 ★5). */
#p-counter { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 지금 읽는 페이지 — 글꼴·크기·색은 --reader-* 변수(보기 설정)를 따른다 */
.page {
  flex: 1; min-height: 0; overflow-y: auto;
  background: var(--reader-bg);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 22px 20px 26px;
  font-family: var(--reader-font);
  font-size: var(--reader-size); line-height: var(--lh-read); letter-spacing: .2px;
  color: var(--reader-fg);
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
  position: relative;
}

/* ───────── 본문 뷰 (읽기·듣기 공통) ─────────
   #r-view(.page)에 글을 흘려 한 화면 = 한 페이지. 두 모드 기하(w·h)는 픽셀 단위로 같다(사용자 결정
   2026-06-14) — 같은 본문 박스에 그려 페이지 경계가 같은 위치다. 차이는 오디오·배경·컨트롤뿐.
   ★ Phase 2(읽기 페이지네이션 재설계) 이후 렌더 "메커니즘"은 모드 분기다(app.js R.slice):
     · 읽기 = 단일 경계 배열(R.breaks) 소비 — 페이지마다 .r-page 박스(컬럼 미사용·아래 .r-page 규칙).
     · 듣기 = 기존 다단(column) 레이아웃 — 오디오 추종(.reading·paraTimes)이 컬럼 모델에 결합(이식 = Phase 4).
   두 메커니즘은 같은 줄바꿈(폭 의존)이라 픽셀은 동일하나, 총 페이지수는 측정계가 달라(breaks vs buildReadMap)
   폭·내용 경계 케이스에서 ±1 발산 가능 — 모드 전환 시 번호/총수가 ±1 흔들릴 수 있다(Phase 4가 듣기도
   breaks로 통일하면 소멸 · 미배포 전환 상태). 하단 컨트롤은 흐름에서 뺀 오버레이라(.player-dock) 듣기도
   읽기와 같은 전체화면이고, mode-read는 색/배경만 바꾼다.
   ※ mode-read에서 #r-view의 박스 치수(padding·border·margin)는 절대 건드리지 말 것 — 건드리면 두 모드
     기하가 어긋난다. 크기는 app.js가 인라인으로 설정하고, transform으로 화면을 넘긴다. */

#r-view {
  display: block;            /* 두 모드 공통 — 유일한 본문 뷰 */
  overflow: hidden;          /* .page의 overflow-y:auto 대체 — 컬럼이 옆으로 흐른다 */
  touch-action: pan-y;       /* 가로 제스처는 JS가 처리 */
  user-select: none;         /* 길게 눌러 텍스트 선택이 스와이프를 가로채지 않게 */
  -webkit-user-select: none;
  background: none;          /* 듣기 카드 배경은 .player-body가 칠한다(여백 바깥까지 — 위 .player-body 주석) ·
                                읽기 모드는 #player-screen::before가 화면 전체를 칠한다. .page의 배경을 여기서 끈다. */
  border: none; border-radius: 0;
  padding: 10px 0 6px;       /* 좌우 0 — 화면 padding 18px가 곧 책 여백. 두 모드 동일 기하 */
}

/* 읽기 모드 = 화면 전체가 책 한 장 — 배경·상단 바·쪽번호 "색"만 --reader-* 를 따른다(치수 불변).
   배경은 기존 다크 그라데이션을 "교체"하지 않고 그 위에 한 겹 얹는다(::before, z -1) —
   기본 프리셋의 --reader-bg는 거의 투명이라 교체하면 뒤의 책장 화면이 비쳐 보인다. */
#player-screen.mode-read::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--reader-bg);
}
#player-screen.mode-read .player-head .ghost-btn,
#player-screen.mode-read #p-title { color: var(--reader-fg-dim); }
/* 쪽번호는 두 모드 모두 책 바탕 위(읽기 = 화면 전체 · 듣기 = .player-body 카드)라 보기 설정 보조색을 따른다(색만 · 치수 불변) */
#p-counter { color: var(--reader-fg-dim); }

#r-flow {
  position: relative;        /* 문단 offsetLeft의 기준점 */
  column-fill: auto;         /* 필수 — 기본 balance면 마지막 화면들이 반쯤 빈다 */
  will-change: transform;
}
#r-flow.anim { transition: transform .26s cubic-bezier(.2, .7, .3, 1); }

/* Phase 2 슬라이스 페이지 박스(읽기 모드) — #r-flow에 페이지마다 한 박스를 left=local*stride로 깔고
   (컬럼 미사용) translateX로 넘긴다. overflow:hidden = 마지막 문단 trailing margin 클리핑(P0-S 제약 ① ·
   빈 여백이라 본문 손실 없음). 박스 안 <p>는 #r-flow p(아래 margin 1.1em·orphans/widows 1)를 후손
   셀렉터로 물려받아 측정(#r-measure-breaks)과 줄바꿈이 일치한다. 잎 복제본(.leaf-flow .r-page)도 동일. */
.r-page { position: absolute; top: 0; overflow: hidden; }

/* 콜드 스타트(앱 시작 후 첫 이어듣기/보기) 복원 중 본문 churn을 가린다. 글자 오프셋 앵커가
   시작점은 이미 보존하므로(§app.js readTopLineAnchor) 이건 폰트 늦은 로드(FOUT)·초기 빌드 같은
   잔여 깜빡임만 덮고, 안정되면(빌드+폰트 준비) reveal한다. 웜 진입(폰트 이미 로드)엔 미적용 —
   showReaderLoading이 document.fonts.status로 가른다. #r-flow는 visibility로만 숨겨(레이아웃 유지)
   그 아래에서 측정·정렬이 정상 동작하고, 오버레이는 absolute라 본문 기하(w·h)를 안 건드린다. */
#player-screen.reader-loading #r-flow { visibility: hidden; }
#r-loading {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;                /* 제스처는 통과 — 평소엔 투명 스피너만 있는 빈 레이어 */
}
.r-spin {
  width: 26px; height: 26px; border-radius: 50%;
  border: 2.5px solid var(--line); border-top-color: var(--faint);
  opacity: 0; transition: opacity .25s ease .3s;  /* 0.3초 넘게 걸릴 때만 보인다(빠른 콜드 로드엔 빈 페이지만) */
  animation: r-spin .8s linear infinite;
}
#player-screen.reader-loading .r-spin { opacity: .55; }
@keyframes r-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .r-spin { animation: none; } }
/* #r-measure-breaks = 단일 경계 배열(근본 재설계) 측정용 오프스크린 박스(app.js). #r-flow p와 같은
   문단 규칙을 입어야 측정이 본 렌더와 일치한다 — 규칙을 바꾸면 둘에 함께 적용된다. */
#r-flow p, #r-measure-breaks p {
  margin: 0 0 1.1em; padding: 0; border-radius: 0;
  color: var(--reader-fg);   /* 읽기 모드엔 하이라이트가 없으니 전부 본문색 */
  orphans: 1; widows: 1;     /* 기본값 2면 컬럼 바닥의 외톨이 줄을 통째로 넘겨 화면 아래가 2줄+ 빈다 */
}
/* ★어절 보존 줄바꿈(T-02) — 브라우저 기본(normal)은 한글을 글자 단위로 끊어 "하늘을"이 줄 끝·페이지 경계에서
   "하"/"늘을"로 쪼개진다. keep-all = 공백에서만 줄바꿈(어절 보존) · overflow-wrap = 공백 없는 긴 토큰(URL·장음
   의성어)이 고정 폭 페이지 박스를 넘치지 않게 글자 단위 비상 줄바꿈. 표시(#r-flow)·측정(#r-measure-breaks — app.js
   breaksMeasureBox가 #r-flow 계산값을 longhand로 복사하기도 한다)·잎 복제본(.leaf-flow — #r-flow 후손이 아니라
   상속 없음)이 **정확히 같은 규칙**이어야 경계 배열과 픽셀이 일치한다. 규칙을 바꾸면 모든 캐시된 경계 배열이
   무효 → app.js READ_BREAKS_CACHE_KEY(read-breaks-cache-vN)를 올릴 것(--lh-read와 같은 박제 규율). */
#r-flow, #r-measure-breaks, .leaf-flow { word-break: keep-all; overflow-wrap: break-word; }
/* 듣기 모드: 지금 읽어주는 문단만 호박색으로 밝힌다(재생 위치 동기화). #r-measure-breaks(경계 측정)는
   .reading을 절대 안 달아 측정이 불변. 패딩/마진을 바꾸면 페이지 레이아웃이 흔들리므로 배경·색만
   바꾸고, 숨 쉴 여백은 box-shadow(레이아웃 영향 없음)로만 준다(id 특이도라 #r-flow p를 이긴다). */
#r-flow p.reading {
  background: var(--reader-hl);
  color: var(--reader-fg);
  box-shadow: 0 0 0 4px var(--reader-hl);
  border-radius: 3px;
  transition: background .3s ease, color .3s ease, box-shadow .3s ease;
}
/* 강제 컬럼 분리는 폐기 — 페이지는 책처럼 이어 붙는다(빈 꼬리 화면 불만, 2026-06-13 사용자
   결정). 재중심 픽셀 안정성은 app.js rebuildReadWindow의 스페이서 정렬이 담당한다. */

/* ── 책장 넘김(잎) — 종이 한 장이 책등(왼쪽 모서리)을 축으로 들려 넘어간다 ──
   .r-leaf 구조는 app.js(leafBegin)가 플립마다 만들어 #player-screen에 얹고 끝나면 지운다.
   #r-view 안이 아니라 화면 직계 자식 — 원근으로 부풀어도 본문 상자에 잘리지 않고,
   들리는 종이가 상단 바·쪽번호 위를 스치는 게 실제 책처럼 자연스럽다. */
.r-leaf {
  position: absolute;
  transform-origin: left center;     /* 책등 = 왼쪽 모서리 */
  will-change: transform;
  pointer-events: none;
  backface-visibility: hidden;       /* 90°를 넘긴 잎(다 넘어간 종이)은 화면 밖 — 그리지 않는다 */
  -webkit-backface-visibility: hidden;
}
.r-leaf .leaf-paper,
.r-leaf .leaf-tint,
.r-leaf .leaf-shade { position: absolute; inset: 0; }
/* 종이 바탕 — 화면 배경(어두운 그라데이션)을 잎 위치만큼 잘라 복제(app.js가 size/position 지정).
   잎이 0°일 때 뒤 화면과 픽셀이 일치해야 플립 시작이 번쩍이지 않는다. */
.r-leaf .leaf-paper { background-color: var(--bg); }
.r-leaf .leaf-tint  { background: var(--reader-bg); } /* 보기 설정 색 — 색이든 그라데이션이든 */
.r-leaf .leaf-shade {                /* 책등 쪽 굴곡 음영 — 들릴수록 진해진다(JS가 opacity) */
  opacity: 0;
  background: linear-gradient(to right, rgba(0, 0, 0, .32), rgba(0, 0, 0, 0) 32%);
}
/* 잎의 글 — .page를 그대로 입어 글꼴·행간이 본문과 동일(컬럼 분리도 동일하게 떨어진다),
   상자 속성만 무력화한다. 잎 자체가 이미 본문 콘텐츠 상자 위치에 놓여 있다. */
.r-leaf .leaf-clip.page {
  position: absolute; inset: 0; overflow: hidden;
  padding: 0; background: none; border: none; border-radius: 0;
  will-change: clip-path;            /* 넘김마다 글자가 우측→좌측 깎임(JS가 clipPath) */
}
.leaf-flow { position: relative; column-fill: auto; } /* #r-flow 복제본 — 동일 다단 규칙 + 슬라이스 박스(.r-page) 절대배치 기준점 */
.leaf-flow p { margin: 0 0 1.1em; padding: 0; color: var(--reader-fg); orphans: 1; widows: 1; }
/* 넘어가는 잎이 아래 페이지에 드리우는 그림자 — 중간쯤에서 가장 짙다(JS가 opacity) */
.r-undershade {
  position: absolute; opacity: 0; pointer-events: none;
  background: linear-gradient(to right, rgba(0, 0, 0, .42), rgba(0, 0, 0, 0) 36%);
}

/* ───────── 하단 조작부 (듣기 모드 — 탭하면 떠오르는 오버레이) ─────────
   본문 영역 기하를 두 모드에서 동일하게 유지하려고 흐름에서 빼 절대배치한다(읽기 모드는
   컨트롤이 없는 전체화면). 평소엔 숨고, 듣기 중 화면 가운데를 탭하면 #player-screen.dock-open이
   붙어 떠오른다(app.js — 잠시 뒤 자동으로 다시 숨는다). 읽기 모드에선 dock-open이 붙지 않는다. */
.player-dock {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 12;
  max-width: var(--reader-maxw); margin: 0 auto; /* 본문 폭을 따른다 — 넓은 본문 밑에 좁은 띠만 떠 있으면 어색하고, 슬라이더는 페이지 위치라 본문 폭이 자연스럽다 */
  padding: 12px 18px calc(10px + var(--safe-bottom));
  background: linear-gradient(180deg, rgba(18, 22, 30, .82), rgba(11, 14, 19, .97));
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border-top: 1px solid var(--line);
  border-radius: 16px 16px 0 0;
  transform: translateY(112%); opacity: 0;
  transition: transform .26s cubic-bezier(.2, .7, .3, 1), opacity .2s ease;
  pointer-events: none;      /* 숨김 중엔 본문 탭을 가리지 않게 */
}
#player-screen.dock-open .player-dock {
  transform: translateY(0); opacity: 1; pointer-events: auto;
}

.progress-row { padding: 2px 4px 10px; }

input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 22px; background: none;
}
input[type="range"]::-webkit-slider-runnable-track {
  height: 3px; border-radius: 2px;
  background: linear-gradient(90deg, var(--amber) var(--fill, 0%), var(--line) var(--fill, 0%));
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--amber-soft);
  border: 3px solid #3a2f17;
  margin-top: -6.5px;
  box-shadow: 0 0 8px rgba(232, 169, 72, .55);
}

.controls {
  display: flex; align-items: center; justify-content: space-between;
  padding: 2px 6px;
  /* 본문 폭(넓게·최대)에서 독이 넓어져도 버튼 줄은 보통 폭(560 − 독 좌우 padding 36 = 524)에 모은다 — space-between이
     1000px로 벌어지면 재생 버튼과 ◀▶가 멀리 흩어진다. 보통 폭·폰에선 원래 폭 그대로라 무변화. 슬라이더만 독 폭을 쓴다. */
  max-width: 524px; margin: 0 auto;
}

.ctl {
  width: 50px; height: 50px; border: none; border-radius: 50%;
  background: none; color: var(--paper-dim);
  display: grid; place-items: center;
  transition: color .15s, background .15s;
}
.ctl:active { color: var(--amber); background: var(--amber-dim); }
.ctl svg { width: 26px; height: 26px; }
/* 10초 앞/뒤 = 둥근 화살표 안 굵은 "10"(UX T-17 — 옛 7.4px 글자가 너무 작았다). 버튼(50px)·배치는 그대로, 아이콘만 키운다. */
#b-back10 svg, #b-fwd10 svg { width: 31px; height: 31px; }

.play-btn {
  width: 72px; height: 72px; border: none; border-radius: 50%;
  background: radial-gradient(120% 120% at 30% 25%, var(--amber-soft), var(--amber) 58%, #c9882e);
  color: #241a07;
  display: grid; place-items: center;
  box-shadow: 0 8px 22px rgba(232, 169, 72, .3);
  transition: transform .12s ease;
}
.play-btn:active { transform: scale(.94); }
.play-btn svg { width: 34px; height: 34px; }
.play-btn.playing { animation: breathe 2.4s ease-in-out infinite; }
@keyframes breathe {
  0%, 100% { box-shadow: 0 8px 20px rgba(232, 169, 72, .26); }
  50% { box-shadow: 0 8px 34px rgba(232, 169, 72, .5); }
}

.opts {
  display: flex; gap: 10px; justify-content: center;
  padding: 12px 0 2px;
}

.chip {
  appearance: none; -webkit-appearance: none;
  background: var(--raised); color: var(--paper-dim);
  border: 1px solid var(--line); border-radius: 999px;
  font-family: inherit; font-size: var(--fs-caption);
  padding: 8px 16px;
  text-align: center;
}
.chip:focus { outline: none; border-color: var(--amber); color: var(--paper); }

/* ───────── 시트 (탐색 / 확인) ───────── */

.sheet {
  position: fixed; inset: 0; z-index: 30;
  /* backdrop-filter(blur) 대신 약간 더 진한 단색 스크림 — 저사양 안드로이드 GPU 부담 제거(P2-10).
     듣기 dock 의 blur 는 불변식(듣기 컨트롤 미접촉)이라 그대로 둔다. */
  background: rgba(8, 10, 14, .72);
  display: flex; align-items: flex-end;
  animation: fade .2s ease;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.sheet-panel {
  width: 100%; max-width: 560px; margin: 0 auto;
  max-height: 76dvh;
  background: linear-gradient(180deg, var(--raised), var(--bg-2));
  border: 1px solid var(--line); border-bottom: none;
  border-radius: 18px 18px 0 0;
  padding: 14px 16px calc(18px + var(--safe-bottom));
  display: flex; flex-direction: column;
  animation: sheet-up .26s cubic-bezier(.2, .7, .3, 1);
}
@keyframes sheet-up {
  from { transform: translateY(40px); opacity: .4; }
  to   { transform: translateY(0); opacity: 1; }
}

/* 탐색 시트는 높이를 고정한다 — 미리보기 글 길이에 따라 패널이 늘었다 줄었다 하면
   페이지를 바꿀 때마다 ‹ ›·슬라이더가 위아래로 출렁이기 때문 */
#toc-sheet .sheet-panel { height: 76dvh; }

.sheet-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 2px 4px 12px;
  border-bottom: 1px solid var(--line);
}
/* 시트 머리 = 굵은 제목 + 작은 보조 설명 2단(T-10 — 모든 시트 통일). 문구가 길어도 닫기 버튼이 짓눌려
   줄바꿈되지 않게 — 제목 묶음은 줄바꿈, 버튼은 고정 폭. 제목은 h2지만 #p-title(플레이어 h2)과 섞이지 않게 클래스로만 꾸민다. */
.sheet-head { align-items: flex-start; }
.sheet-titles { flex: 1; min-width: 0; }
.sheet-title {
  margin: 0; font-size: var(--fs-body); font-weight: var(--fw-title); line-height: 1.4;
  color: var(--paper); letter-spacing: .2px;
}
.sheet-sub { margin: 3px 0 0; font-size: var(--fs-caption); line-height: 1.5; color: var(--faint); }
.sheet-head .ghost-btn { flex: 0 0 auto; white-space: nowrap; }
/* 열릴 때 포커스가 패널(role=dialog · tabindex=-1)로 간다 — 패널 자체엔 포커스 링을 그리지 않는다 */
.sheet-panel:focus { outline: none; }

/* 페이지 탐색: 슬라이더로 크게 훑고, ‹ › 와 번호 입력으로 미세 조정 */
.toc-nav {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 4px 4px;
  font-size: var(--fs-caption);
}
.toc-nav input {
  width: 84px; padding: 8px 10px;
  background: var(--raised); color: var(--paper);
  border: 1px solid var(--line); border-radius: 10px;
  font-family: inherit; font-size: var(--fs-label); text-align: center;
}
.toc-nav input:focus { outline: none; border-color: var(--amber); }
.toc-nav input::-webkit-inner-spin-button,
.toc-nav input::-webkit-outer-spin-button { -webkit-appearance: none; }
#toc-total { color: var(--faint); }
.toc-step { padding: 8px 13px; font-size: 16px; line-height: 1; }
#toc-cur { margin-left: auto; white-space: nowrap; }

/* 탐색 시트 탭(회차 / 페이지) — 번역 시트와 같은 .seg-tabs. 탭 패널은 패널 고정 높이의 나머지를 세로 flex로 채워
   페이지 탭의 미리보기(.toc-preview flex:1)·회차 탭의 목록(.toc-chap-list flex:1)이 종전처럼 늘어난다.
   [hidden]은 전역 !important 규칙이 이긴다. 회차 머리글이 없는 책은 탭바가 hidden이라 시트 모양이 종전과 같다. */
#toc-tabs { margin-top: 0; }
.toc-pane { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.toc-chap-nav { flex-wrap: wrap; row-gap: 6px; }
.toc-chap-label { color: var(--paper-dim); white-space: nowrap; }
.toc-chap-nav input { width: 92px; }
.toc-chap-range { flex: 1 1 100%; color: var(--faint); letter-spacing: .3px; }
/* 회차 목록 — 행 = 번호(흐린 고정폭) + 제목(한 줄 말줄임). 현재 회차는 호박빛 바탕·번호 강조. 1,000+행도 한 번에
   그리되(DocumentFragment) 목록 자체가 스크롤 상자라 패널은 안 늘어난다. offsetTop 기준점(position:relative)은 현재
   회차로 스크롤할 때 쓴다. */
.toc-chap-list {
  list-style: none; margin: 8px 0 0; padding: 0;
  flex: 1; min-height: 120px; overflow-y: auto; position: relative;
  border: 1px solid var(--line); border-radius: 12px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.toc-chap-row {
  display: flex; align-items: baseline; gap: 10px;
  width: 100%; padding: 11px 12px;
  background: none; border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  font-family: inherit; font-size: var(--fs-label); line-height: 1.4; color: var(--paper);
  text-align: left;
}
.toc-chap-list li:last-child .toc-chap-row { border-bottom: none; }
.toc-chap-row:active { background: var(--amber-dim); }
.toc-chap-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--amber); }
.toc-chap-n { flex: 0 0 auto; color: var(--faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
.toc-chap-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--paper-dim); }
.toc-chap-row.on { background: var(--amber-dim); }
.toc-chap-row.on .toc-chap-n { color: var(--amber); font-weight: var(--fw-strong); }
.toc-chap-row.on .toc-chap-t { color: var(--paper); }

/* 본문 문자열 검색 — 회차("236화")로 페이지를 찾는다 */
.toc-search {
  display: flex; gap: 8px;
  padding: 8px 4px 0;
}
.toc-search input {
  flex: 1; min-width: 0; padding: 8px 12px;
  background: var(--raised); color: var(--paper);
  border: 1px solid var(--line); border-radius: 10px;
  font-family: inherit; font-size: var(--fs-label);
}
.toc-search input:focus { outline: none; border-color: var(--amber); }
.toc-search .ghost-btn { flex: 0 0 auto; white-space: nowrap; }
.toc-preview mark {
  background: var(--amber); color: #241a07;
  border-radius: 4px; padding: 0 2px;
}

.toc-slider-row { padding: 8px 4px 0; }

.toc-pct {
  margin: 2px 8px 8px; text-align: right;
  font-size: var(--fs-caption); color: var(--faint); letter-spacing: .5px;
}

/* 선택한 페이지의 전문 미리보기 — 패널 고정 높이의 나머지를 전부 차지 */
.toc-preview {
  flex: 1; min-height: 120px;
  overflow-y: auto;
  background: linear-gradient(165deg, rgba(236, 228, 212, .05), rgba(236, 228, 212, .02));
  border: 1px solid var(--line); border-radius: 12px;
  padding: 14px;
  font-family: var(--serif); font-size: 15.5px; line-height: 1.9;
  color: var(--paper-dim);
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.toc-preview p { margin: 0 0 1em; }
.toc-preview p:last-child { margin-bottom: 0; }

.toc-go {
  margin-top: 12px; width: 100%;
  border: none; border-radius: 12px;
  padding: 14px; font-size: var(--fs-body); font-weight: var(--fw-title);
  font-family: inherit;
  background: radial-gradient(120% 120% at 30% 25%, var(--amber-soft), var(--amber) 58%, #c9882e);
  color: #241a07;
  box-shadow: 0 6px 18px rgba(232, 169, 72, .25);
  transition: transform .12s ease;
}
.toc-go:active { transform: scale(.98); }

/* 확인 시트는 다른 시트(TTS 설정 등) 위에서도 떠야 한다 — DOM 순서상 앞이라 z로 끌어올림.
   무료 음성 첫 재생 안내(#model-sheet)도 같다 — 설정 시트를 연 채 목소리를 바꾸면 그 아래에 깔려 안 보인다. */
#confirm-sheet, #model-sheet { z-index: 35; }
.confirm-panel { gap: 16px; }
.confirm-panel p { margin: 8px 4px 0; font-size: var(--fs-body); line-height: 1.7; }
/* 확인창·첫 재생 안내 머리(UX T-23) — 시트 공통 굵은 제목(.sheet-title) + 아래 본문 문구 2단. 패널 gap(16px) 대신
   제목↔본문은 붙여(6px) 한 덩어리로 읽히게 한다. */
.confirm-panel .sheet-title { padding: 4px 4px 0; }
.confirm-panel .sheet-title + p { margin-top: -10px; color: var(--paper-dim); }
.confirm-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* 무료 음성 첫 재생 안내 — 확인 시트 패널을 그대로 쓰고, 긍정 버튼만 삭제(붉은 .danger-btn)와 구분되는 호박색.
   문구는 줄바꿈(\n)으로 두 문장을 나눠 싣는다 · 진행률 막대는 설정 시트와 같은 .usage-bar. */
#model-msg { white-space: pre-line; }
#model-sheet .usage-bar { margin: 0 4px; }
.go-btn {
  background: var(--amber); border: none; border-radius: 999px;
  padding: 10px 22px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; font-size: var(--fs-label); font-weight: var(--fw-strong); color: #241a07;
}
.go-btn:active { background: var(--amber-soft); }

/* ───────── 플레이어 메뉴(☰) — .sheet를 재사용하되 패널만 우측 사이드바 ───────── */

#menu-sheet { align-items: stretch; justify-content: flex-end; }
#menu-sheet .sheet-panel {
  width: min(62vw, 230px);
  max-height: none; margin: 0;
  border: none; border-left: 1px solid var(--line);
  border-radius: 0;
  padding: calc(24px + var(--safe-top)) 12px calc(20px + var(--safe-bottom));
  gap: 4px; justify-content: flex-start;
  animation: menu-in .26s cubic-bezier(.2, .7, .3, 1);
}
@keyframes menu-in {
  from { transform: translateX(48px); opacity: .4; }
  to   { transform: none; opacity: 1; }
}
.menu-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 14px 12px;
  background: none; border: none; border-radius: 12px;
  font-size: var(--fs-label); color: var(--paper);
  text-align: left;
  position: relative; /* .alert 배지 점의 기준 */
  transition: color .15s, background .15s;
}
.menu-item:active { color: var(--amber); background: var(--amber-dim); }
.menu-ic { flex: 0 0 24px; text-align: center; font-size: 17px; color: var(--amber); }
.menu-item.alert::after {
  content: ""; position: absolute; top: 10px; right: 8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--stamp);
  box-shadow: 0 0 5px rgba(192, 80, 74, .8);
}

/* ───────── 공지 시트 (게시판형 — 제목+날짜 목록 · 제목 탭 = 바로 아래 본문 펼침/닫힘) ───────── */
/* 펼칠 수 있음 표시 = 우측 셰브론(›)이 펼치면 아래로 90° 회전 + 펼친 행 배경·제목 amber. 행 전체가 버튼(탭 ≥44px). */
/* 펼침·접힘 = 높이 전환 애니메이션(grid-template-rows 0fr↔1fr — JS 높이 측정 없이 내용 높이로 부드럽게) · reduced-motion이면 즉시. */
.notice-list { list-style: none; margin: 0; padding: 0; }
.notice-list.in { animation: fade .25s ease; }       /* 목록을 그릴 때마다 페이드인(renderNotices가 클래스를 재시작) */
.notice-item { border-bottom: 1px solid var(--line); transition: background .25s ease; }
.notice-toggle {
  width: 100%; min-height: 48px;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 8px;
  background: none; border: 0; color: var(--paper);
  font-family: inherit; text-align: left; cursor: pointer;
}
.notice-title { flex: 1; min-width: 0; font-size: var(--fs-label); font-weight: var(--fw-strong); line-height: 1.5; }
.notice-date { flex: 0 0 auto; font-size: var(--fs-caption); color: var(--faint); white-space: nowrap; }
.notice-chev {
  flex: 0 0 auto; width: 16px; text-align: center;
  font-size: 18px; line-height: 1; color: var(--faint);
  transition: transform .2s ease, color .2s ease;
}
.notice-toggle:active { background: var(--amber-dim); }
.notice-item.open { background: rgba(232, 169, 72, 0.06); }
.notice-item.open .notice-title { color: var(--amber); }
.notice-item.open .notice-chev { transform: rotate(90deg); color: var(--amber); }
.notice-body {
  display: grid; grid-template-rows: 0fr;
  visibility: hidden;                                  /* 접힌 본문은 보조기기·검색에서도 제외(hidden 대체) */
  transition: grid-template-rows .3s cubic-bezier(.2, .7, .3, 1), visibility 0s linear .3s;
}
.notice-item.open .notice-body {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows .3s cubic-bezier(.2, .7, .3, 1), visibility 0s;
}
.notice-body-in { min-height: 0; overflow: hidden; } /* 0fr까지 접히려면 grid 아이템이 min-height 0 + overflow hidden + 여백 0(여백은 최소 높이로 남는다) */
.notice-body-pad {
  padding: 2px 12px 14px 14px;
  font-size: var(--fs-label); line-height: 1.7; color: var(--paper-dim);
}
.notice-body p { margin: 0 0 6px; }
.notice-body .notice-sub { margin: 12px 0 4px; font-weight: var(--fw-strong); color: var(--paper); }
.notice-body .notice-sub:first-child { margin-top: 2px; }
.notice-body ul { margin: 0 0 6px; padding-left: 18px; }
/* 앱 버전(옛 헤더 배지) — 공지 시트 맨 아래 작은 글씨(UX T-17 · 운영자·Aside가 라이브 빌드 확인에 읽는다) */
.notice-foot { margin: 18px 0 0; text-align: right; opacity: .7; }

/* ───────── 설정 시트 공통 (보기 설정 / TTS 설정) ───────── */

.sheet-body {
  overflow-y: auto; min-height: 0;
  padding: 12px 4px 2px;
  scrollbar-width: thin; scrollbar-color: var(--line) transparent;
}
.set-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; }
.set-row input[type="range"] { flex: 1; min-width: 0; }
.set-label { flex: 0 0 68px; font-size: var(--fs-label); font-weight: var(--fw-strong); color: var(--paper-dim); }
.set-val { flex: 0 0 38px; text-align: right; font-size: var(--fs-caption); color: var(--amber); }
.size-step {
  flex: 0 0 30px; height: 30px;
  border: 1px solid var(--line); border-radius: 8px;
  background: none; color: var(--paper-dim);
  font-size: 18px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.size-step:disabled { opacity: .3; }
.size-step:not(:disabled):active { color: var(--amber); border-color: var(--amber); background: var(--amber-dim); }
/* 본문 폭(개선요청 #7) — 보통·넓게·최대 3등분 선택지(엔진 선택과 같은 .seg + aria-pressed 결). 리더 화면 폭(보통 560px)
   보다 조금 넓은 창까지는 셋이 사실상 같은 화면이라(폰은 화면 폭이 상한) 눌러도 안 바뀌는 고장난 컨트롤로 보인다 →
   640px 이하에선 행을 숨긴다(저장값은 그대로 적용 — 이 폭에선 차이가 거의 없다). */
.look-width-seg { flex: 1; min-width: 0; }
.look-width-seg .ghost-btn { flex: 1 1 0; min-width: 0; padding: 7px 4px; white-space: nowrap; }
@media (max-width: 640px) {
  #look-width-row { display: none; }
}
.set-foot { display: flex; justify-content: space-between; gap: 10px; padding-top: 14px; }
.fine { margin: 4px 0 0; font-size: var(--fs-caption); line-height: 1.7; color: var(--faint); }
.fine a { color: var(--amber); text-decoration: none; }
.note-list { margin: 8px 0 0; padding-left: 1.1em; }
.note-list li { margin-top: 5px; }

/* 입력 필드 — 탐색 시트 입력과 같은 룩(시트 폼 공용) */
.field {
  flex: 1; min-width: 0;
  padding: 9px 12px;
  background: var(--raised); color: var(--paper);
  border: 1px solid var(--line); border-radius: 10px;
  font-family: inherit; font-size: var(--fs-label);
}
.field:focus { outline: none; border-color: var(--amber); }
select.field { appearance: auto; -webkit-appearance: auto; }
.field-suffix { flex: 0 0 auto; font-size: var(--fs-caption); color: var(--faint); }

/* 번역 범위 — 시작 ~ 끝 (숫자 전용 입력 2개, 대시는 정적 라벨). 칸 안내는 placeholder(예시 숫자)와
   aria-label(시작/끝 회차)만 — 칸 위 '시작화/끝화' 라벨은 과한 설명이라 제거(개선요청). */
.range-pick { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }
.range-field { flex: 1; min-width: 0; }
.range-input { width: 100%; text-align: center; }
.range-sep { flex: 0 0 auto; color: var(--faint); font-size: var(--fs-label); }
/* 번역 범위 행 — from~to 입력을 고정폭으로 좁히고(회차 숫자는 짧다) 같은 행 우측에 '예상 비용 계산'
   버튼을 밀어 둔다(개선요청 — 범위 입력 후 바로 누르는 동선). .range-pick은 행을 다 채우지 않게 content-sized. */
/* 입력칸 폭(UX T-03): 50px(내용 40px)이면 placeholder "예: 100"(14px ≈ 43px)이 "예: 10("처럼 잘렸다 →
   내용폭 ≥ 6ch(+여유)로 넓히고, 그 몫은 행 내부 간격·버튼 좌우 패딩에서 덜어 360px 폭에서도 한 줄을 유지한다.
   (72px 두 칸은 360px 한 줄 예산에 안 들어가 미채택.) 폰 폰트 폭 차이로 넘치면 버튼만 다음 줄 오른쪽으로
   내려가게 wrap 안전망을 둔다(입력칸이 찌그러지거나 가로 스크롤이 생기지 않게). */
.trans-range-row { flex-wrap: wrap; column-gap: 8px; row-gap: 6px; }
.trans-range-row .range-pick { flex: 0 0 auto; gap: 4px; }
.trans-range-row .range-field { flex: 0 0 auto; }
.trans-range-row .range-input { width: calc(6ch + 10px); padding-left: 3px; padding-right: 3px; }
.trans-est-btn { margin-left: auto; flex: 0 0 auto; white-space: nowrap; padding-left: 8px; padding-right: 8px; font-size: var(--fs-caption); }

/* 보기 설정: 미리보기 + 색 스와치 */
.look-sample {
  background: var(--reader-bg);
  color: var(--reader-fg-dim);
  font-family: var(--reader-font);
  font-size: min(var(--reader-size), 20px);
  line-height: 1.9; letter-spacing: .2px;
  border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; margin-bottom: 4px;
}
.look-sample p { margin: 0 0 6px; padding: 2px 8px; border-radius: 8px; }
.look-sample p:last-child { margin-bottom: 0; }
.look-sample p.reading { background: var(--reader-hl); color: var(--reader-fg); }

.swatches { display: flex; gap: 12px; }
.swatch {
  width: 34px; height: 34px; border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--sw-bg, #171c26); color: var(--sw-fg, #ece4d4);
  font-family: var(--serif); font-size: var(--fs-label); line-height: 1;
  display: grid; place-items: center; padding: 0;
  transition: border-color .15s, box-shadow .15s;
}
.swatch.on { border-color: var(--amber); box-shadow: 0 0 0 2px var(--amber-dim); }

.color-pick {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-caption); color: var(--paper-dim);
}
input[type="color"] {
  width: 34px; height: 34px; padding: 0;
  border: 2px solid var(--line); border-radius: 50%;
  background: none;
}
input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; }
input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; }
/* 커스텀 색 대비 부족 경고(P1-7) — .fine 위에 붉은 톤. app.js updateContrastWarn 이 hidden 토글. */
.look-warn { color: var(--stamp); }

/* TTS 설정 시트 */
.seg { display: flex; gap: 8px; }
/* 엔진 선택지(UX T-14) — 이름 + 작은 보조 줄(무료 / 내 키) 2단 · 남은 폭을 3등분해 360px에서도 한 줄 안에 든다 */
.eng-seg { flex: 1; min-width: 0; }
.eng-seg .ghost-btn {
  flex: 1 1 0; min-width: 0;
  flex-direction: column; gap: 1px;
  padding: 6px 4px; line-height: 1.2; white-space: nowrap;
  border-radius: 14px;
}
.eng-seg .ghost-btn small { font-size: var(--fs-caption); color: var(--faint); }
.eng-seg .ghost-btn.on small { color: inherit; opacity: .8; }
.eye-btn { flex: 0 0 auto; padding: 8px 11px; }
.tts-test { width: 100%; margin: 4px 0 2px; padding: 11px; font-size: var(--fs-label); }
/* 번역 시작 = 호박빛 채움 1차 버튼(.btn-primary · UX T-11). 터치 44px 하한은 ghost 알약과 같게. */
#trans-start { min-height: 44px; }
/* 무료 음성 파일 내려받기 = 같은 1차 버튼(UX T-14 — ghost 알약이라 눈에 안 띄던 문제). 다운로드 트리거는 그대로(★11). */
#local-dl { min-height: 44px; }
/* 진행 중엔 '번역 시작'이 '번역 중단'으로 — 채움을 걷고 붉은 외곽선으로 구분해 중복 요청을 막는다(개선요청 #1) */
#trans-start.trans-stop { background: none; box-shadow: none; color: var(--stamp); border: 1px solid var(--stamp); }
#trans-start.trans-stop:active { color: var(--stamp); border-color: var(--stamp); background: rgba(192, 80, 74, .14); }
/* 번역 범위 미인식(또는 파일/확인 전·키 0개) — 시작 버튼을 진짜 비활성으로(클릭 불가). 사유는 견적 영역·
   버튼 아래 사유 줄에 상시 노출되므로 .is-off(클릭+토스트) 대신 disabled가 맞다(개선요청 #1). */
#trans-start:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; transform: none; }
.trans-start-why { margin: 2px 0 0; text-align: center; }
/* 키 0개 안내 카드(번역 탭 상단) — 문구 + '키 등록하기'(설정 탭 전환 + 키 입력칸 포커스) */
.trans-keycard {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 2px 0 8px; padding: 10px 12px;
  border: 1px solid var(--amber); border-radius: 10px; background: var(--amber-dim);
}
.trans-keycard-text { flex: 1 1 160px; margin: 0; font-size: var(--fs-label); color: var(--paper); line-height: 1.5; }
.trans-keycard .ghost-btn { flex: 0 0 auto; white-space: nowrap; color: var(--amber); border-color: var(--amber); }
/* 원문 파일 보조 문구(형식·용량) — 행 라벨 폭(68px)+간격만큼 들여 버튼 아래에 맞춘다 */
.trans-file-hint { margin: -6px 0 0 78px; }
/* 설정 탭: 키 발급 안내 + 맨 아래 엔진 버전(내부 정보라 작고 흐리게) */
/* 새 키 입력 행 — 폰 폭(≤480px)에선 '새 키' 라벨을 숨겨 입력칸에 폭을 준다(UX T-28 — 375px에서 라벨·👁·추가와
   한 줄이라 칸이 ~130px, placeholder가 잘렸다). 바로 위 제목(Gemini API 키)과 입력칸 aria-label이 이름을 대신한다. */
.gkey-add-row .ghost-btn { flex: 0 0 auto; }
@media (max-width: 480px) {
  .gkey-add-row .set-label { display: none; }
}
.gkey-guide { margin: 4px 0 6px; }
.gkey-guide .fine { margin-top: 2px; }
.gkey-steps { margin: 2px 0 2px; }
.gkey-steps li { margin-top: 1px; }
.trans-engine-foot { margin-top: 18px; text-align: right; opacity: .7; }
/* 번역 중 입력 잠금(개선요청) — 원문파일·번역범위를 진행 중엔 못 바꾸게(disabled) 하고, 흐림+커서
   차단으로 '지금은 못 바꾼다'를 시각적으로 알린다. 완료/중단/실패 시 해제(setTransInputsLocked). */
#trans-pick:disabled,
#trans-from:disabled, #trans-to:disabled { opacity: .5; cursor: not-allowed; }
/* 예상 비용 결과 카드(원 위주 + 환율기준·USD·화수·콜수 + 안내) */
#trans-estimate-result { margin: 2px 0 6px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; }
#trans-estimate-result .usage-count { margin: 0; }
/* 없는 회차(파일 범위 밖) 입력 경고 — 비용 대신 빨간 테두리·글자로 분명히 알린다(개선요청 #3) */
#trans-estimate-result.est-warn { border-color: var(--stamp); color: var(--stamp); }
/* 업로드·계산 진행 표시(개선요청 — 큰 범위 계산이 멈춘 것처럼 보이지 않게): 스피너 + 단계(업로드 %/서버 계산) + 경과 초 */
.est-busy-row { display: flex; align-items: center; gap: 8px; color: var(--paper-dim); }
.est-spin {
  flex: 0 0 auto; width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--amber);
  animation: r-spin .8s linear infinite;
}
.trans-est-btn.est-cancel { border-color: var(--stamp); color: var(--stamp); }   /* 계산 중 = '중단' 버튼(번역 시작/중단 토글과 같은 관례) */
@media (prefers-reduced-motion: reduce) { .est-spin { animation: none; } }       /* 경과 초가 매초 갱신돼 살아 있음은 계속 보인다 */
/* 예상 비용 카드 위계(UX T-11): 캡션 → 비용(가장 크게) → 환율·화수 → 소요 시간 → 모델명(작게) → 무료 키 칩.
   '무료 키라면 비용 없이' 강조 칩 — 무료 사용자가 과금으로 오해하지 않게(유료/무료 병기를 한 문장으로 통합). */
.est-cap { margin: 0 0 2px; font-size: var(--fs-caption); color: var(--paper-dim); }
#trans-estimate-result .est-won { font-size: 22px; font-weight: var(--fw-strong); color: var(--amber); line-height: 1.3; }
/* 예상 소요 시간 — 비용 아래 보통 굵기(키 종류 무관 · 비용 숫자와 경쟁하지 않게) */
.est-time { margin: 8px 0 0; font-size: var(--fs-label); color: var(--paper); }
.est-model { margin: 0; font-size: var(--fs-caption); color: var(--faint); }
.est-free {
  margin: 9px 0 6px; padding: 7px 10px; border-radius: 8px;
  background: var(--amber-dim); color: var(--amber-soft);
  font-size: var(--fs-caption); font-weight: var(--fw-strong); line-height: 1.5;
}
.usage-title {
  margin: 16px 0 6px; padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: var(--fs-label); font-weight: var(--fw-title); color: var(--amber);
  font-family: inherit;
}
/* '설정' 탭의 첫 제목(Gemini API 키)은 탭바 구분선 바로 아래라, .usage-title 상단 선이
   줄 두 개로 겹쳐 보인다 — 첫 제목만 상단 선·여백 제거. */
#trans-pane-settings > .usage-title:first-child { margin-top: 6px; padding-top: 0; border-top: none; }
.usage-count { margin: 0 0 8px; font-size: 16px; color: var(--paper); }
.usage-bar {
  height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--line); margin-bottom: 6px;
}
.usage-bar i {
  display: block; height: 100%; width: 0;
  background: var(--amber); border-radius: 3px;
  transition: width .3s ease;
}
.usage-bar.warn i { background: var(--amber-soft); }
.usage-bar.over i { background: var(--stamp); }
.set-foot { align-items: center; }

/* 탭바(밑줄 탭, 번역 시트 번역/이력/설정 · 설정 시트 음성 공용) — pill이 아니라 밑줄 탭. 탭바 하단 구분선 위로 활성 탭의
   3px 밑줄이 겹쳐(margin-bottom:-1px) 본문과 이어져, 탭+본문이 한 패널처럼 보인다.
   ※ .ghost-btn/.on은 엔진·티어 선택 pill과 공유 → 반드시 .seg-tabs로 스코프 한정.
   활성 표시는 색+밑줄로만(font-weight는 양 상태 동일) — 전환 때 폭이 변해 옆 탭이 밀리지 않게. */
.seg-tabs {
  gap: 22px;
  margin: 2px 0 0; padding: 0 4px;
  border-bottom: 1px solid var(--line);
}
.seg-tabs .ghost-btn {
  border: 0; border-bottom: 3px solid transparent; border-radius: 0;
  margin-bottom: -1px; padding: 9px 2px;
  /* 비활성 탭은 --faint 로 한 단계 죽여 활성(amber+밑줄)과 대비를 키운다(P2-8). 굵기는 양 상태
     동일 유지(위 주석 — 전환 때 폭 변화로 옆 탭이 밀리지 않게). */
  background: none; font-size: var(--fs-label); font-weight: 500; color: var(--faint);
}
.seg-tabs .ghost-btn.on { color: var(--amber); border-bottom-color: var(--amber); }
.seg-tabs .ghost-btn:active { background: none; color: var(--amber); border-bottom-color: var(--amber); }
/* 번역 시트는 **탭과 무관한 고정 높이**(UX T-22 — 내용 맞춤+최소 높이였을 땐 탭마다 높이가 달라 패널 윗변·탭바가
   번역↔이력↔설정 전환 때 출렁였다: 바텀시트 top 278→357→65px). 높이는 가장 긴 설정 탭 기준 상한으로 두고, 넘치는
   탭 본문(.sheet-body)은 안에서 스크롤한다. 본문이 남은 높이를 채워야(flex:1) 짧은 탭(빈 이력)의 안내를 가운데 둘 수 있다. */
#translate-sheet .sheet-panel { height: min(88dvh, 720px); max-height: none; }
#translate-sheet .sheet-body { flex: 1 1 auto; }
/* 빈 이력 = 안내 두 줄을 남은 공간 가운데에(한 줄만 위에 붙고 아래가 휑하던 문제) */
#trans-pane-history:has(> #thist-empty:not([hidden])) { display: flex; align-items: center; justify-content: center; }
#thist-empty { margin: 0 0 8%; text-align: center; font-size: var(--fs-label); line-height: 1.8; color: var(--paper-dim); }
#thist-empty small { display: block; font-size: var(--fs-caption); color: var(--faint); }

/* 번역 시트는 탭바가 유일한 구분선이 되게 헤더 밑줄을 뺀다(탭+본문이 한 화면으로 이어진다).
   설정 시트(#tts-sheet)는 탭이 하나뿐이라 탭바를 숨겨(T-13) 헤더 밑줄을 그대로 둔다. */
#translate-sheet .sheet-head { border-bottom: none; }

/* 번역 완료(진행) 섹션 — 위 구분선 + 일정한 수직 간격(제목 8px ↓ 안내 14px ↓ 버튼). 이 박스는
   작업이 돌 때만 보이므로(hidden 토글) 구분선도 그때만 나타난다 — 개선요청 #1. */
#trans-progress { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); }
#trans-detail { margin: 6px 0 0; }   /* 진행 중 보조 한 줄(브라우저 경로는 빈 문자열로 접힘 — 잡 폴백용) */

/* 진행 로딩바(개선요청) — 진행 텍스트를 감싸고 진행률(n/총회차)로 채워진다. 바 안 좌측=진행 라벨,
   우측='남은 시간'. 채움은 호박 반투명 그라데이션이라 위 글자(--paper)가 어느 채움 위치에서도 읽힌다. */
.trans-bar {
  position: relative; overflow: hidden;
  min-height: 42px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg);
}
.trans-bar-fill {
  position: absolute; top: 0; bottom: 0; left: 0; width: 0;
  background: linear-gradient(90deg, rgba(232, 169, 72, .20), rgba(232, 169, 72, .42));
  transition: width .45s ease;
}
/* 준비 중(아직 진행률 미상) — 슬라이딩 밴드로 '돌고 있음'을 알린다. */
.trans-bar.is-indeterminate .trans-bar-fill {
  width: 38%;
  background: linear-gradient(90deg, transparent, rgba(232, 169, 72, .40), transparent);
  animation: trans-bar-slide 1.25s ease-in-out infinite;
}
@keyframes trans-bar-slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(320%); } }
.trans-bar-text {
  position: relative; z-index: 1;       /* 채움 위로 */
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  min-height: 42px; padding: 0 14px;
}
.trans-bar-label { font-size: var(--fs-label); color: var(--paper); }
.trans-bar-eta { flex: 0 0 auto; font-size: var(--fs-caption); color: var(--amber-soft); white-space: nowrap; }
@media (prefers-reduced-motion: reduce) {
  .trans-bar.is-indeterminate .trans-bar-fill { animation: none; }
  .trans-bar-fill { transition: none; }
  .notice-body, .notice-item, .notice-chev { transition: none; }
}

/* 번역 이력 — 월별 페이저(‹ 2026년 6월 ›) + 이 달 요약 + 카드형 항목 목록.
   이력은 계속 쌓이므로 한 달씩 페이징한다(개선요청). 탭 본문 첫 요소라 상단 구분선은 두지 않는다. */
.thist-pager {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  padding: 8px 4px 2px;
}
.thist-month-label {                                          /* 달 라벨 — 가운데 고정폭이라 화살표가 안 흔들린다 */
  min-width: 7.5em; text-align: center;
  font-size: var(--fs-body); font-weight: var(--fw-title); color: var(--amber);
}
.thist-nav { padding: 6px 15px; font-size: 17px; line-height: 1; }
.thist-nav:disabled { opacity: .3; }                          /* 경계 달(가장 오래된/최근)에선 비활성 */
.thist-sum {                                                  /* "번역 N건 · 예상 ₩X" — 이 달 요약 */
  margin: 4px 0 8px; text-align: center;
  font-size: var(--fs-caption); color: var(--paper-dim);
}
.thist-sum b { color: var(--paper); font-weight: var(--fw-strong); white-space: nowrap; }   /* 합계 금액 — 줄바꿈 금지 */
.thist-rate {                                                 /* "환율 ₩1,500/$1 기준" — 위 금액이 어떤 환율로 나왔는지 */
  margin: -4px 0 8px; text-align: center;
  font-size: var(--fs-caption); color: var(--faint);
}

.thist-list { list-style: none; margin: 8px 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.thist-row {
  display: flex; align-items: center; gap: 10px;            /* 카드 안 요소는 세로 가운데 — 개선요청 #3 */
  padding: 12px; border: 1px solid var(--line); border-radius: 10px;
}
.thist-main { flex: 1 1 auto; min-width: 0; }                /* 좌측 열: 제목 + 메타 + 걸린 시간(세로 2~3줄) */
.thist-title {
  font-size: var(--fs-label); color: var(--paper); margin-bottom: 4px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;   /* 제목 한 줄, 넘치면 말줄임 */
}
.thist-meta {
  font-size: var(--fs-caption); color: var(--paper-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;   /* 메타 한 줄(범위 · 날짜) */
}
.thist-dur {                                                        /* 번역에 걸린 시간(시·분·초) — 메타 아래 한 줄(개선요청 #3) */
  font-size: var(--fs-caption); color: var(--paper-dim); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.thist-cost { flex: 0 0 auto; font-size: var(--fs-caption); color: var(--paper-dim); white-space: nowrap; }   /* 금액 우측 고정·줄바꿈 금지 */
.thist-del {                                                  /* 휴지통 버튼 — 테두리 없는 아이콘, 우측 정렬 */
  flex: 0 0 auto; background: none; border: none; padding: 6px;
  color: var(--faint); line-height: 1; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
.thist-del:active { color: var(--stamp); }
.thist-del svg { width: 16px; height: 16px; display: block; pointer-events: none; }
/* 내려받기 — 행 좌측 끝(휴지통은 우측 끝)이라 오클릭 삭제를 막는다(개선요청). 유효성 확인 전엔 슬롯만
   차지하고 숨김(visibility)으로 두어 행 정렬을 흔들지 않고, .on일 때만(다운로드 살아 있을 때) 보인다. */
.thist-dl {
  flex: 0 0 auto; visibility: hidden;
  padding: 6px; color: var(--amber); line-height: 1; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
.thist-dl.on { visibility: visible; }
.thist-dl.is-off { opacity: .4; }   /* 내려받기 쿨다운 중 — 흐리게(클릭은 가능, 핸들러가 안내 토스트). 사용자 .ghost-btn.is-off 관례와 동일 */
.thist-dl:active { color: var(--amber-soft); }
.thist-dl svg { width: 18px; height: 18px; display: block; pointer-events: none; }
.thist-log {                                                 /* 번역 기록 버튼 — 휴지통 바로 왼쪽, 테두리 없는 아이콘(개선 ③) */
  flex: 0 0 auto; background: none; border: none; padding: 6px;
  color: var(--faint); line-height: 1; border-radius: 8px;
  display: inline-flex; align-items: center; justify-content: center;
}
.thist-log:active { color: var(--amber); }
.thist-log svg { width: 16px; height: 16px; display: block; pointer-events: none; }

/* 번역 기록 다이얼로그(개선 ③) — 행 로그 아이콘 클릭 시 소요시간·비용·모델·성공/총·실패회차를 보인다 */
.tlog {
  background: var(--raised); color: var(--paper); border: 1px solid var(--line);
  border-radius: 14px; padding: 18px 20px; max-width: 420px; width: calc(100vw - 48px);
  font-family: var(--sans); font-size: var(--fs-body);
}
.tlog::backdrop { background: rgba(0, 0, 0, .55); }
.tlog-title { font-size: var(--fs-label); font-weight: var(--fw-title); color: var(--paper); margin-bottom: 14px; overflow-wrap: anywhere; }   /* keep-all은 body 상속 · 긴 파일명만 비상 줄바꿈(T-21) */
/* 결과 상태 줄(개선요청) — 제목 바로 아래, 아이콘(좌) + 사유/결과 문구. 예전 우상단 배지가 닫기 버튼으로
   오해돼 사유 줄 좌측으로 옮겼다. 줄 전체를 상태색(성공=초록·부분=호박·실패=빨강)으로 칠하고 아이콘은
   currentColor를 상속. 음수 top-margin이 제목 margin-bottom과 collapse돼 제목↔줄은 6px로 좁고 줄↔필드는 14px. */
.tlog-statusline { display: flex; align-items: center; gap: 7px; margin: -8px 0 14px; font-size: var(--fs-caption); }
.tlog-status-ico { flex: 0 0 auto; line-height: 1; display: inline-flex; }
.tlog-status-ico svg { width: 18px; height: 18px; display: block; }
.tlog-status-txt { min-width: 0; overflow-wrap: anywhere; }
.tlog-status-ok { color: var(--ok); }                  /* 전체성공=옥색 초록 ✓ */
.tlog-status-partial { color: var(--amber); }          /* 일부 미번역=호박색 △ */
.tlog-status-fail { color: var(--stamp); }             /* 실행실패=붉은색 ✕ */
.tlog-fields { display: grid; grid-template-columns: auto 1fr; gap: 9px 16px; margin: 0; }
.tlog-fields dt { color: var(--paper-dim); font-size: var(--fs-caption); }
.tlog-fields dd { margin: 0; text-align: right; color: var(--paper); }
.tlog-fail-t { margin-top: 16px; color: var(--amber); font-size: var(--fs-caption); }
.tlog-fail {
  list-style: none; margin: 6px 0 0; padding: 0; max-height: 180px; overflow-y: auto;
  font-size: var(--fs-caption); color: var(--paper-dim);
}
.tlog-fail li { padding: 2px 0; }
.tlog-close {
  margin-top: 18px; width: 100%; padding: 10px; border: 1px solid var(--line);
  background: var(--bg-2); color: var(--paper); border-radius: 10px;
  font-family: var(--sans); font-size: var(--fs-body);
}
.tlog-close:active { background: var(--bg); }

/* 실패 회차 재번역(fail-chapter-retry Phase B) — 로그 다이얼로그 안 컨트롤. 안내/버튼·진행바·중단을
   세로로 쌓고(gap), 버튼은 .tlog-close를 베이스로 재사용하되 재번역 버튼만 호박색 강조(긍정 액션). 진행바는
   기존 .trans-bar 그대로 재사용(스펙 8). '재번역 결과' 머리말·전이 항목은 .tlog-fail 리스트 위에 색만 입힌다. */
.tlog-retry { margin-top: 16px; display: flex; flex-direction: column; gap: 8px; }
.tlog-retry-hint { margin: 0; }
.tlog-retry-btn, .tlog-retry-stop { margin-top: 0; }   /* .tlog-close margin-top 무효화 — 간격은 flex gap이 잡는다 */
.tlog-retry-btn { border-color: var(--amber); color: var(--amber); }
.tlog-retry-btn:disabled { opacity: .5; }
.tlog-retry-t { color: var(--ok); }                    /* '재번역 결과' 머리말 = 성공 강조색(.tlog-fail-t 호박 대신) */
.tlog-retry-ok { color: var(--ok); }                   /* 전이 → 성공 = 초록 */
.tlog-retry-bad { color: var(--amber); }               /* 전이 → 재실패 = 호박 */

/* 번역 키 목록 */
.gkey-list { list-style: none; margin: 8px 0; padding: 0 4px 0 0; display: flex; flex-direction: column; gap: 8px; max-height: 240px; overflow-y: auto; }
/* 키 개수/상한(제목 옆) — 30개 차면 붉게 표시(화면 유효성 체크) */
.gkey-count { font-size: var(--fs-caption); font-weight: 400; color: var(--paper-dim); }
.gkey-count.full { color: var(--stamp); }
.gkey-row { display: flex; align-items: center; gap: 10px; }
.gkey-mask {
  flex: 1; min-width: 0; font-size: var(--fs-caption); color: var(--paper-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gkey-badge {
  flex: 0 0 auto; font-size: var(--fs-caption); padding: 3px 8px;
  border-radius: 999px; border: 1px solid var(--line); color: var(--paper-dim);
}
.gkey-badge.ok { color: var(--amber); border-color: var(--amber); }
.gkey-badge.bad { color: #fff; background: var(--stamp); border-color: var(--stamp); }
.gkey-badge.unknown { color: var(--faint); }
.gkey-del { flex: 0 0 auto; }

/* ───────── 토스트 ───────── */

#toast {
  position: fixed; left: 50%; bottom: calc(30px + var(--safe-bottom));
  transform: translateX(-50%) translateY(16px);
  z-index: 50;
  background: #241f15; color: var(--amber-soft);
  border: 1px solid rgba(232, 169, 72, .4);
  border-radius: 999px;
  padding: 10px 20px; font-size: var(--fs-label);
  width: max-content; max-width: 86vw; /* left:50%면 shrink-to-fit 폭이 화면 절반으로 묶여 짧은 문구도 3줄로 접혔다 */
  opacity: 0; pointer-events: none;
  transition: opacity .25s, transform .25s;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* 리더(읽기·듣기)에선 상단 바 바로 아래로 — 하단엔 쪽번호(#p-counter)와 듣기 조작부(dock)가 있어 가렸다(UX T-17).
   #toast·#player-screen은 둘 다 body 직계라 형제 선택자로 충분(JS 분기 없음). 위에서 내려오며 나타난다. */
#player-screen:not([hidden]) ~ #toast {
  top: calc(52px + var(--safe-top)); bottom: auto;
  transform: translateX(-50%) translateY(-16px);
}
#player-screen:not([hidden]) ~ #toast.show { transform: translateX(-50%) translateY(0); }
/* 탭 가능한 토스트(완료·중단 알림) — 클릭하면 이력 탭으로 이동(개선요청). 동작이 있을 때만 포인터 수신. */
#toast.tappable { pointer-events: auto; cursor: pointer; }

@media (min-width: 480px) {
  .spine { width: 80px; }
}

/* 데스크톱(넓고 높은 창) — 바텀시트를 화면 가운데 모달로 띄운다(T-10b · 높이 900px급에서 하단이 화면 끝에 붙어
   잘린 듯 보이던 문제). 애니메이션(sheet-up)·패널 구조는 그대로, 위치·모서리·여백만 바꾼다. ☰ 메뉴(우측
   사이드바)는 제외 — 아래 #menu-sheet 규칙이 id 특이도로 이긴다. 폰 가로 화면(높이 낮음)은 바텀시트 유지. */
@media (min-width: 768px) and (min-height: 560px) {
  .sheet { align-items: center; padding: 24px; }
  .sheet-panel {
    border-bottom: 1px solid var(--line);
    border-radius: 18px;
    padding-bottom: 18px;
    max-height: min(76dvh, calc(100dvh - 48px));
  }
  #translate-sheet .sheet-panel { height: min(88dvh, 720px, calc(100dvh - 48px)); max-height: none; }   /* 고정 높이(T-22) — 가운데 모달도 탭 전환 때 위치 불변 */
  #toc-sheet .sheet-panel { height: min(76dvh, calc(100dvh - 48px)); }
  #menu-sheet { align-items: stretch; padding: 0; }
  #menu-sheet .sheet-panel { border-radius: 0; border-bottom: none; padding-bottom: calc(20px + var(--safe-bottom)); max-height: none; }
}
