/* ==========================================================
   English Learning - shared styles
   Responsive: PC, tablet, mobile
   Design language: clean, white, soft shadows (LingQ-inspired)
   ========================================================== */

:root {
  --bg: #f4f5f7;
  --card: #ffffff;
  --ink: #1a2332;
  --muted: #6b7280;
  --line: #e5e7eb;

  --accent: #3aa776;       /* green import / primary */
  --accent-ink: #1f7a52;
  --accent-soft: #e6f4ec;

  --blue: #4a72d6;
  --blue-ink: #2a4ea8;

  /* word states (LingQ-inspired)
     state 0 = pale sky blue: not yet advanced (default for unseen + 1st click)
     state 1 = darkest green: 새로운 단어 (clicked twice)
     state 2..4 = progressively lighter green
     state 5 = no highlight: 완전히 아는 단어 / 무시(-1) shares this look */
  --word-new: #c6dfff;  /* sky blue, state 0 — clearly visible without being loud */
  --word-1:   #8ac68c;  /* darkest green */
  --word-2:   #b1d8b1;
  --word-3:   #cee5cb;
  --word-4:   #e3efe0;  /* very pale green */

  --shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
  --shadow:    0 2px 8px rgba(0,0,0,0.06);
  --shadow-lg: 0 10px 40px rgba(0,0,0,0.18);

  --radius: 14px;
  --radius-sm: 10px;

  /* PC sidebar width. Single source of truth — `body.sidebar-open`
     positioning of `.lesson-main`, `.lesson-bar`, `.playbar`, and the
     `.voice-menu` popup all reference this so widening the sidebar
     keeps every other piece of chrome aligned. Bumped from 360 →
     420 px so the sidebar manual's 6-stage word-level row
     (●0 → ●1 → ●2 → ●3 → ●4 → ●V) fits on a single line — the
     row was wrapping at 360 because of the cumulative dot + kbd +
     arrow widths plus flex gap. */
  --sidebar-w: 420px;
}

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

/* Disable iOS / Android double-tap zoom on every interactive element.
   `touch-action: manipulation` keeps pan + pinch-zoom but suppresses
   the heuristic double-tap-to-zoom — so rapid taps on the lesson-bar
   ←/→ buttons (or word spans, sheet pills, etc.) never accidentally
   blow up the page. */
button,
a,
.w,
.lb-arrow,
.lb-mode,
.lb-eye,
.lb-play,
.lb-practice,
.ws-icon,
.ws-tab,
.ws-lvl,
.cb-speak,
.cb-add,
.cb-close,
.pb-btn {
  touch-action: manipulation;
}
html, body {
  margin: 0; padding: 0; min-height: 100vh;
  background: var(--bg); color: var(--ink);
  font-family: 'Pretendard', 'Apple SD Gothic Neo', 'Noto Sans KR', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px; line-height: 1.5;
}
button { font-family: inherit; cursor: pointer; }
input, textarea, select { font-family: inherit; font-size: inherit; color: inherit; }
a { color: inherit; }

