/* JRA web reader — app chrome styling. The lookup CARD is rendered by the shared shadow-DOM component
   (src/content/shadow.ts) and carries its own CSS; this file styles only the page chrome (top bar, home,
   library, reader shell, modals). Design tokens mirror the vocab-card brand kit so the two feel like one
   product. Theme is switched via [data-theme] on <html>. */

:root {
  --bg: #f4f2ec; --surface: #ffffff; --surface2: #f7f5f0; --sunken: #efece4;
  --text: #26231e; --text2: #6e6760; --muted: #9a938a;
  --border: #e6e2da; --borderS: #d4cfc5;
  --indigo: #4b63cc; --indigoInk: #3a50b2;
  --amber: rgb(196, 140, 38); --amberInk: #926612;
  --teal: rgb(31, 138, 130); --tealInk: #166e68;
  --rust: rgb(192, 74, 61); --rustInk: #b03a2e;
  --shadow: 0 0 0 1px rgb(38 35 30 / .05), 0 6px 16px -6px rgb(38 35 30 / .14), 0 16px 44px -10px rgb(38 35 30 / .16);
  --ui: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Noto Sans JP", sans-serif;
  --jp: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Yu Gothic", Meiryo, "Noto Sans JP", "Noto Sans CJK JP", sans-serif;
  --serif: "Newsreader", Georgia, "Times New Roman", serif;
  color-scheme: light;
}
html[data-theme="dark"] {
  --bg: #16140f; --surface: #211e1b; --surface2: #2a2724; --sunken: #191613;
  --text: #ece8e1; --text2: #a8a199; --muted: #756e66;
  --border: #38332d; --borderS: #4a443c;
  --indigo: rgb(128, 149, 236); --indigoInk: #96a8f0;
  --amber: rgb(224, 170, 80); --amberInk: #e6b668;
  --teal: rgb(84, 190, 180); --tealInk: #6cccc2;
  --rust: rgb(229, 131, 117); --rustInk: #e99184;
  --shadow: 0 0 0 1px rgb(0 0 0 / .5), 0 6px 16px -6px rgb(0 0 0 / .55), 0 18px 46px -10px rgb(0 0 0 / .65);
  color-scheme: dark;
}
html[data-theme="sepia"] {
  --bg: #efe6d3; --surface: #f8f1e2; --surface2: #f1e9d6; --sunken: #e7dcc4;
  --text: #3b3322; --text2: #6f6450; --muted: #9d9077;
  --border: #e2d6bc; --borderS: #d2c4a5;
  --amberInk: #7c5710; --tealInk: #13635d; --rustInk: #a5342a;
  --shadow: 0 0 0 1px rgb(59 51 34 / .06), 0 6px 16px -6px rgb(59 51 34 / .14), 0 16px 44px -10px rgb(59 51 34 / .16);
  color-scheme: light;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: var(--ui); font-size: 15px; line-height: 1.55;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
::selection { background: rgba(75, 99, 204, .18); }
a { color: var(--indigoInk); text-decoration: none; -webkit-tap-highlight-color: transparent; }
button { font-family: inherit; -webkit-tap-highlight-color: transparent; }
input, textarea, select { font-family: inherit; color: var(--text); }
input::placeholder, textarea::placeholder { color: var(--muted); }
@keyframes w-pop { from { opacity: 0; transform: translateY(5px) scale(.99); } to { opacity: 1; transform: none; } }
@keyframes w-fade { from { opacity: 0; } to { opacity: 1; } }

/* ---- layout primitives ---- */
.w-wrap { max-width: 1180px; margin: 0 auto; padding: 0 24px; }
.w-wide { max-width: 1280px; }
.w-row { display: flex; align-items: center; gap: 10px; }
.w-spacer { flex: 1; }
.w-hide { display: none !important; }
.w-muted { color: var(--muted); }
.w-tabnum { font-variant-numeric: tabular-nums; }

/* ---- buttons ---- */
.w-btn, .w-btn-ghost, .w-btn-quiet, .w-btn-sm, .w-btn-danger { display: inline-flex; align-items: center; justify-content: center; gap: 7px; cursor: pointer;
  font-size: 14px; font-weight: 600; padding: 10px 16px; border-radius: 8px; border: 1px solid transparent;
  background: var(--indigo); color: #fff; transition: filter .12s, background .12s, border-color .12s; }
.w-btn:hover { filter: brightness(1.05); }
.w-btn:disabled { opacity: .55; cursor: default; filter: none; }
.w-btn-ghost { background: var(--surface); color: var(--text); border-color: var(--borderS); }
.w-btn-ghost:hover { filter: none; border-color: var(--text2); }
.w-btn-quiet { background: none; color: var(--text2); border: none; padding: 8px 10px; }
.w-btn-quiet:hover { color: var(--text); filter: none; }
.w-btn-sm { padding: 7px 12px; font-size: 13px; }
.w-btn-danger { background: none; color: var(--rustInk); border: 1px solid color-mix(in srgb, var(--rust) 45%, transparent); }
.w-btn-danger:hover { filter: none; background: color-mix(in srgb, var(--rust) 8%, transparent); }

/* ---- inputs ---- */
.w-input, .w-textarea, .w-select {
  width: 100%; background: var(--surface); color: var(--text); border: 1px solid var(--borderS);
  border-radius: 8px; padding: 10px 12px; font-size: 14px; outline: none; transition: border-color .12s, box-shadow .12s; }
.w-input:focus, .w-textarea:focus, .w-select:focus { border-color: var(--indigo); box-shadow: 0 0 0 3px rgb(75 99 204 / .16); }
.w-textarea { font-family: var(--jp); line-height: 1.7; resize: vertical; min-height: 120px; }

/* ---- top bar ---- */
.w-topbar { position: sticky; top: 0; z-index: 60; background: var(--bg); border-bottom: 1px solid var(--border); }
.w-topbar-inner { height: 60px; display: flex; align-items: center; gap: 18px; }
.w-logo { display: flex; align-items: center; gap: 10px; background: none; border: none; padding: 0; cursor: pointer; color: var(--text); }
.w-logo-mark { width: 30px; height: 30px; border-radius: 8px; background: var(--indigo); display: grid; place-items: center;
  color: #fff; font-weight: 700; font-size: 18px; font-family: var(--jp); line-height: 1; }
.w-logo-name { font-weight: 680; font-size: 17px; letter-spacing: .04em; }
.w-nav { display: flex; align-items: center; gap: 2px; }
.w-nav button, .w-nav a { position: relative; background: none; border: none; color: var(--text2); font-size: 14px; font-weight: 600; cursor: pointer;
  padding: 8px 13px; border-radius: 8px; }
.w-nav button:hover, .w-nav a:hover { color: var(--text); }
.w-nav button.active { background: var(--sunken); color: var(--text); }
/* Review-due count: a small pill pinned to the button's top-right, with a bg-colored ring so it reads as
   a floating notification badge over the bar. */
.w-navbadge { position: absolute; top: 1px; right: -3px; min-width: 17px; height: 17px; padding: 0 4px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; background: var(--indigo);
  color: #fff; font-size: 10px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; border: 2px solid var(--bg); }
/* Optional web Review launcher: the same quiet circular chrome, review icon, and clipped count pill as the
   extension/userscript badge. It stays above page content but below menus and the sticky top bar. */
.w-review-float { position: fixed; right: calc(8px + env(safe-area-inset-right)); top: 62%; z-index: 55; width: 38px; height: 38px; padding: 0;
  display: flex; align-items: center; justify-content: center; border: 1px solid var(--borderS); border-radius: 50%;
  background: var(--surface); color: var(--text2); box-shadow: var(--shadow); cursor: pointer; opacity: .45;
  transition: opacity .15s ease, color .15s ease; }
.w-review-float:hover, .w-review-float:focus-visible { opacity: .95; color: var(--text); }
.w-review-float:focus-visible { outline: 2px solid var(--indigo); outline-offset: 3px; }
.w-review-float-count { position: absolute; top: -5px; right: -5px; min-width: 18px; height: 18px; padding: 0 5px;
  box-sizing: border-box; display: flex; align-items: center; justify-content: center; border-radius: 9px;
  background: var(--danger, #d84b4b); color: #fff; font-size: 11px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; box-shadow: 0 0 0 2px var(--surface); }
.w-iconbtn { width: 36px; height: 36px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface);
  color: var(--text2); display: grid; place-items: center; cursor: pointer; }
.w-iconbtn:hover { color: var(--text); }
.w-avatar { width: 28px; height: 28px; border-radius: 999px; background: var(--teal); color: #fff; display: grid;
  place-items: center; font-size: 13px; font-weight: 700; }
.w-userchip { display: flex; align-items: center; gap: 9px; padding: 4px 4px 4px 12px; border: 1px solid var(--border);
  border-radius: 999px; background: var(--surface); font-size: 13px; font-weight: 600; color: var(--text2); cursor: pointer; }
.w-topbar-auth { display: flex; align-items: center; gap: 8px; }
/* hamburger: hidden on desktop, revealed at the mobile breakpoint where .w-nav / .w-topbar-auth collapse */
.w-menu-toggle { display: none; }
.w-mobilemenu { position: fixed; top: 60px; right: 12px; z-index: 70; min-width: 204px; max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow);
  padding: 7px; animation: w-pop .1s ease-out; }
.w-mm-item { display: block; width: 100%; text-align: left; padding: 11px 13px; border-radius: 8px; font-size: 15px;
  font-weight: 600; color: var(--text); cursor: pointer; background: none; border: none; }
.w-mm-item:hover { background: var(--surface2); }
.w-mm-item.active { background: var(--sunken); }
.w-mm-item.danger { color: var(--rustInk); }
.w-mm-sep { height: 1px; background: var(--border); margin: 6px 4px; }
.w-mm-email { padding: 9px 13px 3px; font-size: 12px; color: var(--muted); }
/* account dropdown under the avatar (desktop): popover chrome; rows reuse the .w-mm-* styling above */
.w-accountmenu { z-index: 70; min-width: 216px; max-width: calc(100vw - 24px); background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow); padding: 7px; animation: w-pop .1s ease-out; }

/* ---- cards / surfaces ---- */
.w-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; }
.w-pad { padding: 20px; }
.w-h1 { font-family: var(--serif); font-weight: 500; font-size: 34px; letter-spacing: -.01em; margin: 0; }
.w-h2 { font-family: var(--serif); font-weight: 500; font-size: 24px; letter-spacing: -.01em; margin: 0; }
.w-sub { color: var(--text2); margin: 6px 0 0; }
.w-pill { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; font-weight: 600; border-radius: 999px; padding: 3px 9px; }
.w-pill-known { color: var(--tealInk); background: color-mix(in srgb, var(--teal) 12%, transparent); }
.w-pill-src { color: var(--text2); background: var(--sunken); }

/* ---- home / hero ---- */
.w-hero { display: grid; grid-template-columns: 1.04fr .96fr; gap: 48px; align-items: center; padding: 56px 0 28px; }
.w-badge { display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 600; letter-spacing: .04em;
  color: var(--indigoInk); background: rgb(75 99 204 / .1); padding: 6px 12px; border-radius: 999px; }
.w-display { font-family: var(--serif); font-weight: 500; font-size: 54px; line-height: 1.07; letter-spacing: -.015em; margin: 20px 0 0; }
.w-lead { font-size: 18px; line-height: 1.6; color: var(--text2); max-width: 30em; margin: 18px 0 0; }
.w-features { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; padding: 40px 0; }
.w-feature h3 { font-size: 16px; font-weight: 680; margin: 14px 0 6px; }
.w-feature p { font-size: 13.5px; line-height: 1.55; color: var(--text2); margin: 0; }
.w-feat-ic { width: 38px; height: 38px; border-radius: 9px; display: grid; place-items: center; font-size: 19px; }
.w-cta { background: var(--indigo); border-radius: 16px; padding: 48px 40px; text-align: center; color: #fff; margin: 8px 0 56px; }
.w-cta .w-h1 { color: #fff; }
.w-footer { border-top: 1px solid var(--border); }
.w-footer-inner { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 26px 0; font-size: 13px; color: var(--text2); }

/* sample preview legend */
.w-legend { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; font-size: 12px; color: var(--text2); }
.w-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.w-chip { font-family: var(--jp); font-size: 13.5px; font-weight: 500; color: var(--text); background: var(--surface);
  border: 1px solid var(--borderS); border-radius: 999px; padding: 6px 13px; cursor: pointer; }
.w-chip:hover { border-color: var(--text2); }

/* ---- home dashboard (logged-in hub) ---- */
.w-dash-grid { display: grid; grid-template-columns: minmax(0, 1fr) 330px; gap: 28px; align-items: start; margin-top: 22px; }
.w-dash-main { display: flex; flex-direction: column; gap: 28px; min-width: 0; }
.w-dash-side { display: flex; flex-direction: column; gap: 16px; }

.w-compose { padding: 18px; box-shadow: var(--shadow); }
.w-compose-h { font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-bottom: 11px; }
.w-compose-foot { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
.w-compose-actions { display: flex; align-items: center; gap: 10px; }
.w-compose-library { display: flex; align-items: center; margin-left: auto; }

.w-shelf-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.w-dash-shelf:empty { display: none; }
.w-dash-empty { text-align: center; padding: 42px 24px; border: 1px dashed var(--borderS); border-radius: 14px; background: var(--surface2); }
.w-dash-empty-jp { font-family: var(--jp); font-size: 24px; font-weight: 600; color: var(--text2); }
.w-dash-empty p { color: var(--text2); max-width: 42ch; margin: 12px auto 18px; line-height: 1.6; font-size: 14.5px; }

/* sidebar panels */
.w-mini { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px 18px; }
.w-mini-h { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11.5px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-bottom: 14px; }
.w-mini-link { background: none; border: none; cursor: pointer; font-size: 12px; font-weight: 600; letter-spacing: 0;
  text-transform: none; color: var(--indigoInk); padding: 0; }
.w-mini-link:hover { text-decoration: underline; }
.w-mini-empty { font-size: 13px; color: var(--muted); line-height: 1.5; margin: 0; }
.w-mini-skel { height: 62px; border-radius: 8px; background: var(--sunken); opacity: .6; }

/* Comprehension headline on the home Today panel — mirrors the dashboard hero + popup glance. */
.w-comp-hero { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.w-comp-hero b { font-size: 30px; font-weight: 720; letter-spacing: -.02em; line-height: 1; color: var(--tealInk); font-variant-numeric: tabular-nums; }
.w-comp-cap { font-size: 12px; color: var(--muted); line-height: 1.3; }
.w-comp-delta { color: var(--text2); font-weight: 650; font-variant-numeric: tabular-nums; }

.w-hstat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 12px; }
.w-hstat b { display: block; font-size: 22px; font-weight: 680; letter-spacing: -.02em; line-height: 1; font-variant-numeric: tabular-nums; }
.w-hstat span { display: block; font-size: 11.5px; font-weight: 600; color: var(--text2); margin-top: 5px; }

.w-spark { display: flex; align-items: flex-end; gap: 3px; height: 34px; margin-top: 16px; }
.w-spark span { flex: 1; min-height: 2px; border-radius: 2px; background: rgb(75 99 204 / .3); }
.w-spark span.today { background: var(--indigo); }

.w-due-num { font-size: 38px; font-weight: 720; letter-spacing: -.02em; line-height: 1; color: var(--tealInk); font-variant-numeric: tabular-nums; }
.w-due-clear { font-size: 18px; font-weight: 680; color: var(--tealInk); }
.w-due-sub { font-size: 13px; color: var(--text2); margin-top: 7px; }
.w-mini .w-btn-block { margin-top: 14px; }

.w-foc-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; padding: 8px 0; border-radius: 6px; }
.w-foc-row + .w-foc-row { border-top: 1px solid var(--border); }
.w-foc-term { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-foc-w { font-family: var(--jp); font-size: 15px; font-weight: 600; }
.w-foc-r { font-family: var(--jp); font-size: 12px; color: var(--muted); margin-left: 6px; }
.w-foc-n { font-size: 12.5px; font-weight: 700; color: var(--amberInk); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
/* Tap-to-open (Focus words card, home dashboard) — mirrors the shared dashboard's clickable focus rows. */
.w-foc-row--tap { cursor: pointer; margin: 0 -8px; padding: 8px; }
.w-foc-row--tap:hover { background: var(--surface2); }
.w-foc-row--tap:focus-visible { outline: 2px solid var(--indigo); outline-offset: -2px; }
.w-foc-row--graduated { opacity: .5; }
.w-foc-meta { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
/* The per-row ✕ that removes a word from focus, and the collapsed "Removed · Undo" strip it swaps in for. */
.w-foc-x { appearance: none; display: inline-flex; align-items: center; justify-content: center; padding: 3px; border: none; border-radius: 6px; background: none; color: var(--muted); cursor: pointer; line-height: 0; }
.w-foc-x:hover { color: var(--text); background: var(--sunken); }
.w-foc-row--removed { cursor: default; }
.w-foc-row--removed:hover { background: none; }
.w-foc-removed-msg { font-size: 12.5px; color: var(--muted); font-style: italic; }
.w-foc-undo { appearance: none; background: none; border: none; padding: 0; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--indigoInk); cursor: pointer; }
.w-foc-undo:hover { text-decoration: underline; }
.w-foc-undo:disabled { opacity: .5; cursor: default; text-decoration: none; }

@media (max-width: 860px) {
  .w-dash-grid { grid-template-columns: 1fr; }
}

/* ---- library ---- */
.w-libgrid { display: grid; grid-template-columns: 212px 1fr; gap: 28px; align-items: start; margin-top: 24px; }
.w-aside { position: sticky; top: 76px; }
.w-aside-h { font-size: 11px; font-weight: 700; letter-spacing: .06em; color: var(--muted); margin: 0 0 8px; }
.w-aside + .w-aside, .w-asec { margin-top: 22px; }
.w-filter { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px; background: none;
  border: none; color: var(--text); font-size: 13.5px; font-weight: 500; cursor: pointer; padding: 8px 11px; border-radius: 7px; margin-bottom: 2px; }
.w-filter:hover { background: var(--surface2); }
.w-filter.active { background: var(--sunken); font-weight: 650; }
.w-filter .w-count { font-size: 12.5px; color: var(--muted); }
/* A collection row: its filter button + a settings gear that only shows on hover/focus (so the sidebar stays
   quiet), pinned visible while its dialog-less state is active. */
.w-coll-row { display: flex; align-items: center; gap: 2px; }
.w-coll-row .w-filter { flex: 1; margin-bottom: 0; }
.w-coll-row { margin-bottom: 2px; }
.w-coll-gear { flex: 0 0 auto; display: grid; place-items: center; background: none; border: none; color: var(--muted);
  cursor: pointer; padding: 6px; border-radius: 6px; opacity: 0; transition: opacity .1s; }
.w-coll-row:hover .w-coll-gear, .w-coll-gear:focus-visible { opacity: 1; }
.w-coll-gear:hover { background: var(--surface2); color: var(--text); }
.w-tagrow { display: flex; flex-wrap: wrap; gap: 7px; }
.w-tag { font-size: 12px; font-weight: 600; cursor: pointer; border-radius: 999px; padding: 5px 11px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text2); }
.w-tag.active { border-color: var(--indigo); background: rgb(75 99 204 / .1); color: var(--indigoInk); }
.w-stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; margin-top: 20px; }
.w-stat { background: var(--surface); border: 1px solid var(--border); border-radius: 11px; padding: 16px; }
.w-stat-l { font-size: 12px; color: var(--text2); font-weight: 600; }
.w-stat-v { font-size: 24px; font-weight: 680; margin-top: 4px; font-variant-numeric: tabular-nums; }
.w-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.w-search { flex: 1; position: relative; }
.w-search .w-input { padding-left: 34px; }
.w-search-ic { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 15px; }
.w-textgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 16px; }
.w-textcard { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px; position: relative;
  display: flex; flex-direction: column;
  /* grid items default to min-width:auto, which refuses to shrink below the content's min-content — a
     nowrap snippet or an unbreakable title then forces the card past the viewport (horizontal overflow,
     most visible on the single-column mobile grid). Let it shrink to the track instead. */
  min-width: 0; }
.w-textcard-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
/* break a long unbroken title (e.g. a pasted URL/filename) instead of overflowing the card */
.w-textcard-title { font-family: var(--jp); font-size: 17px; font-weight: 600; line-height: 1.3; cursor: pointer; overflow-wrap: anywhere; }
.w-textcard-snip { font-family: var(--jp); font-size: 13px; color: var(--muted); margin-top: 5px; line-height: 1.5;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-progress { height: 5px; border-radius: 99px; background: var(--sunken); overflow: hidden; margin-top: 12px; }
.w-progress > i { display: block; height: 100%; background: var(--indigo); border-radius: 99px; }
.w-textcard-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 9px; font-size: 12px; }
.w-kebab { background: none; border: none; color: var(--muted); font-size: 18px; cursor: pointer; line-height: 1; padding: 2px 6px; }
.w-menu { position: absolute; top: 38px; right: 12px; z-index: 20; background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow); padding: 6px; min-width: 156px; font-size: 13.5px; animation: w-pop .1s ease-out; }
.w-menu div { padding: 8px 10px; border-radius: 6px; cursor: pointer; color: var(--text2); }
.w-menu div:hover { background: var(--surface2); color: var(--text); }
.w-empty { text-align: center; padding: 60px 20px; color: var(--muted); }

/* ---- progress page ----
   The dashboard itself is the shared, transport-agnostic component (src/shared/stats-dashboard.ts) the
   extension page and userscript modal also render — it ships its own scoped CSS keyed to a separate token
   set. .w-dash bridges those tokens onto the web-app palette; the VALUES match the shared card kit, so the
   dashboard looks identical across surfaces. --surface and --border already share names; only the renamed
   tokens and the rgb-triple color tokens are remapped here. */
.w-dash {
  --text-1: var(--text); --text-2: var(--text2); --text-3: var(--muted);
  --surface-2: var(--surface2); --surface-sunken: var(--sunken);
  --border-strong: var(--borderS);
  --font-ui: var(--ui); --font-jp: var(--jp);
  --c-new: 75 99 204; --c-learn: 196 140 38; --c-review: 31 138 130;
  --new-ink: var(--indigoInk); --learn-ink: var(--amberInk); --review-ink: var(--tealInk);
}
html[data-theme="dark"] .w-dash {
  --c-new: 128 149 236; --c-learn: 224 170 80; --c-review: 84 190 180;
}

/* ---- reader ---- */
.w-readerbar { position: sticky; top: 60px; z-index: 40; background: var(--bg); border-bottom: 1px solid var(--border); }
.w-readerbar-inner { height: 54px; display: flex; align-items: center; gap: 14px; }
/* min-width:0 lets the title shrink below its (nowrap) content width inside the flex bar, so a long title
   ellipsizes instead of forcing the whole reader bar past the viewport on a phone. */
.w-readerbar .w-title { flex: 0 1 auto; min-width: 0; font-family: var(--jp); font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.w-readerbar-bar { height: 3px; background: var(--sunken); }
.w-readerbar-bar > i { display: block; height: 100%; background: var(--indigo); transition: width .2s; }
/* logged-out demo reader: an accent strip under the reader bar inviting sign-up */
.w-demobar { background: color-mix(in srgb, var(--indigo) 9%, var(--bg)); border-bottom: 1px solid var(--border); }
.w-demobar > .w-wrap { padding-top: 11px; padding-bottom: 11px; font-size: 13px; line-height: 1.5; color: var(--text2); }
.w-link { background: none; border: 0; padding: 0; font: inherit; color: var(--indigoInk); font-weight: 600; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.w-link:hover { opacity: .8; }
.w-divider { width: 1px; height: 26px; background: var(--border); }
.w-reading { padding: 36px 20px 160px; }
.w-article { margin-left: auto; margin-right: auto; }
.w-article-title { font-family: var(--serif); font-weight: 500; font-size: 30px; letter-spacing: -.01em; margin-bottom: 6px; }
.w-article-meta { font-size: 13px; color: var(--muted); margin-bottom: 28px; font-family: var(--jp); }
.w-passage { font-family: var(--jp); color: var(--text); }
.w-block { margin: 0 0 .35em; }
.w-punct { color: var(--text2); }
/* Words are NOT wrapped in spans: the reading surface paints CSS Custom Highlight ranges straight over the
   block's text nodes (see reader.ts / content/highlighter.ts), and injects real <ruby> for furigana. So
   there is nothing here to style per token — the tints come from the document-level ::highlight() rules
   (content/shadow.ts#writeHighlightStyles) and the readings from #injectFuriganaStyles. That's what keeps
   the Japanese setting solid, with no per-word padding and normal line breaking.
   `overflow-wrap: anywhere` lets an unbroken Latin/URL run still wrap inside a narrow column. */
.w-passage, .w-cue-text, .w-now, .w-iplus-sentence { overflow-wrap: anywhere; }

/* ---- synced YouTube reader: one centered unit — video + caption + a self-contained transcript card ---- */
/* dvh, not vh: on a phone the visible viewport shrinks under Safari's chrome, and 100vh would push the
   transcript card's bottom off-screen. Falls back cleanly — dvh is supported everywhere the app targets. */
.w-video-reader { display: flex; align-items: stretch; gap: 26px; height: calc(100dvh - 55px);
  max-width: 1300px; margin: 0 auto; padding: 26px 28px; }
.w-video-main { flex: 1; min-width: 0; overflow-y: auto; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-start; padding: 2px; }
/* `position: relative` so the fullscreened stage is the containing block for the shadow host that
   remountUiForFullscreen() re-parents into it — the lookup card positions against the viewport, and the
   fullscreen stage's border box IS the viewport. */
.w-stage { position: relative; width: 100%; max-width: 820px; display: flex; flex-direction: column; gap: 12px; }
.w-player { position: relative; width: 100%; aspect-ratio: 16 / 9; background: #000; border-radius: 12px; overflow: hidden;
  box-shadow: var(--shadow); }
/* Only the HOST shell fills the box — the player box also holds the overlaid caption, the click-catcher
   and the in-video control bar, all absolutely positioned, so a blanket `> div` stretch would break them.
   The shell's own child (the local-video host div, or an error card) still fills it; on the YouTube path
   the YT API swaps the inner div for the iframe directly. */
.w-player iframe { width: 100%; height: 100%; border: 0; display: block; }
.w-player-host { position: absolute; inset: 0; }
.w-player-host > div { width: 100%; height: 100%; }
/* a local file plays in a plain <video>; `contain` (not `cover`) so a 4:3 source isn't cropped */
.w-localvideo { width: 100%; height: 100%; display: block; object-fit: contain; background: #000; }
/* Round the MEDIA itself, not just the box that clips it. A <video> or a YouTube <iframe> is promoted to
   its own compositor layer the moment it starts painting, and until the next full repaint that layer
   ignores the rounded `overflow: hidden` clip on .w-player — the corners flash square, then snap round.
   Carrying the radius down the shell chain means the corners are never square to begin with, and
   `inherit` keeps them in lockstep with the box (fullscreen sets it to 0, and these follow). */
.w-player-host, .w-player-host > div, .w-player iframe, .w-localvideo { border-radius: inherit; }
.w-player-frame { display: grid; grid-template-columns: minmax(0, 1fr) 42px; gap: 8px; align-items: stretch; width: 100%; }
.w-player-frame .w-player { min-width: 0; }
.w-jra-rail { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  padding: 7px 4px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface);
  box-shadow: 0 1px 2px rgb(38 35 30 / .06); }
.w-jra-rail-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  padding: 0; border: 0; border-radius: 9px; background: transparent; color: var(--text2); cursor: pointer; }
.w-jra-rail-btn:hover { background: var(--sunken); color: var(--text); }
.w-jra-rail-btn.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
.w-jra-rail .w-vc-btn { width: 32px; height: 32px; border-radius: 9px; color: var(--text2); filter: none; }
.w-jra-rail .w-vc-btn:hover { background: var(--sunken); color: var(--text); }
.w-jra-rail .w-vc-btn.active { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); }
.w-player-err { color: var(--text2); display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
  justify-content: center; height: 100%; padding: 24px; background: var(--sunken); }
/* The line currently being spoken, attached directly under the video — the focal reading line. Its size and
   leading are the user's SUBTITLE prefs (--w-cap-*, written by reader.ts#applyCaptionStyle), separate from
   the reading typography that sizes the transcript: the two are read at different distances. Both placements
   below inherit them, so one control governs the caption whether it sits under the picture or over it. */
.w-now { min-height: 2.2em; padding: 15px 22px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; font-family: var(--jp); font-size: var(--w-cap-size, 23px); line-height: var(--w-cap-lh, 1.7);
  text-align: center; color: var(--text);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; box-shadow: 0 1px 2px rgb(38 35 30 / .04); }
/* One row per cue that's on screen at once (two speakers in a caption block, a sign over dialogue): the
   column stacks them, each its own painted, clickable text run that wraps within the caption's width.
   REQUIRED, not just for stacking: the caption's text MUST live inside a block child, never as a direct
   child of the flex `.w-now`. iOS Safari won't paint a ::highlight() background over an anonymous flex-item
   text run (Chrome does), so text set straight on `.w-now` tinted on desktop but not on iOS — the words
   stayed tappable (hit-testing is geometric) but colorless. Wrapping each line in this block is what makes
   the tint render there, mirroring the transcript's own `.w-cue-text` block. [ios highlight paint] */
.w-now-line { max-width: 100%; }
.w-now-slot:empty { display: none; } /* caption overlaid → the under-video slot takes no vertical space */

/* ---- overlaid caption: the same element, re-parented into .w-player to sit over the picture ----
   It stays pointer-interactive (its words open the lookup card), and `width: max-content` keeps it no wider
   than the subtitle itself, so it covers only the strip it draws on and leaves the rest of the picture
   clickable. Bottom offset clears a native <video> control bar plus breathing room. */
/* Colors/edge/radius/border here are the user's OVERLAY-ONLY subtitle prefs (--w-cap-ov-*, written by
   reader.ts#applyCaptionStyle via video-sync.ts#captionOverlayVars). Each fallback below is the value this
   rule hardcoded before those prefs existed, so a browser with no prefs saved renders identically. */
.w-now-overlay { position: absolute; left: 50%; bottom: 9%; transform: translateX(-50%);
  width: max-content; max-width: 92%; min-height: 0; margin: 0;
  background: var(--w-cap-ov-bg, rgb(0 0 0 / .62)); border: var(--w-cap-ov-border, 0);
  border-radius: var(--w-cap-ov-radius, 8px); box-shadow: none;
  padding: var(--w-cap-ov-py, 6px) var(--w-cap-ov-px, 14px); color: var(--w-cap-ov-fg, #fff);
  text-shadow: var(--w-cap-ov-edge, 0 1px 3px rgb(0 0 0 / .9)); z-index: 2; }
.w-now-overlay:empty { display: none; } /* no active cue: don't float an empty black bar over the video */
/* The cue ended and captionDwell says don't hang on to it. `display: flex` on .w-now beats [hidden]'s UA
   `display: none`, so the rule has to be spelled out. Hiding rather than emptying keeps the tokens (and
   their painted highlight ranges) intact for when the same line comes back.

   UNDER the video the card must keep its box: collapsing it hands its height back to the picture, and since
   fullscreen sizes the picture with `flex: 1`, the video would grow on every silent gap and shrink again on
   the next line — the whole layout pumping in time with the subtitles. `visibility` hides the text and its
   hit-testing while the strip holds its place. (Both placements land on the same element, so the overlaid
   rule below has to win: it comes second at equal specificity.) */
.w-now[hidden] { visibility: hidden; }
/* OVER the picture it's absolutely positioned — removing it moves nothing, and leaving a blank strip of its
   background floating over the video is exactly what we don't want. */
.w-now-overlay[hidden] { display: none; }
/* injected furigana rides the caption's own color (currentColor), and the highlight tints are translucent,
   so both read correctly against the dark strip without a separate palette */

/* ---- fullscreen: the STAGE goes fullscreen — the player box AND the under-video caption slot (never the
   <video>, which paints no child overlays) — so BOTH caption placements survive it and the saved preference
   is never overridden. The shadow-hosted lookup card is remounted into this subtree. Under-video, the
   picture takes only the height the caption card leaves: `flex: 1` + `min-height: 0` beat the 16:9
   aspect-ratio, and the media letterboxes itself inside the shorter box. ---- */
.w-stage:fullscreen, .w-stage.w-fauxfs { width: 100vw; height: 100vh; max-width: none; gap: 0; background: var(--bg); }
/* iOS Safari has no Fullscreen API on non-<video> elements, so the reader falls back to a CSS "faux
   fullscreen": pin the stage over the whole viewport itself (real fullscreen otherwise supplies this). The
   dynamic-viewport units track Safari's collapsing URL bar; z-index sits just under the shadow-hosted lookup
   card's own layer. reader.ts toggles .w-fauxfs and mirrors document.fullscreenElement via isFullscreen(). */
.w-stage.w-fauxfs { position: fixed; inset: 0; width: 100dvw; height: 100dvh; z-index: 2147483645; }
/* The shadow casts DOWN onto the caption strip, so the picture reads as sitting above it: offset-y with a
   negative spread keeps it off the sides and top (there is nothing there but screen edge). It needs the
   z-index — the caption slot is a later sibling and would otherwise paint straight over the shadow. */
.w-stage:fullscreen .w-player, .w-stage.w-fauxfs .w-player { flex: 1; min-height: 0; aspect-ratio: auto; border-radius: 0; z-index: 1;
  box-shadow: 0 10px 22px -6px rgb(0 0 0 / .55); }
.w-stage-native:fullscreen .w-player-frame, .w-stage-native.w-fauxfs .w-player-frame { flex: 1; min-height: 0; grid-template-columns: minmax(0, 1fr) 48px; gap: 0; }
.w-stage-native:fullscreen .w-player, .w-stage-native.w-fauxfs .w-player { width: 100%; height: 100%; }
.w-stage-native:fullscreen .w-jra-rail, .w-stage-native.w-fauxfs .w-jra-rail { border-width: 0 0 0 1px; border-radius: 0; background: #111; }
.w-stage-native:fullscreen .w-jra-rail :is(.w-vc-btn, .w-jra-rail-btn),
.w-stage-native.w-fauxfs .w-jra-rail :is(.w-vc-btn, .w-jra-rail-btn) { color: #fff; }
/* The under-video card, fullscreen: flush against the picture, and capped so a long subtitle scrolls inside
   itself instead of crowding the video out. */
.w-stage:fullscreen .w-now-slot, .w-stage.w-fauxfs .w-now-slot { flex: none; max-height: 34vh; overflow-y: auto; }
/* Scoped to the SLOT — i.e. the under-video placement only. Unscoped (`.w-stage:fullscreen .w-now`) this
   also matched the overlaid caption, which keeps its .w-now class, and at (0,3,0) it outranked
   `.w-now-overlay` (0,1,0): the user's corner-radius and border prefs were silently flattened the moment
   they went fullscreen. */
.w-stage:fullscreen .w-now-slot .w-now, .w-stage.w-fauxfs .w-now-slot .w-now { border: 0; border-radius: 0; box-shadow: none; padding: 16px 26px;
  font-size: clamp(calc(var(--w-cap-size, 23px) * 1.05), 2.2vw, calc(var(--w-cap-size, 23px) * 1.6)); }
/* Fullscreen SCALES the user's chosen subtitle size and padding (rather than overriding them with fixed
   values), so the preferences they set at window size still read as theirs across the room. */
.w-stage:fullscreen .w-player .w-now-overlay, .w-stage.w-fauxfs .w-player .w-now-overlay { bottom: 7%;
  padding: calc(var(--w-cap-ov-py, 6px) * 1.35) calc(var(--w-cap-ov-px, 14px) * 1.3);
  font-size: clamp(calc(var(--w-cap-size, 23px) * 1.05), 2.6vw, calc(var(--w-cap-size, 23px) * 1.75)); }
.w-cue-label { font-size: 11px; font-weight: 700; letter-spacing: .04em; color: var(--muted); text-transform: uppercase; }
/* ---- in-video controls (YouTube-like): a scrim-backed bar overlaid on the bottom of the picture.
   Everything here draws OVER video, so it uses explicit dark values + white text (like .w-now-overlay),
   never the light-theme surface tokens. z-order inside .w-player:
   host shell (auto) < click-catcher (1) < overlaid caption (2) < control bar (3). ---- */
/* the transparent click/move-catcher: receives the pointermove that wakes the controls and the click that
   toggles play/pause — a YouTube <iframe> would otherwise swallow both (see reader.ts) */
.w-vc-catch { position: absolute; inset: 0; z-index: 1; }
/* subtitle-alignment readout: a transient pill in the top-left after each z / x nudge. Above the bar (4) so
   it stays legible when both are up, and pointer-transparent so it never eats a click on the picture. */
.w-vc-offset { position: absolute; top: 12px; left: 12px; z-index: 4; pointer-events: none;
  padding: 6px 12px; border-radius: 8px; background: rgb(0 0 0 / .72); color: #fff;
  font-size: 13.5px; font-weight: 600; font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity .18s; }
.w-vc-offset-on { opacity: 1; }
.w-stage:fullscreen .w-vc-offset, .w-stage.w-fauxfs .w-vc-offset { top: 24px; left: 24px; font-size: 17px; padding: 8px 16px; }
/* subtitle-timing panel: a bar menu, so it sits just above the bar (whose measured height --w-vc-h the
   reader keeps up to date) and inside the player box, which is the fullscreen element. */
.w-vc-align { position: absolute; right: 10px; bottom: calc(var(--w-vc-h, 48px) + 8px); z-index: 4;
  display: none; min-width: 210px; padding: 12px; border-radius: 10px; color: #fff;
  background: rgb(20 20 22 / .94); box-shadow: 0 8px 28px rgb(0 0 0 / .45); }
.w-vc-align-on { display: block; }
.w-vc-align-h { font-size: 11px; font-weight: 700; letter-spacing: .05em; color: rgb(255 255 255 / .62); }
.w-vc-align-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 10px 0 8px; }
/* An <input> wearing a label's clothes: bare until hovered, so the click-to-type affordance appears when the
   pointer is on it and the row otherwise still reads as a readout between two nudge buttons. Not the `font`
   shorthand — it would reset the font-variant-numeric that .w-tabnum puts on this same element. */
.w-vc-align-val { flex: 1; min-width: 0; text-align: center; font-family: inherit; font-size: 17px; font-weight: 600;
  padding: 5px 4px; border: 1px solid transparent; border-radius: 8px; cursor: text;
  background: transparent; color: #fff; }
.w-vc-align-val:hover { background: rgb(255 255 255 / .1); }
.w-vc-align-val:focus { outline: none; background: rgb(255 255 255 / .14); border-color: rgb(255 255 255 / .38); }
.w-vc-align-step { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 32px; height: 32px; border: 0; border-radius: 8px; cursor: pointer;
  background: rgb(255 255 255 / .12); color: #fff; }
.w-vc-align-step:hover { background: rgb(255 255 255 / .22); }
.w-vc-align-hint { font-size: 11.5px; line-height: 1.45; color: rgb(255 255 255 / .55); }
.w-vc-align-reset { margin-top: 10px; width: 100%; padding: 6px 0; border: 0; border-radius: 8px;
  cursor: pointer; font-size: 12.5px; font-weight: 600; background: rgb(255 255 255 / .12); color: #fff; }
.w-vc-align-reset:hover { background: rgb(255 255 255 / .22); }
.w-stage:fullscreen .w-vc-align, .w-stage.w-fauxfs .w-vc-align { right: 24px; min-width: 250px; }

/* options menu (gear): the same bar-anchored corner as the timing panel — they're mutually exclusive, so
   they never collide. Capped in height because the Subtitles page is taller than a short player. */
.w-vm { position: absolute; right: 10px; bottom: calc(var(--w-vc-h, 48px) + 8px); z-index: 4;
  display: none; min-width: 232px; max-height: min(74%, 420px); overflow-y: auto; padding: 6px;
  border-radius: 10px; color: #fff; background: rgb(20 20 22 / .94); box-shadow: 0 8px 28px rgb(0 0 0 / .45);
  font-size: 13.5px; }
.w-vm-on { display: block; }
.w-vm-wide { min-width: 288px; }
.w-vm-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  padding: 9px 10px; border: 0; border-radius: 7px; cursor: pointer; background: none; color: #fff;
  font-size: inherit; text-align: left; }
.w-vm-row:hover { background: rgb(255 255 255 / .12); }
.w-vm-row-val { display: inline-flex; align-items: center; gap: 4px; color: rgb(255 255 255 / .62); }
/* the check occupies its gutter whether or not it's drawn, so picking a row never shifts the list */
.w-vm-check { display: inline-flex; width: 15px; flex: 0 0 15px; }
.w-vm-row-on { font-weight: 600; }
.w-vm-back { display: flex; align-items: center; gap: 6px; width: 100%; padding: 8px 10px; margin-bottom: 4px;
  border: 0; border-bottom: 1px solid rgb(255 255 255 / .14); border-radius: 0; cursor: pointer;
  background: none; color: #fff; font-size: inherit; font-weight: 600; text-align: left; }
.w-vm-note { padding: 2px 10px 8px; font-size: 11.5px; line-height: 1.45; color: rgb(255 255 255 / .55); }
.w-vm-field { padding: 7px 10px; }
.w-vm-label { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 7px;
  font-size: 12px; color: rgb(255 255 255 / .72); }
.w-vm-val { color: #fff; font-weight: 600; }
.w-vm-slider { width: 100%; margin: 0; accent-color: #8095ec; cursor: pointer; }
.w-vm-seg { display: flex; gap: 4px; }
.w-vm-seg button { flex: 1; padding: 6px 0; border: 0; border-radius: 6px; cursor: pointer;
  background: rgb(255 255 255 / .12); color: #fff; font-size: 12px; }
.w-vm-seg button:hover { background: rgb(255 255 255 / .22); }
.w-vm-seg button.active { background: rgb(128 149 236 / .55); font-weight: 600; }
.w-vm-swatches { display: flex; gap: 8px; }
.w-vm-swatch { width: 26px; height: 26px; border-radius: 50%; cursor: pointer;
  border: 1px solid rgb(255 255 255 / .35); }
.w-vm-swatch.active { border: 2px solid #fff; box-shadow: 0 0 0 2px rgb(128 149 236 / .8); }
.w-vm-reset { width: calc(100% - 20px); margin: 6px 10px 4px; padding: 7px 0; border: 0; border-radius: 7px;
  cursor: pointer; font-size: 12.5px; font-weight: 600; background: rgb(255 255 255 / .12); color: #fff; }
.w-vm-reset:hover { background: rgb(255 255 255 / .22); }
.w-stage:fullscreen .w-vm, .w-stage.w-fauxfs .w-vm { right: 24px; }

.w-vc { position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; flex-direction: column;
  padding: 26px 12px 6px; background: linear-gradient(rgb(0 0 0 / 0), rgb(0 0 0 / .5) 45%, rgb(0 0 0 / .78));
  opacity: 0; pointer-events: none; transition: opacity .18s; }
.w-player.w-vc-shown .w-vc { opacity: 1; pointer-events: auto; }
.w-player:not(.w-vc-shown):not(.w-player-native) { cursor: none; } /* hide the mouse over the picture while the custom bar is hidden */
/* a failed player shows an explanatory card instead: nothing to control, and the card's "Open on
   YouTube" link must not sit under the click-catcher */
.w-player-failed .w-vc, .w-player-failed .w-vc-catch { display: none; }
/* seek bar: buffered fill under played fill under the thumb; the wrapper pads the pointer hit area well
   beyond the slim visible track */
.w-vc-seek { padding: 8px 0 6px; cursor: pointer; touch-action: none; }
.w-vc-track { position: relative; height: 4px; border-radius: 99px; background: rgb(255 255 255 / .22); }
.w-vc-buf, .w-vc-played { position: absolute; top: 0; bottom: 0; left: 0; width: 0; border-radius: 99px; }
.w-vc-buf { background: rgb(255 255 255 / .32); }
.w-vc-played { background: #8095ec; } /* explicit indigo (the dark-theme value) — theme tokens go light over video */
.w-vc-thumb { position: absolute; top: 50%; left: 0; width: 12px; height: 12px; border-radius: 50%;
  background: #fff; transform: translate(-50%, -50%); box-shadow: 0 1px 3px rgb(0 0 0 / .6); }
.w-vc-row { display: flex; align-items: center; gap: 4px; }
/* Every control in the bar is a fixed-size ICON button — no text. Labelled buttons are exactly what made
   the row overflow a short/narrow player; the label now lives in `title` (plus aria-pressed on the mode
   toggle), and everything that isn't about driving playback moved out to the reader bar. */
.w-vc-btn { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; background: none; border: 0; border-radius: 50%; color: #fff;
  cursor: pointer; line-height: 1; filter: drop-shadow(0 1px 2px rgb(0 0 0 / .5)); }
.w-vc-btn:hover { background: rgb(255 255 255 / .16); }
/* the pressed state of the stop-after-each-subtitle toggle — all that stands in for its dropped label */
.w-vc-btn.active { background: rgb(128 149 236 / .38); }
.w-vc-btn.active:hover { background: rgb(128 149 236 / .52); }
.w-vc-time { flex: 0 0 auto; color: #fff; font-size: 12.5px; white-space: nowrap; padding: 0 6px;
  text-shadow: 0 1px 2px rgb(0 0 0 / .5); }
/* volume: the slider expands on hover/focus like a normal player, so at rest it costs one button of width */
.w-vc-volwrap { flex: 0 0 auto; display: flex; align-items: center; }
.w-vc-vol { width: 0; margin: 0; opacity: 0; accent-color: #fff; cursor: pointer;
  transition: width .16s, opacity .16s, margin .16s; }
.w-vc-volwrap:hover .w-vc-vol, .w-vc-vol:focus-visible { width: 64px; margin: 0 6px 0 2px; opacity: 1; }

/* A narrow player SHEDS the optional controls instead of wrapping the row. Measured against the PLAYER via
   a container query, not the viewport: the same element is ~320px wide in a stacked phone column and 100vw
   in fullscreen, and only its own box decides what fits. `inline-size` (not `size`) on purpose — size
   containment would make the box ignore its contents for HEIGHT, and .w-player's height comes from its
   aspect-ratio, which is not worth betting the whole player's layout on. The player is 16:9, so its width
   already stands in for how short it is. */
.w-player { container-type: inline-size; }
@container (max-width: 430px) { .w-vc-volwrap { display: none; } }
@container (max-width: 330px) {
  .w-vc-time { display: none; }
  .w-vc { padding-top: 14px; }
  .w-vc-btn { width: 30px; height: 30px; }
}
/* the overlaid caption clears the bar while it's shown — the bar's REAL height is measured into --w-vc-h
   (reader.ts ResizeObserver: it wraps on phones and scales in fullscreen). The explicit fullscreen variant
   is needed because `.w-stage:fullscreen .w-player .w-now-overlay { bottom: 7% }` above already scores
   (0,4,0) and would otherwise outrank the (0,3,0) bar-clearance rule. */
.w-player.w-vc-shown .w-now-overlay, .w-stage:fullscreen .w-player.w-vc-shown .w-now-overlay,
.w-stage.w-fauxfs .w-player.w-vc-shown .w-now-overlay {
  bottom: calc(var(--w-vc-h, 64px) + 12px); }
/* transcript — a self-contained side card with its own header, aligned to the video */
.w-transcript { flex: 0 0 358px; width: 358px; display: flex; flex-direction: column; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 1px 2px rgb(38 35 30 / .04); color: var(--text); }
.w-transcript-head { display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 13px 16px; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
/* the panel dismisses itself from its own header (the reader bar's toggle brings it back) */
.w-transcript-hide { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; background: none; border: 0; border-radius: 6px; cursor: pointer;
  color: var(--muted); }
.w-transcript-hide:hover { background: var(--sunken); color: var(--text); }
/* the reader bar's transcript toggle reads "on" while the panel is up */
.w-readerbar .w-btn-ghost.active { border-color: var(--indigo); color: var(--indigoInk); }
.w-transcript-meta { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.w-transcript-body { flex: 1; overflow-y: auto; padding: 7px 7px; font-family: var(--jp); }
.w-no-panel .w-transcript { display: none; }
.w-cue { position: relative; display: flex; gap: 9px; padding: 8px 10px; cursor: pointer; align-items: baseline; border-radius: 8px;
  border-left: 3px solid transparent; transition: background .1s; }
.w-cue:hover { background: var(--surface2); }
.w-cue.active { background: rgb(75 99 204 / .1); border-left-color: var(--indigo); }
/* gutter: ‹ (prev line) · timestamp (this line) · › (next line) — each seeks the player + transcript */
.w-cue-gutter { flex: 0 0 auto; display: flex; align-items: baseline; gap: 1px; }
.w-cue-ts { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px;
  font-variant-numeric: tabular-nums; color: var(--text2); background: none; border: none; padding: 2px 3px;
  cursor: pointer; min-width: 40px; text-align: center; }
.w-cue.active .w-cue-ts { color: var(--indigoInk); font-weight: 700; }
.w-cue-skip { background: none; border: none; cursor: pointer; color: var(--muted); font-size: 15px;
  line-height: 1; padding: 1px 5px; border-radius: 6px; }
.w-cue-skip:hover { background: var(--sunken); color: var(--text); }
.w-cue.active .w-cue-skip { color: var(--indigoInk); }
/* leading comes from the transcript body's inline `line-height` (the user's reading pref) — a value here
   would silently win over it and make the Line height slider a no-op */
.w-cue-text { flex: 1; min-width: 0; }
.w-cue-sync-action { flex: 0 0 30px; align-self: center; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; border: 0; border-radius: 7px; background: transparent;
  color: var(--muted); cursor: pointer; opacity: 0; pointer-events: none; transition: opacity .1s, color .1s, background .1s; }
.w-cue:hover .w-cue-sync-action, .w-cue:focus-within .w-cue-sync-action {
  opacity: 1; pointer-events: auto; }
.w-cue-sync-action:hover, .w-cue-sync-action:focus-visible {
  background: var(--sunken); color: var(--indigoInk); }
@media (hover: none), (pointer: coarse), (max-width: 820px) {
  .w-cue-sync-action { flex-basis: 44px; width: 44px; height: 44px; opacity: 1; pointer-events: auto; }
}
@media (max-width: 980px) { .w-transcript { flex-basis: 320px; width: 320px; } }
@media (max-width: 820px) { .w-video-reader { flex-direction: column; height: auto; padding: 20px; }
  .w-video-main { justify-content: flex-start; } .w-transcript { flex: none; width: 100%; max-height: 440px; } }

/* ---- drag-to-resize grips: video-reader panes (video ⟷ transcript) + the text reader's column [reader-resize] ----
   Each grip is a wide invisible hit strip (grab anywhere along the edge) centred on the boundary via
   translateX(-50%); reader.ts positions its `left` and toggles `display`. The visible line is a slim child
   so the hit area can be generous without a fat bar. The grips are added by JS (.w-rz-host ⇒ the container
   becomes the positioning context); they never appear at the ≤820px stacked breakpoint (JS hides them). */
.w-rz-host { position: relative; }
.w-rz-grip { position: absolute; top: 0; bottom: 0; width: 16px; transform: translateX(-50%); z-index: 6;
  cursor: col-resize; touch-action: none; }
body.w-rz-dragging { cursor: col-resize; user-select: none; } /* hold the resize cursor through the whole drag */

/* The handle is a short rounded pill centred on the boundary: calm at rest, growing + turning accent on
   hover/drag. */
/* Video reader: centred vertically in the pane. */
.w-video-reader .w-rz-line { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 6px; height: 46px; border-radius: 99px; background: var(--muted); transition: background .12s, height .12s; }
.w-video-reader .w-rz-grip:hover .w-rz-line,
.w-video-reader .w-rz-grip.w-rz-active .w-rz-line { background: var(--indigo); height: 64px; }

/* Text reader: JS bounds the grip to the passage's first-to-last-line box, so its midpoint follows the
   content rather than the viewport or the title/meta above it. */
.w-reading .w-rz-line { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 6px;
  height: 46px; border-radius: 99px; background: var(--muted); transition: background .12s, width .12s; }
.w-reading .w-rz-grip:hover .w-rz-line,
.w-reading .w-rz-grip.w-rz-active .w-rz-line { background: var(--indigo); width: 8px; }

/* settings popover — the Aa reading settings and the local-video Tracks panel share this shell */
.w-pop { position: fixed; z-index: 56; width: 280px; background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow); padding: 16px; animation: w-pop .1s ease-out;
  max-height: calc(100vh - 84px); overflow-y: auto; overscroll-behavior: contain; }
.w-pop-h { font-size: 11px; font-weight: 700; letter-spacing: .05em; color: var(--muted); margin-top: 14px; }
.w-pop-h:first-child { margin-top: 0; }
.w-seg { display: flex; gap: 6px; margin: 8px 0 0; }
.w-seg button { flex: 1; padding: 7px 4px; border-radius: 7px; font-size: 12.5px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface2); color: var(--text2); }
.w-seg button.active { border-color: var(--indigo); background: rgb(75 99 204 / .1); color: var(--indigoInk); }

/* ---- the reading-settings popover: titled sections of labelled sliders + segmented pills ----
   Wider than the base popover because a slider needs a usable travel distance; the value readout sits on the
   label's baseline (right-aligned, tabular) so it doesn't shift as digits change under a drag. */
.w-pop-reading { width: 312px; padding: 0; }
.w-pop-head { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 13px 16px; background: var(--surface); border-bottom: 1px solid var(--border);
  border-radius: 12px 12px 0 0; }
.w-pop-title { font-size: 13.5px; font-weight: 650; color: var(--text); }
.w-pop-reset { background: none; border: 0; padding: 3px 6px; margin-right: -6px; border-radius: 6px; cursor: pointer;
  font-size: 12px; font-weight: 600; color: var(--muted); }
.w-pop-reset:hover { background: var(--sunken); color: var(--text); }
.w-pop-sec { padding: 13px 16px; }
.w-pop-sec + .w-pop-sec { border-top: 1px solid var(--border); }
.w-pop-sech { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 11px; }
.w-pop-field + .w-pop-field { margin-top: 13px; }
.w-pop-fieldhead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
/* sentence-case labels inside this popover — the uppercase tracking of the shared .w-pop-h belongs on the
   section headers above, not on every field */
.w-pop-reading .w-pop-h { margin: 0; font-size: 12.5px; font-weight: 600; letter-spacing: 0; color: var(--text2); }
.w-pop-val { font-size: 12px; color: var(--muted); }
.w-pop-reading .w-seg { margin-top: 7px; }

/* range slider: a slim track with a generous thumb, so it stays draggable on touch without a chunky rail */
.w-pop-range { -webkit-appearance: none; appearance: none; width: 100%; margin: 9px 0 1px; background: none;
  cursor: pointer; }
.w-pop-range::-webkit-slider-runnable-track { height: 4px; border-radius: 99px; background: var(--sunken);
  border: 1px solid var(--borderS); }
.w-pop-range::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 16px; height: 16px;
  margin-top: -7px; border-radius: 50%; background: var(--indigo); border: 2px solid var(--surface);
  box-shadow: 0 1px 3px rgb(38 35 30 / .3); }
.w-pop-range::-moz-range-track { height: 4px; border-radius: 99px; background: var(--sunken); border: 1px solid var(--borderS); }
.w-pop-range::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: var(--indigo);
  border: 2px solid var(--surface); box-shadow: 0 1px 3px rgb(38 35 30 / .3); }
.w-pop-range:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--indigoInk); outline-offset: 2px; }
.w-pop-range:hover::-webkit-slider-thumb { transform: scale(1.08); }

/* ---- modal / overlay ---- */
.w-overlay { position: fixed; inset: 0; z-index: 80; background: rgb(20 18 14 / .42); display: grid; place-items: start center;
  padding: calc(60px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) calc(20px + env(safe-area-inset-left));
  overflow: auto; animation: w-fade .1s ease-out; }
.w-modal { width: 100%; max-width: 560px; background: var(--surface); border: 1px solid var(--border); border-radius: 14px;
  box-shadow: var(--shadow); overflow: hidden; animation: w-pop .12s ease-out; }
.w-modal-head { padding: 16px 22px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
.w-modal-head h3 { margin: 0; font-size: 18px; font-weight: 680; }
.w-modal-body { padding: 18px 22px 22px; }
.w-x { background: none; border: none; color: var(--muted); font-size: 22px; cursor: pointer; line-height: 1; }

/* ---- reusable dialog (dialog.ts): footer, message, and the form-field builders ---- */
.w-dlg-foot { display: flex; justify-content: flex-end; gap: 10px; padding: 14px 22px 18px; border-top: 1px solid var(--border); }
.w-dlg-msg { margin: 2px 0 0; font-size: 14px; line-height: 1.5; color: var(--text); }
.w-btn-danger-solid { background: var(--rust); color: #fff; border: 1px solid var(--rust); }
.w-field { margin-bottom: 16px; }
.w-field:last-child { margin-bottom: 0; }
.w-field-label { display: block; font-size: 12.5px; font-weight: 600; color: var(--text2); margin-bottom: 7px; }
.w-radio { display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow: auto; }
.w-radio-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 11px; border-radius: 9px; cursor: pointer;
  background: none; border: 1px solid transparent; text-align: left; color: var(--text); }
.w-radio-row:hover { background: var(--surface2); }
.w-radio-row.on { border-color: var(--indigo); background: rgb(75 99 204 / .08); }
.w-radio-dot { flex: 0 0 auto; width: 17px; height: 17px; border-radius: 50%; border: 1.5px solid var(--borderS); display: grid;
  place-items: center; margin-top: 1px; color: #fff; }
.w-radio-row.on .w-radio-dot { background: var(--indigo); border-color: var(--indigo); }
.w-radio-dot svg { opacity: 0; }
.w-radio-row.on .w-radio-dot svg { opacity: 1; }
.w-radio-body { display: flex; flex-direction: column; gap: 2px; }
.w-radio-name { font-size: 14px; font-weight: 550; }
.w-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 7px 8px; min-height: 42px;
  border: 1px solid var(--borderS); border-radius: 9px; background: var(--surface); cursor: text; }
.w-chips:focus-within { border-color: var(--indigo); box-shadow: 0 0 0 3px rgb(75 99 204 / .16); }
.w-chip { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--indigoInk);
  background: rgb(75 99 204 / .1); border: 1px solid rgb(75 99 204 / .25); border-radius: 6px; padding: 3px 4px 3px 8px; }
.w-chip-x { display: grid; place-items: center; background: none; border: none; color: inherit; cursor: pointer; padding: 1px; opacity: .7; }
.w-chip-x:hover { opacity: 1; }
.w-chip-input { flex: 1 1 90px; min-width: 90px; border: none; outline: none; background: none; color: var(--text); font: inherit; font-size: 14px; padding: 3px 2px; }
.w-offset-row { display: flex; align-items: center; gap: 8px; }
.w-offset-row .w-btn-sm { flex: 0 0 auto; padding: 8px 12px; }
.w-offset-input { width: 108px; text-align: center; }

/* Collection tag on a library card, beside the source pill. */
.w-pill-coll { color: var(--indigoInk); background: rgb(75 99 204 / .1); border: 1px solid rgb(75 99 204 / .25); }

/* The always-visible "Add to collection" row under the tabs in the Add-content modal. */
.w-addcoll { display: flex; align-items: center; gap: 12px; padding: 12px 22px; border-bottom: 1px solid var(--border); background: var(--sunken); }
.w-addcoll .w-select { width: auto; flex: 1; max-width: 260px; }
.w-addcoll-new { flex: 0 0 auto; gap: 5px; padding: 7px 9px; }
.w-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); padding: 0 22px; }
.w-tabs button { background: none; border: none; border-bottom: 2px solid transparent; color: var(--text2); font-size: 14px;
  font-weight: 600; cursor: pointer; padding: 10px 14px; margin-bottom: -1px; }
.w-tabs button.active { border-color: var(--indigo); color: var(--text); }
.w-drop { border: 1.5px dashed var(--borderS); border-radius: 10px; padding: 36px 20px; text-align: center; background: var(--surface2);
  cursor: pointer; }
.w-drop.drag { border-color: var(--indigo); background: rgb(75 99 204 / .06); }
.w-note { font-size: 12.5px; color: var(--muted); }
.w-error { border: 1px solid var(--border); border-left: 3px solid var(--rust); background: color-mix(in srgb, var(--rust) 6%, transparent);
  border-radius: 8px; padding: 12px 14px; }
.w-error b { color: var(--rustInk); display: block; font-size: 13.5px; }

/* ---- toast (page-level; the card has its own in shadow) ---- */
.w-apptoast { position: fixed; top: calc(16px + env(safe-area-inset-top)); left: 50%; transform: translate(-50%, -8px); z-index: 90; opacity: 0;
  background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow);
  padding: 9px 14px; font-size: 13px; transition: opacity .18s, transform .18s; pointer-events: none; }
.w-apptoast.in { opacity: 1; transform: translate(-50%, 0); }
.w-apptoast.err { border-color: var(--rustInk); color: var(--rustInk); }

/* ---- admin reports (review-queue triage) ---- */
.w-hidden { display: none !important; }
.w-rep-toolbar { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 18px; }
.w-rep-filter { display: inline-flex; align-items: center; padding: 7px 14px; font-size: 13px; font-weight: 600;
  color: var(--text2); background: var(--surface); border: 1px solid var(--borderS); border-radius: 999px; cursor: pointer;
  transition: background .12s, color .12s, border-color .12s; }
.w-rep-filter:hover { border-color: var(--text2); }
.w-rep-filter.active { background: var(--indigo); color: #fff; border-color: var(--indigo); }
.w-rep-list { display: flex; flex-direction: column; gap: 14px; }
.w-rep-card { padding: 16px 18px; }
.w-rep-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 11px; }
.w-rep-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; min-width: 0; font-size: 12.5px; }
/* a long source (e.g. a full page URL) must break + shrink instead of overflowing the card; min-width:0 +
   overflow-wrap:anywhere let the flex item wrap to its own line and break the unspaced URL within it */
.w-rep-source { min-width: 0; max-width: 100%; overflow-wrap: anywhere; }
.w-rep-badge { display: inline-flex; align-items: center; padding: 3px 9px; border-radius: 999px; font-size: 10.5px;
  font-weight: 700; letter-spacing: .03em; text-transform: uppercase; color: var(--text2); background: var(--sunken); }
.w-rep-badge.s-pending { color: var(--amberInk); background: color-mix(in srgb, var(--amber) 15%, transparent); }
.w-rep-badge.s-fixed { color: var(--tealInk); background: color-mix(in srgb, var(--teal) 15%, transparent); }
.w-rep-badge.s-unresolved { color: var(--rustInk); background: color-mix(in srgb, var(--rust) 13%, transparent); }
.w-rep-badge.s-not_a_bug { color: var(--muted); background: var(--sunken); }
.w-rep-text { font-family: var(--jp); font-size: 20px; line-height: 1.5; margin: 0; color: var(--text); word-break: break-word; }
.w-rep-field { display: flex; gap: 12px; font-size: 13px; line-height: 1.5; margin-top: 10px; }
.w-rep-label { flex: 0 0 96px; color: var(--muted); font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding-top: 3px; }
.w-rep-outcome { color: var(--text2); }
.w-rep-mark { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 14px; padding-top: 14px;
  border-top: 1px solid var(--border); }
.w-rep-select, .w-rep-input { font-family: var(--ui); font-size: 13px; color: var(--text); background: var(--surface);
  border: 1px solid var(--borderS); border-radius: 8px; padding: 8px 11px; }
.w-rep-select { flex: 0 0 auto; cursor: pointer; }
.w-rep-input { flex: 1 1 220px; min-width: 0; }
.w-rep-mark .w-btn { flex: 0 0 auto; }
.w-rep-select:focus, .w-rep-input:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px color-mix(in srgb, var(--indigo) 20%, transparent); }

/* ---- install pages (extension / userscript) — in-app views under the site chrome ---- */
.w-install { max-width: 600px; padding-top: 44px; padding-bottom: 72px; }
.w-install-card { padding: 28px 26px; }
.w-install .w-h1 { font-size: 28px; }
.w-install .w-sub { font-size: 14px; line-height: 1.55; }
.w-btn-block { display: flex; width: 100%; padding: 13px 16px; font-size: 15px; margin-top: 16px; }
.w-install ol, .w-install ul { margin: 18px 0 0; padding-left: 20px; color: var(--text2); font-size: 13.5px; line-height: 1.6; }
.w-install li { margin: 8px 0; }
.w-install li b { color: var(--text); font-weight: 600; }
.w-install h2 { font-family: var(--ui); font-size: 13px; font-weight: 650; color: var(--text); margin: 22px 0 0; }
.w-install pre { margin: 9px 0 0; padding: 11px 13px; border: 1px solid var(--border); border-radius: 9px; overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; background: var(--surface2);
  color: var(--text); user-select: all; }
.w-install code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px;
  background: var(--sunken); padding: 1px 5px; border-radius: 4px; }
.w-install-foot { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border); font-size: 12.5px; color: var(--text2); }

/* ---- settings page (the shared options form: extension/userscript parity) ---- */
/* The settings are grouped into TABS (shared/tabs.ts); the tab controller shows one .w-tabpanel at a time.
   Only the Appearance tab carries the live card preview, in a sticky aside beside its controls. */
.w-tabbar { display: flex; gap: 2px; overflow-x: auto; border-bottom: 1px solid var(--border); margin: 0 0 24px; scrollbar-width: thin; }
.w-tab { position: relative; flex: 0 0 auto; font-family: inherit; font-size: 14.5px; font-weight: 560; color: var(--text2); background: none; border: 0; padding: 11px 16px 13px; border-radius: 8px 8px 0 0; cursor: pointer; white-space: nowrap; }
.w-tab:hover { color: var(--text); background: var(--surface2); }
.w-tab[aria-selected="true"] { color: var(--text); font-weight: 660; }
.w-tab[aria-selected="true"]::after { content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 2.5px; border-radius: 2px; background: var(--indigo); }
.w-tab:focus-visible { outline: 2px solid var(--indigo); outline-offset: -3px; border-radius: 8px; }
/* Appearance tab: controls on the left, sticky live card preview on the right; one column at narrow widths */
.w-appearance-grid { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 28px; align-items: start; }
.w-opt-col { min-width: 0; }
.w-opt-aside { position: sticky; top: 76px; min-width: 0; } /* clears the 60px sticky top bar; min-width:0 lets the grid track shrink so the fixed-width preview card scrolls inside .w-preview-stage instead of overflowing the page */
.w-opt-preview-card { padding: 14px; }
.w-opt-preview-title { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
.w-opt-preview-hint { font-size: 11.5px; color: var(--text2); margin: 12px 2px 0; line-height: 1.5; }
.w-optstack { display: flex; flex-direction: column; gap: 18px; }
.w-optcard { padding: 0; }
.w-opt-head { padding: 16px 20px 4px; }
.w-opt-cardinner { padding: 0 20px 18px; }
.w-opt-cardtitle { font-size: 16px; font-weight: 680; }
.w-opt-carddesc { font-size: 13px; color: var(--text2); margin: 4px 0 0; line-height: 1.5; }
.w-opt-body { margin-top: 4px; }
.w-opt { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; padding: 13px 0; border-top: 1px solid var(--border); }
/* no rule above the first row in a card or right under a sub-heading */
.w-opt-body > .w-opt:first-child, .w-subhead + .w-opt { border-top: none; }
.w-opt-main { min-width: 0; flex: 1; }
.w-opt-label { font-size: 14px; font-weight: 600; }
.w-opt-hint { font-size: 12.5px; color: var(--text2); margin-top: 3px; line-height: 1.45; }
.w-opt-ctl { display: flex; align-items: center; gap: 6px; flex-shrink: 0; padding-top: 1px; }
.w-opt-unit { font-size: 12.5px; color: var(--muted); }
.w-opt-sel, .w-opt-num { background: var(--surface); color: var(--text); border: 1px solid var(--borderS);
  border-radius: 8px; padding: 7px 9px; font-size: 13px; }
.w-opt-sel { cursor: pointer; }
.w-opt-num { width: 86px; }
/* compact quick-fill preset buttons beside a number input (e.g. Off / 2k / 6k) */
.w-opt-preset { background: var(--surface); color: var(--text); border: 1px solid var(--borderS); border-radius: 7px; padding: 5px 9px; font-size: 12px; font-weight: 600; cursor: pointer; }
.w-opt-preset:hover { border-color: var(--muted); }
.w-opt-preset[aria-pressed="true"] { background: var(--indigo); color: #fff; border-color: var(--indigo); }
.w-opt-sel:focus, .w-opt-num:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px rgb(75 99 204 / .16); }
/* CSS-only switch for boolean rows */
.w-opt-toggle { appearance: none; -webkit-appearance: none; width: 40px; height: 23px; flex-shrink: 0; position: relative;
  background: var(--sunken); border: 1px solid var(--borderS); border-radius: 999px; cursor: pointer; transition: background .15s, border-color .15s; }
.w-opt-toggle::after { content: ""; position: absolute; top: 2px; left: 2px; width: 17px; height: 17px; border-radius: 999px;
  background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .3); transition: transform .15s; }
.w-opt-toggle:checked { background: var(--indigo); border-color: var(--indigo); }
.w-opt-toggle:checked::after { transform: translateX(17px); }
.w-opt-toggle:disabled { opacity: .5; cursor: default; }
.w-subhead { font-size: 11px; font-weight: 700; letter-spacing: .055em; text-transform: uppercase; color: var(--muted); margin: 16px 0 2px; }
.w-preview-stage { display: flex; justify-content: center; padding: 14px; border-radius: 10px; background: var(--bg); border: 1px solid var(--border); overflow: auto; }
.w-opt-note { font-size: 13px; color: var(--text2); background: var(--surface2); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px; line-height: 1.5; }
/* sticky save bar — the controller toggles its inline display (hidden when clean) */
/* The bottom padding clears the iOS home indicator (and Safari's bottom bar) — env() is 0 everywhere else. */
.w-savebar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 50; align-items: center; gap: 12px;
  padding: 12px calc(24px + env(safe-area-inset-right)) calc(12px + env(safe-area-inset-bottom)) calc(24px + env(safe-area-inset-left));
  background: var(--surface); border-top: 1px solid var(--border); box-shadow: 0 -4px 18px -10px rgb(0 0 0 / .35); }
.w-savestatus { margin-right: auto; font-size: 13px; color: var(--text2); }
.w-savestatus.ok { color: var(--tealInk); }

/* ---- tablet / large phone ---- */
@media (max-width: 860px) {
  .w-hero { grid-template-columns: 1fr; gap: 28px; padding: 36px 0 20px; }
  .w-features { grid-template-columns: repeat(2, 1fr); }
  .w-stats { grid-template-columns: repeat(2, 1fr); }
  .w-libgrid { grid-template-columns: 1fr; }
  .w-aside { position: static; }
  .w-display { font-size: 40px; }
  .w-cta { padding: 36px 28px; }

  /* settings: drop the sticky side preview to a single column with the preview on top */
  .w-appearance-grid { grid-template-columns: 1fr; }
  .w-opt-aside { position: static; order: -1; }
}

/* ---- phones: collapse the top-bar nav into the hamburger, single-column everything ---- */
@media (max-width: 640px) {
  .w-wrap { padding: 0 16px; }
  .w-topbar-inner { gap: 10px; }
  .w-nav, .w-topbar-auth { display: none; }
  .w-menu-toggle { display: grid; }
  .w-iconbtn { width: 40px; height: 40px; }
  .w-mobhide { display: none !important; }

  /* Keep the primary reading actions aligned on one row; Library gets a deliberate second row instead of
     wrapping unpredictably among them as the available phone width changes. */
  .w-compose-foot { flex-direction: column; align-items: stretch; }
  .w-compose-actions { width: 100%; }
  .w-compose-actions > button { flex: 1 1 0; }
  .w-compose-library { width: 100%; margin-left: 0; }
  .w-compose-library > button { width: 100%; }

  .w-h1 { font-size: 27px; }
  .w-h2 { font-size: 21px; }
  .w-display { font-size: 33px; }
  .w-lead { font-size: 16px; }

  .w-textgrid { grid-template-columns: 1fr; }
  .w-stats { gap: 10px; }
  .w-stat { padding: 13px; }
  .w-stat-v { font-size: 21px; }

  .w-reading { padding: 22px 16px 120px; }
  .w-readerbar-inner { gap: 10px; }

  /* roomier tap targets on the filter sidebar */
  .w-filter { padding: 11px; }
  .w-tag { padding: 7px 13px; }

  /* modals hug a narrow viewport */
  .w-overlay { padding: calc(20px + env(safe-area-inset-top)) calc(12px + env(safe-area-inset-right)) calc(20px + env(safe-area-inset-bottom)) calc(12px + env(safe-area-inset-left)); }
  .w-tabs { overflow-x: auto; padding: 0 16px; scrollbar-width: thin; }
  .w-tabs button { flex: 0 0 auto; white-space: nowrap; }
  .w-addcoll { flex-wrap: wrap; gap: 8px; padding: 11px 16px; }
  .w-addcoll > label { flex: 1 0 100%; }
  .w-addcoll .w-select { min-width: 0; max-width: none; }

  /* settings: tighter card padding (head + inner own it, not .w-optcard) ... */
  .w-opt-head { padding: 15px 16px 4px; }
  .w-opt-cardinner { padding: 0 16px 16px; }
  /* ... and STACK each row so the label + hint get the full width. Otherwise a wide control (the number
     input + Off/2k/6k presets, or a long select) squeezes the text column down to one word per line. The
     control drops below the label and wraps if it's still too wide for the viewport. */
  .w-opt { flex-direction: column; align-items: stretch; gap: 9px; }
  .w-opt-ctl { padding-top: 0; flex-wrap: wrap; }
  .w-opt-sel { max-width: 100%; } /* a long option label (e.g. the four-button grade select) can't overflow */
  /* toggles are only ~40px wide — keep those rows inline (label left, switch right); they never squeezed. */
  .w-opt:has(.w-opt-toggle) { flex-direction: row; align-items: flex-start; gap: 14px; }
  .w-opt:has(.w-opt-toggle) .w-opt-ctl { padding-top: 1px; }
  .w-savebar { padding: 11px calc(16px + env(safe-area-inset-right)) calc(11px + env(safe-area-inset-bottom)) calc(16px + env(safe-area-inset-left)); }

  /* synced YouTube reader: tighter focal caption + padding on a narrow stage */
  .w-video-reader { padding: 14px 12px; }
  .w-player-frame { grid-template-columns: 1fr; gap: 8px; }
  .w-jra-rail { flex-direction: row; justify-content: center; padding: 4px 7px; }
  .w-stage-native:fullscreen .w-player-frame, .w-stage-native.w-fauxfs .w-player-frame {
    grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) 42px; }
  .w-stage-native:fullscreen .w-jra-rail, .w-stage-native.w-fauxfs .w-jra-rail { border-width: 1px 0 0; }
  /* a phone caps the caption rather than overriding it — a user who sized it down keeps their size */
  .w-now { font-size: min(var(--w-cap-size, 23px), 19px); padding: 12px 14px; }
  /* in-video bar: the row may wrap into a second (centered) line rather than overflow the picture; the
     volume slider + the transcript toggle's text label are w-mobhide (low priority on a phone), and the
     spacer goes so the wrapped rows read as one centered cluster. The caption clearance keeps up because
     the bar's height is MEASURED (--w-vc-h), not guessed. */
  .w-vc { padding: 18px 8px 4px; }
  .w-vc-row { flex-wrap: wrap; justify-content: center; gap: 4px; }
  .w-vc-row .w-spacer { display: none; }
  /* bigger tap targets for the per-line skip buttons on touch */
  .w-cue-gutter { gap: 2px; }
  .w-cue-skip { padding: 4px 8px; font-size: 17px; }
  .w-cue-ts { padding: 2px 4px; }
}

/* ---- small phones ---- */
@media (max-width: 460px) {
  .w-features { grid-template-columns: 1fr; }
  .w-display { font-size: 29px; }
  .w-pop { width: min(280px, calc(100vw - 24px)); }
  .w-cta { padding: 28px 18px; }
  /* CTA actions stack full-width instead of squishing two long labels onto one line */
  .w-cta .w-row { flex-direction: column; align-items: stretch; }
  .w-cta .w-row .w-btn { width: 100%; }
}

/* ---- Study Sentences page ---- */
/* Page shell: a full-width header over a two-column body — a sticky filter rail + the results column. */
.w-iplus-page { max-width: 1140px; padding-top: 30px; padding-bottom: 84px; }
.w-iplus-head { margin-bottom: 22px; }
.w-iplus-layout { display: grid; grid-template-columns: 248px minmax(0, 1fr); gap: 34px; align-items: start; }

/* filter rail: controls grouped by type, sticky so they stay in view while the results scroll */
.w-iplus-rail { position: sticky; top: 76px; display: flex; flex-direction: column; gap: 20px; }
.w-iplus-fgroup { display: flex; flex-direction: column; gap: 9px; }
.w-iplus-flabel { font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin: 0; }
.w-iplus-seg { display: flex; background: var(--sunken); border: 1px solid var(--border); border-radius: 10px; padding: 3px; gap: 2px; }
.w-iplus-seg button { flex: 1; border: none; background: none; color: var(--text2); font-family: var(--ui); font-size: 13px; font-weight: 600; padding: 6px 0; border-radius: 7px; cursor: pointer; font-variant-numeric: tabular-nums; }
.w-iplus-seg button:hover { color: var(--text); }
.w-iplus-seg button.on { background: var(--surface); color: var(--text); box-shadow: 0 1px 2px rgba(0, 0, 0, .12); }
.w-toggles { display: flex; flex-direction: column; gap: 7px; }
.w-toggle { display: flex; align-items: center; gap: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: 8px 11px; color: var(--text); font-family: var(--ui); font-size: 13.5px; font-weight: 500; text-align: left; cursor: pointer; }
.w-toggle:hover { border-color: var(--borderS); }
.w-toggle-sw { width: 30px; height: 18px; border-radius: 99px; background: var(--sunken); border: 1px solid var(--borderS); position: relative; flex: none; transition: background .12s; }
.w-toggle-sw::after { content: ""; position: absolute; top: 1px; left: 1px; width: 14px; height: 14px; border-radius: 50%; background: var(--muted); transition: transform .12s, background .12s; }
.w-toggle.on { border-color: var(--teal); }
.w-toggle.on .w-toggle-sw { background: var(--teal); border-color: var(--teal); }
.w-toggle.on .w-toggle-sw::after { transform: translateX(12px); background: #fff; }
.w-toggle-lbl { flex: 1; }
.w-toggle-tag { font-size: 11px; color: var(--text2); font-weight: 600; }
.w-toggle.on .w-toggle-tag { color: var(--tealInk); }
.w-iplus-range-out { display: flex; justify-content: space-between; font-size: 13px; color: var(--text2); font-weight: 600; font-variant-numeric: tabular-nums; }
.w-iplus-range-out b { color: var(--text); font-weight: 700; }
.w-iplus-rail input[type="range"] { width: 100%; accent-color: var(--teal); }

/* results column */
.w-iplus-results { min-width: 0; }
.w-iplus-status { font-size: 13px; color: var(--text2); margin: 0 0 18px; padding: 0 2px 14px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; }
.w-iplus-card { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 18px 20px 14px; margin-bottom: 14px; box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 12px 28px -20px rgba(0, 0, 0, .22); }
.w-iplus-lead { font-size: 14px; line-height: 1.7; color: var(--muted); margin: 0 0 8px; padding-left: 11px; border-left: 2px solid var(--border); font-family: var(--jp); }
.w-iplus-sentence { font-family: var(--jp); font-size: 21px; line-height: 2.05; color: var(--text); margin: 2px 0 4px; }
.w-iplus-meta { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; font-size: 14px; margin: 10px 0 12px; }
.w-iplus-unknowns { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
.w-iplus-uchip { display: inline-flex; align-items: center; gap: 7px; border: none; border-radius: 999px; padding: 5px 11px; font-size: 13.5px; font-weight: 500; cursor: pointer; font-family: var(--jp); }
.w-iplus-uchip:hover { filter: brightness(0.96); }
.w-iplus-uchip:disabled { cursor: default; opacity: .85; }
.w-iplus-uname { font-size: 11px; opacity: .7; font-family: var(--ui); }
.w-iplus-add { font-weight: 600; opacity: .75; }
/* per-kanji breakdown under the new-word chips (the reviewShowKanji setting) — collapsed behind a toggle */
.w-iplus-kanji { margin: 2px 0 4px; }
.w-iplus-kanji-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 2px 0; border: none; background: transparent; cursor: pointer; color: var(--text2); }
.w-iplus-kanji-toggle:hover { color: var(--text); }
.w-iplus-kanji-caret { font-size: 9px; line-height: 1; }
.w-iplus-kanji-label { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; font-weight: 700; }
.w-iplus-kanji-rows { display: flex; flex-wrap: wrap; align-items: center; gap: 7px 14px; margin-top: 6px; }
.w-iplus-kanji-row { display: inline-flex; align-items: baseline; gap: 5px; font-size: 12.5px; color: var(--text2); }
.w-iplus-kanji-row b { font-size: 17px; color: var(--text); font-family: var(--jp); }
.w-iplus-kanji-mean { line-height: 1.3; }
.w-iplus-foot { display: flex; align-items: center; gap: 8px; margin-top: 13px; padding-top: 11px; border-top: 1px solid var(--border); }
.w-iplus-undo { display: flex; align-items: center; justify-content: space-between; gap: 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 10px; padding: 9px 13px; margin-bottom: 14px; font-size: 13px; color: var(--text2); }

@media (max-width: 820px) {
  .w-iplus-layout { grid-template-columns: 1fr; gap: 18px; }
  .w-iplus-rail { position: static; top: auto; gap: 16px; }
  .w-iplus-sentence { font-size: 19px; }
}

/* ---- Review (native SRS) page. The flashcard itself is styled by REVIEW_CSS (shared/review-card-dom.ts,
   injected at runtime so it's reused across surfaces); this is just the page chrome. ---- */
.srs-wrap { max-width: 640px; padding-top: 28px; padding-bottom: 56px; }
.srs-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
.srs-title { font-size: 23px; font-weight: 700; margin: 0; }
.srs-head-right { display: flex; align-items: center; gap: 14px; }
.srs-counts { font-size: 13px; }
/* Vocab | Sentence segmented toggle */
.srs-modetoggle { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.srs-modebtn { font-size: 12.5px; font-weight: 600; padding: 5px 12px; border: none; cursor: pointer; background: var(--surface); color: var(--text2); }
.srs-modebtn + .srs-modebtn { border-left: 1px solid var(--border); }
.srs-modebtn:hover { background: var(--surface2); }
.srs-modebtn.active { background: var(--teal); color: #fff; }
.srs-stage { min-height: 300px; }
.srs-empty { text-align: center; color: var(--text2); padding: 64px 20px; font-size: 15px; }
.srs-done { text-align: center; padding: 52px 20px; display: flex; flex-direction: column; gap: 12px; align-items: center; }
.srs-done-emoji { font-size: 42px; line-height: 1; }
.srs-done-title { font-size: 20px; font-weight: 700; }
.srs-done-sub { font-size: 14px; max-width: 42ch; }

/* ---- installed app (PWA / Add to Home Screen) ----
   pwa.ts puts .w-standalone on <html> when the app launched from the home screen instead of a browser tab.
   There is no URL bar or Back button in that mode, and with viewport-fit=cover the layout owns the full
   screen — including whatever the status bar / notch / home indicator sit over. These rules pad the app
   chrome back inside the safe area; env() resolves to 0 on hardware without insets, so the offsets below
   collapse to the tab-mode values on a desktop install. */
html.w-standalone, html.w-standalone body { overscroll-behavior-y: none; } /* no rubber-band under the fixed bar */
html.w-standalone body { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }

/* The sticky top bar grows upward into the status-bar strip; everything that sticks BELOW it (the reader
   bar, the page rails) has to shift down by the same amount or it would tuck under the bar. */
html.w-standalone .w-topbar { padding-top: env(safe-area-inset-top); }
html.w-standalone .w-readerbar, html.w-standalone .w-mobilemenu { top: calc(60px + env(safe-area-inset-top)); }
html.w-standalone .w-aside, html.w-standalone .w-opt-aside, html.w-standalone .w-iplus-rail { top: calc(76px + env(safe-area-inset-top)); }
html.w-standalone .w-pop { max-height: calc(100dvh - 84px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }

/* Faux-fullscreen video covers the whole screen by design; keep its controls out of the indicator strip. */
html.w-standalone .w-stage.w-fauxfs { padding-bottom: env(safe-area-inset-bottom); }

/* Long-scrolling pages end above the home indicator rather than under it. */
html.w-standalone .w-reading { padding-bottom: calc(120px + env(safe-area-inset-bottom)); }
html.w-standalone .srs-wrap, html.w-standalone .w-iplus-page { padding-bottom: calc(84px + env(safe-area-inset-bottom)); }