/* ============== top bar (header) ============== */
.topbar {
  position: sticky; top: 0; z-index: 30;
  background: #fff; border-bottom: 1px solid var(--line);
  padding: 10px 18px;
  display: flex; align-items: center; gap: 14px;
}
.topbar .brand {
  font-weight: 900; font-size: 20px; color: var(--accent-ink);
  background: var(--accent-soft); border-radius: 999px;
  padding: 5px 14px;
  letter-spacing: -0.5px;
  text-decoration: none; white-space: nowrap;
  display: inline-flex; align-items: center; gap: 6px;
}
.topbar .brand:hover { background: #d8ebe1; }

/* Language switcher — green pill next to the 또박또박 brand. The
   button shows the current target-language flag (🇬🇧 default, 🇯🇵
   when Japanese mode is active). Clicking opens a small dropdown
   with both options. Color matches the brand (var(--accent-soft))
   so the two pills read as a paired control row. */
.topbar .lang-switch {
  position: relative;
}
.topbar .lang-switch-btn {
  background: var(--accent-soft);
  border: none;
  border-radius: 999px;
  padding: 5px 10px 5px 12px;
  font-size: 18px;
  line-height: 1;
  color: var(--accent-ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  transition: background .12s;
}
.topbar .lang-switch-btn:hover { background: #d8ebe1; }
.topbar .lang-switch-btn .lang-flag {
  font-size: 18px;
  line-height: 1;
}
.topbar .lang-switch-btn .lang-caret {
  font-size: 11px;
  color: var(--accent-ink);
  opacity: 0.7;
}
.topbar .lang-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  margin: 0; padding: 4px;
  list-style: none;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
  min-width: 130px;
  z-index: 200;
}
.topbar .lang-menu[hidden] { display: none; }
.topbar .lang-menu li { margin: 0; padding: 0; }
.topbar .lang-opt {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border: none;
  background: transparent;
  border-radius: 7px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}
.topbar .lang-opt:hover  { background: #f1f3f5; }
.topbar .lang-opt.active { background: var(--accent-soft); color: var(--accent-ink); font-weight: 700; }
.topbar .lang-opt .lang-flag { font-size: 18px; line-height: 1; }
.topbar .lang-opt .lang-name { font-weight: 600; }
.topbar .spacer { flex: 1; }
/* Bidoro return button — matches focus/index.html .tab-page-btn:
   - PC:     pill with the word "Bidoro"
   - Mobile: 36×32 pill with the italic glyph "ƒ"
   Same shape, font, and active treatment as the Focus tab on the Bidoro app. */
.topbar .bidoro-tab {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #f3f4f6;
  color: #4b5563;
  font-family: 'Times New Roman', serif;
  font-style: italic;
  font-weight: 900;
  font-size: 14px;
  text-decoration: none;
  white-space: nowrap;
  transition: background .12s, color .12s, border-color .12s;
}
.topbar .bidoro-tab:hover {
  background: var(--ink); color: #fff; border-color: var(--ink);
}
.topbar .bidoro-tab .ico  { display: none; font-size: 1.3rem; line-height: 1; }
.topbar .bidoro-tab .text { display: inline; }
.topbar .acct {
  display: flex; align-items: center; gap: 8px;
  background: var(--accent-soft); border-radius: 999px;
  padding: 6px 14px 6px 6px; font-weight: 700; font-size: 14px;
  max-width: 200px;
  white-space: nowrap;
}
.topbar .acct #acct-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.topbar .acct .avatar {
  width: 28px; height: 28px; border-radius: 999px;
  background: #c0d6ff; display: grid; place-items: center;
  font-weight: 800; color: #2a4ea8; font-size: 13px;
}
.topbar .menu-btn {
  width: 36px; height: 36px; border: none; background: transparent;
  border-radius: 8px; display: grid; place-items: center;
  font-size: 22px; color: var(--ink);
}
.topbar .menu-btn:hover { background: #f1f3f5; }

/* ============== container ============== */
.container { max-width: 1200px; margin: 0 auto; padding: 18px; }
@media (min-width: 768px) { .container { padding: 24px; } }

/* ============== buttons ============== */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 16px; border-radius: 10px; border: 1px solid var(--line);
  background: #fff; color: var(--ink); font-weight: 700; font-size: 14px;
  text-decoration: none; transition: transform .05s, box-shadow .15s, background .15s;
  white-space: nowrap;
}
.btn:hover { box-shadow: var(--shadow); }
.btn:active { transform: translateY(1px); }
.btn.primary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.btn.primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); }
.btn.ghost { background: transparent; border-color: transparent; }
.btn.ghost:hover { background: #f1f3f5; }
.btn.lg { padding: 12px 22px; font-size: 15px; }
.btn.block { width: 100%; justify-content: center; }
.btn.danger { color: #b03a3a; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }

/* ============== sign-in modal ============== */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(20,30,50,0.35);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  display: grid; place-items: center; padding: 16px;
}
.modal {
  width: 100%; max-width: 420px;
  background: #fff; border-radius: 18px;
  box-shadow: var(--shadow-lg);
  padding: 28px 24px 22px;
  animation: pop .25s ease;
}
@keyframes pop {
  from { transform: translateY(8px) scale(.98); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.modal h1 { margin: 0 0 4px; font-size: 22px; }
.modal .sub { color: var(--muted); font-size: 14px; margin-bottom: 18px; }
.modal .tabs {
  display: flex; gap: 6px; padding: 4px; background: #f1f3f5; border-radius: 10px;
  margin-bottom: 18px;
}
.modal .tab {
  flex: 1; padding: 9px; border: none; background: transparent;
  font-weight: 700; color: var(--muted); border-radius: 7px; font-size: 14px;
}
.modal .tab.active { background: #fff; color: var(--ink); box-shadow: var(--shadow-sm); }
.modal .field { margin-bottom: 12px; }
.modal label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.modal input {
  width: 100%; padding: 11px 13px;
  border: 1px solid var(--line); border-radius: 10px; background: #fff;
}
.modal input:focus { outline: none; border-color: var(--accent); }
.modal .err {
  color: #b03a3a; font-size: 13px; min-height: 18px; margin: 4px 0 8px;
}
.modal .or {
  text-align: center; color: var(--muted); font-size: 13px; margin: 14px 0 10px;
}

/* ============== home page sections ============== */
.section { margin: 26px 0; }
.section-tight { margin-top: 6px; }      /* tighter spacing above 계속 학습하기 */

/* Section header right-side hint count, e.g. "(12) 다음까지 4개". */
.section h2 .hsub {
  font-size: 13px; font-weight: 600; color: var(--muted);
  margin-left: 6px;
}

/* ===== Collapsible sections (워드 마스터 / 단어 스토리)
   The header is a full-width button that, when tapped, expands the body
   below it. The count "(N)" sits next to the title so users can see how
   many items are inside without expanding. ============================ */
.section-collapse { margin: 18px 0; }
.section-collapse .sect-toggle {
  width: 100%;
  background: transparent;
  border: 0;
  padding: 12px 4px;
  cursor: pointer;
  text-align: left;
  display: flex; align-items: center; gap: 10px;
  border-bottom: 1px solid var(--line);
  transition: background .12s, border-color .12s;
}
.section-collapse .sect-toggle:hover {
  background: rgba(15, 20, 28, 0.03);
}
.section-collapse .sect-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.section-collapse .sect-title {
  font-size: 17px; font-weight: 800; color: var(--ink);
  flex-shrink: 0;
}
.section-collapse .sect-count {
  margin-left: 4px;
  font-size: 14px; font-weight: 700; color: var(--muted);
}
.section-collapse .sect-meta {
  flex: 1; min-width: 0;
  font-size: 12px; font-weight: 600; color: var(--muted);
  margin-left: 6px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.section-collapse .sect-caret {
  margin-left: auto;
  font-size: 14px; color: var(--muted);
  flex-shrink: 0;
  transition: transform .2s ease;
}
.section-collapse .sect-toggle[aria-expanded="true"] .sect-caret {
  transform: rotate(-180deg);
}
.section-collapse .sect-toggle[aria-expanded="true"] {
  border-bottom-color: transparent;
}
.section-collapse .sect-body {
  padding: 12px 0 4px;
}
.section-collapse .sect-body[hidden] { display: none; }

/* ===== 워드 마스터 — filterable view across states 2..5. =====
   Top filter row picks ONE state at a time; chip grid below shows
   the words at that state as toggle buttons. */
.wm-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}
.wm-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s, transform .05s;
}
.wm-filter-btn:hover  { background: #f8f9fa; border-color: #cdd2d8; }
.wm-filter-btn:active { transform: translateY(1px); }
.wm-filter-btn .wm-filter-dot {
  display: inline-grid; place-items: center;
  width: 22px; height: 22px;
  border-radius: 999px;
  font-size: 12px; font-weight: 800;
  color: var(--ink);
  border: 1px solid transparent;
}
.wm-filter-btn.s2 .wm-filter-dot { background: var(--word-2); }
.wm-filter-btn.s3 .wm-filter-dot { background: var(--word-3); }
.wm-filter-btn.s4 .wm-filter-dot { background: var(--word-4); }
.wm-filter-btn.s5 .wm-filter-dot { background: #fff; border-color: var(--ink); }
.wm-filter-btn .wm-filter-count {
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Active filter — colored fill so it's obvious which tier is selected. */
.wm-filter-btn.active {
  border-color: var(--ink);
  background: #f0f7ff;
  color: var(--ink);
}
.wm-filter-btn.active .wm-filter-count { color: var(--ink); }

.wm-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 8px;
}

/* === Mobile (≤ 600 px) — narrow viewport adjustments ===
   The desktop filter row (4 long-labelled pills) doesn't fit on
   phones at 360–390 px wide. Reflow as a 2 × 2 grid with smaller
   padding / font so all four are reachable in the thumb zone, and
   tighten the chip grid so two columns fit reliably down to 320 px. */
@media (max-width: 600px) {
  .wm-filter {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin-bottom: 10px;
  }
  .wm-filter-btn {
    padding: 8px 10px;
    font-size: 12px;
    gap: 6px;
    /* Push the count to the far right so the row reads "[dot] label
       … N" cleanly even when a long label squeezes the pill. */
    justify-content: flex-start;
  }
  .wm-filter-btn .wm-filter-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .wm-filter-btn .wm-filter-dot {
    width: 20px; height: 20px; font-size: 11px;
    flex-shrink: 0;
  }
  .wm-grid {
    /* 140 px floor → reliable 2 columns from 320 px (iPhone SE 1st)
       up. Above ~700 px the auto-fill kicks back to 3+ columns. */
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  }
}

/* Toggle chip — shows ONE label at a time (the source word, or the
   Korean meaning after the user taps). Single centered line so the
   swap is visually obvious. The `.wm-chip-ko` modifier paints the
   Korean state with a softer accent tint, giving the toggle a clear
   "active" look that distinguishes it from the default word state. */
.wm-chip {
  text-align: center;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 12px 14px;
  cursor: pointer;
  display: grid; place-items: center;
  min-height: 44px;
  transition: background .12s, border-color .12s, color .12s, transform .05s;
}
.wm-chip:hover  { background: #f8f9fa; border-color: #cdd2d8; }
.wm-chip:active { transform: translateY(1px); }
.wm-chip .wm-text {
  font-weight: 800; font-size: 14px; color: var(--ink);
  line-height: 1.2;
  word-break: keep-all;
}
/* Korean-revealed state — softer background + accent text so the
   user sees instantly that this chip is currently flipped. */
.wm-chip.wm-chip-ko {
  background: #f0f7ff;
  border-color: #c4dcf5;
}
.wm-chip.wm-chip-ko .wm-text {
  color: #1d4ed8;
}

/* ===== 단어 스토리 — bar list ===== */
.ws-list {
  display: flex; flex-direction: column; gap: 8px;
}
.ws-bar {
  text-align: left;
  background: linear-gradient(135deg, #e8f5e9 0%, #fff 100%);
  border: 1px solid #c8e6c9;
  border-radius: 12px;
  padding: 14px 16px;
  cursor: pointer;
  display: flex; flex-direction: column; gap: 4px;
  transition: transform .08s, box-shadow .15s;
}
.ws-bar:hover  { box-shadow: 0 4px 12px rgba(0,0,0,0.06); transform: translateY(-1px); }
.ws-bar:active { transform: translateY(0); }
.ws-bar .ws-bar-title {
  font-weight: 800; font-size: 15px; color: var(--ink);
}
.ws-bar .ws-bar-meta {
  font-size: 12px; color: var(--muted);
}

/* Story modal — opened when a bar is tapped. */
.story-overlay {
  position: fixed; inset: 0; z-index: 240;
  background: rgba(20,30,50,0.45);
  backdrop-filter: blur(4px);
  display: grid; place-items: center;
  padding: 16px;
}
.story-card {
  background: #fff; border-radius: 16px;
  width: 100%; max-width: 440px;
  padding: 24px 22px 20px;
  box-shadow: var(--shadow-lg);
  position: relative;
}
.story-card .sc-close {
  position: absolute; top: 12px; right: 14px;
  width: 32px; height: 32px; border-radius: 999px;
  border: none; background: #f1f3f5; cursor: pointer;
  font-size: 20px; line-height: 1;
}
.story-card h3 { margin: 0 0 12px; font-size: 18px; }
.story-card .sc-body {
  font-size: 16px; line-height: 1.7; color: var(--ink);
  margin-bottom: 12px;
}
.story-card .sc-words {
  display: flex; flex-wrap: wrap; gap: 6px;
  border-top: 1px solid var(--line); padding-top: 12px;
}
.story-card .sc-word {
  background: var(--word-new);
  border: 1px solid #a8c2dc;
  border-radius: 8px;
  padding: 6px 10px;
  font-size: 13px; font-weight: 700; color: var(--ink);
  cursor: pointer;
}
.story-card .sc-word:hover { background: #d8e5f3; }
.section h2 {
  font-size: 18px; margin: 0 0 12px;
  display: flex; align-items: center; gap: 10px;
}
.section h2 .view-all {
  margin-left: auto; font-size: 13px; color: var(--muted);
  text-decoration: none; font-weight: 600;
}

/* lesson cards row (horizontal scroll on mobile, grid on desktop) */
.cards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.card {
  background: #fff; border-radius: var(--radius); overflow: hidden;
  border: 1px solid var(--line); cursor: pointer;
  transition: transform .12s, box-shadow .15s;
  display: flex; flex-direction: column;
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.card .thumb {
  aspect-ratio: 16/10; background: linear-gradient(135deg, #aac4f0, #d8a8e8);
  display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 22px;
  position: relative;
}
/* Two-badge stack in the top-left corner: 텍스트 always shown, 오디오
   added when the lesson has audio. */
.card .thumb .badges {
  position: absolute; top: 8px; left: 8px;
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 4px;
}
.card .thumb .badge {
  background: rgba(255,255,255,0.92); color: var(--ink);
  font-size: 12px; padding: 3px 8px; border-radius: 999px;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
.card .thumb .badge.audio {
  background: rgba(20, 132, 91, 0.92); color: #fff;
  font-weight: 700;
}
.card .thumb .bm-flag {
  position: absolute; top: 8px; right: 8px;
  background: rgba(255,255,255,0.85); color: #d23578;
  font-size: 14px; line-height: 1;
  padding: 4px 7px; border-radius: 999px;
}
/* Bookmark-toggle heart button — replaces the old read-only `bm-flag`
   indicator AND the lesson-page title heart. Sits at the top-right of
   the card's thumb area, always visible (hollow when not bookmarked,
   filled red when bookmarked). Lives OUTSIDE the card's <a>, so it
   captures its own click and toggles the bookmark via DB.setBookmark
   without navigating into the lesson. */
.card-wrap .card-heart {
  position: absolute;
  top: 8px; right: 8px;
  z-index: 2;
  width: 32px; height: 32px;
  border-radius: 999px;
  border: 1px solid rgba(0,0,0,0.06);
  background: rgba(255,255,255,0.92);
  color: #9ca3af;                         /* hollow heart = muted gray */
  display: grid; place-items: center;
  cursor: pointer; padding: 0;
  font-size: 16px; line-height: 1;
  box-shadow: 0 1px 3px rgba(0,0,0,0.10);
  transition: background .12s, color .12s, border-color .12s, transform .05s;
}
.card-wrap .card-heart:hover {
  background: #fff;
  border-color: rgba(0,0,0,0.12);
  color: #d23578;
}
.card-wrap .card-heart:active { transform: translateY(1px); }
.card-wrap .card-heart.bookmarked {
  background: #fff;
  color: #d23578;                         /* filled heart = pink-red */
  border-color: rgba(210, 53, 120, 0.35);
}
.card-wrap .card-heart .ch-ico {
  display: block;
  font-size: 18px; line-height: 1;
}

/* Card wrapper hosts the link + a kebab button so the kebab is OUTSIDE
   the navigation target. The grid sees `.card-wrap` as a child instead
   of `.card`, so we mirror enough of the column behaviour. */
.card-wrap {
  position: relative;
  display: flex; flex-direction: column;
}
.card-wrap > .card { flex: 1; }

/* Kebab (⋮) button at bottom-right of the card. Sits above the card
   surface; clicks here don't navigate. */
.card-wrap .card-kebab {
  position: absolute;
  right: 8px; bottom: 8px;
  width: 30px; height: 30px;
  border-radius: 999px;
  background: rgba(255,255,255,0.95);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 18px; line-height: 1;
  display: grid; place-items: center;
  cursor: pointer;
  z-index: 2;
  box-shadow: 0 1px 3px rgba(0,0,0,0.06);
  transition: background .12s, transform .05s;
  padding: 0;
}
.card-wrap .card-kebab:hover  { background: #fff; }
.card-wrap .card-kebab:active { transform: translateY(1px); }
/* Vertically center the ⋮ glyph inside the round kebab. The previous
   -1px nudge made the dots ride a hair too high; +1px places them
   right at the visual middle (was -1, +2 px shift = 1). */
.card-wrap .card-kebab span   { transform: translateY(1px); }

/* Floating popover with 수정 / 삭제 entries. Positioned by JS — we just
   make sure it renders above everything. */
.card-menu {
  position: absolute;
  z-index: 70;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 30px rgba(15,20,28,0.18);
  min-width: 130px;
  padding: 4px;
  display: flex; flex-direction: column;
}
.card-menu .cm-item {
  background: transparent; border: 0;
  padding: 9px 12px; border-radius: 6px;
  font-size: 14px; font-weight: 600;
  color: var(--ink); cursor: pointer;
  text-align: left;
  display: flex; align-items: center; gap: 8px;
}
.card-menu .cm-item:hover  { background: #f1f3f5; }
.card-menu .cm-item.danger { color: #b91c1c; }
.card-menu .cm-item.danger:hover { background: #fef1f1; }
/* The ✏️ / 🗑 leading glyphs: bump font-size so the pencil renders
   at roughly the same visual weight as the trash emoji. The text
   characters that follow keep the inherited 14 px size. */
.card-menu .cm-icon {
  font-size: 16px;
  line-height: 1;
  display: inline-flex; align-items: center;
  width: 18px; justify-content: center;
}
.card .body { padding: 12px 12px 14px; }
.card .title {
  font-weight: 700; font-size: 14px; line-height: 1.35;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card .meta {
  margin-top: 6px; color: var(--muted); font-size: 12px;
  display: flex; gap: 10px; align-items: center;
}
.card .progress { height: 4px; background: #f1f3f5; border-radius: 999px; overflow: hidden; margin-top: 8px;}
.card .progress > i { display: block; height: 100%; background: var(--accent); }

/* tabs under section title (모든 레슨 / 찜한 레슨 / 워드 마스터 / 단어 스토리) */
.tabrow {
  display: flex; gap: 18px; margin: -6px 0 14px; font-size: 14px;
  border-bottom: 1px solid var(--line); padding-bottom: 8px;
  flex-wrap: wrap;                /* let extra tabs wrap to second row on phones */
}
.tabrow a {
  text-decoration: none; color: var(--muted); font-weight: 700; padding-bottom: 8px;
  margin-bottom: -9px; border-bottom: 2px solid transparent;
  white-space: nowrap;
}
.tabrow a.active { color: var(--ink); border-bottom-color: var(--ink); }
.tabrow .tab-count {
  margin-left: 3px;
  font-size: 12px; font-weight: 700; color: var(--muted);
}
.tabrow a.active .tab-count { color: var(--ink); }
@media (max-width: 480px) {
  .tabrow { gap: 14px; font-size: 13px; }
  .tabrow .tab-count { font-size: 11px; }
}

/* "다음 스토리까지 N개" hint that sits above the bar list when the
   단어 스토리 tab is active. */
.ws-progress-line {
  font-size: 12px; font-weight: 600;
  margin: -4px 0 10px;
}

/* ============== empty state ============== */
.empty {
  background: #fff; border: 1px dashed var(--line); border-radius: var(--radius);
  padding: 30px 20px; text-align: center; color: var(--muted);
}
.empty h3 { color: var(--ink); margin: 0 0 6px; }

/* ============== floating import (FAB) ============== */
.fab {
  position: fixed; bottom: 22px; right: 22px;
  width: 56px; height: 56px; border-radius: 999px;
  background: var(--accent); color: #fff; border: none;
  font-size: 26px; box-shadow: 0 8px 24px rgba(58,167,118,0.4);
  display: grid; place-items: center; z-index: 50;
}
.fab:hover { background: var(--accent-ink); }

/* ============== import page ============== */
.import-form {
  background: #fff; border-radius: var(--radius); padding: 22px;
  max-width: 760px; margin: 0 auto;
}
.import-form .field { margin-bottom: 18px; }
.import-form label {
  display: block; font-weight: 700; margin-bottom: 8px; font-size: 14px;
}
.import-form input[type=text],
.import-form textarea {
  width: 100%; padding: 12px 14px; border: 1px solid var(--line);
  border-radius: 10px; background: #fff; font-size: 15px; resize: vertical;
}
.import-form textarea { min-height: 220px; line-height: 1.5; }
.import-form input:focus, .import-form textarea:focus {
  outline: none; border-color: var(--accent);
}
.import-form .audio-zone {
  border: 1.5px dashed var(--line); border-radius: var(--radius);
  padding: 28px; text-align: center; color: var(--muted); cursor: pointer;
  transition: border-color .15s, background .15s;
}
.import-form .audio-zone:hover { border-color: var(--accent); background: #fafffb; }
.import-form .audio-zone .icon { font-size: 32px; margin-bottom: 6px; }
.import-form .audio-zone.has-file { color: var(--accent-ink); border-style: solid; border-color: var(--accent); background: var(--accent-soft); }

/* Thumbnail upload zone — same dashed-box look as audio, but holds an image
   preview when a file is chosen. */
.import-form .thumb-zone {
  position: relative; min-height: 120px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.import-form .thumb-zone .thumb-placeholder {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.import-form .thumb-zone img {
  max-width: 100%; max-height: 240px;
  object-fit: contain; border-radius: 8px;
  display: block;
}
.import-form .thumb-zone.has-file { padding: 8px; }

/* Clipboard-paste zone — sits directly under the thumb dropzone.
   The user clicks once to focus the box, then ⌘V / Ctrl+V drops a
   clipboard image into the same `setThumb()` pipeline as the
   regular file picker. Visually echoes the dropzone (dashed
   border, muted text) but is shorter — it's just the entry point
   for the keyboard paste, not a preview surface. */
.import-form .thumb-paste-zone {
  margin-top: 8px;
  border: 1.5px dashed var(--line); border-radius: var(--radius);
  padding: 14px 16px;
  text-align: center; color: var(--muted);
  font-size: 13px;
  cursor: text;
  user-select: none;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  transition: border-color .15s, background .15s, color .15s;
  outline: none;
}
.import-form .thumb-paste-zone:hover {
  border-color: var(--accent);
  background: #fafffb;
}
.import-form .thumb-paste-zone:focus {
  border-color: var(--accent);
  border-style: solid;
  background: #fafffb;
  color: var(--accent-ink);
}
/* Brief flash after a successful paste, then fades back. */
.import-form .thumb-paste-zone.pasted {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.import-form .thumb-paste-zone .tp-icon { font-size: 18px; }
.import-form .thumb-paste-zone kbd {
  display: inline-block;
  padding: 1px 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px; font-weight: 700;
  color: #1f2937;
  background: #f3f4f6;
  border: 1px solid #d1d5db;
  border-bottom-width: 2px;
  border-radius: 4px;
  vertical-align: 1px;
  line-height: 1;
}
.import-form .actions {
  display: flex; gap: 10px; justify-content: flex-end; margin-top: 8px;
}

/* ============== lesson page ============== */
.lesson-layout { display: block; }   /* single column — popup floats anchored */
.lesson-main {
  background: #fff; border-radius: var(--radius);
  padding: 18px 20px;
  display: flex; flex-direction: column;
  position: relative;
  overflow: hidden;        /* clip horizontal slide animation */
  /* height set via JS so the body fits between topbar and playbar */
  transition: transform .22s cubic-bezier(.22,.6,.36,1);
}
/* When the mobile cont-bar opens, shift the white card UP by the bar's
   exact height (CSS var set by JS). The bottom of the lesson text stays
   visible just above the cont-bar; only the title row scrolls off the
   top of the viewport (still inside the card box, just clipped). */
body.cont-bar-open .lesson-main {
  transform: translateY(var(--cont-bar-shift, -120px));
}
@media (min-width: 1024px) {
  /* PC uses the sidebar instead of the cont-bar — never shift here. */
  body.cont-bar-open .lesson-main { transform: none; }
}
.lesson-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 14px;
  padding-bottom: 12px; border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.lesson-head .thumb {
  width: 44px; height: 44px; border-radius: 10px;
  background: linear-gradient(135deg, #aac4f0, #d8a8e8);
  display: grid; place-items: center; color: #fff; font-weight: 800;
  flex-shrink: 0;
}
.lesson-head h1 { margin: 0; font-size: 17px; line-height: 1.3; }
.lesson-head .crumb { color: var(--muted); font-size: 12px; margin-top: 2px; }

/* ===== lesson header grid =====
   Two columns:
     [ left column  ]  [ thumbnail ]
     [ title + ♡    ]
     [ tool row     ]
   The thumbnail is a sibling of the left column, so it stretches across
   both rows of the left column (title row + tool row). It's a div with a
   background-image so its height can flex with the left column. */
.lesson-header-grid {
  display: flex; align-items: stretch;
  gap: 14px;
  margin: 4px 0 12px;
  flex-shrink: 0;
}
.lesson-header-grid .lh-left {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  gap: 8px;
}
.lesson-header-grid .lesson-thumb-img {
  position: relative;       /* anchor the page-count overlay */
  width: 180px;
  align-self: stretch;
  background-size: cover;
  background-position: center;
  background-color: #f1f3f5;
  border-radius: 14px;
  flex-shrink: 0;
  overflow: hidden;         /* keep overlay within the rounded corners */
}
.lesson-header-grid .lesson-thumb-img[hidden] { display: none; }

/* Page-flip buttons inside the thumbnail (page-counter overlay).
   PC-only — phones use the playbar's sentence-step controls instead.
   LEFT = previous, RIGHT = next (standard convention). When an image is
   present the buttons are translucent so the image still shows through;
   without an image they sit on a neutral background. */
.lh-page-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px; height: 28px;
  border-radius: 999px;
  border: none;
  display: grid; place-items: center;
  cursor: pointer;
  z-index: 2;
  padding: 0;
  transition: background .12s, opacity .12s, transform .05s;
}
.lh-page-prev { left: 6px; }
.lh-page-next { right: 6px; }
.lh-page-btn:active { transform: translateY(calc(-50% + 1px)); }
.lh-page-btn[disabled] { opacity: 0.32; pointer-events: none; }
.lh-page-btn[hidden]   { display: none; }

/* Variant 1: thumb HAS an image. Buttons are translucent white so the
   image is still legible behind them. Subtle backdrop blur lifts the
   icon off busy photo backgrounds. */
.lesson-thumb-img:not(.no-thumb) .lh-page-btn {
  background: rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: #1f2937;
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
.lesson-thumb-img:not(.no-thumb) .lh-page-btn:hover {
  background: rgba(255, 255, 255, 0.82);
}

/* Variant 2: no thumbnail image. The page counter renders in plain
   black on the page background — give the buttons a soft gray pill so
   they're clearly tappable without competing with the digits. */
.lesson-thumb-img.no-thumb .lh-page-btn {
  background: #f1f3f5;
  color: var(--ink);
  border: 1px solid var(--line);
}
.lesson-thumb-img.no-thumb .lh-page-btn:hover { background: #e5e7eb; }

/* On phones the thumbnail shrinks to 120 px wide. Pull the buttons in
   slightly so the page counter still has room between them, and shave
   the icon size a touch so the pill stays visually balanced. */
@media (max-width: 600px) {
  .lh-page-btn {
    width: 24px; height: 24px;
  }
  .lh-page-prev { left: 4px; }
  .lh-page-next { right: 4px; }
  .lh-page-btn svg {
    width: 14px; height: 14px;
  }
}

/* Page-count overlay (e.g. "2 / 5"). Only shown when the lesson is
   paginated to 2+ pages. The dark scrim makes the white digits readable
   over any background image. */
.lesson-header-grid .lesson-thumb-img .thumb-pages {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-weight: 800; font-size: 18px;
  color: #fff;
  /* Lighter scrim so the underlying thumbnail still shows through;
     a stronger text-shadow keeps the digits crisp regardless. */
  background: rgba(0, 0, 0, 0.18);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7), 0 0 6px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  letter-spacing: 0.5px;
}
.lesson-header-grid .lesson-thumb-img .thumb-pages[hidden] { display: none; }

/* No-thumbnail variant — strip the gray placeholder background AND the
   dark scrim, render the page count in plain black. So when the lesson
   has no uploaded image, the corner just shows "2 / 5" floating in the
   header space. */
.lesson-header-grid .lesson-thumb-img.no-thumb {
  background-color: transparent;
}
.lesson-header-grid .lesson-thumb-img.no-thumb .thumb-pages {
  background: transparent;
  color: var(--ink);
  text-shadow: none;
  font-weight: 800;
}
@media (max-width: 600px) {
  .lesson-header-grid .lesson-thumb-img .thumb-pages { font-size: 15px; }
}
@media (max-width: 600px) {
  /* On phones, give it a touch less width so the title + heart still
     have room. Still stretches across both rows. */
  .lesson-header-grid .lesson-thumb-img { width: 120px; border-radius: 12px; }
  .lesson-header-grid { gap: 10px; }
}

/* Lesson title row: back button on the left, title + heart together. The
   heart sits IMMEDIATELY after the title text (not pushed to the right
   edge), with no surrounding circle — just the glyph. */
.lesson-title-row {
  display: flex; align-items: center; gap: 8px;
  flex-shrink: 0;
}
.lesson-title-row h1 {
  margin: 0; font-size: 19px; line-height: 1.3; font-weight: 800;
  color: var(--ink);
  flex: 0 1 auto; min-width: 0;
  /* Up to TWO lines on every viewport. The line-clamp + min-width: 0
     pair lets the h1 take all remaining horizontal space inside
     .lh-left (which is itself flex: 1 next to the 180 px thumbnail),
     so the title wraps to a second line BEFORE running into the
     thumbnail's left edge. Long enough to need a third line? Tail is
     truncated with an ellipsis. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-word;
}
@media (max-width: 600px) {
  /* Phones: tighter font + line-height. The 2-line clamp from the
     base rule above continues to apply. */
  .lesson-title-row h1 {
    font-size: 17px;
    line-height: 1.25;
  }
}
.lesson-title-row .back-btn {
  width: 36px; height: 36px;
  border-radius: 999px;
  display: grid; place-items: center;
  background: #fff; border: 1px solid var(--line);
  text-decoration: none; color: var(--ink);
  font-size: 18px; flex-shrink: 0;
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .05s;
}
.lesson-title-row .back-btn:hover  { background: #f1f3f5; border-color: #cbd1d9; }
.lesson-title-row .back-btn:active { background: #e5e8ec; transform: translateY(1px); }
.lesson-title-row .back-btn svg { display: block; }
/* Legacy <img> rule kept for any cached HTML; new markup uses inline SVG. */
.lesson-title-row .back-btn img { width: 22px; height: 22px; object-fit: contain; }

/* The lesson page no longer renders the global topbar — the back button +
   title row above the body takes its place. Hide topbar JUST on this page. */
.lesson-page > .topbar { display: none; }

/* Lock document scrolling on the lesson page — the white card is sized by
   JS to fit the viewport, the lesson-bar is fixed at the bottom, and the
   text inside paginates instead of scrolling. Without this, the body would
   sometimes scroll a few pixels on mobile (rounding errors, browser chrome
   resize, etc.).
   Height fallbacks (top → bottom precedence):
     1. 100vh                       — universal baseline
     2. 100dvh                      — dynamic viewport (modern Chrome/Safari)
     3. var(--app-vh, 100dvh)       — JS-set value from visualViewport,
        always exactly the visible area on iOS Safari even when 100dvh
        lags behind the URL-bar transition.  */
html:has(.lesson-page),
body:has(.lesson-page) {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
  height: var(--app-vh, 100dvh);
}
.lesson-page {
  height: 100vh;
  height: 100dvh;
  height: var(--app-vh, 100dvh);
  overflow: hidden;
}
.lesson-head { margin: 0; padding: 0; border: 0; }
.lesson-head .head-tools { display: flex; gap: 8px; flex-shrink: 0; align-items: center; }
/* Pill-shaped toggle button used inside .head-tools — currently the
   focused-reading "1문장씩" toggle. Active when single-sent mode is
   on (the JS flips aria-pressed + .active). */
.lesson-head .ht-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  font-size: 13px; font-weight: 700;
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s, transform .05s;
}
.lesson-head .ht-btn:hover  { background: #f1f3f5; border-color: #cdd2d8; }
.lesson-head .ht-btn:active { transform: translateY(1px); }
.lesson-head .ht-btn.active,
.lesson-head .ht-btn[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.lesson-head .ht-btn .ht-ico   { font-size: 14px; line-height: 1; }
.lesson-head .ht-btn .ht-label { line-height: 1; }

/* ===== Single-sentence focused-reading mode =====
   Body-wide class set when the user toggles the 1문장씩 button.
   The lesson-text now hosts ONE sentence rendered at 2× the
   inherited font-size, vertically centered and left-aligned. The
   slide-in/out animations and per-page paginate logic stay disabled
   while this class is on; navigation is sentence-based via goPage()
   trampolines (see lesson.html). */
body.single-sent-mode .lesson-text {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-align: left;
  /* Tighter line-height in this mode — at 2× size the default
     spacing feels airy and creates needless scroll. */
  line-height: 1.5;
}
body.single-sent-mode .single-sent-wrap {
  width: 100%;
  /* 2× the inherited font (so the user's font-size selection in the
     sidebar is preserved as the BASE). */
  font-size: 2em;
}
body.single-sent-mode .single-sent-wrap .para { margin: 0; }
/* Hide normal paragraph margins / large gaps that look weird at 2×. */
body.single-sent-mode .lesson-text br { display: none; }
@media (max-width: 600px) {
  /* Mobile: toggle row stays left-aligned, just under the title row. */
  .lesson-head .head-tools  { flex-wrap: wrap; }
}

/* Toggle button: a single pill that shows its CURRENT state in its label.
   Click flips state and label. Background is always pale sky-blue — never
   black — so the row reads consistently. */
.toggle-btn {
  background: var(--word-new);
  border: 1px solid #a8c2dc;
  border-radius: 8px;
  padding: 6px 12px;
  color: var(--ink);
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .05s;
  white-space: nowrap;
}
.toggle-btn:hover  { background: #d8e5f3; border-color: #8fb1cf; }
.toggle-btn:active { transform: translateY(1px); }
.toggle-btn.dict {
  padding: 6px 10px;
  font-family: 'Times New Roman', serif;
  font-style: italic;
  min-width: 28px;
  text-align: center;
  /* Override the toggle-btn sky-blue default — the dict tools (✨ AI,
     font−, font+) read better as plain white pills against the
     lesson-card so only the mode toggle on the right wears the blue. */
  background: #fff;
  border-color: var(--line);
  color: var(--ink);
}
.toggle-btn.dict:hover {
  background: #f1f3f5;
  border-color: #cfd4d9;
}

/* Bookmark toggle (찜) — pink fill while active. Same shape as other
   toggles, only the colour communicates state. */
.toggle-btn.bookmarked {
  background: #fde0ec;
  border-color: #f0b9d2;
  color: #9c1450;
}
.toggle-btn.bookmarked:hover {
  background: #fbcedc; border-color: #e09bba;
}

/* ✨ "Better translation" toggle — soft gold while active. */
.toggle-btn#gpt-toggle-btn.gpt-on {
  background: linear-gradient(135deg, #fff8d6 0%, #ffe082 100%);
  border-color: #f5b800;
  color: #6b4f00;
  box-shadow: 0 0 0 2px rgba(245,184,0,0.18);
}
.toggle-btn#gpt-toggle-btn.gpt-on:hover {
  background: linear-gradient(135deg, #fff3b3 0%, #ffd54f 100%);
}
@media (max-width: 600px) {
  .toggle-btn { padding: 5px 9px; font-size: 12px; }
  .toggle-btn.dict { padding: 5px 8px; min-width: 26px; }
}

/* Wrapper holds (potentially) two stacked .lesson-text elements during a slide. */
.lesson-text-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  /* `overflow: clip` + `overflow-clip-margin` keeps the slide animation
     from leaking, but lets the focused-word OUTER GLOW (box-shadow)
     extend up to 16px BEYOND the wrap edge — exactly the lesson-main's
     padding area. Without this, glows on edge-words got clipped at the
     padding and looked half-cut. lesson-main's own overflow:hidden still
     clips at the rounded card corner so nothing escapes the white card.

     Modern browser support (Chrome 90+, Safari 14.1+, Firefox 100+).
     Older fallback uses plain hidden — clipping resumes its old behaviour. */
  overflow: hidden;
  overflow: clip;
  /* 24px lets the focused word's outer glow (~14px blur + 4px spread)
     and the wider chunk glow (~18px blur + 6px spread) extend past the
     wrap edge without being clipped at the left/right margins. */
  overflow-clip-margin: 24px;
}
.lesson-text {
  position: absolute;
  inset: 0;
  font-size: 20px; line-height: 2.4;     /* bigger glyphs + more breathing room */
  user-select: none; -webkit-user-select: none;
  overflow: hidden;        /* explicitly no scroll */
  will-change: transform;
  /* Left-aligned (browser default for LTR). Justify was tried but
     produced inconsistent inter-word spacing that interfered with
     the per-word tap targets, focus glow, chunk outline, and the
     TTS reading-underline — a learner app needs predictable word
     positions, not aesthetically-balanced columns. */
}
@media (max-width: 600px) {
  /* Bigger glyphs + more line spacing on phones so individual words are
     easy to tap without misfiring on a neighbour. */
  .lesson-text { font-size: 21px; line-height: 2.6; }
}
@media (max-width: 380px) {
  /* Very small phones (iPhone SE-class) — push it a bit further. */
  .lesson-text { font-size: 22px; line-height: 2.7; }
}
.lesson-text .para { margin: 0 0 14px; }

/* Furigana — small hiragana on top of kanji-bearing tokens in
   Japanese mode. <ruby> is browser-native; <rt> is the reading
   that rides above the base. Sized at ~0.5em so it doesn't
   dominate the line, with no descender so adjacent rows don't
   collide. JP-only, but since EN body never emits <ruby> it's
   safe to leave the rule global. */
.lesson-text ruby {
  ruby-align: center;
}
.lesson-text rt {
  font-size: 0.55em;
  color: var(--muted);
  font-weight: 600;
  /* User-select fix: keep furigana text from breaking word taps */
  user-select: none;
  -webkit-user-select: none;
}

/* JP first-load splash — kuromoji + IPADIC dict download takes a few
   seconds the first time. We show this so the user knows the page
   isn't frozen. Subsequent loads hit the browser cache and the
   splash flashes briefly or skips entirely. */
.jp-load-splash {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(255,255,255,0.94);
  display: grid; place-items: center;
  opacity: 1;
  transition: opacity .22s ease;
}
.jp-load-splash.fade-out { opacity: 0; }
.jp-load-card {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 8px 32px rgba(0,0,0,0.08);
  padding: 28px 32px;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  max-width: 320px; text-align: center;
}
.jp-load-spinner {
  width: 36px; height: 36px;
  border: 3px solid #e5e7eb;
  border-top-color: var(--accent-ink);
  border-radius: 50%;
  animation: jpLoadSpin 0.9s linear infinite;
}
@keyframes jpLoadSpin { to { transform: rotate(360deg); } }
.jp-load-title {
  font-size: 15px; font-weight: 800; color: var(--ink);
}
/* (Progress bar / counter were removed — splash is now spinner + text only.) */
.jp-load-sub {
  font-size: 12px; color: var(--muted); line-height: 1.5;
}
@media (min-width: 1024px) {
  /* Tighter paragraph gap on PC so the last paragraph isn't half-cut by
     the playbar / lesson-bar. Pagination's safety margin (in JS) is also
     bumped to make this robust. */
  .lesson-text .para { margin: 0 0 10px; }
}

/* Word chip — inline (default) so the background hugs the character only,
   with no surrounding padding. The line-height of the parent gives breathing
   room between lines without making each highlight thicker than the text. */
.w {
  background: var(--word-new);             /* state 0: pale sky blue */
  border-radius: 2px;
  cursor: pointer;
  transition: background .2s;
}
.w.s1 { background: var(--word-1); }       /* 새로운 단어 (clicked ≥ 2) */
.w.s2 { background: var(--word-2); }       /* 어디선가 본 단어 */
.w.s3 { background: var(--word-3); }       /* 익숙한 단어 */
.w.s4 { background: var(--word-4); }       /* 배운 단어 */
.w.s5 { background: transparent; }         /* 완전히 아는 단어 */
.w.sx { background: transparent; }         /* 무시 */
/* `.w.active` was the legacy "cursor" outline (1.5 px black border) on
   the focused word in 하나씩 mode. The amber focus glow (`.focused`)
   now communicates that role visually, so the black box is removed —
   the class is still used for state tracking by trySingleStep but has
   no visual style. */
.w.sel { box-shadow: 0 0 0 1.5px #888; background: rgba(0,0,0,0.04) !important; }
.w.punct { background: transparent !important; cursor: default; }
/* ===== Motivation feedback animations =====
   Tiny rewards that fire on every word tap to make learning feel alive. */

/* Word tap-pulse: brief scale + slight glow without affecting layout. */
@keyframes tap-pulse-kf {
  0%   { transform: scale(1);    }
  40%  { transform: scale(1.18); }
  100% { transform: scale(1);    }
}
.w.tap-pulse {
  animation: tap-pulse-kf .32s cubic-bezier(.22,.6,.36,1);
  transform-origin: center;
}

/* Floating coin amount (e.g. "3¢") near the tapped word. Lifetime
   tuned to 1.5 s per spec — long enough for the user to read the
   number without losing the connection to the source word. */
.plus-one {
  position: absolute;
  z-index: 220;
  pointer-events: none;
  font-size: 14px; font-weight: 800;
  color: var(--accent-ink);
  text-shadow: 0 1px 2px rgba(0,0,0,0.08);
  transform: translate(-50%, 0);
  opacity: 0;
  transition: transform 1.5s ease, opacity 1.5s ease;
}
.plus-one.go {
  transform: translate(-50%, -28px);
  opacity: 1;
}
.plus-one.go { animation: plus-one-fade 1.5s forwards; }
@keyframes plus-one-fade {
  0%   { opacity: 0; transform: translate(-50%, 0); }
  15%  { opacity: 1; transform: translate(-50%, -8px); }
  70%  { opacity: 1; transform: translate(-50%, -22px); }
  100% { opacity: 0; transform: translate(-50%, -32px); }
}

/* Bottom-right combo bubble — two visual variants share the same slot:
     .combo     — "🔥 콤보 ×N" once N ≥ 3, warm orange, mid lifetime
     .milestone — "🔥 콤보 ×10/20/30..." extra-large + glow + longer hold */
.streak-bubble {
  position: fixed;
  right: 18px; bottom: 88px;
  z-index: 220;
  font-weight: 800;
  border-radius: 999px;
  pointer-events: none;
  opacity: 0; transform: translateY(8px) scale(0.95);
  transition: opacity .22s ease, transform .22s ease;
  white-space: nowrap;
}
.streak-bubble.show { opacity: 1; transform: translateY(0) scale(1); }

.streak-bubble.combo {
  background: linear-gradient(135deg, #ff8a3d 0%, #ff5722 100%);
  color: #fff;
  font-size: 14px;
  padding: 8px 14px;
  box-shadow: 0 6px 16px rgba(255,87,34,0.35);
}
.streak-bubble.milestone {
  background: linear-gradient(135deg, #ffd54f 0%, #ff8a3d 50%, #ff5722 100%);
  color: #fff;
  font-size: 18px;
  padding: 14px 22px;
  box-shadow: 0 0 0 4px rgba(255, 138, 61, 0.15),
              0 10px 28px rgba(255, 87, 34, 0.45);
  animation: milestone-pop .5s cubic-bezier(.22,.6,.36,1);
}
@keyframes milestone-pop {
  0%   { transform: translateY(8px) scale(0.7); opacity: 0; }
  60%  { transform: translateY(0)   scale(1.15); opacity: 1; }
  100% { transform: translateY(0)   scale(1);    opacity: 1; }
}

/* Random bonus toast — appears center-top, fades in/out. */
.bonus-toast {
  position: fixed;
  top: 14%; left: 50%;
  transform: translate(-50%, -10px);
  z-index: 230;
  background: #fff;
  border: 1.5px solid var(--accent);
  border-radius: 16px;
  padding: 14px 22px;
  box-shadow: 0 12px 30px rgba(0,0,0,0.16);
  font-size: 18px; font-weight: 800; color: var(--accent-ink);
  text-align: center;
  opacity: 0;
  transition: opacity .26s ease, transform .26s ease;
  pointer-events: none;
}
.bonus-toast.show { opacity: 1; transform: translate(-50%, 0); }
.bonus-toast .bt-sub { display: block; font-size: 12px; font-weight: 600; color: var(--muted); margin-top: 2px; }

/* Sentence currently being read aloud — a continuous underline that runs
   across the whole sentence (including the spaces / punctuation between
   words, so there are no gaps). NO bold so the line height never shifts
   when playback advances to a new sentence. */
.w.reading {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  /* 3px lower than before (was 4px) so the underline sits clearly
     beneath descenders and reads as a separate visual element. */
  text-underline-offset: 7px;
}
.w.punct.reading {
  /* punct elements get the underline too so the line is continuous. */
  text-decoration-color: var(--accent);
}

/* Corpus-jump highlight: pink underline applied for ~3 s when the user
   taps a sentence in the corpus popup and we navigate to that sentence
   in the body. The .fading variant kicks in 3 s later so the underline
   softly disappears (color drains to transparent over 0.4 s). */
.w.cp-jump-hl {
  text-decoration: underline;
  text-decoration-color: #db5395;
  text-decoration-thickness: 2px;
  text-underline-offset: 7px;
  transition: text-decoration-color 0.4s ease;
}
.w.punct.cp-jump-hl {
  text-decoration-color: #db5395;
}
.w.cp-jump-hl.fading,
.w.punct.cp-jump-hl.fading {
  text-decoration-color: transparent;
}
/* Pink RECTANGLE outline applied to the corpus's search word itself
   (in addition to the sentence underline) so the user immediately sees
   exactly which word triggered the jump. Same 3 s + 0.4 s fade timing
   as the underline. Outline (not border) so it doesn't shift the
   word's layout. */
.w.cp-jump-word-hl {
  outline: 2px solid #db5395;
  outline-offset: 2px;
  border-radius: 3px;
  transition: outline-color 0.4s ease;
}
.w.cp-jump-word-hl.fading {
  outline-color: transparent;
}

/* ===== 이어서 (cont) mode =====
   Pops a card next to the selection (mobile) or fills the right sidebar
   (PC). Line spacing stays IDENTICAL to 하나씩.
   Selected words are framed by <div class="cont-outline"> boxes (one per
   visual line) — they stay at the SAME weight as the surrounding text so
   the line-height never reflows when a word is added/removed. */
.lesson-text.mode-cont .w.cont-sel {
  /* font-weight intentionally NOT bolded — outline already conveys the
     selection state and bolding caused subtle line-shifts. */
}

/* "이어서(밑줄)" mode (contU): inline gray Korean translation rendered in
   the gap below the selection's last line. We DO widen line-height here so
   there's room for the label without the next line of text overlapping. */
.lesson-text.mode-contU { line-height: 2.7; }
.lesson-text.mode-contU .para { margin-bottom: 18px; }
.lesson-text.mode-contU .w.cont-sel {
  /* No bold — same reason as cont mode above. */
}
.lesson-text.mode-contU .cont-trans {
  font-size: 14px;
  font-weight: 600;
  /* 밑줄이어서 mode prioritises READABILITY over compactness — when the
     user has selected enough words that the Korean translation would
     overflow one line, let it wrap to 2-3 lines instead of truncating
     with an ellipsis or escaping sideways past the lesson box. The
     line-height: 2.7 + 18px paragraph margin in this mode gives plenty
     of vertical room before the next sentence's words. */
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  word-break: keep-all;
  overflow-wrap: break-word;
  line-height: 1.4;
  /* Pinning the right edge guarantees the Korean wraps inside the lesson
     box's bounds. JS still sets `left` to the leftmost selected-word
     position; with both edges fixed, the width is auto-computed and the
     Korean wraps to multiple lines instead of overflowing past the right
     padding (which used to trigger the ellipsis truncation). */
  right: 4px;
  max-width: none;
}

/* One rectangle per visual line of the running selection. JS recomputes
   these on every selection change. The boxes do NOT capture clicks so
   tapping "through" them still hits the words underneath. */
.cont-outline {
  position: absolute;
  border: 1.5px solid var(--ink);
  border-radius: 4px;
  pointer-events: none;
  background: rgba(0, 0, 0, 0.02);
  z-index: 1;
}

/* ===== PC sidebar (이어서 mode only) =====
   Right-side fixed panel that replaces the popup card on desktop. Shows the
   currently selected word / phrase with translation, level, heart, and an
   "add to phrases" button. Hidden on tablets and phones (uses popup there). */
.lesson-sidebar {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  /* Width comes from --sidebar-w (defined at :root). Changing that
     variable propagates to every chrome element that reserves space
     on the sidebar's left edge (lesson-main, lesson-bar, playbar,
     voice-menu) so the layout stays in sync. */
  width: var(--sidebar-w);
  background: #fff;
  border-left: 1px solid var(--line);
  box-shadow: -8px 0 24px rgba(0,0,0,0.06);
  /* Two-row flex layout: scrollable .ls-scroll fills the top, the
     fixed .ls-bottom-fixed strip pins to the bottom (level buttons
     always visible regardless of scroll position). Padding moved to
     the inner .ls-scroll so the bottom strip sits flush against the
     sidebar's bottom edge. */
  padding: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;             /* inner .ls-scroll handles scrolling */
  z-index: 90;
  transform: translateX(0);
  transition: transform .28s cubic-bezier(.22,.6,.36,1);
}
.lesson-sidebar .ls-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 56px 22px 16px;
}
/* Bottom-fixed strip: matches the lesson-bar's 64 px height so the
   sidebar's bottom row visually aligns with the bar (when present).
   Always visible whenever a word card is shown — never scrolls. */
.lesson-sidebar .ls-bottom-fixed {
  flex-shrink: 0;
  height: 64px;
  border-top: 1px solid var(--line);
  background: #fff;
  display: flex; align-items: center; justify-content: center;
  padding: 0 14px;
}
.lesson-sidebar .ls-bottom-fixed[hidden] { display: none; }
.lesson-sidebar .ls-bottom-fixed .level-bar {
  display: flex; align-items: center; gap: 8px;
}
/* Tooltip on hover for level buttons inside the bottom-fixed strip.
   Pops above the button so the user sees the stage's full Korean
   label without obscuring the body text below. */
.lesson-sidebar .ls-bottom-fixed .lv-btn {
  position: relative;
}
.lesson-sidebar .ls-bottom-fixed .lv-btn[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  background: #1f2937;
  color: #fff;
  padding: 5px 9px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease;
  z-index: 5;
  box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.lesson-sidebar .ls-bottom-fixed .lv-btn[data-tip]:hover::after {
  opacity: 1;
}
.lesson-sidebar[hidden] { display: block; transform: translateX(100%); }
.lesson-sidebar .ls-close {
  position: absolute; top: 14px; right: 14px;
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  display: grid; place-items: center;
  cursor: pointer; font-size: 18px; color: var(--ink);
}
.lesson-sidebar .ls-close img { width: 22px; height: 22px; object-fit: contain; }
/* Manual-open pill, parked immediately to the LEFT of the close (×)
   pill at the top-right. 14 (close right edge) + 36 (close width)
   + 8 (gap) = 58 px from the sidebar's right edge. Same shape /
   size as `.ls-close` so the two read as a matched pair. Hidden
   via [hidden] when the manual is already showing. */
.lesson-sidebar .ls-manual-btn {
  position: absolute; top: 14px; right: 58px;
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  display: grid; place-items: center;
  cursor: pointer; font-size: 18px; color: var(--ink);
  /* Above the scrollable .ls-scroll content so it stays clickable
     even when long word cards scroll behind it. */
  z-index: 2;
  transition: background .12s, border-color .12s, transform .05s;
}
.lesson-sidebar .ls-manual-btn:hover  { background: #f1f3f5; border-color: #cdd2d8; }
.lesson-sidebar .ls-manual-btn:active { transform: translateY(1px); }
.lesson-sidebar .ls-manual-btn[hidden] { display: none; }
/* Empty-state container is now a usage manual rather than a hint
   placard. Left-aligned, full color, body-text size — designed to be
   readable, not just a decorative "no selection" message. */
.lesson-sidebar .ls-empty {
  text-align: left; color: var(--ink);
  margin-top: 0; line-height: 1.55; font-size: 13px;
}
/* When the manual itself is rendered into ls-empty, push the title
   down a bit (so it clears the top buttons with comfortable air)
   and pull the bottom flush against the sidebar's lower edge — the
   font/line steppers are the last thing in the manual and read fine
   without trailing space. */
.lesson-sidebar .ls-empty .ls-manual {
  padding-top: 16px;
}
.lesson-sidebar .ls-scroll:has(.ls-manual) {
  padding-bottom: 0;        /* removes the default 16 px tail */
}
.lesson-sidebar .ls-empty .ls-manual > :last-child {
  margin-bottom: 0;
}
.lesson-sidebar .ls-manual-title {
  margin: 0 0 16px;
  font-size: 16px; font-weight: 800;
  color: var(--ink); text-align: center;
}
.lesson-sidebar .ls-manual-sec { margin-bottom: 14px; }
.lesson-sidebar .ls-manual-sec h4 {
  margin: 0 0 6px;
  font-size: 11px; font-weight: 800;
  color: #6b7280;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.lesson-sidebar .ls-manual-sec h4 .muted {
  font-weight: 600; text-transform: none; letter-spacing: 0;
  color: #9ca3af;
}
.lesson-sidebar .ls-manual-sec ul {
  margin: 0; padding-left: 16px;
  list-style-type: disc;
}
.lesson-sidebar .ls-manual-sec li {
  margin-bottom: 4px;
  color: var(--ink);
}
.lesson-sidebar .ls-manual-sec strong {
  color: #2563eb;
  font-weight: 700;
}
/* Inline level-color dots — small pills that pair with the level
   number/letter (0/1/2/3/4/V) so the user can see in the sidebar
   exactly which color each shortcut produces in the body. The
   intent is reinforced visually: the green darkens at level 1
   then fades through 2 → 3 → 4 → white(V), so "repeat the lesson
   to fade green to white" reads at a glance. Used both in the
   manual and (where useful) in tooltips / hint text. */
.kbd-dot {
  display: inline-block;
  width: 10px; height: 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
  vertical-align: middle;
  margin: 0 2px 0 0;
}
.kbd-dot.s0 { background: #fff; }                          /* 새로운 단어 — 색 없음 */
.kbd-dot.s1 { background: var(--word-1); border-color: var(--word-1); }
.kbd-dot.s2 { background: var(--word-2); border-color: var(--word-2); }
.kbd-dot.s3 { background: var(--word-3); border-color: var(--word-3); }
.kbd-dot.s4 { background: var(--word-4); border-color: var(--word-4); }
.kbd-dot.s5 { background: #fff; border: 1.5px solid var(--ink); }   /* V — 흰색 + 진한 테두리 */

/* Inline quiz-add icon for the manual text — wraps the same SVG
   that's used on every quiz button so the manual reference visually
   matches the actual buttons. Sized to ride alongside body text. */
.lesson-sidebar .quiz-ico-inline {
  display: inline-block;
  width: 14px; height: 14px;
  vertical-align: -3px;
  color: #db5395;
}
.lesson-sidebar .quiz-ico-inline svg {
  width: 100%; height: 100%;
  display: block;
}

/* Word-level emphasis section in the sidebar manual. Highlighted
   block so the progression and its meaning stand out from the
   surrounding shortcut list. */
.lesson-sidebar .ls-manual-sec.ls-manual-levels {
  background: #f8fafc;
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  padding: 8px 10px;
}
.lesson-sidebar .ls-manual-sec.ls-manual-levels h4 {
  margin-top: 0;
}
.lesson-sidebar .ls-manual-level-row {
  /* `nowrap` + tighter gap so all six stages
     (●0 → ●1 → ●2 → ●3 → ●4 → ●V) line up on a single row inside
     the (now ~420 px) sidebar. The arrow separators are inline
     spans rendering "→" so we don't need extra margin between
     them — the gap itself is the visual rhythm. */
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 3px;
  margin: 6px 0;
  font-size: 12px;
  white-space: nowrap;
}
.lesson-sidebar .ls-manual-level-row kbd {
  margin: 0;                /* gap handles spacing inside the row */
}
.lesson-sidebar .ls-manual-level-row .kbd-dot {
  margin: 0;                /* same — gap-driven spacing */
}
.lesson-sidebar .ls-manual-level-tip {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ink);
  line-height: 1.5;
}
.lesson-sidebar .ls-manual-level-tip strong {
  color: var(--ink);
}
/* Inline keyboard-key chips — used in the manual to mark shortcut keys
   (←, →, 0~4, V, Esc, ',', '.'). Subtle gray pill so the key reads as
   a discrete object inside the prose. */
.lesson-sidebar .ls-empty kbd {
  display: inline-block;
  padding: 1px 6px;
  margin: 0 1px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px; font-weight: 700;
  color: #1f2937;
  background: #f3f4f6;
  border: 1px solid #d1d5db;
  border-bottom-width: 2px;
  border-radius: 4px;
  vertical-align: 1px;
  line-height: 1;
}
/* Font + line-height adjustment block. Two side-by-side stepper
   groups (글자 크기 / 줄 간격) so the user can dial both axes from
   the sidebar without leaving the lesson. Visually separated from
   the manual sections above by a dashed divider. */
.lesson-sidebar .ls-manual-fontctl {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
}
.lesson-sidebar .ls-empty-fontctl-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
}
.lesson-sidebar .ls-empty-fontctl-group {
  flex: 1;
  text-align: center;
  min-width: 0;
}
.lesson-sidebar .ls-manual-fontctl h4 {
  margin: 0 0 8px;
  font-size: 11px; font-weight: 800;
  color: #6b7280;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.lesson-sidebar .ls-empty-fontctl {
  display: inline-flex; gap: 8px;
  margin-top: 0;
}
.lesson-sidebar .ls-empty-fontctl .toggle-btn {
  width: 32px; height: 32px;
  border-radius: 999px;
  font-size: 16px; font-weight: 700;
  line-height: 1;
  padding: 0;
  display: grid; place-items: center;
}
.lesson-sidebar .ls-head {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.lesson-sidebar .ls-head .speak {
  border: none; background: #f1f3f5;
  width: 34px; height: 34px; border-radius: 999px;
  display: grid; place-items: center; font-size: 16px;
  flex-shrink: 0; cursor: pointer;
}
.lesson-sidebar .ls-word {
  flex: 1; min-width: 0;
  font-size: 22px; font-weight: 800;
  overflow-wrap: break-word;
  /* Pink headword per spec — matches the example-sentence
     pink-highlighting so the user immediately sees the link. */
  color: #db5395;
}
.lesson-sidebar .ls-trans {
  font-size: 18px; color: var(--ink);
  padding: 12px 0 8px; border-top: 1px solid var(--line);
}
.lesson-sidebar .ls-trans .ko { font-weight: 700; }
.lesson-sidebar .wp-senses {
  border-top: 1px dashed var(--line); padding-top: 8px;
  font-size: 14px; max-height: none; margin-top: 8px;
}
.lesson-sidebar .ls-add {
  display: flex; align-items: center; gap: 8px; justify-content: center;
  width: 100%; margin-top: 18px;
  padding: 10px 14px;
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  color: var(--accent-ink); font-weight: 700; font-size: 14px;
  border-radius: 10px; cursor: pointer;
}
.lesson-sidebar .ls-add:hover { background: #d8ebe1; }
.lesson-sidebar .ls-add img { width: 22px; height: 22px; object-fit: contain; }
.lesson-sidebar .ls-add .ls-add-ico {
  display: block;
  flex-shrink: 0;
  /* `currentColor` on the SVG strokes means the icon tints together
     with the button's text color — both green by default, both pink in
     the .added state. The PNG used to sit there couldn't be tinted. */
}
.lesson-sidebar .ls-add.added { background: #fde0ec; border-color: #f0b9d2; color: #9c1450; }
/* Default state: show the plus, hide the check. .added flips both. */
.lesson-sidebar .ls-add .ls-add-check { display: none; }
.lesson-sidebar .ls-add.added .ls-add-plus  { display: none; }
.lesson-sidebar .ls-add.added .ls-add-check { display: inline; }
.lesson-sidebar .ls-add.added:hover { background: #fbcfdc; }

/* Floating "open sidebar" toggle. Sits in the upper-right corner of the
   viewport when the sidebar is closed. */
.lesson-sidebar-toggle {
  position: fixed;
  top: 16px; right: 16px;
  width: 38px; height: 38px;
  border-radius: 999px;
  background: #fff;
  border: 1px solid var(--line);
  cursor: pointer; z-index: 95;
  display: grid; place-items: center;
  font-size: 18px; color: var(--ink);
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
}
.lesson-sidebar-toggle img { width: 22px; height: 22px; object-fit: contain; transform: scaleX(-1); }
.lesson-sidebar-toggle[hidden] { display: none; }

/* On PC we always reserve space for the sidebar in the lesson layout, so
   that closing the sidebar leaves the text at the SAME width — just centered
   in the viewport instead of shifted left. */
@media (min-width: 1024px) {
  .lesson-main {
    max-width: calc(100% - var(--sidebar-w));
    margin-left: auto;
    margin-right: auto;
    /* PC body padding bumped from default 18 px 20 px →
       28 px 50 px (vertical +10, horizontal +30) per spec — gives the
       text more breathing room on wide viewports without changing
       the mobile padding. */
    padding: 28px 50px;
    transition: margin-left .28s, margin-right .28s;
  }
  body.sidebar-open .lesson-main {
    margin-right: var(--sidebar-w);
    margin-left: 0;
  }
  /* Playbar mirrors the lesson-main's outer width so it looks like
     a connected unit: same width, same horizontal position as the
     white card directly above it, with rounded TOP corners only
     (the bottom edge meets the viewport bottom). */
  .playbar {
    left: 0; right: 0;
    max-width: calc(min(100vw, 1200px) - 24px - var(--sidebar-w));
    margin-left: auto;
    margin-right: auto;
    padding: 10px 50px;        /* match lesson-main inner padding */
    /* Round only the top corners — the bottom is flush with the
       viewport's bottom edge so its corners don't show. */
    border-top-left-radius: var(--radius);
    border-top-right-radius: var(--radius);
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    /* The faint top border was useful when the playbar spanned the
       full viewport (separating it from the card). Now that it's a
       narrow rounded strip directly under the card — visually a
       sibling of `.lesson-main` itself — the border fights the
       corner radii. Drop it; `.lesson-main` has no border / shadow
       either, so the two read as the same card system. */
    border-top: none;
    box-shadow: none;
    transition: margin-left .28s, margin-right .28s;
  }
  body.sidebar-open .playbar {
    /* Same calc as lesson-bar — keeps the playbar's left edge
       glued to .lesson-main's left edge while the sidebar pushes
       both leftward. */
    margin-left: calc((100vw - min(100vw, 1200px)) / 2 + 12px);
    margin-right: var(--sidebar-w);
  }
  /* Voice picker pops above the playbar's voice button — track the
     playbar's right padding edge so the popup stays anchored under
     the voice button as the playbar's outer width changes with the
     viewport. Default (sidebar closed): mirror the playbar's
     centered width. Sidebar-open: pinned to sidebar + inner padding. */
  .voice-menu {
    right: calc((100vw - min(100vw, 1200px) + 24px + var(--sidebar-w)) / 2 + 50px);
  }
  body.sidebar-open .voice-menu {
    right: calc(var(--sidebar-w) + 50px);    /* sidebar + 50 inner padding */
  }
}

/* Don't show the sidebar or its toggle on small screens — those use the
   anchored popup card instead. */
@media (max-width: 1023px) {
  .lesson-sidebar, .lesson-sidebar-toggle { display: none !important; }
  body.sidebar-open .lesson-main { margin-right: 0; }
}

/* ===== practice overlay & shared card frame =====
   Two visual modes share this overlay:
     1. Menu state — small popup card snapped above the practice button.
        Backdrop is dimmed (no blur) per practice.jpeg.
     2. Active practice (words / sentences / quiz) — content fills the
        whole screen (no centered max-width card). */
.practice-overlay {
  position: fixed; inset: 0; z-index: 250;
  background: rgba(20,25,40,0.55);
  /* No blur — user wants a clean dim, not a frosted backdrop. */
}
.practice-overlay[hidden] { display: none; }
.practice-stage { position: absolute; inset: 0; }

/* Shared frame for word / sentence practice. Like .quiz-screen above,
   this now fills the viewport — full-screen vertically AND horizontally. */
.practice-card, .practice-menu {
  background: #fff; border-radius: 0;
  width: 100vw; height: 100vh;
  max-width: none;
  padding: 24px 22px 22px;
  box-shadow: none;
  position: absolute; inset: 0;
  max-height: 100vh;
  overflow-y: auto;
}
.practice-card .pm-close,
.practice-menu .pm-close {
  position: absolute; top: 12px; left: 14px;
  width: 32px; height: 32px; border-radius: 999px;
  border: none; background: #f1f3f5; cursor: pointer;
  font-size: 18px; line-height: 1;
}
.practice-card .pm-x {
  position: absolute; top: 12px; right: 14px;
  width: 32px; height: 32px; border-radius: 999px;
  border: none; background: #f1f3f5; cursor: pointer;
  font-size: 20px; line-height: 1;
}
.practice-menu .pm-close:hover, .practice-card .pm-close:hover, .practice-card .pm-x:hover { background: #e5e7eb; }

.practice-menu h3 { margin: 0 0 14px 30px; font-size: 18px; }
.practice-menu .pm-item {
  display: flex; align-items: center; gap: 14px;
  width: 100%; padding: 14px;
  border: 1px solid var(--line); border-radius: 12px;
  background: #fff; cursor: pointer;
  margin-bottom: 8px; text-align: left;
  transition: background .12s, border-color .12s;
}
.practice-menu .pm-item:hover { background: #f1f3f5; border-color: #cfd4d9; }
.practice-menu .pm-item:last-child { margin-bottom: 0; }
.practice-menu .pm-ico { font-size: 28px; flex-shrink: 0; }
.practice-menu .pm-body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.practice-menu .pm-title { font-weight: 800; font-size: 15px; }
.practice-menu .pm-desc  { color: var(--muted); font-size: 12px; line-height: 1.4; }

.practice-card .empty-msg { text-align: center; padding: 30px 10px 8px; }
.practice-card .empty-msg h3 { margin: 0 0 8px; font-size: 18px; }
.practice-card .empty-msg p  { color: var(--muted); margin: 0 0 12px; line-height: 1.5; }

/* progress / meta line shared across word, sentence, quiz */
.practice-card .wp-progress {
  height: 4px; background: #eef0f3; border-radius: 999px;
  overflow: hidden; margin: 24px 0 8px;
}
.practice-card .wp-progress-bar { height: 100%; background: var(--accent); transition: width .25s; }
.practice-card .wp-meta { color: var(--muted); font-size: 12px; text-align: center; margin-bottom: 14px; }

/* ===== Word practice: flip card ===== */
.flip-card {
  perspective: 1000px;
  height: 220px;
  margin: 16px 0;
  cursor: pointer;
}
.flip-card .fc-inner {
  position: relative; width: 100%; height: 100%;
  transform-style: preserve-3d;
  transition: transform .45s cubic-bezier(.22,.6,.36,1);
}
.flip-card.flipped .fc-inner { transform: rotateY(180deg); }
.flip-card .fc-front, .flip-card .fc-back {
  position: absolute; inset: 0;
  border-radius: 14px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
  padding: 16px;
  box-shadow: 0 4px 18px rgba(0,0,0,0.08);
}
.flip-card .fc-front {
  background: linear-gradient(135deg, #c9def0 0%, #e6eef7 100%);
}
.flip-card .fc-back {
  background: linear-gradient(135deg, #d8edd8 0%, #e3efe0 100%);
  transform: rotateY(180deg);
}
.flip-card .fc-en { font-size: 32px; font-weight: 800; color: var(--ink); }
.flip-card .fc-en-small { font-size: 14px; color: var(--muted); margin-top: 12px; }
.flip-card .fc-ko { font-size: 26px; font-weight: 700; color: var(--ink); text-align: center; }
.flip-card .fc-hint { font-size: 12px; color: var(--muted); margin-top: 12px; }
.flip-card .fc-speak {
  position: absolute; top: 10px; right: 10px;
  width: 36px; height: 36px; border-radius: 999px;
  border: none; background: rgba(255,255,255,0.7);
  cursor: pointer; font-size: 16px;
}

.wp-rating { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 4px; }
.wp-rate {
  border: 1px solid var(--line); background: #fff;
  padding: 10px 6px; border-radius: 10px;
  display: flex; flex-direction: column; gap: 2px;
  font-weight: 700; font-size: 14px; cursor: pointer;
}
.wp-rate span { font-size: 10px; color: var(--muted); font-weight: 500; }
.wp-rate:hover { background: #f1f3f5; }
.wp-rate.r0 { border-color: #f0b9d2; }
.wp-rate.r1 { border-color: #a8c2dc; }
.wp-rate.r2 { border-color: var(--accent); color: var(--accent-ink); }

/* ===== Sentence practice: tap-to-reorder ===== */
.sp-ko {
  text-align: center; font-size: 17px; font-weight: 600;
  padding: 10px 6px 14px; color: var(--ink);
  border-bottom: 1px solid var(--line); margin-bottom: 14px;
}
.sp-answer {
  min-height: 56px; padding: 8px;
  border: 2px dashed var(--line); border-radius: 12px;
  display: flex; flex-wrap: wrap; gap: 6px; align-content: flex-start;
  margin-bottom: 12px;
}
.sp-pool {
  display: flex; flex-wrap: wrap; gap: 6px;
  min-height: 40px;
}
.sp-tile {
  border: 1px solid var(--line); background: #fff;
  padding: 8px 12px; border-radius: 8px;
  font-size: 15px; font-weight: 600;
  cursor: pointer;
  transition: background .12s, transform .05s;
}
.sp-tile:hover { background: #f1f3f5; }
.sp-tile:active { transform: translateY(1px); }
.sp-tile.placed { background: var(--word-new); border-color: #a8c2dc; }
.sp-feedback { text-align: center; margin-top: 14px; min-height: 36px; }
.sp-feedback .sp-ok { color: var(--accent-ink); font-weight: 800; margin-bottom: 8px; }
.sp-feedback .sp-bad { color: #b03a3a; font-weight: 700; margin-bottom: 8px; }
.sp-feedback .btn { margin: 0 4px; }

/* ===== Quiz (LingQ-style reference) =====
   Full-screen card — fills the viewport vertically and horizontally so
   the user has plenty of room for the audio prompt + 4 large options.
   Choice option width is constrained to 84vw (centered) per spec. */
.practice-overlay .quiz-screen {
  background: #fff;
  width: 100vw; height: 100vh;
  max-width: none;
  border-radius: 0;
  box-shadow: none;
  padding: 18px 20px 28px;
  position: absolute; inset: 0;
  max-height: 100vh;
  overflow-y: auto;
}
.quiz-header {
  display: flex; align-items: center; gap: 14px;
  padding: 4px 0 24px;
}
.quiz-header .qz-x,
.quiz-header .qz-gear,
.quiz-header .qz-dots {
  width: 32px; height: 32px; border-radius: 8px;
  border: none; background: transparent;
  display: grid; place-items: center;
  cursor: pointer; color: var(--ink); flex-shrink: 0;
}
.quiz-header .qz-x:hover,
.quiz-header .qz-gear:hover,
.quiz-header .qz-dots:hover { background: #f1f3f5; }
.quiz-header .qz-bar {
  flex: 1; height: 5px;
  background: #e5e7eb; border-radius: 999px; overflow: hidden;
}
.quiz-header .qz-bar-fill {
  height: 100%; background: #46b855;     /* LingQ green */
  border-radius: 999px;
  transition: width .25s;
}

.quiz-body { padding: 0 4px; }
.qz-loading { text-align: center; color: var(--muted); padding: 60px 0; }

/* Audio prompt (quiz1.png): big circular ▶ + small turtle slow-play. */
.qz-audio-prompt {
  position: relative;
  width: 120px; height: 120px;
  margin: 24px auto 36px;
}
.qz-play {
  width: 100px; height: 100px; border-radius: 999px;
  border: 2.5px solid #1f2937; background: #fff;
  display: grid; place-items: center; cursor: pointer;
  color: #1f2937;
  transition: transform .08s;
}
.qz-play:active { transform: scale(0.97); }
.qz-play svg { margin-left: 6px; }   /* nudge ▶ to optical center */
.qz-slow {
  position: absolute; right: -4px; bottom: -4px;
  width: 44px; height: 44px; border-radius: 999px;
  border: 2px solid #1f2937; background: #fff;
  display: grid; place-items: center; cursor: pointer;
  font-size: 20px;
}

/* Text prompt (quiz3.png): smaller speaker icon + word below it. */
.qz-text-prompt {
  text-align: center; padding: 16px 0 24px;
}
.qz-text-prompt .qz-icon-speak {
  border: none; background: transparent;
  cursor: pointer; color: #1f2937;
  display: inline-block; margin-bottom: 12px;
}
.qz-prompt-word {
  font-size: 26px; font-weight: 600; color: #1f2937;
  letter-spacing: -0.2px;
}

.qz-helper {
  color: #6b7280; font-size: 14px;
  margin: 12px 4px 14px;
}
.qz-options {
  display: grid; grid-template-columns: 1fr; gap: 10px;
  /* Choice options span 84% of viewport width, centered — matches the
   spec for the full-screen quiz layout. */
  width: 84vw;
  max-width: 84vw;
  margin: 0 auto;
}
.qz-option {
  text-align: left;
  border: 1.5px solid var(--line);
  background: #fff;
  padding: 18px 18px;
  border-radius: 10px;
  font-size: 15px; font-weight: 500; color: #1f2937;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.qz-option:hover:not(:disabled) { background: #fafbfc; border-color: #c0c4ca; }
.qz-skip {
  display: block; margin: 22px auto 6px;
  border: none; background: transparent;
  color: #6b7280; font-size: 15px; font-weight: 500;
  cursor: pointer; text-decoration: none;
  padding: 10px 16px;
}
.qz-skip:hover { color: var(--ink); }

/* "You answered: …" small caption above the feedback card */
.qz-you-said {
  text-align: center; color: #6b7280; font-size: 13px;
  padding: 4px 0 14px;
}

/* Feedback card (quiz2.png / quiz4.png) */
.qz-feedback-card {
  background: #fff;
  border-radius: 14px;
  border: 1px solid var(--line);
  padding: 28px 22px 18px;
  text-align: center;
  position: relative;
}
.qz-feedback-card .qz-fb-status {
  font-weight: 700; font-size: 16px;
  margin-bottom: 14px;
}
.qz-feedback-card .qz-fb-status.ok  { color: #2e7d32; }
.qz-feedback-card .qz-fb-status.bad { color: #d97706; }
.qz-feedback-card .qz-icon-speak {
  border: none; background: transparent; cursor: pointer;
  color: #1f2937; margin-bottom: 6px;
}
.qz-feedback-card .qz-fb-word {
  font-size: 22px; font-weight: 700; color: #1f2937;
  margin-bottom: 12px;
}
.qz-feedback-card .qz-fb-ko {
  color: #2e7d32; font-weight: 700; font-size: 16px;
  margin-bottom: 12px;
}
.qz-feedback-card .qz-fb-ctx {
  color: #6b7280; font-size: 13px; line-height: 1.55;
  padding: 0 8px; margin-bottom: 18px;
}
.qz-continue {
  display: block; width: 100%;
  margin-top: 14px;
  padding: 14px 20px;
  background: #fff;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  font-size: 15px; font-weight: 700; color: #1f2937;
  cursor: pointer;
}
.qz-continue:hover { background: #fafbfc; border-color: #c0c4ca; }

/* ===== Shared level-picker bar (🗑 1 2 3 4 ✓) =====
   Used in the quiz feedback card AND the PC sidebar bottom row. */
.level-bar {
  display: flex; align-items: center; justify-content: center;
  gap: 8px; flex-wrap: wrap;
  padding: 6px 0;
}
.level-bar .lv-btn {
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1.5px solid #d1d5db;
  background: #fff;
  font-size: 14px; font-weight: 700; color: #4b5563;
  cursor: pointer;
  transition: background .12s, border-color .12s;
}
.level-bar .lv-btn:hover { background: #f1f3f5; }
.level-bar .lv-btn.ignore { color: #9ca3af; font-size: 16px; }
.level-bar .lv-btn.known  { color: #1f2937; font-size: 14px; }
/* Quiz-add pill — same shape/size as the level buttons but uses a
   pink fill in its active (hearted = added to quiz) state to
   distinguish it from the green "active level" treatment. Icon is
   the inline SVG (same pictogram as the lesson-bar's practice
   button), centered and sized down to fit the 36 px pill. */
.level-bar .lv-btn.heart {
  color: #db5395;                  /* outline papers in pink */
  padding: 0;
  display: grid; place-items: center;
}
.level-bar .lv-btn.heart .quiz-ico {
  width: 20px; height: 20px; display: block;
}
.level-bar .lv-btn.heart.active {
  background: #fde0ec;
  border-color: #f0b9d2;
  color: #9c1450;                  /* deeper pink stroke when added */
}
.level-bar .lv-btn.heart.active:hover {
  background: #fbcfdc;
}
.level-bar .lv-btn.active {
  background: #c8e6c9; border-color: #66bb6a; color: #2e7d32;
}

/* ===== Practice menu (anchored bottom-right card) =====
   Matches practice.jpeg: dim full-screen with a small white card snapped
   directly above the practice button (lesson-bar's right end), listing
   icon + label + (count) per item. */
.practice-anchor.menu-anchor {
  position: fixed;
  right: 12px;
  /* Sits just above the lesson-bar (~64px tall) with a small gap. */
  bottom: calc(env(safe-area-inset-bottom, 0px) + 76px);
  display: block;
  margin: 0;
}
/* Non-menu uses (kept for backwards compat — should be empty now). */
.practice-anchor:not(.menu-anchor) {
  display: flex; justify-content: flex-end;
  margin-top: 30px;
}
.practice-card-anchored {
  width: 260px;
  background: #fff;
  border-radius: 14px;
  border: 1px solid var(--line);
  box-shadow: 0 18px 40px rgba(0,0,0,0.18);
  padding: 8px;
  display: flex; flex-direction: column; gap: 2px;
}
.practice-card-anchored .pa-item {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px;
  border: none; background: transparent;
  cursor: pointer; font-size: 15px; color: #1f2937;
  border-radius: 10px; text-align: left;
}
.practice-card-anchored .pa-item:hover { background: #f4f6f8; }
.practice-card-anchored .pa-ico {
  font-size: 22px; flex-shrink: 0;
  width: 32px; text-align: center;
}
.practice-card-anchored .pa-text { flex: 1; min-width: 0; }
.practice-card-anchored .pa-count { color: #6b7280; font-weight: 500; }

/* ===== Sidebar — popular meanings + pos tags + level bar ===== */
.lesson-sidebar .ls-card-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 14px; flex-wrap: wrap;
}
.lesson-sidebar .ls-card-head .ls-word {
  font-size: 22px; font-weight: 800; color: #1f2937;
  flex-shrink: 0;
}
.lesson-sidebar .ls-card-head .speak {
  margin-left: auto;
  border: none; background: #f1f3f5;
  width: 32px; height: 32px; border-radius: 999px;
  display: grid; place-items: center; font-size: 14px;
  cursor: pointer;
}
/* Speaker icon was removed per spec — push the right-side button group
   (heart / 코퍼스 / 사전) to the right edge using the FIRST of those
   buttons as the auto-margin anchor (replaces the role .speak used to
   play). Heart is hidden on phrase selections, so we also tag the
   corpus button as a fallback anchor. */
.lesson-sidebar .ls-card-head .wp-heart,
.lesson-sidebar .ls-card-head .ls-corpus-btn:first-of-type {
  margin-left: auto;
}
.lesson-sidebar .ls-card-head .wp-heart ~ .ls-corpus-btn {
  margin-left: 0;     /* if heart claimed the auto-margin, corpus stays adjacent */
}
.lesson-sidebar .ls-pos-tags {
  display: inline-flex; gap: 6px; flex-wrap: wrap;
}
.lesson-sidebar .ls-pos-tag {
  display: inline-block;
  font-size: 11px; font-weight: 700; color: #4b5563;
  background: #f1f3f5;
  padding: 3px 8px; border-radius: 999px;
  text-transform: capitalize;
}
.lesson-sidebar .ls-section-label {
  font-size: 12px; font-weight: 700; color: #6b7280;
  text-transform: uppercase; letter-spacing: 0.5px;
  margin-top: 14px; margin-bottom: 6px;
}
.lesson-sidebar .ls-section-label[hidden] { display: none; }
.lesson-sidebar .ls-trans {
  font-size: 16px; color: #1f2937; padding: 4px 0 8px;
}
.lesson-sidebar .ls-trans .ko { font-weight: 700; }
/* Popular Meanings — see the canonical block ~line 2870 below. The earlier
   flex-row layout caused Korean text to wrap one character per line when the
   example was long (Korean has no word boundaries to break on, so a narrow
   `flex: 1` cell collapses each char onto its own row). The block below uses
   display: block + word-break: keep-all to keep meanings on one line. */

.lesson-sidebar .level-bar { margin-top: 4px; }

/* PC-only arrow-key direction toggle inside the sidebar. Two-segment pill
   that flips which side of the running selection the ← / → keys control. */
.ls-anchor-toggle {
  display: inline-flex;
  background: #f1f3f5;
  border-radius: 10px;
  padding: 3px;
  gap: 2px;
}
.ls-anchor-toggle .lat-btn {
  width: 56px; height: 32px;
  border: none; background: transparent;
  border-radius: 8px;
  font-size: 17px; font-weight: 700;
  color: #6b7280;
  cursor: pointer;
  transition: background .12s, color .12s, box-shadow .12s;
}
.ls-anchor-toggle .lat-btn:hover { color: var(--ink); }
.ls-anchor-toggle .lat-btn.active {
  background: #fff;
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(0,0,0,0.10);
}

/* ===== Sidebar empty-state word list ===== */
.lesson-sidebar .ls-list-head {
  display: flex; align-items: baseline; gap: 8px; margin-bottom: 10px;
}
.lesson-sidebar .ls-list-title { font-weight: 800; font-size: 15px; }
.lesson-sidebar .ls-list-count {
  font-size: 12px; color: var(--muted);
  background: #f1f3f5; padding: 2px 8px; border-radius: 999px;
}
.lesson-sidebar .ls-word-list {
  list-style: none; margin: 0; padding: 0;
  font-size: 14px;
}
.lesson-sidebar .ls-word-item {
  display: flex; align-items: baseline; gap: 8px;
  padding: 6px 4px; border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.lesson-sidebar .ls-word-item:hover { background: #fafbfc; }
.lesson-sidebar .ls-word-item .ls-w {
  font-weight: 700; padding: 1px 5px; border-radius: 3px;
  flex-shrink: 0;
}
.lesson-sidebar .ls-word-item .ls-tx { flex: 1; min-width: 0; color: var(--muted); }

/* The floating phrase translation in 이어서 mode. JS positions it just below
   the last line of the current selection; the inter-line gap has enough room
   for one row of small gray text. */
.cont-trans {
  position: absolute;
  font-size: 12px;
  color: #6b7280;
  font-weight: 500;
  line-height: 1.15;
  pointer-events: none;
  background: #fff;
  padding: 0 4px;
  border-radius: 3px;
  z-index: 5;
  white-space: nowrap;
  max-width: calc(100% - 8px);
  overflow: hidden;
  text-overflow: ellipsis;
}

.tx-loading { color: var(--muted); font-style: italic; }

/* ===== anchored word-pop: small popup directly below the clicked word.
   Drag up to expand into a Naver Endic dictionary view, drag down to close. ===== */
.word-pop {
  position: fixed;
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 14px 40px rgba(0,0,0,0.22);
  z-index: 150;
  width: 280px;
  max-width: calc(100vw - 24px);
  user-select: none; -webkit-user-select: none;
  touch-action: none;             /* drag handler owns gestures */
  overflow: hidden;
}
.word-pop[hidden] { display: none; }
.word-pop .wp-small  { padding: 12px 14px 10px; cursor: grab; }
.word-pop .wp-small:active { cursor: grabbing; }
.word-pop .wp-large  { display: none; flex-direction: column; height: 100%; }
.word-pop.expanded { transition: top .25s ease, left .25s ease, width .25s ease, height .25s ease, border-radius .25s ease; }
.word-pop.expanded .wp-small  { display: none; }
.word-pop.expanded .wp-large  { display: flex; }
.word-pop.expanded {
  top: 16px !important; left: 16px !important;
  width: calc(100vw - 32px); height: calc(100vh - 32px);
  border-radius: 16px;
  cursor: default;
  touch-action: auto;
}

.wp-head {
  display: flex; align-items: center; gap: 10px;
}
.wp-head .speak {
  border: none; background: #f1f3f5;
  width: 32px; height: 32px; border-radius: 999px;
  display: grid; place-items: center; font-size: 15px;
  flex-shrink: 0; cursor: pointer;
}
.wp-head .speak:hover { background: #e7eaee; }
.wp-head .word-text {
  flex: 1; min-width: 0;
  font-size: 18px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wp-trans {
  font-size: 16px; color: var(--ink);
  padding: 8px 0 4px; margin-top: 6px;
  border-top: 1px solid var(--line);
}
.wp-trans .ko { font-weight: 600; }
.wp-hint {
  font-size: 11px; color: var(--muted);
  text-align: center; margin-top: 4px;
}
.wp-hint kbd {
  font-family: ui-monospace, monospace; font-size: 10px;
  background: #f1f3f5; border: 1px solid var(--line);
  border-radius: 4px; padding: 0 4px;
}

/* Multi-meaning list under the main translation. */
.wp-senses {
  list-style: none; margin: 6px 0 0; padding: 0;
  font-size: 13px; line-height: 1.5;
  color: var(--ink);
  border-top: 1px dashed var(--line);
  padding-top: 6px;
  max-height: 140px; overflow-y: auto;
}
.wp-senses li { padding: 2px 0; }
.wp-senses .pos {
  color: var(--muted); font-size: 11px; font-style: italic;
  margin-right: 4px;
}

/* Quiz-add button (formerly heart) on the word popup. The icon is
   an inline SVG; this rule sizes the button + tints the stroke
   based on `.on` (added → pink). */
.wp-heart {
  border: none; background: transparent;
  width: 28px; height: 28px; border-radius: 999px;
  display: grid; place-items: center;
  line-height: 1; color: #c0c4ca;
  cursor: pointer; flex-shrink: 0;
  padding: 0;
}
.wp-heart:hover { background: #f1f3f5; color: #d23578; }
.wp-heart.on    { color: #d23578; }
.wp-heart .quiz-ico { width: 18px; height: 18px; display: block; }

/* Small X close on the word card (PC convenience; matches keyboard Esc). */
.wp-close-x {
  border: none; background: #f1f3f5;
  width: 26px; height: 26px; border-radius: 999px;
  display: grid; place-items: center;
  font-size: 16px; line-height: 1; color: var(--ink);
  cursor: pointer; flex-shrink: 0;
}
.wp-close-x:hover { background: #e5e7eb; }

/* expanded large view */
.wp-large-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-bottom: 1px solid var(--line);
  flex-shrink: 0;
}
.wp-large-head .word-text { flex: 1; font-size: 18px; font-weight: 700; }
.wp-large-head .open-naver {
  font-size: 12px; color: var(--accent-ink); text-decoration: none;
  border: 1px solid var(--accent); padding: 5px 10px; border-radius: 999px;
  white-space: nowrap;
}
.wp-large-head .open-naver:hover { background: var(--accent-soft); }
.wp-close {
  width: 34px; height: 34px; border: none; background: #f1f3f5;
  border-radius: 999px; font-size: 22px; line-height: 1;
  cursor: pointer; flex-shrink: 0;
}
.wp-close:hover { background: #e5e7eb; }
.wp-iframe-wrap {
  flex: 1; position: relative; background: #f8f9fa;
}
.wp-iframe-wrap iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%; border: 0;
}
.wp-iframe-wrap .wp-fallback {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; padding: 24px;
  color: var(--muted); font-size: 14px;
  pointer-events: none;
}
.wp-iframe-wrap .wp-fallback a {
  pointer-events: auto;
  margin-top: 10px;
  color: var(--accent-ink); font-weight: 700;
  border: 1px solid var(--accent); padding: 8px 16px;
  border-radius: 999px; text-decoration: none;
}

/* ===== KWIC concordance corpus view (built from the user's own lessons) ===== */
.wp-corpus {
  position: absolute; inset: 0;
  background: #fff; overflow-y: auto;
  padding: 16px 20px 24px;
  font-size: 14px;
}
/* (Old "N개 문장 발견" info line removed — count is now shown next to the
   big keyword title in the popup header.) */
.wp-corpus .corpus-loading,
.wp-corpus .corpus-empty {
  text-align: center; color: var(--muted);
  padding: 40px 20px; line-height: 1.5;
}
.wp-corpus .corpus-empty .hint { font-size: 12px; margin-top: 8px; }
/* All rows share one CSS grid so the keyword column aligns vertically
   across every sentence. The grid itself is the single scroll container —
   swiping any row pans ALL rows together (mobile + desktop). */
.wp-corpus .corpus-grid {
  display: grid;
  grid-template-columns: auto auto auto auto;   /* num | left | kw | right */
  column-gap: 8px;
  row-gap: 6px;
  align-items: baseline;
  font-size: 14px;
  line-height: 1.6;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  padding-bottom: 12px;
}
.wp-corpus .corpus-grid::-webkit-scrollbar       { width: 6px; height: 6px; }
.wp-corpus .corpus-grid::-webkit-scrollbar-thumb { background: #c0c4ca; border-radius: 999px; }
.wp-corpus .corpus-grid .num {
  text-align: right;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* Pin the row number to the left edge so it stays visible while the
     sentences scroll horizontally. */
  position: sticky; left: 0;
  background: #fff;
  padding-right: 4px;
  z-index: 1;
}
.wp-corpus .corpus-grid .left {
  text-align: right;
  white-space: nowrap;
  color: var(--ink);
}
.wp-corpus .corpus-grid .right {
  text-align: left;
  white-space: nowrap;
  color: var(--ink);
}
.wp-corpus .corpus-grid mark {
  background: #c5e8c5; color: var(--ink);
  font-weight: 700;
  padding: 2px 6px; border-radius: 3px;
  white-space: nowrap;
}

/* ===== state badge: small round button on the right of the word row ===== */
.tx-state-badge {
  width: 36px; height: 36px; border-radius: 999px;
  border: 1.5px solid var(--line); background: #fff;
  display: grid; place-items: center;
  font-weight: 800; font-size: 14px; color: var(--ink);
  cursor: pointer; flex-shrink: 0;
  transition: transform .08s, box-shadow .15s;
}
.tx-state-badge:hover { box-shadow: var(--shadow); }
.tx-state-badge:active { transform: scale(0.95); }
.tx-state-badge.s1 { background: var(--word-1); border-color: var(--word-1); }
.tx-state-badge.s2 { background: var(--word-2); border-color: var(--word-2); }
.tx-state-badge.s3 { background: var(--word-3); border-color: var(--word-3); }
.tx-state-badge.s4 { background: var(--word-4); border-color: var(--word-4); }
.tx-state-badge.s5 { background: #fff; border-color: var(--ink); }
.tx-state-badge.sx { background: #f1f3f5; color: var(--muted); }

/* ===== state picker menu — positioned via JS, lives in <body> ===== */
.state-menu {
  position: fixed;
  background: #fff; border: 1px solid var(--line); border-radius: 14px;
  box-shadow: var(--shadow-lg);
  z-index: 200;            /* above sheet (40) and playbar (35) */
  width: 260px; padding: 6px;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}
.state-menu-item {
  display: flex; align-items: center; gap: 12px;
  width: 100%; padding: 10px 12px; border: none; background: transparent;
  text-align: left; border-radius: 9px; font-size: 14px; cursor: pointer;
  font-weight: 600; color: var(--ink);
}
.state-menu-item:hover { background: #f4f6f8; }
.state-menu-item.active { background: var(--accent-soft); }
.state-menu-item .icon {
  width: 30px; height: 30px; border-radius: 999px;
  border: 1.5px solid var(--line); background: #fff;
  display: grid; place-items: center;
  font-weight: 800; font-size: 13px; flex-shrink: 0;
}
.state-menu-item[data-state="-1"] .icon { background: #fff; color: var(--muted); border-style: dashed; }
.state-menu-item[data-state="1"]  .icon { background: var(--word-1); border-color: var(--word-1); }
.state-menu-item[data-state="2"]  .icon { background: var(--word-2); border-color: var(--word-2); }
.state-menu-item[data-state="3"]  .icon { background: var(--word-3); border-color: var(--word-3); }
.state-menu-item[data-state="4"]  .icon { background: var(--word-4); border-color: var(--word-4); }
.state-menu-item[data-state="5"]  .icon { background: #fff; color: var(--ink); border-color: var(--ink); }

/* ===== lesson page progress (top, mobile-style scrubber) ===== */
.lesson-progress {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
.lesson-progress .lp-track {
  position: relative; flex: 1; height: 4px; background: #eef0f3;
  border-radius: 999px; overflow: visible;
}
.lesson-progress .lp-fill {
  position: absolute; left: 0; top: 0; height: 100%;
  background: var(--accent); border-radius: 999px;
}
.lesson-progress .lp-dot {
  position: absolute; top: 50%; transform: translate(-50%, -50%);
  width: 12px; height: 12px; border-radius: 999px;
  background: var(--accent); box-shadow: 0 0 0 4px rgba(58,167,118,0.18);
}
.lesson-progress .lp-pages {
  font-size: 12px; color: var(--muted); white-space: nowrap;
}
.lesson-progress .lp-btn {
  border: none; background: transparent; width: 32px; height: 32px;
  border-radius: 8px; display: grid; place-items: center;
  font-size: 16px; color: var(--muted); cursor: pointer;
}
.lesson-progress .lp-btn:hover { background: #f1f3f5; color: var(--ink); }
.lesson-progress .lp-btn[disabled] { opacity: .35; cursor: not-allowed; }

/* horizontal page slide — old slides out, new slides in simultaneously */
.lesson-text.slide-anim { transition: transform .26s cubic-bezier(.22,.6,.36,1); }
.lesson-text.slide-out-left  { transform: translateX(-100%); }
.lesson-text.slide-out-right { transform: translateX( 100%); }
.lesson-text.slide-from-right { transform: translateX( 100%); }
.lesson-text.slide-from-left  { transform: translateX(-100%); }

/* ===== bottom strips ===== */
/* Default lesson bar — slim strip with a circle play button on the left and
   the word-card icon on the right. Pressing play swaps it for the playbar. */
.lesson-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
  background: #fff; border-top: 1px solid var(--line);
  padding: 10px 18px;
  display: flex; align-items: center; justify-content: space-between;
  height: 64px;
}
.lesson-bar[hidden] { display: none; }
@media (min-width: 1024px) {
  /* PC layout — the white lesson-bar background is removed per spec.
     The bar element stays (so its buttons keep working) but it
     becomes a transparent strip whose horizontal bounds match the
     lesson-main card; the buttons inside appear to "float" in front
     of the page background. Each button gets its own drop-shadow
     (added per-button below) so the floating effect reads cleanly.

     ⚠️ The bar is `position: fixed` so its `100%` resolves to the
     viewport, while `.lesson-main` lives inside `.container`
     (max-width: 1200, horizontal padding 12 px each side) so its
     `100%` resolves to the container's content width. Without the
     `min(100vw, 1200px)` cap below, viewports wider than 1200 px
     would make the bar substantially wider than the white text
     card and the play / practice buttons would float OUTSIDE the
     card's vertical edges. Mirroring the container math (cap at
     1200, subtract 24 for padding, subtract --sidebar-w for the
     sidebar reserve) keeps the bar's outer edges flush with the
     card's outer edges at every viewport width. */
  .lesson-bar {
    background: transparent;
    border-top: none;
    box-shadow: none;
    left: 0; right: 0;
    max-width: calc(min(100vw, 1200px) - 24px - var(--sidebar-w));
    margin-left: auto;
    margin-right: auto;
    /* 50 px = lesson-main's inner horizontal padding. With the bar's
       outer edges aligned to lesson-main's outer edges (above), this
       padding lands the play button on the left and the practice
       button on the right exactly at the body-text vertical lines —
       same x-coords where the body's words start / end. */
    padding: 10px 50px;
    transition: margin-left .28s, margin-right .28s;
  }
  /* Sidebar open — lesson-main shifts left (margin-left: 0,
     margin-right: var(--sidebar-w)). The bar is `position: fixed` so
     its containing block is the viewport, NOT `.container` — auto
     margins won't reproduce the container's left offset. We mirror
     it explicitly: `(100vw - container_outer) / 2 + 12 (container
     padding)` puts the bar's left edge exactly at .lesson-main's
     left edge for every viewport width. */
  body.sidebar-open .lesson-bar {
    margin-left: calc((100vw - min(100vw, 1200px)) / 2 + 12px);
    margin-right: var(--sidebar-w);
  }
  /* Drop-shadow on every button in the bar so each one reads as a
     floating pill against the page background (no longer a row of
     pills sitting on a white plate). */
  .lesson-bar .lb-play,
  .lesson-bar .lb-arrow,
  .lesson-bar .lb-mode,
  .lesson-bar .lb-practice {
    box-shadow: 0 2px 8px rgba(15, 20, 28, 0.12),
                0 1px 2px rgba(15, 20, 28, 0.08);
  }
}
.lesson-bar .lb-play {
  /* Match the adjacent arrow button's size + border on mobile so the
     bottom bar reads as a row of similarly-sized circular controls
     instead of one big black-bordered play button next to small gray
     arrows. PC bumps these up via the @media block below to match
     PC's larger arrow size. */
  width: 36px; height: 36px; border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff; color: var(--ink);
  display: grid; place-items: center;
  font-size: 14px; cursor: pointer;
  transition: background .12s, color .12s;
}
.lesson-bar .lb-play:hover { background: #f1f3f5; }
@media (min-width: 1024px) {
  .lesson-bar .lb-play { width: 48px; height: 48px; font-size: 16px; }
}
.lesson-bar .lb-spacer { flex: 1; }
.lesson-bar .lb-card {
  display: inline-flex; align-items: center; justify-content: center;
  height: 44px; padding: 0 6px;
  text-decoration: none;
}
.lesson-bar .lb-card img {
  height: 36px; width: auto; display: block;
  object-fit: contain;
}
/* Progress slider sits between play and practice; no own padding so the
   track stretches the full middle gap. */
.lesson-bar .lb-progress {
  flex: 1; min-width: 0;
  margin: 0 12px;
}
.lesson-bar .lb-progress .lp-pages { font-size: 11px; }
.lesson-bar .lb-practice {
  width: 44px; height: 44px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  font-size: 13px; font-weight: 700;
  color: #4b5563;                                 /* icon stroke base color */
  flex-shrink: 0;
  transition: background .12s, border-color .12s, color .12s, transform .05s;
}
.lesson-bar .lb-practice:hover {
  background: #e5e7eb;
  border-color: #cfd4d9;
  color: #1f2937;                                 /* icon darkens on hover */
}
.lesson-bar .lb-practice:active {
  background: #d1d5db;
  color: #1f2937;
  transform: translateY(1px);
}
/* Optional .open class for when the practice popup is showing — icon
   shifts to the same darker tone so the button reads as "active". */
.lesson-bar .lb-practice.open {
  background: #e5e7eb;
  border-color: #9ca3af;
  color: #1f2937;
}
.lesson-bar .lb-practice img { width: 30px; height: 30px; object-fit: contain; }
.lesson-bar .lb-practice .lb-practice-ico {
  display: block;
  /* `currentColor` on the SVG strokes means the icon tracks the button's
     `color` property — gray by default, darker on hover/active. The
     inner rect stays white-filled so the "stack of cards" silhouette
     is still readable against the dark stroke. */
}

/* Mobile-only middle controls when the user is in 이어서 mode: replaces the
   page-progress slider with [←] [eye toggle] [→]. The eye is a single button
   whose icon (eye-left.svg / eye-right.svg) reflects the current ANCHOR_MODE. */
/* Cont-mode middle controls are centered horizontally regardless of which
   modes are active. With the page slider removed, the middle of the bar is
   either these three buttons (← eye →) or empty space. */
.lesson-bar .lb-cont-controls {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: flex; align-items: center;
  /* Tightened on mobile so the [← mode →] group doesn't collide with
     the play button on the left or the practice button on the right
     in narrow viewports. PC overrides this further down. */
  gap: 6px;
}
.lesson-bar .lb-cont-controls[hidden] { display: none; }
.lesson-bar .lb-arrow {
  /* Smaller on mobile to leave room for the mode pill — the arrows
     used to be 44 px (matching play / practice) but that pushed total
     center-group width past iPhone-SE-class viewports. PC bumps these
     back up via the @media block below. */
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  color: var(--ink);
  transition: background .12s, transform .05s;
}
.lesson-bar .lb-arrow svg { width: 18px; height: 18px; }
.lesson-bar .lb-arrow:hover  { background: #f1f3f5; }
.lesson-bar .lb-arrow:active { transform: translateY(1px); }

/* PC layout — the same .lb-cont-controls group that lives in the bottom
   bar on mobile is now also shown on PC (replaces the top-bar mode pill).
   Buttons get a slight size bump so they read clearly on a wider viewport,
   and the mode pill gets a fixed minimum width so all three labels
   (단어 뜻 하나씩 / 여러개 이어서 / 이어서 아래) line up to the same chip
   size. */
@media (min-width: 1024px) {
  .lesson-bar .lb-cont-controls { gap: 16px; }
  .lesson-bar .lb-arrow {
    width: 48px; height: 48px;
  }
  .lesson-bar .lb-arrow svg { width: 24px; height: 24px; }
  .lesson-bar .lb-mode {
    /* Hard-fixed width sized for the LONGEST label ("단어 뜻 하나씩")
       at the PC font-size — every mode renders at the same width so
       cycling between modes never reflows the row. The shorter
       labels ("여러개 이어서" / "이어서 아래") sit centered inside. */
    width: 200px;
    min-width: 200px;
    height: 44px;
    padding: 0 18px;
    font-size: 15px;
    font-weight: 700;
  }
}
/* Mobile: fixed-width pill sized for the longest label
   ("단어 뜻 하나씩") at a SMALLER 12 px font so the [← mode →] group
   stays clear of the play / practice buttons on either side. Total
   center-group width = arrow(36) + gap(6) + mode(108) + gap(6) +
   arrow(36) = 192 px, which fits an iPhone-SE 320 px viewport with
   a small margin and leaves comfortable room on 375 px+ phones. */
.lesson-bar .lb-mode {
  width: 108px;
  min-width: 108px;
  padding: 5px 8px;
  font-size: 12px;
  text-align: center;
}
.lesson-bar .lb-eye {
  width: 56px; height: 44px;
  border-radius: 999px;
  border: 1px solid #cdd2d8;
  background: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  padding: 0;
}
.lesson-bar .lb-eye:hover { background: #f8f9fa; }
.lesson-bar .lb-eye img { width: 36px; height: 28px; object-fit: contain; }

/* Practice menu icons — matched to practice.jpeg's gray paper-stack style. */
.practice-card-anchored .pa-ico {
  width: 36px; height: 36px;
  flex-shrink: 0;
  display: grid; place-items: center;
  color: #6b7280;
}
.practice-card-anchored .pa-ico svg { display: block; }

/* Audio playback bar (revealed when the lesson-bar play button is pressed).
   All buttons share the same circular pill shape. */
.playbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 35;
  background: #fff; border-top: 1px solid var(--line);
  padding: 10px 14px;
  display: flex; align-items: center; gap: 10px;
  height: 64px;
}
.playbar[hidden] { display: none; }
.playbar .pb-btn {
  width: 38px; height: 38px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff; color: var(--ink);
  display: grid; place-items: center;
  font-size: 14px; font-weight: 700; line-height: 1;
  cursor: pointer; flex-shrink: 0;
  transition: background .12s, border-color .12s, color .12s;
}
.playbar .pb-btn:hover { background: #f1f3f5; }
.playbar .pb-btn.pb-play {
  background: var(--ink); color: #fff; border-color: var(--ink);
}
.playbar .pb-btn.pb-play:hover { background: #2a3447; }
.playbar .pb-btn.pb-close      { font-size: 18px; }

/* Inline SVG icons inside the sentence-step buttons. They inherit
   currentColor (so the dark play button gets white icons automatically)
   and never overflow the round button — the viewBox + width/height
   combination guarantees crisp rendering on any DPR / button size. */
.playbar .pb-btn .pb-icon {
  width: 16px; height: 16px;
  display: block;
  fill: currentColor;
  stroke: none;
  pointer-events: none;
}
.playbar .pb-btn.pb-play .pb-icon { fill: #fff; }   /* against the ink bg */
.playbar #speed-btn {
  width: auto; min-width: 38px; padding: 0 12px;
  font-size: 13px;
}
/* Sentence counter "3 / 12" sits between play and next-sentence. */
.playbar .pb-counter {
  font-size: 14px; font-weight: 700;
  color: var(--ink);
  min-width: 56px; text-align: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.playbar .pb-spacer { flex: 1; }   /* push speed/voice/× to right edge */

/* Variant-bar: when the user opens playback with a word selected in
   하나씩 / 이어서, the playbar swaps the sentence-step controls for
   per-word / per-phrase repeat buttons. CSS toggles based on a class
   on .playbar; JS owns the class. */
.playbar .pb-variant-only { display: none; }
.playbar.variant-word .pb-default-only,
.playbar.variant-phrase .pb-default-only { display: none; }
.playbar.variant-word .pb-variant-only,
.playbar.variant-phrase .pb-variant-only { display: grid; place-items: center; }

/* In 이어서 mode the selection IS the unit — the per-word loop button
   is meaningless (the "unit" is the phrase, not a single word). Hide
   단어반복 in variant-phrase; only 밑줄반복 (loop the phrase) appears. */
.playbar.variant-phrase .pb-word-rep { display: none; }

/* The repeat buttons are wider than the icon-only ones so the Korean
   labels read comfortably. Keep them tappable on phones. */
.playbar .pb-word-rep,
.playbar .pb-chunk-rep {
  width: auto; min-width: 70px; padding: 0 12px;
  font-size: 12px; font-weight: 700;
  letter-spacing: -0.3px;
}
.playbar .pb-word-rep.active,
.playbar .pb-chunk-rep.active {
  background: #fff8d6;
  border-color: #f5b800;
  color: #6b4f00;
}

/* Repeat-mode cycler. The mode label ("문장" / "의미" / "단어") sits
   inside the round button so it reads at a glance. Modes 2/3 (chunk
   and word loops) get a subtle amber tint to distinguish them from the
   default sequential mode. */
.playbar .pb-repeat {
  width: auto; min-width: 38px; padding: 0 10px;
  font-size: 12px; font-weight: 700;
  letter-spacing: -0.3px;
}
.playbar .pb-repeat .pb-rep-label {
  display: inline-block;
  white-space: nowrap;
  pointer-events: none;
}
.playbar .pb-repeat.mode-chunk {
  background: #fff8e6;
  border-color: #f5b800;
  color: #b76e00;
}
.playbar .pb-repeat.mode-word {
  background: #fff0d6;
  border-color: #f5b800;
  color: #944800;
}

/* Brief "select a word first" toast that floats over the playbar when
   the user cycles to chunk/word loop without an active selection. */
.pb-toast {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 72px;                      /* sits ABOVE the playbar */
  background: rgba(31, 41, 55, 0.92);
  color: #fff;
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 6px 20px rgba(0,0,0,0.18);
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s ease, transform .2s ease;
}
.pb-toast[hidden] { display: none; }
.pb-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(-4px);
}
.playbar .time { font-size: 12px; color: var(--muted); white-space: nowrap; }

/* Narrow phones: tighten gaps so all the sentence-step controls still
   fit without wrapping. Speed and voice pills shrink slightly too. */
@media (max-width: 420px) {
  .playbar { gap: 6px; padding: 10px 10px; }
  .playbar .pb-btn { width: 34px; height: 34px; font-size: 13px; }
  .playbar .pb-counter { min-width: 48px; font-size: 12px; }
  .playbar #speed-btn { padding: 0 8px; min-width: 32px; }
  .playbar .pb-btn.pb-voice { padding: 0 8px; min-width: 30px; }
}

/* ============== utilities ============== */
.muted { color: var(--muted); }
.row { display: flex; gap: 8px; align-items: center; }
.center { display: grid; place-items: center; }
.hidden { display: none !important; }
.kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ============== blurred background while signed-out ============== */
.signed-out .behind {
  filter: blur(8px) saturate(0.85);
  pointer-events: none; user-select: none;
}

/* ============== mobile tweaks ============== */
@media (max-width: 600px) {
  .topbar {
    padding: 8px 12px; gap: 6px;
    flex-wrap: nowrap;       /* never wrap to a second line */
  }
  .topbar .brand { font-size: 16px; padding: 4px 10px; }
  .topbar .acct {
    font-size: 12px; padding: 4px 10px 4px 4px; gap: 6px;
    max-width: 110px;
  }
  .topbar .acct .avatar { width: 24px; height: 24px; font-size: 11px; }
  .topbar .bidoro-tab {
    width: 36px; height: 32px; padding: 0; min-width: 36px;
  }
  .topbar .bidoro-tab .ico  { display: inline; }
  .topbar .bidoro-tab .text { display: none; }
  .topbar .btn { padding: 6px 10px; font-size: 12px; }
  .container { padding: 14px; }
  .lesson-text { font-size: 17px; line-height: 2.3; }
  /* Tightened from 100px to 14px so the white card extends almost down to
     the lesson-bar — minimises the gray strip the user sees below it.
     Horizontal padding stays at 22px so the focused-word OUTER GLOW
     (24px clip-margin) on edge words isn't clipped by lesson-main's
     overflow:hidden — the glow needs ≥22px of breathing room either side. */
  .lesson-main { padding: 14px 22px 14px; }
  /* On the lesson page squeeze the page padding even tighter so the card
     fills more of the viewport (less top/bottom gray gutter). */
  .lesson-page .container { padding: 8px 12px 4px; }
  .modal { padding: 22px 18px 18px; }
  .fab { right: 16px; bottom: 78px; width: 52px; height: 52px; }
}
@media (max-width: 380px) {
  /* Very narrow phones: shrink the account chip further so logout still fits. */
  .topbar .acct { max-width: 80px; }
  .topbar .acct #acct-name { font-size: 11px; }
}

/* ============================================================
   Title-row heart (♡ / ♥) — sits immediately to the right of the
   lesson title text. NO circle, NO border, NO background — just
   the glyph. Hollow gray by default, solid red when bookmarked.
   ============================================================ */
.lesson-title-row .title-heart {
  background: transparent;
  border: 0;
  padding: 2px 4px;
  margin: 0;
  color: #adb5bd;            /* gray hollow ♡ */
  font-size: 22px; line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition: color .15s, transform .05s;
}
.lesson-title-row .title-heart:hover  { color: #6c757d; }
.lesson-title-row .title-heart:active { transform: translateY(1px); }
.lesson-title-row .title-heart.bookmarked        { color: #e0245e; }   /* solid red ♥ */
.lesson-title-row .title-heart.bookmarked:hover  { color: #c11d50; }
.lesson-title-row .title-heart-ico {
  display: inline-block;
  font-size: 22px;
  line-height: 1;
  /* Slight optical centering since ♥ glyph rides high. */
  transform: translateY(-1px);
}

/* ============================================================
   Mobile lesson-bar center group: ← [mode-toggle] →. The mode
   button uses the same pill style as .toggle-btn but is sized to
   match the round arrows on either side.
   ============================================================ */
.lesson-bar .lb-cont-controls {
  /* Override the prior gap to make space for the wider mode pill. */
  gap: 10px;
}
.lesson-bar .lb-mode {
  height: 44px;
  min-width: 76px;
  padding: 0 14px;
  border-radius: 999px;
  font-size: 14px; font-weight: 800;
}
.lesson-bar .lb-mode u { text-decoration: underline; }

/* Hide the top-right mode-toggle pill on phones — the lesson-bar's
   center mode button takes over there to save head-tools space. */
@media (max-width: 600px) {
  #mode-toggle-btn { display: none; }
}

/* ============================================================
   Home-page status row (under "계속 학습하기"). Three pill
   buttons that summarise the user's progress and open detail
   modals on tap.
   ============================================================ */
.status-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin: 6px 0 14px;
}
.status-btn {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px;
  padding: 10px 8px;
  border-radius: 14px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  transition: background .12s, border-color .12s, transform .05s;
  text-align: center;
  min-height: 76px;
}
.status-btn:hover  { background: #f8f9fa; border-color: #cbd1d9; }
.status-btn:active { transform: translateY(1px); }
.status-btn .status-ico { font-size: 18px; line-height: 1; }
.status-btn .status-num {
  font-size: 22px; font-weight: 800; line-height: 1.1; color: var(--ink);
}
.status-btn .status-label {
  font-size: 11px; color: var(--muted); font-weight: 600;
}
@media (max-width: 380px) {
  .status-btn .status-num { font-size: 18px; }
  .status-btn .status-label { font-size: 10px; }
}

/* Status detail modals (streak / money / mastered). Shared scaffolding
   with story modal, but slightly bigger so the calendar / weekly chart
   fit comfortably on a phone. */
.status-overlay {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(15, 20, 28, 0.42);
  display: grid; place-items: center;
  padding: 20px;
}
.status-overlay .status-card {
  background: #fff;
  border-radius: 18px;
  padding: 22px 22px 18px;
  width: min(520px, 100%);
  max-height: 86vh; overflow-y: auto;
  position: relative;
  box-shadow: 0 24px 64px rgba(15, 20, 28, 0.2);
}
.status-overlay .sc-close {
  position: absolute; top: 8px; right: 10px;
  width: 32px; height: 32px;
  border-radius: 999px;
  background: transparent; border: 0;
  font-size: 22px; line-height: 1; color: var(--muted);
  cursor: pointer;
}
.status-overlay .sc-close:hover { background: #f1f3f5; color: var(--ink); }

/* Lesson cache prewarm modal — pops up from the kebab menu on a
   lesson card. Shows a progress bar driven by postMessage from a
   hidden iframe running `lesson.html?prewarm=1`. */
.prewarm-card { width: min(440px, 100%); }
.prewarm-card .prewarm-title {
  margin: 0 0 6px;
  font-size: 14px; font-weight: 700; color: var(--ink);
}
.prewarm-card .prewarm-desc {
  margin: 0 0 14px;
  color: var(--muted); font-size: 13px; line-height: 1.5;
}
.prewarm-card .prewarm-bar-wrap {
  width: 100%; height: 8px;
  background: #e5e7eb;
  border-radius: 999px;
  overflow: hidden;
  margin: 12px 0 8px;
}
.prewarm-card .prewarm-bar {
  height: 100%; width: 0%;
  background: var(--accent-ink);
  border-radius: 999px;
  transition: width .2s ease;
}
.prewarm-card .prewarm-status {
  font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-bottom: 14px;
}
.prewarm-card .prewarm-actions {
  display: flex; justify-content: flex-end;
}
.status-overlay h3 {
  margin: 0 0 10px;
  font-size: 18px; font-weight: 800;
}
.status-overlay h3 .hsub {
  margin-left: 6px; color: var(--muted); font-weight: 600; font-size: 13px;
}

/* ----- streak modal: big number + 6-week calendar ----- */
.streak-big {
  font-size: 48px; font-weight: 800; line-height: 1; text-align: center;
  margin: 4px 0 2px; color: #ff6b35;
}
.streak-big .su {
  font-size: 18px; font-weight: 700; color: var(--muted);
  margin-left: 4px; vertical-align: middle;
}
.streak-cap { text-align: center; color: var(--muted); font-size: 12px; margin-bottom: 14px; }
.streak-cal { display: flex; flex-direction: column; gap: 4px; }
.streak-cal .cal-row {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px;
}
.streak-cal .cal-head span {
  text-align: center;
  font-size: 11px; color: var(--muted); font-weight: 700;
  padding: 4px 0;
}
.streak-cal .cal-cell {
  aspect-ratio: 1; border-radius: 8px;
  background: #f1f3f5; color: var(--muted);
  display: grid; place-items: center;
  font-size: 12px; font-weight: 600;
}
.streak-cal .cal-cell.done {
  background: #ffe6d5; color: #b8410d; border: 1px solid #ffb887;
}
.streak-cal .cal-cell.today {
  outline: 2px solid #ff6b35;
  outline-offset: 1px;
  color: var(--ink);
}
.streak-cal .cal-cell.future { opacity: 0.35; }

/* ----- money modal: big total + weekly bar chart + expense list ----- */
.money-totals { text-align: center; margin: 4px 0 14px; }
.money-totals .mt-big {
  font-size: 40px; font-weight: 800; line-height: 1; color: #15803d;
}
.money-totals .mt-sub { color: var(--muted); font-size: 12px; margin-top: 2px; }

.money-week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
  align-items: end;
  margin-bottom: 14px;
  min-height: 92px;
}
.money-week .mw-col {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  font-size: 10px;
}
.money-week .mw-bar {
  width: 100%;
  min-height: 2px;
  background: linear-gradient(180deg, #34d399, #15803d);
  border-radius: 4px;
}
.money-week .mw-lbl { color: var(--muted); }
.money-week .mw-c   { font-weight: 700; color: var(--ink); }

.money-row {
  display: flex; align-items: center; gap: 10px;
  margin: 8px 0 4px;
  flex-wrap: wrap;
}
.money-row .muted { font-size: 12px; }

.money-exp-list {
  margin-top: 8px;
  border-top: 1px solid var(--line);
  padding-top: 8px;
  display: flex; flex-direction: column; gap: 6px;
  max-height: 180px; overflow-y: auto;
}
.money-exp-list .me-row {
  display: grid;
  grid-template-columns: 80px 1fr auto;
  gap: 10px;
  align-items: center;
  font-size: 13px;
  padding: 4px 0;
}
.money-exp-list .me-date  { color: var(--muted); }
.money-exp-list .me-cents { color: #b91c1c; font-weight: 700; }

.money-add {
  display: grid;
  grid-template-columns: 1fr 2fr auto;
  gap: 8px;
  margin-top: 10px;
}
.money-add input {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  /* 16px is the iOS Safari threshold — anything smaller triggers the
     auto-zoom-on-focus that blows up the whole modal when the user taps
     the amount or memo field. Keeping it AT or ABOVE 16px disables the
     zoom without us having to touch the viewport meta tag. */
  font-size: 16px;
  min-width: 0;
}

/* ----- settings modal ----- */
.settings-overlay .settings-card { max-width: 480px; }
.settings-card .set-block {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.settings-card .set-block:first-of-type {
  border-top: 0; padding-top: 0;
}
.settings-card .set-block h4 {
  margin: 0 0 12px;
  font-size: 14px; font-weight: 800;
  color: var(--ink);
}

/* Stepper row: label + [-] [value] [+] aligned to the right. */
/* Label + stepper sit side-by-side, centered as a group inside the row.
   The label has a fixed-width right-aligned column so the steppers across
   different rows line up vertically (글자 크기 / 행간 buttons all start at
   the same x-position). Visually balanced, not flush left or right. */
.settings-card .set-row {
  display: flex; align-items: center; gap: 16px;
  justify-content: center;
  margin-bottom: 12px;
}
.settings-card .set-label {
  font-size: 14px; font-weight: 700; color: var(--ink);
  width: 80px; text-align: right;
  flex-shrink: 0;
}
.settings-card .set-stepper {
  display: flex; align-items: center; gap: 10px;
  flex-shrink: 0;
}
.settings-card .set-stepper .btn {
  width: 44px; height: 36px;
  font-size: 16px; font-weight: 800;
  font-family: 'Times New Roman', serif;
  letter-spacing: -0.5px;
  padding: 0;
}
/* Per-button optical centering: the serif glyphs render with a
   left-leaning optical center, so text-align: center alone leaves
   them looking pushed to the left edge. The "A−"/"A+" pair (글자 크기)
   gets a 5 px nudge; the bare "−"/"+" pair (행간) needs a larger
   10 px nudge because those glyphs are visually narrower so the
   centroid sits further left. */
.settings-card #set-font-down,
.settings-card #set-font-up   { text-indent: 5px; }
.settings-card #set-lh-down,
.settings-card #set-lh-up     { text-indent: 10px; }
.settings-card .set-stepper-val {
  font-size: 15px; font-weight: 800; color: var(--ink);
  min-width: 56px; text-align: center;
}

/* Live preview text — mirrors the lesson page styling so the user can
   see exactly what their font / line-height choice will look like. */
.settings-card .set-preview-label {
  margin-top: 14px;
  font-size: 11px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.6px;
  color: var(--muted);
}
.settings-card .set-preview {
  margin-top: 6px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #fafbfc;
  color: var(--ink);
  font-size: 18px;
  line-height: 2.4;
  max-height: 220px; overflow-y: auto;
  transition: font-size .12s ease, line-height .12s ease;
}
.settings-card .set-preview .para { margin: 0; }

/* Save / cancel row — primary on the right (Korean reading order). */
.settings-card .set-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  margin-top: 14px;
}
.settings-card .set-actions .btn {
  padding: 9px 16px; font-weight: 700;
}
.settings-card .set-actions .btn.primary {
  background: var(--ink); color: #fff; border-color: var(--ink);
}
.settings-card .set-actions .btn.primary:hover {
  background: #2a3447;
}

.settings-card .set-hint { font-size: 12px; margin-top: 8px; }
.settings-card .set-resets {
  display: flex; flex-direction: column; gap: 8px;
}
.settings-card .btn.danger {
  background: #fff;
  border: 1px solid #f0b9b9;
  color: #b91c1c;
  font-weight: 700;
}
.settings-card .btn.danger:hover { background: #fef1f1; }
.settings-card .btn.block { width: 100%; padding: 12px 14px; }

/* ----- mastered word list ----- */
.mw-list {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 4px;
}
.mw-pill {
  background: #fff; border: 1px solid #d4edbf;
  background: #f3fbe9;
  color: #2c5e1f;
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 13px; font-weight: 700;
  cursor: pointer;
}
.mw-pill:hover { background: #e7f5d6; border-color: #b3d99a; }
.mw-pill .mw-en { color: inherit; }
/* Korean meaning revealed on click — appears next to the word, slightly
   smaller and gray so the headword stays the visual anchor. The `.muted`
   modifier class is removed once the translation lands so the loading
   ellipsis fades out into the proper meaning. */
.mw-pill .mw-ko {
  margin-left: 6px;
  font-size: 12px;
  font-weight: 600;
  color: #4a7a3a;
}
.mw-pill .mw-ko.muted { color: #9aaa90; font-weight: 500; }
.mw-pill .mw-ko[hidden] { display: none; }

/* ----- font-size buttons (A+ / A−) inherit .toggle-btn.dict styling. The
   minus glyph used in HTML is U+2212 to keep the pill width consistent. */
#font-up-btn, #font-down-btn {
  font-family: 'Times New Roman', serif;
  font-style: normal;
  font-weight: 800;
  letter-spacing: -0.5px;
}

/* =============================================================
   Mobile cont sticky bottom translation bar
   Pinned just above the lesson-bar (which is 64px tall). Renders
   instantly when 이어서 mode picks up its first word — much faster
   than the per-sentence anchored popup it replaces, and uses the
   full screen width for the Korean translation.
   ============================================================= */
.cont-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: 64px;                       /* sit on top of the lesson-bar */
  z-index: 32;
  background: #fff;
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(15,20,28,0.08);
  padding: 12px 14px 14px;
  transform: translateY(110%);
  transition: transform .22s cubic-bezier(.22,.6,.36,1);
  /* 2-column / 2-row grid:
       col 1 = content (phrase + translation, full available width)
       col 2 = actions column (× on top, + below, spans both rows)
     The actions column is auto-width (just wide enough for the buttons),
     so the phrase and Korean translation each get the full remaining
     width — letting longer selections / translations breathe. */
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 10px;
  row-gap: 4px;
  align-items: start;
}
.cont-bar.show { transform: translateY(0); }
.cont-bar[hidden] { display: none; }

/* Tiny chevron at the bottom edge — same visual hint as the
   word-sheet's `.ws-edge-down`. Indicates the bar responds to a
   swipe-down gesture (Naver dictionary lookup on the selected
   phrase). Pointer-events:none so it never intercepts the touch
   handler on the bar itself. */
.cont-bar .cb-edge {
  position: absolute;
  color: #cbd5e1;
  font-size: 14px;
  line-height: 1;
  pointer-events: none;
  user-select: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.cont-bar .cb-edge-down {
  bottom: 1px; left: 50%;
  transform: translateX(-50%) rotate(90deg);
}

.cont-bar .cb-phrase {
  grid-column: 1; grid-row: 1;
  min-width: 0;
  font-weight: 800; font-size: 16px;
  color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  align-self: center;
}
.cont-bar .cb-actions {
  grid-column: 2; grid-row: 1 / span 2;
  display: flex; flex-direction: column;
  gap: 6px;
  align-self: stretch;
  flex-shrink: 0;
}
.cont-bar .cb-add,
.cont-bar .cb-close {
  width: 32px; height: 32px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  font-size: 15px; line-height: 1;
  flex-shrink: 0;
  transition: background .12s, transform .05s;
}
.cont-bar .cb-add:hover,
.cont-bar .cb-close:hover { background: #f1f3f5; }
.cont-bar .cb-add:active,
.cont-bar .cb-close:active { transform: translateY(1px); }
.cont-bar .cb-add { color: #15803d; font-weight: 800; font-size: 18px; }
.cont-bar .cb-add.added {
  background: #e6f4ec; border-color: #b3d9c2; color: #15803d;
}
.cont-bar .cb-close { font-size: 18px; color: var(--muted); }

.cont-bar .cb-trans {
  grid-column: 1; grid-row: 2;
  font-size: 17px; font-weight: 700;
  color: var(--ink); line-height: 1.4;
  word-break: break-word;
  /* No nowrap/ellipsis — Korean translations can be 2+ lines now that
     the actions column doesn't steal width from this row. */
}
.cont-bar .cb-trans .tx-loading { color: var(--muted); font-weight: 600; font-size: 14px; }
.cont-bar .cb-trans .ko { color: var(--ink); }
.cont-bar .cb-trans .muted { font-weight: 600; font-size: 14px; }

/* The cont-bar is mobile-only. PC uses the sidebar. */
@media (min-width: 1024px) { .cont-bar { display: none !important; } }

/* =============================================================
   Mobile word sheet (하나씩 mode)
   Compact bottom panel with action pills + mnemonic + collapsible
   panels for examples / collocations. No headword (the body word
   glows instead — saves vertical space).
   ============================================================= */
.word-sheet {
  position: fixed;
  left: 0; right: 0;
  bottom: 64px;                       /* sit on top of the lesson-bar */
  z-index: 50;
  background: #fff;
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(15, 20, 28, 0.10);
  padding: 12px 14px 14px;
  transform: translateY(110%);
  transition: transform .22s cubic-bezier(.22,.6,.36,1);
  max-height: 60vh;
  overflow-y: auto;
  /* Clip horizontal — during the carousel-style swipe between
     text/pm/co views, the incoming panel sits at translateX(±100%)
     until the user drags it in. Without overflow-x:hidden the
     off-screen panel would visually overflow past the sheet edge.
     Y stays auto so the sheet itself can scroll on small viewports. */
  overflow-x: hidden;
}
/* While a horizontal carousel swipe is in progress, both the current
   and the incoming panel are visible at once. Pin them BOTH to the
   sheet's bottom so each panel keeps its natural height while the
   bottom rail stays put — the user sees two bottom-anchored panels
   sliding past each other, with their top edges at different
   y-coordinates whenever their content heights differ. */
.word-sheet.is-swiping .ws-mnemonic,
.word-sheet.is-swiping .ws-panel {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 14px;          /* matches sheet's padding-bottom */
  margin: 0;
}
.word-sheet.show { transform: translateY(0); }
.word-sheet[hidden] { display: none; }

/* Ephemeral mode label, centered on the bottom-sheet's panel area.
   Per spec:
     - First tap (no swipe yet) — no label.
     - Swipe LEFT → "의미", RIGHT → "연어" — center, gray, fades after ~1 s.
     - Default chunk-panel state ("본문") — same center treatment.
   Font size 12, gray, fades softly. JS sets textContent + .show class. */
.ws-toast {
  position: absolute;
  /* Anchored to the BOTTOM of the sheet (was vertically centered) so
     the big "의미"/"연어"/"본문" label sits BELOW the small text in
     the sheet's top row (ws-meaning: "<word>: <ko>") instead of
     overlapping it. The user wants the small label text to stay
     visible while the big swipe-direction label briefly flashes. */
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 40px;
  color: #d1d5db;            /* lighter gray per spec */
  pointer-events: none;
  opacity: 0;
  transition: opacity .22s ease;
  z-index: 6;
  letter-spacing: 0.5px;
  font-weight: 700;
  white-space: nowrap;
}
.ws-toast.show { opacity: 1; }

/* Tiny gray chevrons centered on each edge of the sheet, hinting at
   the four swipe directions. Pointer-events: none so they don't
   intercept the touch handler on the sheet itself. */
.word-sheet .ws-edge {
  position: absolute;
  color: #cbd5e1;
  font-size: 14px;
  line-height: 1;
  pointer-events: none;
  user-select: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.word-sheet .ws-edge-up {
  top: 4px; left: 50%;
  transform: translateX(-50%) rotate(90deg);
}
.word-sheet .ws-edge-down {
  /* Pulled 3 px LOWER than ws-edge-up (which sits at top: 4px) so the
     bottom chevron hugs the sheet's lower edge per spec. */
  bottom: 1px; left: 50%;
  transform: translateX(-50%) rotate(90deg);
}
.word-sheet .ws-edge-left  { left:  4px; top: 50%; transform: translateY(-50%); }
.word-sheet .ws-edge-right { right: 4px; top: 50%; transform: translateY(-50%); }

/* First action row — speaker · contextual meaning · 예 / 연 / level / × */
.word-sheet .ws-row {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 8px;
}
.word-sheet .ws-icon {
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  display: grid; place-items: center;
  cursor: pointer;
  font-size: 16px; line-height: 1;
  flex-shrink: 0;
  transition: background .12s, transform .05s;
}
.word-sheet .ws-icon:hover  { background: #f1f3f5; }
.word-sheet .ws-icon:active { transform: translateY(1px); }
.word-sheet .ws-close { font-size: 20px; color: var(--muted); }
.word-sheet .ws-meaning {
  flex: 1; min-width: 0;
  font-weight: 800; font-size: 16px;
  color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  padding: 0 4px;
}
.word-sheet .ws-tab {
  width: 32px; height: 32px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  font-weight: 800; font-size: 13px;
  display: grid; place-items: center;
  cursor: pointer;
  flex-shrink: 0;
  transition: background .12s, color .12s, border-color .12s, transform .05s;
}
.word-sheet .ws-tab:hover  { background: #f1f3f5; }
.word-sheet .ws-tab:active { transform: translateY(1px); }
.word-sheet .ws-tab.active {
  background: var(--ink); color: #fff; border-color: var(--ink);
}
/* Level circle — color follows the word's state class. */
.word-sheet .ws-level-tab.s1 { background: var(--word-1); }
.word-sheet .ws-level-tab.s2 { background: var(--word-2); }
.word-sheet .ws-level-tab.s3 { background: var(--word-3); }
.word-sheet .ws-level-tab.s4 { background: var(--word-4); }

/* (✨ GPT toggle and 🔊 speaker tab were removed from the sheet row. The
   head-tools ✨ pill is the single source of truth for AI translation;
   word selection auto-speaks via TTS.speak so a dedicated speaker
   button on the sheet was redundant.) */

/* (.ws-drag-hint and the inline 4-way arrow icon were removed — the
   four-edge chevron markers now hint at swipe directions; the 연
   button is gone too. See .ws-edge-* rules elsewhere.) */

/* Mnemonic — visible by default. Highlights are pink.
   Reserve EXACTLY 2 typical lines (구문 + Korean) so the panel
   doesn't grow when async content lands — but doesn't reserve a
   spare third line either. Fine-tuned so the loading "…" sits at
   the same vertical center the final 2-line content will. */
.word-sheet .ws-mnemonic {
  font-size: 14px; line-height: 1.55;
  color: var(--ink);
  padding: 6px 10px;
  background: #fafbfc;
  border-radius: 8px;
  border: 1px solid var(--line);
  min-height: 56px;     /* 14 px × 1.55 × 2 + 12 px padding ≈ 56 */
  box-sizing: border-box;
}
html.lang-ja .word-sheet .ws-mnemonic {
  min-height: 56px;     /* match EN — user prefers a tighter 2-line
                           target; if JP content overflows it auto-
                           expands beyond the min. */
}
.word-sheet .ws-mnemonic .hl,
.lesson-sidebar .ls-mnemonic .hl {
  color: #ec4899;                     /* soft pink emphasis */
  font-weight: 800;
}
.word-sheet .ws-mnemonic .tx-loading,
.word-sheet .ws-mnemonic .muted { color: var(--muted); font-weight: 600; font-size: 13px; }

/* Skeleton shimmer — pulsing gray bars shown while GPT response loads.
   Visible activity makes the perceived wait shorter even if total time
   is unchanged. */
.word-sheet .ws-skel {
  display: flex; flex-direction: column; gap: 6px;
  padding: 2px 0;
}
.word-sheet .skel-line {
  display: inline-block;
  height: 12px; border-radius: 4px;
  background: linear-gradient(90deg, #eef0f3 0%, #f7f8fa 50%, #eef0f3 100%);
  background-size: 200% 100%;
  animation: skel-shimmer 1.2s linear infinite;
}
.word-sheet .skel-line.w55 { width: 55%; }
.word-sheet .skel-line.w70 { width: 70%; }
.word-sheet .skel-line.w90 { width: 90%; }
.word-sheet .ws-list li:has(.skel-line) { padding: 2px 0; }
@keyframes skel-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}

/* Panels under the action row (examples / collocations) */
.word-sheet .ws-panel {
  margin-top: 8px;
  padding: 8px 10px;
  background: #fafbfc;
  border-radius: 8px;
  border: 1px solid var(--line);
}
.word-sheet .ws-panel[hidden] { display: none; }
.word-sheet .ws-list {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.word-sheet .ws-examples li {
  font-size: 14px; line-height: 1.45;
  color: var(--ink);
}
.word-sheet .ws-collocs li {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 14px; line-height: 1.45;
}

/* Popular Meanings panel — opened by swipe-LEFT on the word-sheet.
   Each row collapses to ONE line: "<Korean>  |  <example>". Tapping
   the example reveals the example's Korean translation BELOW. So
   N senses → N rows by default, exactly as the user spec'd. */
.word-sheet .ws-pm-list { gap: 4px; }
.word-sheet .ws-pm-row {
  display: block;
  font-size: 14px; line-height: 1.5;
  padding: 4px 0;
  border-bottom: 1px dashed var(--line);
}
.word-sheet .ws-pm-row:last-child { border-bottom: none; }
.word-sheet .ws-pm-ko {
  font-weight: 700; color: var(--ink);
  margin-right: 6px;
}
.word-sheet .ws-pm-divider {
  color: #d1d5db; margin: 0 4px;
}
.word-sheet .ws-pm-ex {
  color: var(--muted); font-style: italic;
  /* Tap target: pointer cursor + slightly larger touch target. */
  display: inline;
}
.word-sheet .ws-pm-ex-ko {
  display: block;
  margin-top: 2px;
  padding: 4px 8px;
  background: #f1f3f5;
  border-radius: 4px;
  font-size: 13px;
  color: var(--ink);
  font-weight: 600;
}
.word-sheet .ws-pm-ex-ko[hidden] { display: none; }

/* [한글] reveal toggle inside example list */
.ex-en { color: var(--ink); }
.ex-ko-toggle {
  display: inline-block;
  margin-left: 4px;
  background: transparent; border: 0;
  font-size: 11px; font-weight: 700; color: #1a73e8;
  cursor: pointer; padding: 0 2px;
}
.ex-ko-toggle.open { color: #6c757d; }
.ex-ko {
  margin-top: 2px;
  font-size: 12px; color: var(--muted);
  font-weight: 600;
}
.ex-ko[hidden] { display: none; }

/* Collocation row */
.co-en { color: var(--ink); font-weight: 700; }
.co-ko { font-size: 13px; }

/* Lemma highlight inside examples / collocations — soft pink (matches mnemonic) */
.hl-lemma {
  color: #ec4899;
  font-weight: 800;
}
/* Inflection suffix (e.g. "ed" on "consulted", "s" on "books") — sky-
   blue. Sits visually adjacent to .hl-lemma so a learner reading the
   example sees lemma+inflection as a two-tone unit:
     "I <pink>consult</pink><sky>ed</sky> the team."
   Same font-weight as .hl-lemma so the colors carry the distinction. */
.hl-infl {
  color: #38bdf8;
  font-weight: 800;
}
/* Additional inflection palette — used when a Japanese word has a
   multi-segment suffix (e.g. 見せませんでした → 見せ + ませ + ん + で
   し + た) and we want each auxiliary to read as a distinct unit.
   Cycles in the order .hl-infl → .hl-infl-2 → .hl-infl-3 → loop. */
.hl-infl-2 {           /* light green (연두) */
  color: #16a34a;
  font-weight: 800;
}
.hl-infl-3 {           /* magenta-pink, distinct from .hl-lemma */
  color: #d946ef;
  font-weight: 800;
}

/* Chunk-translation panel ("구문 풀이") — replaces the old mnemonic.
   English chunk on top (with the focused lemma in pink), Korean below
   in muted gray. Reuses the .ws-mnemonic / .ls-mnemonic containers. */
.ck-en {
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 4px;
  line-height: 1.4;
}
.ck-ko {
  font-size: 13px;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.4;
}
.ck-ko .tx-loading { color: var(--muted); font-weight: 500; }
/* Pink highlight on the Korean substring corresponding to the selected
   English word — only applied when the mapping is unambiguous (single
   substring match). Matches the pink used by .ck-en's lemma highlight. */
.ck-ko-hl {
  color: #db5395;
  font-weight: 700;
}
.lesson-sidebar .ls-mnemonic .ck-en { font-size: 15px; }
.lesson-sidebar .ls-mnemonic .ck-ko { font-size: 14px; }

/* Japanese-mode-only sizing — applied only when <html> carries
   `.lang-ja`. English mode keeps its original sizes. The user wanted
   the hiragana chunk and the headword visibly larger than English
   because Japanese characters carry more information per glyph and
   the underlined kanji-derived hiragana needs room to read clearly.

   `ck-en-ja` is set on the chunk row when JP mode renders it as
   all-hiragana with kanji-readings underlined. The <u> tags inside
   come straight from `JPT.rubyToHiraganaWithUnderlines`. */
html.lang-ja .word-sheet .ws-meaning {
  font-size: 22px;                /* vs 16px in EN */
  font-weight: 800;
}
html.lang-ja .ck-en-ja {
  font-size: 18px;                /* vs 14px in EN */
  font-weight: 600;
  line-height: 1.55;
}
html.lang-ja .ck-en-ja u {
  /* Underlined run = a kanji's hiragana reading. Slight color shift
     + softer underline so it reads as a "this hiragana = a kanji"
     hint rather than a hyperlink. */
  text-decoration: underline;
  text-decoration-color: #1a73e8;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  color: #1a73e8;
  font-weight: 700;
}
html.lang-ja .word-sheet .ck-ko,
html.lang-ja .lesson-sidebar .ck-ko {
  font-size: 15px;                /* vs 13–14px in EN */
}
html.lang-ja .lesson-sidebar .ls-mnemonic .ck-en-ja {
  font-size: 17px;
}

/* Level popover — appears above the .ws-level-tab circle when tapped.
   Lives as a SIBLING of .word-sheet (not inside) so the sheet's
   overflow-y: auto doesn't clip it. Positioned in JS via inline
   bottom/right when shown. */
.ws-level-pop {
  position: fixed;
  z-index: 60;                  /* above the sheet (z-50) */
  display: flex; gap: 6px;
  padding: 6px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: 0 8px 24px rgba(15,20,28,0.18);
}
.ws-level-pop[hidden] { display: none; }
.ws-lvl {
  width: 32px; height: 32px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: #fff;
  font-weight: 800; font-size: 13px;
  display: grid; place-items: center;
  cursor: pointer;
}
.ws-lvl:hover { transform: translateY(-1px); }
.ws-lvl-trash { color: #6b7280; }
.ws-lvl-check { background: transparent; color: #15803d; font-size: 16px; }
/* Quiz-add button (formerly heart) inside the mobile word-sheet's
   level popover. Same compact size as the surrounding level dots,
   with a pink tint when the word is queued for the quiz. */
.ws-lvl-heart { color: #e0245e; padding: 0; display: grid; place-items: center; }
.ws-lvl-heart.on { background: #ffe2eb; }
.ws-lvl-heart .quiz-ico { width: 20px; height: 20px; display: block; }

/* PC: word-sheet hidden — sidebar carries the rich card */
@media (min-width: 1024px) { .word-sheet { display: none !important; } }

/* =============================================================
   Word focus visual (mobile single-mode)
   The tapped word gets an amber outer glow; the surrounding
   syntactic chunk gets a wider, lighter glow. The rest of the
   page stays at full brightness — no dim overlay (the user found
   it distracting). The text auto-shifts up via JS so a word near
   the bottom never hides behind the word sheet.
   ============================================================= */

/* The clicked word — primary glow */
.lesson-text .w.focused {
  position: relative;
  z-index: 40;
  background: rgba(245, 158, 11, 0.18);
  box-shadow:
    0 0 0 2px rgba(245, 158, 11, 0.95),
    0 0 14px 4px rgba(245, 158, 11, 0.45);
  border-radius: 5px;
  padding: 0 3px;
  transition: box-shadow .18s ease, background .18s ease;
}
/* The surrounding syntactic chunk — same continuous-underline style as
   the playback `.reading` highlight, but in amber/orange-yellow (matching
   the focused word's outline color) so it's clearly distinct from the
   green TTS-playback line. No outer glow — the underline alone is
   plenty to convey the meaning unit. */
.lesson-text .w.focused-chunk {
  text-decoration: underline;
  text-decoration-color: rgba(245, 158, 11, 0.9);
  text-decoration-thickness: 2px;
  text-underline-offset: 7px;
  /* Drop the prior box-shadow / background ring entirely. */
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  transition: text-decoration-color .18s ease;
}
/* Continuous underline through the spaces / punctuation that sit between
   chunk words, matching how `.reading` flows continuously. */
.lesson-text .w.punct.focused-chunk {
  text-decoration: underline;
  text-decoration-color: rgba(245, 158, 11, 0.9);
  text-decoration-thickness: 2px;
  text-underline-offset: 7px;
}

/* While the word sheet is open we DON'T shift the entire lesson card
   (that hid the title and font-size buttons). Instead the JS function
   ensureWordVisibleAboveSheet() applies a small `translateY` to just the
   .lesson-text so the focused word lifts above the sheet by exactly the
   overlap amount. Header stays put; only the inner text scrolls.

   The wrap clips the shifted text via overflow:hidden so we don't get a
   doubled-up render at the top of the page. */
/* (The CSS-variable-driven shift was replaced by inline transform on
   .lesson-main, set/cleared by ensureWordVisibleAboveSheet and
   clearWordFocus respectively. Inline beats class-rule transforms
   reliably and is immune to the same-task var-collapse race that left
   the card stuck at translateY(0).) */

/* =============================================================
   PC sidebar — new rich layout (IPA, level tag, mnemonic, examples,
   collocations) layered on top of the existing card structure.
   ============================================================= */
.lesson-sidebar .ls-meta-row {
  display: flex; align-items: center; gap: 8px;
  margin: 4px 0 12px;
  font-size: 12px;
}
.lesson-sidebar .ls-ipa {
  font-family: 'Times New Roman', serif;
  font-style: italic;
  color: var(--muted);
  /* 1 pt smaller than the headword (.ls-word is 22 px) so the IPA
     reads as a clearly-legible secondary line, not the previous
     near-invisible 12 px caption. */
  font-size: 21px;
  line-height: 1.1;
}
.lesson-sidebar .ls-pos-tag {
  background: #f1f3f5;
  color: var(--ink);
  font-weight: 700;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
}
.lesson-sidebar .ls-level-tag {
  background: #fff8d6;
  color: #6b4f00;
  font-weight: 800;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid #f5b800;
  margin-left: auto;
}
.lesson-sidebar .ls-section {
  margin-top: 14px;
}
.lesson-sidebar .ls-mnemonic-block { margin-top: 14px; }
.lesson-sidebar .ls-mnemonic {
  font-size: 14px; line-height: 1.55;
  color: var(--ink);
  padding: 10px 12px;
  background: #fffaf3;
  border: 1px solid #ffe0a8;
  border-radius: 8px;
  margin-top: 4px;
}
.lesson-sidebar .ls-collapse {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.lesson-sidebar .ls-collapse[hidden] { display: none; }
.lesson-sidebar .ls-collapse > summary {
  list-style: none;
  cursor: pointer;
  font-size: 12px; font-weight: 800;
  color: var(--muted);
  letter-spacing: 0.4px;
  text-transform: uppercase;
  padding: 4px 0;
  display: flex; align-items: center;
  /* No justify-content: space-between — that pushed the (count) span
     into the middle of the row. Let count sit IMMEDIATELY after the
     title; chevron uses margin-left:auto to float right. */
}
.lesson-sidebar .ls-collapse > summary::-webkit-details-marker { display: none; }
.lesson-sidebar .ls-collapse > summary::after {
  content: '▾';
  margin-left: auto;          /* push chevron to the right edge */
  font-size: 18px;            /* bumped from 11px — was hard to see */
  line-height: 1;
  transition: transform .18s ease;
  color: var(--ink);
  font-weight: 600;
}
.lesson-sidebar .ls-collapse[open] > summary::after { transform: rotate(180deg); }
.lesson-sidebar .ls-count {
  margin-left: 4px;            /* tight gap so it reads "Title (3)" */
  font-weight: 700; color: var(--muted);
  font-size: 11px;
  text-transform: none;
  letter-spacing: 0;
}

.lesson-sidebar .ls-senses {
  list-style: none; margin: 6px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 8px;
}
/* Two-row layout per sense:
     line 1 — Korean meaning + POS pill INLINE (POS as gray button-style
              chip, matching the headword's POS tag at the top of the card)
     line 2 — example sentence (with headword pink + inflection sky-blue)
   Korean text gets `word-break: keep-all` so a narrow column never wraps
   a single Korean syllable to its own line. */
.lesson-sidebar .ls-senses li {
  display: block;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px; line-height: 1.5;
  word-break: keep-all;
  overflow-wrap: break-word;
}
.lesson-sidebar .ls-senses li:last-child { border-bottom: none; }
.lesson-sidebar .ls-sense-ko {
  display: inline;             /* sit next to the POS pill */
  font-weight: 700; color: var(--ink);
  font-size: 15px; line-height: 1.45;
  margin-right: 6px;
  vertical-align: middle;
}
/* Reuse the gray-pill .ls-pos-tag style applied at the top-of-card POS
   row. The renderSenseList JS adds both `.ls-sense-pos` AND
   `.ls-pos-tag` classes so the styling stays consistent. */
.lesson-sidebar .ls-sense-pos {
  display: inline-block;
  vertical-align: middle;
  margin-left: 0;
}
.lesson-sidebar .ls-sense-ex {
  display: block;              /* drop to next line */
  margin-top: 3px;
  color: var(--muted); font-size: 13px;
  font-style: italic; line-height: 1.55;
}

.lesson-sidebar .ls-examples {
  list-style: none; margin: 6px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 8px;
}
.lesson-sidebar .ls-examples li { font-size: 14px; line-height: 1.45; }

.lesson-sidebar .ls-collocs {
  list-style: none; margin: 6px 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.lesson-sidebar .ls-collocs li {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 14px; line-height: 1.4;
}

/* =============================================================
   Playbar voice picker
   The button sits between speed and close on the playbar; tapping
   pops a small menu UPWARD listing each Neural2 voice. Persists in
   localStorage via TTS.setVoice — applies to every speak() call.
   ============================================================= */
.playbar .pb-btn.pb-voice {
  width: auto; min-width: 38px; padding: 0 10px;
  font-weight: 800; letter-spacing: 0.5px;
  font-family: 'Times New Roman', serif;
  font-style: italic;
  font-size: 15px;
}

.voice-menu {
  position: fixed;
  right: 14px;
  bottom: 78px;                       /* sit just above the playbar */
  /* Must beat the PC sidebar (z=90) AND the playbar (z=35) — otherwise the
     popup gets hidden behind the sidebar on desktop. */
  z-index: 95;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 12px 32px rgba(15, 20, 28, 0.18);
  padding: 8px 6px 6px;
  min-width: 240px; max-width: 88vw;
  transform: translateY(8px);
  opacity: 0;
  transition: transform .16s ease-out, opacity .16s ease-out;
}
.voice-menu.show { transform: translateY(0); opacity: 1; }
.voice-menu[hidden] { display: none; }
.voice-menu .vm-head {
  font-size: 12px; font-weight: 800;
  color: var(--ink);
  padding: 4px 10px 8px;
  letter-spacing: 0.3px;
}
.voice-menu .vm-head .vm-sub {
  margin-left: 6px; color: var(--muted); font-weight: 600;
  font-family: 'Times New Roman', serif; font-style: italic;
}
.voice-menu .vm-list {
  list-style: none; margin: 0; padding: 0;
  max-height: 320px; overflow-y: auto;
}
.voice-menu .vm-item {
  width: 100%; box-sizing: border-box;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center; gap: 10px;
  padding: 8px 10px;
  background: transparent;
  border: 0; border-radius: 8px;
  cursor: pointer;
  text-align: left;
  color: var(--ink);
  transition: background .12s;
}
.voice-menu .vm-item:hover { background: #f1f3f5; }
.voice-menu .vm-item.active { background: var(--word-new); }
.voice-menu .vm-item.active:hover { background: #d8e5f3; }
.voice-menu .vm-letter {
  display: grid; place-items: center;
  min-width: 38px; height: 32px;
  padding: 0 9px;
  border-radius: 999px;
  background: var(--word-new);
  color: var(--ink);
  font-weight: 800; font-size: 14px;
  font-family: 'Times New Roman', serif;
  font-style: italic;
  letter-spacing: 0.3px;
  white-space: nowrap;
}
.voice-menu .vm-item.active .vm-letter { background: #fff; border: 1px solid #a8c2dc; }
.voice-menu .vm-meta {
  display: flex; flex-direction: column; gap: 1px;
  min-width: 0;
}
.voice-menu .vm-gender { font-size: 13px; font-weight: 700; color: var(--ink); }
.voice-menu .vm-note   { font-size: 11px; color: var(--muted); }
.voice-menu .vm-check  { font-size: 16px; color: #1a73e8; font-weight: 800; }

/* =============================================================
   코퍼스 / Corpora popup
   Vertical list of sentences from across the user's lessons that use
   the target word. Each row's translateX is pre-computed so the
   pink-highlighted word lands at the popup's horizontal center; a
   shared --cp-x CSS variable lets the whole list slide left/right in
   lockstep on user drag — every sentence moves as a single column.
   ============================================================= */
.corpora-popup {
  position: fixed; inset: 0;
  background: rgba(20, 25, 40, 0);   /* fades up from transparent */
  z-index: 220;
  display: grid;
  place-items: center;
  padding: 24px;
  transition: background .25s ease;
}
.corpora-popup[hidden] { display: none; }
.corpora-popup.show {
  background: rgba(20, 25, 40, 0.55);
}
.corpora-popup .cp-card {
  background: #fff;
  border-radius: 18px;
  width: 100%;
  max-width: 920px;
  height: min(78vh, 640px);
  display: flex;
  flex-direction: column;
  box-shadow: 0 18px 40px rgba(0,0,0,0.18);
  overflow: hidden;
  /* Card animates UP from the bottom-sheet's resting position when
     the swipe-up gesture opens it — gives the spatial sense that the
     sheet is "growing" into a full card rather than appearing
     suddenly. JS toggles the .show class one frame after rendering. */
  transform: translateY(40vh) scale(0.96);
  opacity: 0;
  transition: transform .28s cubic-bezier(.22,.6,.36,1),
              opacity   .22s ease;
}
.corpora-popup.show .cp-card {
  transform: translateY(0) scale(1);
  opacity: 1;
}
.cp-header {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}
.cp-title { font-weight: 700; font-size: 15px; color: var(--ink); }
.cp-title .cp-word { color: #db5395; font-weight: 800; }
.cp-meta  { color: var(--muted); font-size: 12px; }
.cp-close {
  margin-left: auto;
  width: 32px; height: 32px; border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  font-size: 18px; line-height: 1; color: var(--ink);
  cursor: pointer; flex-shrink: 0;
}
.cp-close:hover { background: #f1f3f5; }
.cp-list {
  flex: 1; min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px 0;
  /* `--cp-x` is the shared horizontal offset every row reads; JS sets
     it on this list element on drag and rows inherit. */
  --cp-x: 0px;
  cursor: grab;
  user-select: none; -webkit-user-select: none;
  position: relative;
}
.cp-list.dragging { cursor: grabbing; }
.cp-row {
  white-space: nowrap;
  font-size: 15px; line-height: 1.7;
  padding: 4px 0;
  color: var(--ink);
  /* Row's `--cp-row-offset` (set per-row by JS) centers its highlighted
     word; `--cp-x` adds the user's drag delta on top. */
  transform: translateX(calc(var(--cp-row-offset, 0px) + var(--cp-x, 0px)));
  will-change: transform;
}
.cp-row .cp-hl {
  color: #db5395; font-weight: 800;
  background: rgba(219, 83, 149, 0.08);
  border-radius: 4px;
  padding: 0 2px;
}
.cp-row .cp-meta-tag {
  color: var(--muted); font-size: 11px;
  margin-left: 12px;
}
/* (The pink center guide line was removed — it just looked like noise
   without an explanation, and the highlighted-word column itself is
   already clearly visible.) */
.cp-row { position: relative; z-index: 1; }
.cp-empty { padding: 30px 18px; text-align: center; }
.cp-hint  {
  padding: 8px 14px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--muted);
  text-align: center;
  flex-shrink: 0;
}

/* Mobile: the popup stretches to match the lesson page's white card —
   nearly full screen, generous rounded corners. */
@media (max-width: 1023px) {
  .corpora-popup { padding: 8px; }
  .corpora-popup .cp-card {
    max-width: 100%;
    width: 100%;
    height: 100%;
    border-radius: 14px;
  }
}

/* Sidebar buttons that open corpora / Naver dict — small pills next to
   the headword, color-matched to the existing speak/heart row. */
.lesson-sidebar .ls-corpus-btn,
.lesson-sidebar .ls-naver-btn {
  border: 1px solid var(--line);
  background: #fff;
  border-radius: 999px;
  padding: 4px 10px;
  font-size: 11px; font-weight: 700;
  color: #4b5563;
  cursor: pointer;
  flex-shrink: 0;
  transition: background .12s, border-color .12s, color .12s;
}
.lesson-sidebar .ls-corpus-btn:hover,
.lesson-sidebar .ls-naver-btn:hover {
  background: #f1f3f5; border-color: #cfd4d9; color: #1f2937;
}

/* When the sidebar is showing the Naver iframe, hide the word card
   contents and let the iframe fill the vertical space. */
.lesson-sidebar.naver-mode .ls-card,
.lesson-sidebar.naver-mode .ls-empty { display: none; }
.lesson-sidebar .ls-naver-frame {
  position: absolute; inset: 0;
  background: #fff;
  display: flex; flex-direction: column;
  z-index: 5;
}
.lesson-sidebar .ls-naver-frame[hidden] { display: none; }
.lesson-sidebar .ls-naver-head {
  display: flex; align-items: center; padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.lesson-sidebar .ls-naver-head .ls-naver-w {
  font-weight: 700; color: var(--ink);
}
.lesson-sidebar .ls-naver-head .ls-naver-x {
  margin-left: auto;
  width: 28px; height: 28px; border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  font-size: 16px; line-height: 1; cursor: pointer;
}
.lesson-sidebar .ls-naver-iframe {
  flex: 1; min-height: 0;
  border: none; width: 100%;
}

/* =============================================================
   Naver dict popup (mobile)
   Same overlay/card pattern as the corpora popup. Triggered by a
   downward drag on the word-sheet's row.
   ============================================================= */
.naver-popup {
  position: fixed; inset: 0;
  background: rgba(20, 25, 40, 0);    /* fades up from transparent */
  z-index: 220;
  display: grid; place-items: center;
  padding: 8px;
  transition: background .25s ease;
}
.naver-popup[hidden] { display: none; }
.naver-popup.show { background: rgba(20, 25, 40, 0.55); }
.naver-popup .np-card {
  background: #fff;
  border-radius: 14px;
  width: 100%;
  height: 100%;
  display: flex; flex-direction: column;
  overflow: hidden;
  box-shadow: 0 18px 40px rgba(0,0,0,0.22);
  /* Per spec: Naver dict slides DOWN from the top of the screen when
     the swipe-down gesture opens it. */
  transform: translateY(-100%);
  opacity: 0;
  transition: transform .28s cubic-bezier(.22,.6,.36,1),
              opacity   .22s ease;
}
.naver-popup.show .np-card {
  transform: translateY(0);
  opacity: 1;
}
.np-header {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}
.np-title { font-weight: 700; font-size: 14px; color: var(--ink); }
.np-title .np-word { color: var(--accent-ink); font-weight: 800; }
.np-close {
  margin-left: auto;
  width: 32px; height: 32px; border-radius: 999px;
  border: 1px solid var(--line); background: #fff;
  font-size: 18px; line-height: 1; cursor: pointer;
}
.np-iframe {
  flex: 1; min-height: 0;
  border: none; width: 100%;
}
@media (min-width: 1024px) {
  /* PC uses the sidebar iframe instead — never show this popup. */
  .naver-popup { display: none !important; }
}
