

@font-face { font-family:"AptCJK"; font-weight:100 400; src:local("PingFangTC-Regular"), local("PingFang TC Regular"), local("PingFang TC"), local("Heiti TC"), local("Microsoft JhengHei"), local("Noto Sans TC"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
@font-face { font-family:"AptCJK"; font-weight:500; src:local("PingFangTC-Medium"), local("PingFang TC Medium"), local("Microsoft JhengHei"), local("Noto Sans TC Medium"), local("Noto Sans TC"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
@font-face { font-family:"AptCJK"; font-weight:600 900; src:local("PingFangTC-Semibold"), local("PingFang TC Semibold"), local("Microsoft JhengHei Bold"), local("Noto Sans TC Bold"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
@font-face { font-family:"AptCJKJa"; font-weight:100 400; src:local("HiraginoSans-W3"), local("Hiragino Sans W3"), local("HiraKakuProN-W3"), local("Hiragino Kaku Gothic ProN W3"), local("YuGothic-Medium"), local("Yu Gothic Medium"), local("Meiryo"), local("NotoSansJP-Regular"), local("Noto Sans JP"), local("Noto Sans CJK JP Regular"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF,U+3040-30FF,U+31F0-31FF; }
@font-face { font-family:"AptCJKJa"; font-weight:500; src:local("HiraginoSans-W5"), local("Hiragino Sans W5"), local("HiraginoSans-W4"), local("Hiragino Sans W4"), local("HiraginoSans-W3"), local("Hiragino Sans W3"), local("HiraKakuProN-W3"), local("YuGothic-Medium"), local("Yu Gothic Medium"), local("NotoSansJP-Medium"), local("Noto Sans JP Medium"), local("Noto Sans JP"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF,U+3040-30FF,U+31F0-31FF; }
@font-face { font-family:"AptCJKJa"; font-weight:600 900; src:local("HiraginoSans-W6"), local("Hiragino Sans W6"), local("HiraKakuProN-W6"), local("Hiragino Kaku Gothic ProN W6"), local("YuGothic-Bold"), local("Yu Gothic Bold"), local("Meiryo Bold"), local("NotoSansJP-Bold"), local("Noto Sans JP Bold"); unicode-range:U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF,U+3040-30FF,U+31F0-31FF; }
@font-face { font-family:"AptCJKKo"; font-weight:100 400; src:local("AppleSDGothicNeo-Regular"), local("Apple SD Gothic Neo Regular"), local("Apple SD Gothic Neo"), local("Malgun Gothic"), local("NotoSansKR-Regular"), local("Noto Sans KR"), local("NanumGothic"); unicode-range:U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7FF,U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
@font-face { font-family:"AptCJKKo"; font-weight:500; src:local("AppleSDGothicNeo-Medium"), local("Apple SD Gothic Neo Medium"), local("NotoSansKR-Medium"), local("Noto Sans KR Medium"), local("Malgun Gothic"), local("Noto Sans KR"); unicode-range:U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7FF,U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
@font-face { font-family:"AptCJKKo"; font-weight:600 900; src:local("AppleSDGothicNeo-SemiBold"), local("Apple SD Gothic Neo SemiBold"), local("AppleSDGothicNeo-Bold"), local("Apple SD Gothic Neo Bold"), local("Malgun Gothic Bold"), local("NotoSansKR-Bold"), local("Noto Sans KR Bold"); unicode-range:U+1100-11FF,U+3130-318F,U+A960-A97F,U+AC00-D7FF,U+2E80-2FDF,U+3000-303F,U+3400-4DBF,U+4E00-9FFF,U+F900-FAFF,U+FE30-FE4F,U+FF00-FFEF; }
:root { --apt-font-sans:"AptCJK", Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang TC", "Heiti TC", "Microsoft JhengHei", "Noto Sans TC", sans-serif; --apt-font-display:"AptSerifHeadline", "AptCJK", serif; --apt-font-figures:"AptSerifFigures", "AptSerifFiguresHant", "Noto Serif TC", "Songti TC", serif; --apt-display-weight:500; --apt-display-thicken:.45px; }
:lang(en) { --apt-font-sans:"AptCJK", Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; --apt-font-display:"AptSerifHeadline", "AptCJK", serif; --apt-font-figures:"AptSerifFigures", "AptSerifFiguresHant", "Noto Serif TC", "Songti TC", serif; --apt-display-weight:700; --apt-display-thicken:0px; }
:lang(ja) { --apt-font-sans:"AptCJKJa", Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic", YuGothic, "Noto Sans JP", "Noto Sans CJK JP", Meiryo, sans-serif; --apt-font-display:"Hiragino Mincho ProN", "Hiragino Mincho Pro", "Yu Mincho", YuMincho, "Noto Serif JP", "Noto Serif CJK JP", "Source Han Serif JP", serif; --apt-font-figures:"AptSerifFigures", "Hiragino Mincho ProN", "Hiragino Mincho Pro", "Yu Mincho", YuMincho, "Noto Serif JP", "Noto Serif CJK JP", "Source Han Serif JP", serif; --apt-display-weight:700; --apt-display-thicken:0px; }
:lang(ko) { --apt-font-sans:"Pretendard Variable", Pretendard, "AptCJKKo", Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; --apt-font-display:"Pretendard Variable", Pretendard, "AptCJKKo", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; --apt-font-figures:"Pretendard Variable", Pretendard, "AptCJKKo", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; --apt-display-weight:800; --apt-display-thicken:0px; }
/* The root, and any element that declares its own language, re-resolves the face for that language. */
:root, [lang] { font-family:var(--apt-font-sans); }
/* Hangul is spaced text: break between words, never in the middle of one (the browser default breaks between any two syllables). */
:lang(ko) { word-break:keep-all; overflow-wrap:anywhere; }
/* Japanese line breaking: no line starts with a small kana, a prolonged-sound mark or closing punctuation. */
:lang(ja) { line-break:strict; }

:root {
  --apt-bg: #fbf9f4;
  --apt-surface: #fffefb;
  --apt-ink: #182637;
  --apt-muted: #657080;
  --apt-line: #dcd8ce;
  --apt-navy: #17324d;
  --apt-gold: #a9823a;
  --apt-sage: #386653;
  --apt-success: #24634b;
  --apt-alert: #9b4038;
  /* Semantic tokens: tinted surfaces and the filled-button pair, so dark mode can restyle them in one place. */
  --apt-tint: #f1efe8;
  --apt-tint-strong: #ece9e1;
  --apt-success-soft: #EAF3EA;
  --apt-alert-soft: #F7EAE8;
  --apt-sage-soft: #EDF2EC;
  --apt-fill: var(--apt-navy);
  --apt-on-fill: #fff;
  --apt-fill-hover: #0f273e;
  color: var(--apt-ink);
  font-family: var(--apt-font-sans);
  font-synthesis: none;
}
* { box-sizing: border-box; }
/* No rubber-band. In the store shell the WKWebView slides the whole page on a drag even where the
   content fits exactly, so every screen felt loose (founder, 2026-09-24: "每一頁都還能上下捲動").
   This stops the bounce without taking away real scrolling where content genuinely overflows. */
/* iOS WKWebView paints a grey-black box on every tapped link/button; the app gives its own feedback. */
/* Every document paints the paper canvas inline in its first bytes (src/web/first-paint.ts), before this
   file arrives. Once it has, the canvas comes from <body> again exactly as before, so nothing changes. */
html { background: none; overscroll-behavior: none; -webkit-tap-highlight-color: transparent; }
/* Safe area (TestFlight build 9). In the app every page is edge to edge (viewport-fit=cover, see
   src/native/shell.ts), so the page itself starts below the status bar, an opaque scrim keeps scrolled
   content from showing behind the clock, and sticky headers stop under the status bar instead of at the
   very top. In a browser env(safe-area-inset-top) is 0 and all of this is a no-op. */
:root { --safe-top: env(safe-area-inset-top, 0px); }
/* No page is ever wider than the screen, at any text size: nothing pans sideways, and a browser zoom
   always returns to 1×. Double-tap zoom is off; pinch stays available in a browser (not in the app). */
html, body { overflow-x: clip; }
html { touch-action: manipulation; }
body { background: var(--apt-bg); margin: 0; padding-top: var(--safe-top); }
body::before { background: var(--apt-surface); content: ""; height: var(--safe-top); left: 0; pointer-events: none; position: fixed; right: 0; top: 0; z-index: 1000; }
body:has(.product-shell)::before { background: #f6f3ea; }
img, svg, video { max-width: 100%; }

/**
 * The one display face in the product: the sentence a photograph is carrying. It draws the onboarding
 * headline, and — since 2026-09-24 — the slogan on 首頁 and on the login screen, which are the same
 * sentence. A subset of Noto Serif TC Medium (SIL Open Font License 1.1) holding exactly the 28 code
 * points those two strings use, 6.5 KB; the unicode-range keeps every other character on AptCJK, so
 * nothing else in the app changes if this file ever fails to load.
 */
@font-face { font-family:"AptSerifHeadline"; font-style:normal; font-weight:500; font-display:swap; src:url("/brand/serif-headline.woff2") format("woff2"); unicode-range:U+3002,U+4E86,U+503C,U+51FA,U+5206,U+529B,U+5728,U+5730,U+578B,U+5BE6,U+5F97,U+5FEB,U+628A,U+62FF,U+65B9,U+6642,U+6700,U+6709,U+6E2C,U+73FE,U+7684,U+82B1,U+89E3,U+901F,U+9593,U+9650,U+984C,U+FF0C; }
/* Native-only blocks stay hidden on the web even when their own class sets a display type; the native shell strips the attribute. */
[data-native-only][hidden] { display:none !important; }
.product-shell { --product-ink:#252824; --product-muted:#5e665f; --product-green:#315f4a; --product-red:#9b4038; --product-gold:#806030; --product-blue:#3a5f80; --product-terracotta:#a8622e; --product-canvas:#f6f3ea; --product-surface:#fdfbf6; --product-line:#dcd6ca; --product-on-fill:#fff; background:var(--product-canvas); color:var(--product-ink); min-height:calc(100svh - var(--safe-top,0px)); }
/* No rubber-band on any page (founder, 2026-09-26: the intro pages still bounced and slid the logo under the clock). practice-shell-css and
   onboarding-css already had this; every other page shell shares this sheet. The native shell also turns the WKWebView bounce off. */
html { overscroll-behavior:none; -webkit-tap-highlight-color:transparent; }
html[data-theme="dark"] .product-shell { --product-ink:#f2f2ec; --product-muted:#c1c7c0; --product-green:#91c7a3; --product-red:#e2a49b; --product-gold:#d5b87a; --product-blue:#9dc2e0; --product-terracotta:#e3b083; --product-canvas:#182019; --product-surface:#202a21; --product-line:#455347; --product-on-fill:#182019; }
.product-shell a { color:inherit; }
.product-topbar { background:var(--product-surface); border-bottom:1px solid var(--product-line); }
.product-topbar-inner { align-items:center; display:flex; justify-content:space-between; margin:auto; max-width:760px; min-height:54px; padding:0 20px; }
.product-brand { font-size:0.8125rem; font-weight:750; letter-spacing:.13em; margin-left:auto; }
/* Sits in the page's own cream canvas, not the white topbar strip (founder, 2026-09-25: "放在米色的
   位置...不要擠在白色那行"), positioned relative to .vocab-page below the topbar. Sized as a real
   tap target ("大一點方便點擊"), not the small inline label first tried. */
.vocab-page { position:relative; }
/* A real on/off switch, the same .product-switch as the Settings page (founder, 2026-09-26: "左開右關 或
   反之 和設定頁一樣 全域要統一"): the label never changes, only the track (green + knob right = on). Kept
   in the pill on the cream canvas, nudged up-right so it never covers the headword's 詞性 (2026-09-25). */
.vocab-sound-toggle { align-items:center; background:var(--product-surface); border:1px solid var(--product-line); border-radius:999px; color:var(--product-ink); cursor:pointer; display:flex; gap:12px; font-size:0.8125rem; font-weight:650; min-height:44px; padding:8px 12px 8px 16px; position:absolute; right:8px; top:8px; z-index:2; }
.vocab-sound-toggle .product-switch, .vocab-sound-toggle .product-switch-track { display:block; }
/* "Autoplay pronunciation" is 160px of English: on one line the pill reached back under the "New word 1 / 10" counter at its left. The label wraps
   inside the pill instead, and the counter keeps clear of whatever width the pill ends up (text size included). */
.vocab-sound-toggle .vocab-sound-label { line-height:1.2; max-width:6.25rem; }
.vocab-page .vocab-progress { max-width:max(4.5rem, calc(100% - 11.3rem)); }
.brand-lockup { align-items:center; display:flex; }
.brand-mark { border-radius:22%; display:block; flex:none; object-fit:cover; }
.brand-lockup-compact { gap:8px; margin-left:auto; }
.brand-lockup-compact .brand-mark { height:22px; width:22px; }
.brand-lockup-compact .brand-wordmark { font-size:0.8125rem; font-weight:750; letter-spacing:.1em; }
.product-back { align-items:center; display:inline-flex; font-size:1rem; font-weight:650; gap:4px; min-height:44px; text-decoration:none; }
.product-back::first-letter { font-size:1.5rem; }
.product-frame { margin:auto; max-width:760px; min-width:0; padding:30px 20px calc(40px + env(safe-area-inset-bottom)); }
.product-shell.has-nav .product-frame { padding-bottom:calc(112px + env(safe-area-inset-bottom)); }
.product-intro { margin:2px 0 30px; }
.product-kicker { color:var(--product-green); font-size:0.8125rem; font-weight:750; letter-spacing:.04em; margin:0 0 11px; }
.product-intro h1 { font-size:clamp(1.625rem,7vw,2.125rem); font-weight:750; letter-spacing:-.025em; line-height:1.25; margin:0 0 12px; }
.product-intro p:not(.product-kicker):not(.product-note) { color:var(--product-muted); font-size:1rem; line-height:1.65; margin:0; }
.product-primary-link { align-items:center; background:var(--product-green); border-radius:12px; color:var(--product-on-fill) !important; display:flex; font-size:1.0625rem; font-weight:700; justify-content:space-between; min-height:54px; padding:14px 18px; text-decoration:none; }
.product-shell { isolation:isolate; }
.vocab-backdrop { background:center / cover no-repeat; inset:0; opacity:.13; pointer-events:none; position:fixed; z-index:-1; -webkit-mask-image:linear-gradient(#000 55%,transparent); mask-image:linear-gradient(#000 55%,transparent); }
html[data-theme="dark"] .vocab-backdrop { display:none; }
.product-note { color:var(--product-muted); font-size:0.875rem; line-height:1.65; margin:24px 0 0; }
.product-text-link { color:var(--product-green) !important; display:inline-flex; font-weight:700; gap:8px; margin-top:22px; min-height:44px; align-items:center; text-decoration:none; }
.product-detail-list { border-top:1px solid var(--product-line); }
.product-detail-row { align-items:center; border-bottom:1px solid var(--product-line); display:flex; gap:12px; justify-content:space-between; min-height:62px; padding:13px 2px; text-decoration:none; }
.product-detail-row strong { font-size:1rem; font-weight:650; }
.product-detail-row span { color:var(--product-muted); font-size:0.875rem; text-align:right; }
.product-detail-row > span:first-child { display:grid; gap:3px; min-width:0; text-align:left; }
.product-detail-row small { color:var(--product-muted); font-size:.8125rem; line-height:1.45; }
.product-detail-row.is-link span { font-size:1.4375rem; }
.product-detail-row.is-link > span:first-child { font-size:inherit; }
.product-detail-row.is-danger strong { color:var(--product-red); }
.product-notice { background:#eaf2ed; border:1px solid #b6cfbd; border-radius:10px; color:#315f4a; font-size:.9375rem; line-height:1.55; margin:18px 0; padding:12px 14px; }
.product-support-form { display:grid; gap:9px; margin-top:25px; }
.product-support-form label { font-size:.9375rem; font-weight:700; }
.product-support-form textarea { background:var(--product-surface); border:1px solid var(--product-line); border-radius:10px; color:var(--product-ink); font:inherit; line-height:1.55; min-height:132px; padding:12px; resize:vertical; }
.product-support-form button { background:var(--product-green); border:1px solid var(--product-green); border-radius:10px; color:var(--product-on-fill); cursor:pointer; font:inherit; font-weight:700; min-height:50px; padding:12px 16px; }
.product-card { background:var(--product-surface); border:1px solid var(--product-line); border-radius:14px; margin:0 0 18px; padding:16px; }
.product-card h2 { font-size:1rem; margin:0 0 12px; }
.product-card p { color:var(--product-muted); line-height:1.6; }
.my-page-header { align-items:center; display:flex; justify-content:space-between; margin:2px 0 18px; }
/* 27px flat, the §5 分頁大標題 role — was a vw-based clamp (28-36px) that grew past 練習's own
   title and no longer matched it at any width (founder, 2026-09-23: "練習頁的字明顯比其他頁大"). */
.my-page-header h1 { font-size:1.6875rem; letter-spacing:-.025em; margin:0; }
.my-page-header a { align-items:center; border:1px solid var(--product-line); border-radius:50%; display:inline-flex; font-size:1.25rem; height:44px; justify-content:center; text-decoration:none; width:44px; }
.my-profile-card { align-items:center; background:var(--product-surface); border:1px solid var(--product-line); border-radius:16px; display:grid; gap:13px; grid-template-columns:52px minmax(0,1fr) auto; margin:0 0 18px; min-height:84px; padding:15px; text-decoration:none; }
.my-profile-card > span:nth-child(2) { display:grid; gap:3px; min-width:0; }
.my-profile-card strong { font-size:1.0625rem; overflow-wrap:anywhere; text-wrap:balance; }
.my-profile-card small { color:var(--product-muted); font-size:.8125rem; line-height:1.4; }
.my-avatar { align-items:center; border-radius:50%; color:#fff; display:inline-flex; font-size:1.25rem; font-weight:750; height:52px; justify-content:center; width:52px; }
.product-toggle-row, .product-disclosure > summary { align-items:center; border-top:1px solid var(--product-line); display:flex; gap:12px; justify-content:space-between; min-height:56px; padding:9px 0; }
/* A toggle row is a <label>, and a direct-child label of a form inside .product-card already has its
   own generic rule (".product-card form > label", below) that turns it into a single-column grid —
   meant for a plain label+input pair, but higher specificity than .product-toggle-row's own display:flex
   above, so a toggle nested that way (使用偏好's autoplay/消息提醒 rows) stacked the switch under the
   text instead of beside it. Rescoped so this always wins (founder, 2026-09-23: "把開關放到右邊"). */
.product-card .product-toggle-row { display:flex; }
.product-card > .product-toggle-row:first-child { border-top:0; }
/* One column that may shrink, so a raw device name (a User-Agent string on 登入的裝置) wraps inside its card and does not run past it. */
.product-toggle-row span { display:grid; gap:3px; grid-template-columns:minmax(0,1fr); min-width:0; overflow-wrap:anywhere; }
.product-toggle-row small { color:var(--product-muted); font-size:.78rem; line-height:1.4; }
.product-toggle-row input { block-size:22px; inline-size:22px; }
.product-switch { display:inline-flex; flex:0 0 auto; position:relative; }
.product-toggle-row .product-switch, .product-toggle-row .product-switch-track { display:block; }
.product-switch input { block-size:1px; inline-size:1px; opacity:0; position:absolute; }
.product-switch-track { background:var(--product-line); border-radius:999px; display:block; height:28px; position:relative; transition:background .18s ease; width:48px; }
.product-switch-track::after { background:#fff; border-radius:50%; box-shadow:0 1px 3px rgb(0 0 0 / .2); content:""; height:22px; left:3px; position:absolute; top:3px; transition:transform .18s ease; width:22px; }
.product-switch input:checked + .product-switch-track { background:var(--product-green); }
.product-switch input:checked + .product-switch-track::after { transform:translateX(20px); }
.product-switch input:focus-visible + .product-switch-track { outline:3px solid var(--product-green); outline-offset:3px; }
.product-disclosure { border:0; }
.product-disclosure > summary { cursor:pointer; list-style:none; }
.product-disclosure > summary::-webkit-details-marker { display:none; }
.product-disclosure label, .product-card form > label { color:var(--product-muted); display:grid; font-size:.875rem; gap:6px; margin:0 0 12px; }
.product-card input, .product-card select { background:var(--product-surface); border:1px solid var(--product-line); border-radius:9px; color:var(--product-ink); font:inherit; min-height:44px; padding:8px 10px; width:100%; }
.product-scale-picker { border:0; border-top:1px solid var(--product-line); margin:0; padding:15px 0 9px; }
.product-scale-picker legend { align-items:center; color:var(--product-ink); display:flex; font-size:1rem; font-weight:650; justify-content:space-between; padding:0; width:100%; }
.product-scale-picker legend span { color:var(--product-muted); font-size:.9rem; font-weight:600; }
.product-scale-options { display:grid; gap:6px; grid-template-columns:repeat(5,minmax(0,1fr)); margin-top:10px; }
/* Five across is only wide enough for the one- and two-character labels of 中文 and 日本語; "Smallest" and 가장 작게 were split
   mid-word. Every other script takes three above two, each chip wide enough for its whole label. */
html[data-writing-system="latin"] .product-scale-options, html[data-writing-system="ko-KR"] .product-scale-options { grid-template-columns:repeat(6,minmax(0,1fr)); }
html[data-writing-system="latin"] .product-scale-option, html[data-writing-system="ko-KR"] .product-scale-option { grid-column:span 2; }
html[data-writing-system="latin"] .product-scale-option:nth-child(n+4), html[data-writing-system="ko-KR"] .product-scale-option:nth-child(n+4) { grid-column:span 3; }
.product-scale-option { cursor:pointer; min-width:0; }
.product-card .product-scale-option input { block-size:1px; border:0; inline-size:1px; margin:0; min-height:1px; opacity:0; padding:0; position:absolute; }
.product-scale-option span { align-items:center; border:1px solid var(--product-line); border-radius:9px; color:var(--product-muted); display:flex; font-size:.875rem; justify-content:center; line-height:1.25; min-height:44px; overflow-wrap:anywhere; padding:5px 3px; text-align:center; transition:background .18s ease,border-color .18s ease,color .18s ease; }
.product-scale-option input:checked + span { background:var(--product-green); border-color:var(--product-green); color:var(--product-on-fill); font-weight:700; }
.product-scale-option input:focus-visible + span { outline:3px solid var(--product-green); outline-offset:2px; }
.product-card button, .product-intro form button { background:var(--product-green); border:1px solid var(--product-green); border-radius:10px; color:var(--product-on-fill); cursor:pointer; font:inherit; font-weight:700; min-height:46px; padding:10px 14px; }
/* 刪除帳號 confirmation field: a bare <input> keeps the browser's size=20 intrinsic width, which with the Korean font (Pretendard) is wider
   than the column (371px in a 343px column at 375pt, 484px at the max text size) and pushed the page sideways in ko-KR (QA SW-02). Stack the
   label and stretch the field to the column instead. Scoped to this one form: .product-intro form also hosts the goal chips and sliders. */
/* Founder 2026-10-03: the field and the button sat on top of each other. The form is a column with room between the
   instruction, the field and the destructive button; the field is tall enough to type into comfortably. */
form[action="/settings/delete-account"] { display:flex; flex-direction:column; gap:20px; margin-top:24px; }
form[action="/settings/delete-account"] label { display:grid; font-weight:600; gap:10px; line-height:1.5; }
form[action="/settings/delete-account"] button { min-height:52px; width:100%; }
form[action="/settings/delete-account"] input { background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; box-sizing:border-box; color:var(--product-ink); display:block; font:inherit; font-weight:400; letter-spacing:.08em; max-width:100%; min-height:56px; min-width:0; padding:10px 12px; width:100%; }
.product-kv { align-items:baseline; display:flex; gap:12px; justify-content:space-between; }
.product-kv span { color:var(--product-muted); flex:none; max-width:45%; }
/* The value (an e-mail address, a device name) is the part that may be long: it wraps, the label keeps its words. */
.product-kv strong { min-width:0; overflow-wrap:anywhere; text-align:end; }
/* 訂閱方案 (renamed from 目前方案, founder, 2026-09-25). The state card names the plan once — the
   page title already says 訂閱方案, so a row labelled that under it said it twice. */
.plan-state-name { font-size:1.25rem; font-weight:750; letter-spacing:-.02em; margin:0 0 8px; }
.plan-pitch { font-size:1rem; line-height:1.6; margin:0 0 6px; }
.plan-trial { color:var(--product-green); font-size:.9375rem; font-weight:700; margin:0 0 4px; }
/* The reassurance sits quiet under the offer: one coloured line is a draw, two compete. */
.plan-reminder { color:var(--product-muted); font-size:.875rem; line-height:1.55; margin:0; }
/* The tier list is where the decision happens, so it carries the page's only emphasis. The per-month
   figure leads because that is what the four tiers are compared on; the amount actually charged sits
   under it. Both are filled by the native bridge from StoreKit, never by the server. */
.product-tier { align-items:center; border-top:1px solid var(--product-line); column-gap:12px; display:grid; grid-template-columns:minmax(0,1fr) auto; margin-top:14px; padding:14px 0 0; row-gap:2px; }
.product-tier-label { align-items:center; display:flex; flex-wrap:wrap; font-size:.9375rem; font-weight:700; gap:8px; grid-area:1 / 1; }
/* Gold is already this product's colour for the goal worth reaching (金色證書), so it carries the
   best-value mark rather than a new accent. One row only, and only once real prices can be compared. */
.product-tier-flag { background:var(--product-gold); border-radius:5px; color:#fff; font-size:.6875rem; font-weight:700; letter-spacing:.04em; padding:2px 7px; }
.product-tier-rate { font-size:1.125rem; font-variant-numeric:tabular-nums; font-weight:750; grid-area:2 / 1; letter-spacing:-.01em; min-height:1.5em; }
.product-tier-billed { color:var(--product-muted); font-size:.8125rem; font-variant-numeric:tabular-nums; grid-area:3 / 1; }
.product-tier-billed:empty { display:none; }
.product-tier.is-best { box-shadow:inset 3px 0 0 var(--product-gold); padding-left:13px; }
/* A failed price request replaces the tier rows with the message and a retry (src/native/bridge-script.ts). */
[data-native-purchase][data-prices="failed"] .product-tier { display:none; }
.product-prices-error { border-top:1px solid var(--product-line); margin-top:14px; padding-top:14px; }
.product-prices-error p { color:var(--product-muted); font-size:.9375rem; line-height:1.55; margin:0; }
.product-card .product-prices-error [data-purchase-retry] { margin-top:12px; }
.product-tier-save { color:var(--product-green); font-size:.8125rem; font-weight:700; }
.product-tier [data-purchase-buy]:disabled { cursor:default; opacity:.45; }
/* While StoreKit answers, the price slot is a neutral grey bar, never a number: every amount on this page is StoreKit's own for the learner's storefront, so there is nothing true to show yet. */
[data-native-purchase][data-prices="loading"] .product-tier-rate:empty::before { background:var(--product-line); border-radius:6px; content:""; display:block; height:1.1rem; width:7.5rem; }
@media (prefers-reduced-motion:no-preference) { [data-native-purchase][data-prices="loading"] .product-tier-rate:empty::before { animation:product-price-pulse 1.1s ease-in-out infinite alternate; } }
@keyframes product-price-pulse { from { opacity:.45; } to { opacity:1; } }
.product-tier [data-purchase-buy] { background:var(--product-green); border:0; border-radius:10px; color:var(--product-on-fill); cursor:pointer; font:inherit; font-weight:700; grid-area:1 / 2 / span 3 / auto; min-height:46px; padding:0 22px; }
/* Scoped to the card so it beats the generic filled ".product-card button": restoring or managing a
   subscription is secondary next to the four 訂閱 buttons and must not compete with them. */
.product-card [data-purchase-restore], .product-card [data-manage-subscriptions], .product-card [data-purchase-retry] { background:transparent; border:1px solid var(--product-line); border-radius:10px; color:var(--product-ink); cursor:pointer; font:inherit; font-weight:650; margin-top:20px; min-height:46px; padding:0 16px; width:100%; }
[data-purchase-status] { color:var(--product-muted); font-size:.875rem; line-height:1.55; margin:12px 0 0; }
[data-purchase-status]:empty { display:none; }
/* Home (docs/visual-system-standard.md §13): a photo that fades into the paper, the estimate and the countdown on the fade, the certificate track, today's lists, GO. */
/* The figures subset holds the digits plus three Traditional Chinese characters (分 天 約). Digits have no language, so every locale may use them; the characters would be a Traditional Chinese glyph in the middle of a Japanese or Korean sentence, so they sit in a second family only the zh stack lists (cjk-font-css.ts). */
@font-face { font-family:"AptSerifFigures"; font-display:swap; font-style:normal; font-weight:600; src:url(/img/v2/serif-figures.woff2) format("woff2"); unicode-range:U+30-39; }
@font-face { font-family:"AptSerifFiguresHant"; font-display:swap; font-style:normal; font-weight:600; src:url(/img/v2/serif-figures.woff2) format("woff2"); unicode-range:U+5206,U+5929,U+7D04; }
.product-shell[data-active-tab="home"][data-page-kind="primary"] .product-frame { max-width:none; padding:0 0 calc(64px + env(safe-area-inset-bottom)); }
/* Reflows with the content instead of squeezing it. It used to be a fixed height (hero flex-basis 36dvh, shrinkable down to a 200px floor), and on a short screen or at a large text size the hero's own content (brand, headline, figures) was taller than that floor, so it spilled over the body: the headline sat on the logo and the estimate line on the "points to goal" line (founder, 2026-10-02, Japanese UI). Now the container is at least one screen tall, the hero takes the slack and never goes below its own content, and a screen that is too small for it simply scrolls. */
.home { display:flex; flex-direction:column; min-height:calc(100dvh - 64px - env(safe-area-inset-bottom) - var(--safe-top,0px)); }
.home-col { --home-gutter:24px; box-sizing:border-box; margin-inline:auto; max-width:760px; padding-inline:var(--home-gutter); width:100%; }
/* The headline and the figures keep the 760px text column but their box is the hero's full width, so the veils behind them (below) run edge to edge instead of ending in a visible vertical edge on a tablet. */
.home-hero-wide { margin-inline:0; max-width:none; padding-inline:calc(max(0px, (100% - 760px) / 2) + var(--home-gutter)); }
.home-hero { display:flex; flex:1 0 auto; flex-direction:column; position:relative; }
.home-hero-photo { height:100%; inset:0; -webkit-mask-image:linear-gradient(to bottom,#000 40%,transparent 80%); mask-image:linear-gradient(to bottom,#000 40%,transparent 80%); object-fit:cover; object-position:70% 50%; position:absolute; width:100%; }
/* margin-bottom: the headline never touches the mark, however little room the hero has. */
.home-hero-brand { align-items:center; column-gap:12px; display:flex; margin-bottom:12px; padding-top:12px; position:relative; z-index:1; }
/* Its own line under the wordmark, not beside it. */
.home-hero-brand .brand-wordmark { font-size:.875rem; font-weight:700; letter-spacing:.17em; }
/* The face and weight come from the language (cjk-font-css.ts), never a Traditional Chinese subset under a Japanese sentence. Two lines, one per half of the sentence; a half that is still too wide wraps balanced rather than leaving one or two characters on a line.
   Legibility over the photograph (founder, 2026-10-02: will it read in every state?). Where the hero is short (a long list below, a large text size) the headline sits on the unfaded part of the photo, the tree or the road, and a thin dark serif stroke on that is a coin flip. Three layers, no outline: (1) the heaviest real weight of the language's headline face (Mincho W6, Pretendard ExtraBold, Times Bold); the Traditional Chinese serif subset ships at 500 only, so it is thickened by --apt-display-thicken, a same-colour hairline that adds weight to the glyph, not a visible edge; (2) a veil of the paper colour behind the lines (::before below) that feathers out on every side, so the photo stays a photograph and the text sits on haze; (3) the paper-colour halo. Measured by contrast of the ink against the pixels under every glyph, in every Home state (scripts in the PR). */
.home-slogan { -webkit-text-stroke:var(--apt-display-thicken,0px) currentColor; color:var(--product-ink); font-family:var(--apt-font-display); font-size:clamp(1.15rem,5.9vw,1.6rem); font-weight:var(--apt-display-weight); isolation:isolate; letter-spacing:.02em; line-height:1.45; margin:auto 0 16px; position:relative; text-shadow:0 0 14px color-mix(in srgb, var(--product-canvas) 90%, transparent), 0 0 3px color-mix(in srgb, var(--product-canvas) 70%, transparent); text-wrap:balance; z-index:1; }
.home-slogan::before { -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 28px,#000 calc(100% - 20px),transparent 100%); background:linear-gradient(to right, color-mix(in srgb, var(--product-canvas) var(--home-veil,60%), transparent) 0, color-mix(in srgb, var(--product-canvas) var(--home-veil,60%), transparent) 78%, color-mix(in srgb, var(--product-canvas) calc(var(--home-veil,60%) / 2), transparent) 100%); content:""; inset:-30px 0 -16px; mask-image:linear-gradient(to bottom,transparent 0,#000 28px,#000 calc(100% - 20px),transparent 100%); pointer-events:none; position:absolute; z-index:-1; }
/* Wraps instead of overflowing: at a large text size or in a longer language the days block drops under the estimate. */
/* The score and the countdown sit at the foot of the photo. On a short hero (a long list below, a large text size) that foot is still photograph, and a green numeral or a grey caption on olive grass is 1:1 to 2.6:1. The veil (paper colour, ramping in over the 34px above the row) brings the photo down to the paper under the figures, so they are read on paper at every height; on a tall hero the photo has already faded there and the veil changes nothing you can see. */
.home-figures { align-items:flex-end; display:flex; flex-wrap:wrap; gap:8px 16px; isolation:isolate; justify-content:space-between; margin-top:0; padding-bottom:16px; position:relative; }
.home-figures::before { background:linear-gradient(to bottom, transparent 0, color-mix(in srgb, var(--product-canvas) var(--home-figures-veil,92%), transparent) 34px); content:""; inset:-34px 0 0; pointer-events:none; position:absolute; z-index:-1; }
.home-estimate { max-width:100%; min-width:0; }
.home-big { align-items:baseline; display:flex; flex-wrap:wrap; font-family:var(--apt-font-figures); font-weight:600; gap:0 4px; line-height:1; margin:0; }
.home-big b { font-weight:600; }
.home-estimate .home-big b { color:var(--cert-text,var(--product-ink)); font-size:3.5rem; letter-spacing:-.01em; }
.home-days { margin-left:auto; max-width:100%; min-width:0; text-align:right; }
.home-days .home-big { justify-content:flex-end; }
.home-days .home-big b { font-size:2rem; }
.home-unit { font-size:1.0625rem; }
.home-cap { color:var(--product-muted); font-size:.8125rem; line-height:1.5; margin:8px 0 0; overflow-wrap:anywhere; }
.home-cap a { text-decoration:underline; text-underline-offset:2px; }
/* No score yet: no em dash placeholder above this line (founder, 2026-09-23) — the link stands alone, so it needs no top margin reserved for a number that isn't there. */
.home-cap-standalone { margin-top:0; }
.home-body { display:flex; flex:none; flex-direction:column; padding-top:16px; }
.home-togo { color:var(--product-muted); font-size:.8125rem; line-height:1.5; margin:0 0 8px; text-align:center; }
.home-togo b { color:var(--product-ink); font-size:.9375rem; font-weight:600; margin:0 4px; }
.home-bar { height:10px; margin:0 4px; position:relative; }
.home-bar::before { background:color-mix(in srgb, var(--product-ink) 12%, transparent); content:""; height:2px; left:0; position:absolute; right:0; top:4px; }
.home-zone { background:var(--cert-line); height:2px; position:absolute; top:4px; }
.home-tick { background:var(--cert-line); height:10px; position:absolute; top:0; transform:translateX(-50%); width:2px; }
.home-node { border-radius:50%; box-sizing:border-box; height:10px; position:absolute; top:0; width:10px; }
.home-node.is-now { background:var(--cert-line); left:0; transform:translateX(-50%); }
.home-node.is-goal { background:var(--product-canvas); border:2px solid var(--cert-line); right:0; transform:translateX(50%); }
/* Two columns for the end labels, a row of their own for the middle one: no label can sit on another, whatever the language or text size. The labels wrap instead of running past the card. */
.home-stops { display:grid; gap:6px 12px; grid-template-columns:minmax(0,1fr) minmax(0,1fr); margin-top:8px; }
.home-stops span { display:flex; flex-direction:column; gap:4px; min-width:0; }
.home-stops b { font-size:.9375rem; font-weight:600; line-height:1; }
.home-stops em { color:var(--cert-text); font-size:.75rem; font-style:normal; line-height:1.3; overflow-wrap:anywhere; }
.home-stops .is-now { align-items:flex-start; grid-area:1 / 1; text-align:left; }
.home-stops .is-next { align-items:center; grid-area:2 / 1 / 3 / -1; justify-self:start; max-width:50%; position:relative; text-align:center; transform:translateX(-50%); }
.home-stops .is-goal { align-items:flex-end; grid-area:1 / 2; text-align:right; }
.home-tasks { background:var(--product-surface); border-radius:12px; margin-top:20px; padding:0 16px; }
.home-task { align-items:center; display:flex; gap:4px; min-height:48px; padding:12px 0; text-decoration:none; }
.home-task + .home-task { border-top:1px solid var(--product-line); }
.home-task span { flex:1; font-size:.9375rem; min-width:0; overflow-wrap:anywhere; }
.home-task b, .home-task i, .home-task svg { flex:none; }
.home-task b { font-size:1.1875rem; font-weight:600; line-height:1; }
.home-task i { color:var(--product-muted); font-size:.8125rem; font-style:normal; }
.home-task svg { color:var(--product-muted); height:16px; margin-left:8px; width:16px; }
/* The one vocabulary row: the label on the left, what is waiting on the right ("複習 10 · 新 20"), or a check and "done". When the label and both parts cannot share a line (English at the default size, any language at a large one) the parts drop to their own line under the label, right-aligned, rather than squeezing the label to a sliver or leaving a stray dot at the end of a line. The caret is taken out of the flow so it stays centred on the row either way. */
.home-task-vocab { column-gap:12px; flex-wrap:wrap; padding-right:24px; position:relative; row-gap:2px; }
.home-task-vocab span { flex:1 1 6.5em; min-width:0; }
.home-task-vocab > svg { margin:-8px 0 0; position:absolute; right:0; top:50%; }
.home-task em { font-style:normal; }
.home-task-parts { align-items:baseline; color:var(--product-muted); display:flex; flex:0 0 auto; flex-wrap:wrap; font-size:.8125rem; gap:2px 6px; justify-content:flex-end; margin-left:auto; max-width:100%; min-width:0; text-align:right; }
.home-task-part { white-space:nowrap; }
.home-task-part b { color:var(--product-ink); font-size:1.0625rem; }
.home-task-part.is-done { align-items:center; color:var(--product-green); display:inline-flex; font-size:.9375rem; font-weight:600; gap:4px; }
.home-task .home-task-part svg { color:var(--product-green); height:16px; margin:0; width:16px; }
.home-primary-action { display:flex; justify-content:center; padding:20px 0; }
.home-go { align-items:center; background:var(--product-green); border-radius:50%; box-shadow:0 8px 22px rgba(32,33,31,.16); color:var(--product-on-fill) !important; display:flex; flex-direction:column; height:152px; justify-content:center; text-decoration:none; width:152px; }
.home-go > span { font-size:2rem; font-weight:600; letter-spacing:.14em; line-height:1; padding-left:.14em; }
.home-go > small { font-size:.8125rem; line-height:1; opacity:.92; }
.home-go > small::before { border-top:1px solid currentColor; content:""; display:block; margin:8px auto; opacity:.55; width:32px; }
@media (hover:hover) { .home-go:hover { box-shadow:0 12px 28px rgba(32,33,31,.2); } }
/* Certificate colours are data: line and dot, and a darker value for text on the paper. */
.cert-orange { --cert-line:#c96a3e; --cert-text:#9a4a22; }
.cert-brown { --cert-line:#8a5a3a; --cert-text:#8a5a3a; }
.cert-green { --cert-line:#4f8a63; --cert-text:#3e6f4f; }
.cert-blue { --cert-line:#3d6ea8; --cert-text:#2f5a8c; }
.cert-gold { --cert-line:#a47b3c; --cert-text:#7a5e17; }
html[data-theme="dark"] .cert-orange { --cert-line:#e0956a; --cert-text:#eba882; }
html[data-theme="dark"] .cert-brown { --cert-line:#c79a78; --cert-text:#d4ad8f; }
html[data-theme="dark"] .cert-green { --cert-line:#8fcb9f; --cert-text:#8fcb9f; }
html[data-theme="dark"] .cert-blue { --cert-line:#8fb4e3; --cert-text:#a3c2ea; }
html[data-theme="dark"] .cert-gold { --cert-line:#d8b65e; --cert-text:#d8b65e; }
/* Dark mode has no photo yet (§13.3): the figures sit on the dark paper. */
html[data-theme="dark"] .home-hero-photo, html[data-theme="dark"] .home-slogan::before, html[data-theme="dark"] .home-figures::before { display:none; }
.sr-only { border:0; clip:rect(0 0 0 0); clip-path:inset(50%); height:1px; margin:-1px; overflow:hidden; padding:0; position:absolute; white-space:nowrap; width:1px; }
.strategy-page { margin:0 auto; max-width:760px; min-width:0; }
.strategy-hero { padding:0; }
/* 模擬考 opens the page, so it carries no rule or top gap of its own — the title row above already
   sets the spacing (founder, 2026-09-24: "上半部要放模擬考試"). */
.strategy-hero + .strategy-section { border-top:0; margin-top:0; padding-top:0; }
.strategy-section { border-top:1px solid var(--product-line); margin-top:34px; padding-top:25px; min-width:0; }
.strategy-section h2 { font-size:1.25rem; font-weight:710; letter-spacing:-.015em; line-height:1.35; margin:0 0 15px; }
/* The one still-prose block: INSUFFICIENT/STATE_UNAVAILABLE's single explanatory item, ahead of the
   page's one CTA. Every navigable row uses .product-detail-row instead (founder, 2026-09-24: "If a
   row can be expressed with title + quiet supporting line + divider + chevron, prefer that"). */
.strategy-item-copy { min-width:0; }
.strategy-item-copy h3 { font-size:1.0625rem; font-weight:710; line-height:1.45; margin:0; overflow-wrap:anywhere; }
.strategy-item-copy p { color:var(--product-muted); font-size:.9375rem; line-height:1.65; margin:8px 0 0; overflow-wrap:break-word; }
.strategy-rows { margin:0; }
.strategy-cta { margin-top:16px; }
.product-detail-row.is-unavailable { color:var(--product-muted); }
/* One block per mock form: title, then 聽力 left and 閱讀 right, each with its own thin progress track underneath. The page shows all six. */
.strategy-mock-card { border-bottom:1px solid var(--product-line); display:grid; gap:12px; padding:15px 2px 16px; }
.strategy-mock-card-head { align-items:center; display:flex; justify-content:space-between; }
.strategy-mock-card-head strong { font-size:1rem; font-weight:650; }
.strategy-mock-card-head span { color:var(--product-muted); font-size:1.4375rem; line-height:1; }
.strategy-mock-card-head.is-link { color:inherit; text-decoration:none; }
.strategy-mock-halves { display:grid; gap:22px; grid-template-columns:1fr 1fr; }
/* 聽力/閱讀 are each their own tappable half once there is something to start or resume (founder,
   2026-09-28: no more single whole-card link to an intermediate page) — a finished half is plain text. */
.strategy-mock-half-cell { color:inherit; display:grid; gap:7px; min-width:0; text-decoration:none; }
.strategy-mock-half-cell b { font-size:.875rem; font-weight:650; }
.strategy-mock-half-cell em { color:var(--product-muted); font-size:.8125rem; font-style:normal; line-height:1.4; }
.strategy-mock-review-cue { color:var(--product-green); display:block; font-weight:650; margin-top:2px; }
.strategy-mock-track { background:var(--product-line); border-radius:999px; display:block; height:5px; overflow:hidden; }
.strategy-mock-track i { background:var(--product-green); border-radius:999px; display:block; height:100%; }

.product-detail-row.is-unavailable strong { color:inherit; }
.strategy-pacing-list { display:grid; gap:0; }
.strategy-pacing-list > div { align-items:baseline; border-bottom:1px solid var(--product-line); display:flex; gap:18px; justify-content:space-between; min-width:0; padding:13px 0; }
.strategy-pacing-list > div:first-child { border-top:1px solid var(--product-line); }
.strategy-pacing-list strong { font-size:.9375rem; }
.strategy-pacing-list span { color:var(--product-muted); font-size:.875rem; text-align:right; }
/* 考場策略與配速: one quiet row that expands in place, the same title+chevron language as every other
   row on the page, rather than the two large static sections it replaces (founder, 2026-09-24). */
.strategy-tactics-details { border-top:1px solid var(--product-line); margin-top:34px; padding-top:25px; }
.strategy-tactics-details summary { align-items:center; cursor:pointer; display:flex; flex-wrap:wrap; font-size:1.0625rem; font-weight:700; gap:4px 12px; list-style:none; min-height:44px; }
.strategy-tactics-details summary::-webkit-details-marker { display:none; }
.strategy-tactics-details summary span { margin-left:auto; transition:transform .18s ease; }
.strategy-tactics-details[open] summary span { transform:rotate(90deg); }
.strategy-tactics-details summary small { color:var(--product-muted); flex-basis:100%; font-size:.875rem; font-weight:400; }
.strategy-tactics-body { display:grid; gap:24px; margin-top:20px; }
.strategy-tactics-body h3 { font-size:.9375rem; font-weight:710; margin:0 0 8px; }
.strategy-tactics-body p { color:var(--product-muted); font-size:.9375rem; line-height:1.65; margin:0; }
@media (max-width:374px) { .product-frame { padding-left:14px; padding-right:14px; } .my-profile-card { grid-template-columns:46px minmax(0,1fr) auto; padding:13px; } .my-avatar { height:46px; width:46px; } }
.vocab-unavailable h1, .vocab-complete h1 { font-size:clamp(1.625rem,7vw,2.125rem); line-height:1.25; margin:0 0 22px; }
.vocab-unavailable p, .vocab-complete p { color:var(--product-muted); font-size:1rem; line-height:1.65; }
.vocab-learning, .vocab-learning-loop { display:flex; flex-direction:column; min-height:calc(100svh - 160px - var(--safe-top,0px)); min-width:0; }
/* Quiz only: anchored from the top, NOT vertically centred. It used to centre the word+options as one
   group, but answering adds a 答對了/答錯了 line under the options, which made the group taller and
   pushed the whole thing (word included) up ~45px the moment you answered (founder, 2026-09-26:
   "單字和詞姓要再往上放 要不然答題後會飄"). With a fixed top the word stays put and the new line just
   grows downward. 64px + the h1's 24px lands the word right under the sound toggle, like the reveal
   screen. */
.vocab-learning-loop[data-learning-phase="VERIFY_MEANING"] { justify-content:flex-start; padding-top:64px; }
.vocab-learning-loop[data-learning-phase="VERIFY_MEANING"] h1 { margin-top:24px; }
.vocab-learning h1, .vocab-learning-loop h1 { color:var(--apt-ink); font-weight:800; line-height:1.12; margin:14px 0 10px; overflow-wrap:anywhere; }
/* Every phase except the centered surface/FIRST_SURFACE judgment card (its own h1 already sits well
   clear, inside .vocab-first-surface) and the top-anchored VERIFY_MEANING quiz: the headword row
   is the first thing in the section, right where the sound toggle sits (founder, 2026-09-25, real
   device build: the word's inline 詞性 was rendering under the toggle) -- pushed down to clear it. */
.vocab-learning:not([data-exposure-phase="surface"]) h1,
.vocab-learning-loop:not([data-learning-phase="FIRST_SURFACE"]):not([data-learning-phase="VERIFY_MEANING"]) h1 { margin-top:64px; }
.vocab-surface { font-size:clamp(2rem,9vw,3rem); }
.vocab-progress, .vocab-review-kicker { color:var(--product-muted); font-size:.8125rem; font-weight:700; letter-spacing:.02em; margin:0 0 10px; }
/* padding-top eats into the fixed min-height, not adds to it, so it shifts the whole centered word +
   judgment row down together without changing the word's own centered position within its stage
   (founder, 2026-09-25: word stays centered; separately wanted the group lower for thumb reach). */
.vocab-first-surface { display:flex; flex-direction:column; min-height:calc(100svh - 260px - var(--safe-top,0px)); padding-top:56px; text-align:center; touch-action:pan-y; transition:transform .15s ease, opacity .15s ease; width:100%; }
/* The word centers in its own stage; the judgment row keeps a fixed gap below it instead of being
   pinned to the very bottom edge (founder, 2026-09-25: "單字放中間置中 按鍵也往下一點"). */
.vocab-surface-stage { align-items:center; display:flex; flex:1 1 auto; flex-direction:column; justify-content:center; }
.vocab-first-surface .vocab-surface { font-size:clamp(2.75rem,13vw,4.25rem); margin:0 0 12px; }
.vocab-first-surface .vocab-pos { margin:0; }
.vocab-pos { color:var(--apt-muted); font-size:.9375rem; font-weight:700; line-height:1.4; margin:0 0 20px; }
.vocab-pos-inline { color:var(--apt-muted); font-size:.9375rem; font-weight:700; margin-inline-start:.4em; white-space:nowrap; }
.vocab-prompt { color:var(--product-muted); font-size:1.0625rem; margin:0 0 24px; }
.vocab-correction-feedback { background:var(--product-surface); border:1px solid var(--product-line); border-radius:10px; color:var(--product-ink); font-size:.9375rem; font-weight:600; line-height:1.5; margin:0 0 18px; padding:11px 14px; }
.vocab-correction-feedback .option-symbol { color:var(--product-alert,#9B4038); font-size:1.05rem; line-height:1; margin-right:6px; }
.vocab-verification .option-choice.is-correct { background:var(--product-success-soft,#EAF3EA); border-color:var(--product-success,#24634B); }
.vocab-verification .option-choice.is-incorrect { background:var(--product-alert-soft,#F7EAE8); border-color:var(--product-alert,#9B4038); }
.vocab-verification .option-choice .option-symbol { font-size:1.05rem; line-height:1; margin-right:6px; }
.vocab-verification .option-choice.is-correct .option-symbol { color:var(--product-success,#24634B); }
.vocab-verification .option-choice.is-incorrect .option-symbol { color:var(--product-alert,#9B4038); }
.vocab-verdict { font-size:1.0625rem; font-weight:700; margin:12px 0 0; text-align:center; }
.vocab-verdict.is-correct { color:var(--product-success,#24634B); }
.vocab-verdict.is-incorrect { color:var(--product-alert,#9B4038); }
.vocab-judgment { display:grid; gap:12px; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:48px; width:100%; }
.vocab-judgment button { border:0; border-radius:16px; color:var(--product-on-fill); cursor:pointer; display:flex; flex-direction:column; align-items:center; font-size:1.0625rem; font-weight:750; gap:2px; min-height:58px; padding:14px 16px; }
.vocab-judgment button.is-know { background:var(--product-green); }
.vocab-judgment button.is-not-sure { background:var(--product-red); }
.vocab-judgment .vocab-swipe-hint { color:var(--product-on-fill); font-size:.75rem; font-weight:600; opacity:.75; }
/* Review (a due word): the new-word first card and learning card, so it reuses their classes. Only the
   h1 needs saying again -- the rule above that clears the sound toggle would otherwise push the word of
   the first card down by 64px, since this section's phase is REVIEW, not FIRST_SURFACE. */
.vocab-learning-loop[data-learning-phase="REVIEW"] .vocab-first-surface .vocab-surface { margin:0 0 12px; }
.vocab-review-show { grid-template-columns:minmax(0,1fr); }
.vocab-judgment button.is-reveal { background:var(--apt-navy); }
.vocab-review-grade { margin-top:28px; }
.vocab-review-answer:focus { outline:none; }
/* An author display value (the first card is a flex column) beats the browser's own [hidden] rule, so the cards need it said again. */
.vocab-first-surface[hidden], .vocab-review-answer[hidden] { display:none; }
.vocab-reveal { border-top:1px solid var(--product-line); padding-top:18px; text-align:left; width:100%; }
.vocab-reveal h2 { color:var(--product-muted); font-size:0.875rem; font-weight:700; margin:0 0 7px; }
.vocab-meaning { font-size:1.375rem; font-weight:650; line-height:1.5; margin:0 0 14px; }
.vocab-playback { align-items:center; display:flex; flex-wrap:wrap; gap:10px; margin:18px 0 22px; padding:0; }
.vocab-playback .speed-control { border:0; flex:1 1 auto; margin:0; min-width:0; padding:0; }
.vocab-playback .speed-options { display:grid; gap:0; grid-template-columns:repeat(3,minmax(0,1fr)); margin:0; width:100%; }
.vocab-playback .speed-options button { background:var(--product-surface); border:1px solid var(--product-line); border-radius:0; color:var(--product-muted); cursor:pointer; font:inherit; min-height:48px; min-width:0; width:100%; }
.vocab-playback .speed-options button:first-child { border-radius:7px 0 0 7px; }
.vocab-playback .speed-options button:last-child { border-radius:0 7px 7px 0; }
.vocab-playback .speed-options button[aria-pressed="true"] { background:color-mix(in srgb,var(--product-green) 16%,var(--product-surface)); border-color:var(--product-green); box-shadow:none; color:var(--product-green); }
.vocab-playback .replay-action { background:transparent; border:1px solid var(--product-line); border-radius:8px; color:var(--product-ink); flex:0 0 auto; font-size:0.8125rem; min-height:48px; min-width:94px; order:1; padding-inline:12px; white-space:nowrap; }
.vocab-playback .vocab-audio-error { flex-basis:100%; margin:0; }
.vocab-playback .vocab-audio-error:empty { display:none; }
.vocab-hidden-audio { clip:rect(0 0 0 0); clip-path:inset(50%); height:1px; overflow:hidden; pointer-events:none; position:absolute; white-space:nowrap; width:1px; }
.vocab-audio-error, .vocab-media-unavailable { color:var(--product-muted); font-size:0.875rem; line-height:1.5; margin:6px 0 15px; }
.vocab-image { margin:21px 0 0; max-width:28rem; }
.vocab-image img { aspect-ratio:4/3; border-radius:10px; display:block; height:auto; max-height:220px; max-width:100%; object-fit:cover; width:100%; }
/* 背單字's action buttons stay in the thumb zone however long the card is (founder, 2026-10-04: no scrolling to the end
   to reach 下一個): the bar sticks to the bottom of the screen, above the home indicator, over the card's own scrolling. */
.vocab-page .vocab-action-bar { background:linear-gradient(to bottom, transparent, var(--product-canvas) 28%); bottom:0; box-sizing:border-box; margin-bottom:0; padding:16px 0 calc(12px + env(safe-area-inset-bottom)); position:sticky; z-index:5; }
.vocab-page .vocab-next-bar button { width:100%; }
/* The picture sits under the meaning, before the example, capped so the card stays short: about 28vh (35vh on iPad). */
.vocab-image-capped { margin:16px 0 0; }
.vocab-image-capped .media-zoom-trigger img { aspect-ratio:auto; border-radius:12px; max-height:28vh; object-fit:contain; width:auto; max-width:100%; }
@media (min-width:768px) { .vocab-image-capped .media-zoom-trigger img { max-height:35vh; } }
/* The shared picture viewer on 背單字 (same markup and script as the question pages). */
.vocab-image .zoomable-media { margin:0; position:relative; }
.vocab-image .media-zoom-trigger { background:transparent; border:0; cursor:zoom-in; display:block; padding:0; position:relative; width:100%; }
.vocab-image .zoom-hint { align-items:center; background:color-mix(in srgb, var(--product-surface,#fff) 88%, transparent); border:1px solid var(--product-line,#d9ddd6); border-radius:999px; bottom:8px; color:var(--product-ink,#1f2a24); display:inline-flex; font-size:.75rem; font-weight:700; gap:4px; line-height:1; min-height:28px; padding:4px 9px; pointer-events:none; position:absolute; right:8px; }
html[data-zoom-hint-seen="true"] .vocab-image .zoom-hint-text { display:none; }
.vocab-image .media-zoom-dialog { background:rgba(15,20,26,.94); border:0; box-sizing:border-box; height:100dvh; margin:0; max-height:none; max-width:none; overflow:hidden; padding:0; width:100vw; }
.vocab-image .media-zoom-dialog[open] { display:block; }
.vocab-image .media-zoom-dialog::backdrop { background:rgba(15,20,26,.94); }
.vocab-image .media-zoom-toolbar { display:flex; gap:8px; position:absolute; right:max(12px,env(safe-area-inset-right)); top:calc(10px + env(safe-area-inset-top)); z-index:2; }
.vocab-image .media-zoom-toolbar button { align-items:center; background:rgba(255,255,255,.92); border:0; border-radius:50%; color:#182637; cursor:pointer; display:inline-flex; font-size:1.375rem; font-weight:600; height:44px; justify-content:center; line-height:1; padding:0; width:44px; }
.vocab-image .media-zoom-toolbar button:disabled { opacity:.4; cursor:default; }
.vocab-image .media-zoom-stage { align-items:center; box-sizing:border-box; display:flex; height:100%; justify-content:center; overflow:hidden; padding:calc(64px + env(safe-area-inset-top)) 8px calc(16px + env(safe-area-inset-bottom)); touch-action:none; -webkit-user-select:none; user-select:none; width:100%; }
.vocab-image .media-zoom-stage:focus { outline:none; }
.vocab-image .media-zoom-stage img { aspect-ratio:auto; border-radius:0; display:block; height:auto; max-height:100%; max-width:100%; object-fit:contain; transform-origin:0 0; -webkit-user-drag:none; width:auto; }
.vocab-example, .vocab-usage { border-top:1px solid var(--product-line); margin-top:20px; padding-top:17px; }
.vocab-example p, .vocab-example-translation p, .vocab-usage p { line-height:1.65; margin:0 0 8px; overflow-wrap:anywhere; }
.vocab-example p[lang="en"] { font-size:1.1875rem; font-weight:600; line-height:1.6; }
.vocab-example-translation { margin-top:10px; }
.vocab-example-translation summary { color:var(--product-muted); cursor:pointer; font-size:.9375rem; font-weight:700; line-height:1.5; }
.vocab-example-translation summary:focus-visible { outline:3px solid var(--apt-sage); outline-offset:3px; }
.vocab-example-translation p { color:var(--product-muted); font-size:1rem; margin-top:8px; }
.vocab-usage p { font-size:1rem; }
.vocab-next { margin-top:auto; padding-top:24px; width:100%; }
.vocab-next button { width:100%; }
.vocab-verification { display:grid; gap:12px; margin-top:24px; text-align:left; width:100%; }
.vocab-verification form { min-width:0; }
.vocab-verification .option-choice { align-items:center; background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:16px; color:var(--apt-ink); cursor:pointer; display:flex; font-size:1.0625rem; font-weight:650; line-height:1.45; min-height:58px; padding:12px 16px; text-align:left; width:100%; overflow-wrap:anywhere; }
.vocab-learning-loop > form button { background:var(--apt-navy); border:1px solid var(--apt-navy); border-radius:16px; color:var(--product-on-fill); cursor:pointer; font-size:1rem; font-weight:700; line-height:1.45; min-height:56px; padding:10px 16px; width:100%; overflow-wrap:anywhere; }
.vocab-state-note { color:var(--product-muted); font-size:1rem; line-height:1.65; margin:22px 0 0; }
.vocab-learning-loop > form { margin-top:22px; }
.vocab-review-prompt { border-top:1px solid var(--product-line); font-size:1.25rem; line-height:1.55; margin:20px 0; overflow-wrap:anywhere; padding-top:18px; }
.vocab-review-actions { display:grid; gap:12px; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:24px; }
.vocab-review-actions button:last-child { background:var(--product-surface); color:var(--product-green); }
.auth-page { align-items:center; display:flex; flex-direction:column; justify-content:center; min-height:calc(100svh - 160px - var(--safe-top,0px)); padding:12px 0; }
.auth-card { max-width:26rem; width:100%; }
.auth-brand { align-items:center; display:flex; gap:12px; justify-content:center; margin:0 0 18px; }
.auth-brand .brand-mark { border-radius:22%; height:48px; width:48px; }
.auth-wordmark { color:var(--product-gold); font-size:1.375rem; font-weight:800; letter-spacing:.01em; }
.auth-lede { color:var(--product-muted); font-size:1rem; line-height:1.5; margin:0 0 22px; text-align:center; }
/* /login only (docs/auth-flow-spec.md 2.1): the mark at 72 and the slogan in place of the wordmark. */
.login-brand { text-align:center; }
.login-brand .brand-mark { border-radius:22%; display:block; filter:drop-shadow(0 4px 12px rgba(32,33,31,.16)); height:72px; margin:0 auto; width:72px; }
.brand-slogan-line { display:block; }
.login-slogan { color:var(--product-ink); font-size:1.4375rem; font-weight:700; letter-spacing:-.01em; line-height:1.5; margin:24px 0 0; }
.auth-lede.login-lede { font-size:.875rem; line-height:1.7; margin:12px 0 24px; }
.auth-hint { color:var(--product-muted); font-size:.9375rem; line-height:1.55; margin:0 0 16px; }
.auth-notice { background:#f7eae8; border-radius:10px; color:#9b4038; font-size:.9375rem; line-height:1.5; margin:0 0 16px; padding:11px 14px; }
.auth-notice.is-success { background:#eaf2ed; color:#315f4a; }
.auth-provider-list { display:grid; gap:12px; }
.auth-provider-button { align-items:center; border:0; border-radius:12px; cursor:pointer; display:flex; font-size:1rem; font-weight:700; gap:10px; justify-content:center; min-height:52px; padding:12px 16px; width:100%; }
.auth-provider-button svg { flex:none; }
.auth-provider-button.is-apple { background:#000; color:#fff; }
.auth-provider-button.is-google { background:#fff; border:1px solid #dadce0; color:#3c4043; }
.auth-provider-button.is-line { background:#06c755; color:#fff; }
.auth-provider-button.is-email { background:var(--product-surface); border:1px solid var(--product-line); color:var(--product-ink); text-decoration:none; }
.auth-divider { align-items:center; color:var(--product-muted); display:flex; font-size:.8125rem; gap:12px; margin:18px 0; }
.auth-divider::before, .auth-divider::after { background:var(--product-line); content:""; flex:1; height:1px; }
/* One shrinkable column, not the implicit auto one: an auto grid track never gets narrower than its widest item's
   min-content, so a single control with an intrinsic width (iOS WebKit's native date field, a long <select> option)
   pushed the whole form past the right edge (founder, 2026-10-02: 目標分與考試日期 on an iPhone). minmax(0,1fr) lets the
   track follow the page; min-width:0 on the items lets them follow the track. */
.auth-form { display:grid; gap:14px; grid-template-columns:minmax(0,1fr); margin-top:4px; }
.auth-form > * { min-width:0; }
.auth-form label { color:var(--product-muted); display:grid; font-size:.875rem; font-weight:650; gap:6px; grid-template-columns:minmax(0,1fr); }
.auth-form input { background:var(--product-surface); border:1px solid var(--product-line); border-radius:10px; color:var(--product-ink); font:inherit; min-height:48px; padding:10px 12px; width:100%; }
.auth-submit { background:var(--product-green); border:1px solid var(--product-green); border-radius:10px; color:var(--product-on-fill); cursor:pointer; font:inherit; font-weight:700; margin-top:4px; min-height:50px; padding:12px 16px; width:100%; }
/* Korea age gate (founder, 2026-10-02): one required tick before an account is created. A row with the box on the left, not the stacked label-over-input field layout above. */
.auth-form .auth-age { align-items:flex-start; background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; color:var(--product-ink); cursor:pointer; display:flex; gap:12px; padding:14px; }
.auth-form .auth-age input { accent-color:var(--product-green); flex:none; height:22px; margin:1px 0 0; min-height:0; padding:0; width:22px; }
.auth-age strong { display:block; font-size:1rem; font-weight:700; line-height:1.4; }
.auth-age small { color:var(--product-muted); display:block; font-size:.8125rem; font-weight:500; line-height:1.5; margin-top:2px; }
/* Korean breaks between syllables by default, so a short sentence split "주 / 세요"; keep its words whole, and balance the lede. Korean only: keep-all would stop Chinese and Japanese breaking at all. */
.auth-lede.age-lede { text-wrap:balance; }
html[lang="ko"] .auth-age, html[lang="ko"] .auth-lede.age-lede, html[lang="ko"] .auth-notice { word-break:keep-all; }
.auth-switch { color:var(--product-muted); font-size:.9375rem; margin:16px 0 0; text-align:center; }
.auth-switch a, .auth-legal a { color:var(--product-green) !important; font-weight:700; text-decoration:none; }
/* "No account yet?" + the link are two separate strings with no space between them (中文 and 日本語 do not use one). A space that
   comes before the link, rendered, so it collapses away when the link starts a line and a link standing alone does not shift. */
html[data-writing-system="latin"] .auth-switch a::before, html[data-writing-system="ko-KR"] .auth-switch a::before { content:" "; }
/* 之後再說 / 先跳過 post a form rather than follow a link, since postponing is saved to the account. */
.auth-switch button, .diagnostic-skip button, .ob-skip button { background:none; border:0; color:var(--product-muted); cursor:pointer; font:inherit; padding:8px 4px; text-decoration:underline; text-underline-offset:3px; }
@media (hover:hover) { .auth-switch button:hover, .diagnostic-skip button:hover, .ob-skip button:hover { color:var(--product-ink); } }
.diagnostic-skip { margin:18px auto 28px; text-align:center; }
.ob-skip { padding:10px 0 4px; text-align:center; }
.auth-legal { color:var(--product-muted); font-size:.8125rem; line-height:1.7; margin-top:24px; text-align:center; }
/* App language (founder, 2026-10-02: nothing linked to it). Signed out: a small globe + current-language pill at the top-right of each auth screen (above the centred card, so it stays at the screen's edge),
   a native <select> so the device's own picker opens. 44px tall, in the product's pill/line language. Signed in: /settings/language, radio rows in a
   card like the other Settings cards, the tick showing the current one. The radio itself is visually hidden, never removed, so it stays focusable. */
.lang-switch { display:flex; justify-content:flex-end; margin:0; }
.lang-switch-field { display:inline-flex; position:relative; }
.lang-switch-field select { -webkit-appearance:none; appearance:none; background:var(--product-surface); border:1px solid var(--product-line); border-radius:999px; color:var(--product-ink); cursor:pointer; font:inherit; font-size:.9375rem; font-weight:650; min-height:44px; padding:0 38px 0 40px; }
.lang-switch-globe, .lang-switch-caret { color:var(--product-muted); pointer-events:none; position:absolute; top:50%; transform:translateY(-50%); }
.lang-switch-globe { left:14px; }
.lang-switch-caret { right:15px; }
.product-shell select:focus-visible { outline:3px solid var(--product-green); outline-offset:2px; }
.lang-switch-save { background:none; border:0; color:var(--product-green); cursor:pointer; font:inherit; font-weight:700; min-height:44px; padding:0 12px; }
.lang-form { margin-top:24px; }
.lang-card { padding:0 16px; }
.lang-option { align-items:center; cursor:pointer; display:flex; gap:12px; justify-content:space-between; min-height:60px; padding:10px 0; position:relative; }
.lang-option + .lang-option { border-top:1px solid var(--product-line); }
.product-card .lang-option input { block-size:1px; border:0; inline-size:1px; margin:0; min-height:1px; opacity:0; padding:0; position:absolute; }
.lang-option-name { font-size:1.0625rem; font-weight:650; min-width:0; }
.lang-option input:checked + .lang-option-name { font-weight:750; }
.lang-option-mark { color:color-mix(in srgb, var(--product-muted) 62%, var(--product-surface)); flex:none; }
.lang-option-tick { opacity:0; stroke:var(--product-on-fill); }
.lang-option input:checked ~ .lang-option-mark { color:var(--product-green); }
.lang-option input:checked ~ .lang-option-mark .lang-option-ring { fill:var(--product-green); }
.lang-option input:checked ~ .lang-option-mark .lang-option-tick { opacity:1; }
.lang-option input:focus-visible ~ .lang-option-mark { border-radius:50%; outline:3px solid var(--product-green); outline-offset:2px; }
.lang-form .lang-save { border-radius:12px; font-size:1.0625rem; margin-top:0; min-height:54px; }
/* 設定目標 (first run) gets the same light photo band as 練習 and 策略 (founder, 2026-09-27: "放個什麼…背景圖", then "高山的照片 目標的照片",
   picked the misty dawn peaks): the mountain is the goal. Same 260px band, fade to canvas; hidden in dark mode like the others. The
   name is new (goal-bg.jpg) because /img/v2/* is immutable-cached. */
.product-shell[data-goal-page] { position:relative; }
.product-shell[data-goal-page]::before { background:url("/img/v2/goal-bg.jpg") 50% 50% / cover no-repeat; content:""; height:280px; left:0; opacity:.55; pointer-events:none; position:absolute; right:0; top:0; -webkit-mask-image:linear-gradient(#000 35%,transparent); mask-image:linear-gradient(#000 35%,transparent); }
.product-shell[data-goal-page] .product-frame { position:relative; z-index:1; }
html[data-theme="dark"] .product-shell[data-goal-page]::before { display:none; }
/* Type per docs/visual-system-standard.md §5 (founder, 2026-09-27: 整個頁面字體大小對一下規範): 標題 27/700 balanced against the score 32/650 (founder, 2026-09-27: 設定目標和分數平衡一下; 42 had overpowered the title), 按鈕 16/600, 標籤 13, 次要說明 14. */
.product-shell[data-goal-page] .product-intro h1 { font-size:1.6875rem; font-weight:700; letter-spacing:0; line-height:1.2; margin:0; }
.product-shell[data-goal-page] .auth-switch { font-size:.875rem; margin:16px 0 0; }
/* Type per docs/visual-system-standard.md §5 (founder, 2026-09-29: 這頁字體符合規範嗎, scoped to this
   page only, not the shared .product-card/.product-detail-row/.product-intro rules every other
   Settings sub-page also uses): 頁面標題 23/700/1.4, 卡片標題 19/600/1.4, 清單列標題 17/600, 次要說明
   14/400, 按鈕 16/600. §5 allows only weights 400/500/600/700; the role table's own "650" entries for
   card titles and list-row titles are rounded down to 600, the nearest allowed weight. */
.product-shell[data-account-page] .product-intro h1 { font-size:1.4375rem; font-weight:700; line-height:1.4; }
.product-shell[data-account-page] .product-kicker { font-weight:400; }
.product-shell[data-account-page] .product-card h2 { font-size:1.1875rem; font-weight:600; line-height:1.4; }
.product-shell[data-account-page] .product-detail-row strong { font-size:1.0625rem; font-weight:600; }
.product-shell[data-account-page] .product-detail-row small { font-size:.875rem; }
.product-shell[data-account-page] .product-card button { font-size:1rem; font-weight:600; }
/* 練習題圖片黑白 (founder, 2026-09-27): only the pictures and charts inside practice and mock questions (.visual-media, including the zoomed view),
   never the app's own photos. Set by html[data-image-mode="mono"], from the preference or this device's own choice. */
html[data-image-mode="mono"] .visual-media img, html[data-image-mode="mono"] .zoomable-media img { filter:grayscale(1); }
/* Spacing (founder, 2026-09-27: "ICON間距改改"): the rows of chips were 8px apart and the window chips sat right under the mode chips. */
.goal-label { color:var(--product-muted); font-size:.8125rem; font-weight:600; margin:32px 0 12px; }
.goal-chips { display:flex; gap:12px; min-width:0; }
/* An author-stylesheet display value (flex, here) always beats the UA stylesheet's own bare
   [hidden] { display: none } rule, so a plain hidden attribute alone never actually hides a
   .goal-chips element (found 2026-09-29 on the exam-region session chips, but the pre-existing
   known/window toggle above shared the same latent risk). The extra attribute selector here
   outranks the bare class rule, so hidden genuinely hides again. */
.goal-chips[hidden] { display:none; }
/* Styled directly (not left to ambient .auth-form/.goal-form input rules) since this picker renders
   identically on both the settings page (.auth-form) and onboarding (.goal-form), and only .auth-form
   happens to style a bare input today. */
/* overflow:hidden: at the largest text size WebKit let the longest option ("My date isn't listed...") widen the body's scrollable
   overflow past the viewport (QA SE-02, en 375/390 x1.45) although the box itself stayed in the column; clipping the control ends it. */
.goal-session-select { overflow:hidden; text-overflow:ellipsis; background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; color:var(--product-ink); display:block; font:inherit; font-weight:600; margin:0 0 12px; max-width:100%; min-height:48px; min-width:0; padding:12px; text-overflow:ellipsis; width:100%; }
/* The native <input type="date"> behind 日期不在名單中. iOS WebKit sizes it from the widest date it could show (and centres
   the value), so a bare width:100% still overflowed on a 390pt phone; appearance:none + min/max-width:0/100% pins it to the
   column, and the value is left-aligned like the <select> above it. input.goal-date-input outranks ".auth-form input". */
input.goal-date-input { -webkit-appearance:none; appearance:none; background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; box-sizing:border-box; color:var(--product-ink); display:block; font:inherit; font-weight:600; margin:0; max-width:100%; min-height:48px; min-width:0; padding:12px; text-align:start; width:100%; }
input.goal-date-input::-webkit-date-and-time-value { margin:0; min-width:0; text-align:start; }
input.goal-date-input::-webkit-datetime-edit { padding:0; }
.goal-chips + .goal-chips, .goal-chips + [data-exam-panel], [data-exam-panel] + .goal-chips { margin-top:12px; }
/* The four rough-window choices sit in a 2x2 grid on a phone: four across squeezed each label onto
   two lines ("1 個月 / 內"). */
.goal-chips[data-exam-panel="window"]:not([hidden]) { display:grid; gap:12px; grid-template-columns:repeat(2,minmax(0,1fr)); }
@media (min-width:520px) { .goal-chips[data-exam-panel="window"]:not([hidden]) { grid-template-columns:repeat(4,minmax(0,1fr)); } }
/* Wraps only when a label truly does not fit its half-width cell (large 文字大小); the four-across layout keeps one line. */
@media (min-width:520px) { .goal-chips[data-exam-panel="window"] .goal-chip { white-space:nowrap; } }
[data-exam-other-panel] { margin-bottom:12px; }
/* Scoped to .goal-chips (not just .goal-chip) so this reliably outranks the generic ".product-intro
   form button" rule below — a page whose goal-chips form is nested in .product-intro (考試與個人設定)
   would otherwise have that lower-specificity rule win and every chip render green, selected or not
   (found 2026-09-23). */
.goal-chips .goal-chip { background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; color:var(--product-ink); cursor:pointer; flex:1 1 0; font:inherit; font-weight:600; min-height:48px; min-width:0; overflow-wrap:anywhere; padding:8px 8px; text-wrap:balance; }
.goal-chips .goal-chip.is-on { background:var(--product-green); border-color:var(--product-green); color:var(--product-on-fill); }
/* 考試日期: the SELECTED chip is filled light warm yellow (founder, 2026-09-27: "不是拉 是選擇的方框" after a first version tinted the unselected ones); a warm
   yellow border and ink text keep it reading as chosen (founder, 2026-09-27: the green border was ugly). The target-score chips (600 / 730 / 860) keep the green selected state. */
.goal-chips .goal-chip[data-exam-mode].is-on, .goal-chips .goal-chip[data-exam-window].is-on { background:#F8EFD0; border-color:#E2CB84; color:var(--product-ink); }
/* input.goal-range outranks ".auth-form input", which otherwise boxed the slider on 考試與個人設定 only (border, 48px, padding) and
   left it narrower than the chips above; both pages now draw the same bare slider, full column width, 44px to grab. */
input.goal-range { -webkit-appearance:none; appearance:none; background:transparent; border:0; border-radius:0; box-sizing:border-box; cursor:pointer; display:block; height:44px; margin:16px 0 0; max-width:100%; min-height:0; min-width:0; padding:0; width:100%; }
.goal-range::-webkit-slider-runnable-track { background:var(--product-line); border-radius:2px; height:4px; }
.goal-range::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; background:var(--product-surface); border-radius:50%; box-shadow:0 0 0 2px var(--product-green); height:28px; margin-top:-12px; width:28px; }
.goal-range::-moz-range-track { background:var(--product-line); border-radius:2px; height:4px; }
.goal-range::-moz-range-thumb { background:var(--product-surface); border:0; border-radius:50%; box-shadow:0 0 0 2px var(--product-green); height:28px; width:28px; }
.goal-readout { color:var(--product-muted); font-size:.875rem; margin:16px 0 0; text-align:center; }
/* Both read in the band's own colour (--cert-text, set by the cert-{color} class on .goal-readout
   itself — see the cert-* rules below) rather than a fixed ink/green, so a score visibly says what
   certificate it earns instead of just naming one in text (founder, 2026-09-23). */
.goal-readout strong { color:var(--cert-text,var(--product-ink)); font-size:2rem; font-weight:650; line-height:1; }
.goal-readout em { color:var(--cert-text,var(--product-green)); font-style:normal; font-weight:600; }
.goal-select { background:var(--product-surface); border:1px solid var(--product-line); border-radius:12px; color:var(--product-ink); font:inherit; min-height:52px; padding:0 16px; width:100%; }
.goal-form .auth-submit { border-radius:12px; font-weight:600; margin-top:24px; min-height:52px; }
/* The note under 之後再說 says why the two answers matter, so it is centred and in the product red (founder, 2026-09-27: "置中 紅色強調"). */
.goal-note { color:var(--product-red); font-weight:600; margin-top:24px; text-align:center; text-wrap:balance; }
/* Each phrase stays whole, but the phrases wrap against each other. A bare white-space:nowrap made the trailing
   space of every English/Korean phrase unbreakable too, so the three sentences became one 660px line that blew the
   whole goal page past the screen (found 2026-10-02, en/ko at 375). inline-block keeps the phrase together, still
   wraps inside itself when one phrase alone is wider than the column, and the gap the stripped trailing space used
   to make is put back below for the scripts that separate words with spaces. */
.goal-note .nowrap { display:inline-block; max-width:100%; }
html[data-writing-system="latin"] .goal-note .nowrap + .nowrap, html[data-writing-system="ko-KR"] .goal-note .nowrap + .nowrap { margin-inline-start:.3em; }
/* 設定目標 is one fixed screen. A longer language (3-4 line note, two-line chips) pushed its last line below an 812-844px phone, so on a
   shorter screen the spacing between its groups gives up the difference; the frame still scrolls if a language needs more. */
@media (max-height:900px) {
  .product-shell[data-goal-page] .goal-label { margin-top:22px; }
  .product-shell[data-goal-page] input.goal-range { margin-top:8px; }
  .product-shell[data-goal-page] .goal-readout { margin-top:12px; }
  .product-shell[data-goal-page] .goal-form .auth-submit, .product-shell[data-goal-page] .goal-note { margin-top:16px; }
}
@media (max-height:830px) {
  .product-shell[data-goal-page] .goal-label { margin-bottom:8px; margin-top:18px; }
  .product-shell[data-goal-page] .goal-chips + .goal-chips, .product-shell[data-goal-page] .goal-chips + [data-exam-panel], .product-shell[data-goal-page] [data-exam-panel] + .goal-chips { margin-top:8px; }
  .product-shell[data-goal-page] .goal-chips[data-exam-panel="window"]:not([hidden]) { gap:8px; }
  .product-shell[data-goal-page] .goal-chips .goal-chip { padding-block:5px; }
  .product-shell[data-goal-page] .goal-note { margin-top:12px; }
}
.auth-legal a { margin:0 2px; }
.credential-status { color:var(--product-muted); display:block; font-size:.8125rem; margin-top:2px; }
.credential-status.is-connected { color:var(--product-green); }
.credential-status small { color:var(--product-muted); display:inline; margin-left:6px; }
html[data-theme="dark"] .auth-provider-button.is-google { background:#131314; border-color:#5f6368; color:#e8eaed; }
.product-bottom-nav { background:var(--product-canvas); border-top:1px solid var(--product-line); bottom:0; left:0; padding-bottom:env(safe-area-inset-bottom); position:fixed; right:0; z-index:30; }
.product-bottom-nav-inner { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); margin:auto; max-width:760px; }
.product-tab { align-items:center; color:var(--product-muted) !important; display:flex; flex-direction:column; font-size:0.75rem; font-weight:650; gap:3px; justify-content:center; min-height:64px; text-decoration:none; }
.product-tab svg { height:23px; width:23px; }
.product-tab.is-active { color:var(--product-green) !important; font-weight:750; }
.product-tab:focus-visible, .product-shell a:focus-visible, .product-shell button:focus-visible, .product-shell input:focus-visible { outline:3px solid var(--product-green); outline-offset:2px; }
@media (max-width:430px) { .product-topbar-inner { padding:0 16px; } .product-frame { padding-left:16px; padding-right:16px; } }
@media (max-width:430px) { .home-col { --home-gutter:20px; } }
@media (max-width:374px) { .home-col { --home-gutter:16px; } }
@media (min-width:600px) { .home-hero-photo { object-position:60% 55%; } .home-figures { padding-bottom:20px; } .home-body { padding-top:24px; } .home-primary-action { padding:40px 0; } }
/* Privacy policy and terms: dated header, then ruled sections. */
.legal-updated { color:var(--product-muted); font-size:.875rem; margin:0 0 4px; }
.legal-section { border-top:1px solid var(--product-line); margin-top:18px; padding-top:16px; }
.legal-section h2 { font-size:1.0625rem; margin:0 0 8px; }
.product-intro .legal-section p { line-height:1.8; margin:0 0 12px; overflow-wrap:anywhere; }
.legal-section em { font-style:normal; font-weight:650; }
/* Tab pages carry the brand mark at the left of their own title row (no separate top bar). */
.tab-title { align-items:center; display:inline-flex; gap:12px; min-width:0; }
/* No border-radius: the mark's own PNG carries the squircle with transparent corners, so a CSS radius
   only clips into the artwork. */
.tab-brand-mark { flex:none; height:32px; width:32px; }
/* One title row for 練習/策略/設定: same mark size, same 27px ink title, same position and the same gap
   to the content below. 策略 used to carry a small green kicker and the slogan as its h1 instead
   (founder, 2026-09-24: "練習 策略 設定 請一致 … 包括位置 字體顏色和大小"). 首頁 keeps its own
   mark + APTENYO wordmark over the photo. Declared after .my-page-header so it wins on both. */
.tab-page-header { align-items:center; display:flex; gap:12px; justify-content:space-between; margin:0 0 22px; }
.tab-page-header h1 { color:var(--product-ink); font-size:1.6875rem; font-weight:710; letter-spacing:-.025em; line-height:1.2; margin:0; }
/* The status bar is cleared once, by body's padding (practice-shell-css), not again here. */
.product-shell[data-page-kind="primary"] .product-frame, .product-shell[data-page-kind="entry"] .product-frame { padding-top:22px; }
.product-rail-brand { display:none; }
/* Desktop (>= 1024): the four tabs become a left rail instead of a bottom bar; the content column stays centred beside it. */
@media (min-width:1024px) {
  .product-shell.has-nav { padding-left:216px; }
  .product-bottom-nav { border-right:1px solid var(--product-line); border-top:0; bottom:0; left:0; padding:28px 12px; right:auto; top:0; width:216px; }
  .product-bottom-nav-inner { display:flex; flex-direction:column; gap:4px; margin:0; max-width:none; }
  .product-tab { border-radius:10px; flex-direction:row; font-size:1rem; gap:12px; justify-content:flex-start; min-height:48px; padding:0 14px; }
  @media (hover:hover) { .product-tab:hover { background:color-mix(in srgb, var(--product-green) 7%, transparent); } }
  .product-tab.is-active { background:color-mix(in srgb, var(--product-green) 12%, transparent); }
  .product-shell.has-nav .product-frame { padding-bottom:calc(40px + env(safe-area-inset-bottom)); }
  .product-rail-brand { display:block; padding:0 14px 22px; }
  .tab-brand-mark { display:none; }
  .product-shell[data-active-tab="home"][data-page-kind="primary"] .product-frame { padding:40px 24px; }
  .home { min-height:0; }
  .home-hero { aspect-ratio:2; border-radius:16px 16px 0 0; flex:none; margin-inline:auto; max-width:760px; overflow:hidden; width:100%; }
  .home-hero-photo { -webkit-mask-image:linear-gradient(to bottom,#000 36%,transparent 80%); mask-image:linear-gradient(to bottom,#000 36%,transparent 80%); object-position:50% 46%; }
  .home-hero-brand { display:none; }
  .home-body { align-items:center; display:grid; gap:48px; grid-template-columns:minmax(0,1fr) 152px; }
  .home-primary-action { padding:0; }
}
html[data-theme="dark"] .product-notice { background:#20362a; border-color:#3f6b52; color:#8fd0a3; }
html[data-theme="dark"] .auth-notice { background:#3b2624; color:#e2a49b; }
html[data-theme="dark"] .auth-notice.is-success { background:#20362a; color:#8fd0a3; }
/* 「你的第一份策略」: Home's card and the report page (src/web/first-strategy-view.ts). */
.home-report { align-items:center; background:var(--product-surface); border:1px solid var(--product-green); border-radius:12px; display:flex; gap:12px; margin-top:16px; padding:14px 16px; text-decoration:none; }
.home-report > span { display:grid; flex:1; gap:4px; min-width:0; }
.home-report strong { font-size:1rem; font-weight:700; overflow-wrap:anywhere; }
.home-report small { color:var(--product-muted); font-size:.8125rem; line-height:1.45; overflow-wrap:anywhere; }
.home-report em { color:var(--product-green); flex:none; font-size:.875rem; font-style:normal; font-weight:650; white-space:nowrap; }
.first-strategy-section p { color:var(--product-muted); font-size:.9375rem; line-height:1.6; margin:0 0 10px; overflow-wrap:anywhere; }
.first-strategy-section p.first-strategy-weakest { color:var(--product-ink); line-height:1.7; }
.first-strategy .fs-estimate { background:var(--product-surface); border:1px solid var(--product-line); border-radius:16px; padding:18px 20px 8px; }
.first-strategy .fs-estimate h2 { color:var(--product-green); font-size:.9375rem; letter-spacing:.02em; }
.first-strategy-section p.first-strategy-figure { align-items:baseline; color:var(--product-ink); display:flex; flex-wrap:wrap; font-size:1rem; font-weight:600; gap:0 4px; line-height:1.3; margin:6px 0 10px; }
.fs-fig { font-family:var(--apt-font-figures); font-size:1.5rem; font-variant-numeric:tabular-nums; font-weight:600; }
.fs-fig.fs-fig-now { font-size:2.5rem; }
.first-strategy-section p.first-strategy-thin { background:color-mix(in srgb, var(--product-gold) 12%, transparent); border-radius:10px; color:var(--product-ink); font-size:.875rem; padding:10px 12px; }
.first-strategy-priorities { display:grid; gap:12px; list-style:none; margin:16px 0 0; padding:0; }
.first-strategy-priority { align-items:flex-start; background:var(--product-surface); border:1px solid var(--product-line); border-radius:14px; display:flex; gap:12px; padding:14px 14px 12px; }
.first-strategy-priority:first-child { border-color:var(--product-green); }
.first-strategy-priority > b { align-items:center; border:1.5px solid var(--product-green); border-radius:50%; color:var(--product-green); display:inline-flex; flex:none; font-family:var(--apt-font-figures); font-size:.9375rem; font-weight:700; height:30px; justify-content:center; width:30px; }
.first-strategy-priority:first-child > b { background:var(--product-green); color:var(--product-on-fill); }
.first-strategy-priority > span { display:grid; flex:1; gap:6px; min-width:0; }
.first-strategy-priority strong { font-size:1rem; font-weight:700; overflow-wrap:anywhere; }
.first-strategy-priority small { color:var(--product-muted); font-size:.8125rem; line-height:1.45; overflow-wrap:anywhere; }
.fs-bar { background:linear-gradient(to right, var(--product-green) var(--share), var(--product-line) var(--share)); border-radius:999px; display:block; height:6px; }
.first-strategy-priority .fs-when { color:var(--product-green); font-size:.8125rem; font-style:normal; font-weight:650; }
.first-strategy-weeks { list-style:none; margin:14px 0 0; padding:0; position:relative; }
.first-strategy-week { padding:0 0 18px 28px; position:relative; }
.first-strategy-week::before { background:var(--product-surface); border:2px solid var(--product-green); border-radius:50%; box-sizing:border-box; content:""; height:14px; left:0; position:absolute; top:5px; width:14px; }
.first-strategy-week:first-child::before { background:var(--product-green); }
.first-strategy-week[data-plan-kind="MOCK"]::before { background:var(--product-gold); border-color:var(--product-gold); border-radius:3px; }
.first-strategy-week:not(:last-child)::after { background:var(--product-line); bottom:-1px; content:""; left:6px; position:absolute; top:21px; width:2px; }
.first-strategy-week > span { display:grid; gap:2px; text-align:left; }
.first-strategy-week strong { color:var(--product-ink); font-size:.9375rem; font-variant-numeric:tabular-nums; font-weight:650; }
.first-strategy-week small { color:var(--product-muted); font-size:.875rem; line-height:1.5; }
.first-strategy-closing { background:var(--product-surface); border:1px solid var(--product-line); border-radius:16px; padding:18px 20px; }
.first-strategy-closing .product-primary-link { margin-top:6px; }
@media (prefers-reduced-motion:reduce) { .product-shell *, .product-shell *::before, .product-shell *::after { scroll-behavior:auto !important; transition-duration:0ms !important; } }


/* An onboarding screen never moves. The frame, not the page, takes any overflow — a learner on the
   largest text size still has to be able to reach 開始, so this is overflow:auto, not hidden. */
/* Flex column, so the frame gets whatever is left under the 返回 bar on the pages that have one —
   a flat 100dvh on the frame would push its own bottom out of a shell that is already exactly 100dvh. */
.product-shell[data-fixed-page] { display:flex; flex-direction:column; height:calc(100dvh - var(--safe-top,0px)); overflow:hidden; }
.product-shell[data-fixed-page] .product-frame { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:none; }

/* height, not min-height: a flex child can only actually shrink (.ob-hero's flex-shrink:1) against a
   real ceiling — with only a minimum, the browser grows the frame past the viewport instead of ever
   compressing the photo, which is what let 開始 end up below the fold in the first place. */
.product-shell[data-page-kind="fullbleed"] .product-frame { display:flex; flex-direction:column; height:calc(100dvh - var(--safe-top,0px)); max-width:none; padding:0 0 calc(32px + env(safe-area-inset-bottom)); }
.ob-col { box-sizing:border-box; margin-inline:auto; max-width:600px; padding-inline:24px; width:100%; }

/* The photo takes whatever height the list and the button leave, so 開始 stays in the thumb zone; the floor is rem because it has to fit the headline. */
/* Same fix as .home-hero in app-shell-css.ts: flex-basis in dvh so the photo doesn't grow with 介面
   文字大小, and flex-shrink:1 with a floor so it compresses for a taller body instead of pushing 開始
   off screen with no way to shrink. */
/* min-height:min-content, not a fixed 200px: the headline is 2-4 lines depending on the language and the text size, and a floor
   shorter than its own text cut the last lines off (ja at 375, every language at the largest 介面文字大小). The photo is what
   gives way — the frame scrolls if the list below no longer fits. */
.ob-hero { display:flex; flex:0 0 auto; flex-direction:column; height:clamp(220px, 36dvh, 420px); min-height:min-content; overflow:hidden; position:relative; }
/* 2026-10-03, founder's iPhone: on a second visit the photo was gone — the shrinkable hero had collapsed. The hero now keeps a real
   height (clamped to the viewport) and never shrinks; the frame below scrolls instead when the list does not fit. */
.ob-hero .ob-photo { height:100%; inset:0; object-fit:cover; object-position:50% 72%; position:absolute; width:100%; }
/* The founder compared this page with a paper fade and ink headline on 2026-09-22 and kept the photo as it was: its own grade, two scrims and a white headline.
   Measured on phone, tablet and desktop, the headline keeps at least 3:1 against the brightest pixel behind it (large-text floor). */
.ob-hero::after { background:linear-gradient(to bottom, rgba(28,24,18,0) 50%, rgba(28,24,18,.58) 76%, rgba(28,24,18,.7) 100%), linear-gradient(to bottom, rgba(28,24,18,.46) 0%, rgba(28,24,18,0) 26%, rgba(28,24,18,.12) 44%, rgba(28,24,18,.74) 100%); content:""; inset:0; position:absolute; }
.ob-brand { align-items:center; display:flex; gap:8px; padding-top:20px; position:relative; z-index:1; }
.ob-brand img { height:32px; width:32px; }
.ob-brand span { color:#fbf8f1; font-size:1.0625rem; font-weight:600; letter-spacing:.04em; }
.ob-hero h1 { color:#fcf9f2; font-family:var(--apt-font-display); font-size:2rem; font-weight:var(--apt-display-weight); letter-spacing:.02em; line-height:1.45; margin:auto 0 0; padding-bottom:20px; position:relative; text-shadow:0 1px 12px rgba(20,16,10,.45); z-index:1; }
/* 日本語 and 한국어 headlines run 14-25 full-width glyphs a line pair; at 32px they take four lines. A size down keeps them to three. */
html[data-writing-system="ja-JP"] .ob-hero h1, html[data-writing-system="ko-KR"] .ob-hero h1 { font-size:1.75rem; line-height:1.4; }
.ob-hero h1 { text-wrap:balance; }

.ob-body { container-type:inline-size; padding-top:24px; }
.ob-step { align-items:baseline; display:flex; justify-content:space-between; margin:0 0 12px; }
.ob-step h2 { color:var(--product-ink); font-size:1.0625rem; font-weight:600; margin:0; }
.ob-step span { color:var(--product-muted); font-size:.8125rem; font-variant-numeric:tabular-nums; }
.ob-cols { display:grid; gap:0 14px; grid-template-columns:minmax(0,1fr) minmax(0,1fr); }
/* Measured in rem, so it trips on the text size and not on the phone: at the largest 介面文字大小 a 160px column holds neither
   "Conversations" nor "100 questions", and the two lists stack instead. */
@container (max-width:18rem) { .ob-cols { grid-template-columns:minmax(0,1fr); row-gap:18px; } }
.ob-ch { align-items:baseline; border-bottom:1px solid var(--product-line); display:flex; flex-wrap:wrap; gap:2px 6px; justify-content:space-between; padding-bottom:8px; }
.ob-ch h3 { align-items:center; color:var(--product-ink); display:flex; font-size:.875rem; font-weight:600; gap:4px; margin:0; min-width:0; }
.ob-ch h3 svg { color:var(--product-muted); flex:0 0 auto; height:16px; width:16px; }
.ob-ch em { color:var(--product-muted); flex:none; margin-inline-start:auto; font-size:.75rem; font-style:normal; font-variant-numeric:tabular-nums; white-space:nowrap; }
.ob-row { align-items:baseline; display:flex; gap:6px; min-height:2rem; }
.ob-row i { color:var(--product-muted); font-size:.75rem; font-style:normal; font-variant-numeric:tabular-nums; text-align:right; width:12px; }
.ob-row p { color:var(--product-ink); flex:1; font-size:.9375rem; margin:0; min-width:0; overflow-wrap:anywhere; text-wrap:balance; }
.ob-row b { color:var(--product-ink); flex:none; font-size:1.0625rem; font-variant-numeric:tabular-nums; font-weight:600; }

.ob-foot { padding-top:20px; }
.ob-say { color:var(--product-muted); font-size:.8125rem; line-height:1.75; margin:0 0 12px; text-align:center; }
.ob-sound { align-items:center; color:var(--product-red); display:flex; font-size:.8125rem; font-weight:600; gap:8px; justify-content:center; margin:0 0 16px; }
.ob-sound svg { flex:0 0 auto; height:16px; width:16px; }
.ob-cta { align-items:center; background:var(--product-green); border:0; border-radius:12px; color:var(--product-on-fill); cursor:pointer; display:flex; font:inherit; font-size:1.0625rem; font-weight:600; justify-content:center; min-height:52px; text-decoration:none; width:100%; }
.ob-cta:focus-visible { outline:2px solid var(--product-green); outline-offset:4px; }
/* The intro's CTA is a link, and the shell's rule that links inherit their colour would otherwise turn its label dark on green. */
.product-shell a.ob-cta { color:var(--product-on-fill); }

/* Intro pages fill the screen, so the photo takes the height the text and 開始 leave and 開始 stays in the thumb zone.
   Same fix as .home-hero/.ob-hero: a real height (not min-height) on the frame so flex-shrink has a
   ceiling to shrink against, min-height:0 down the chain so a flex item's own content doesn't override
   that shrink, and the banner itself shrinkable (flex-shrink:1) instead of fixed — it had been flex:1 0
   auto (shrink:0), so at larger 介面文字大小 it held its full height and pushed 開始 below the fold
   exactly like the two pages this was first found on (founder, 2026-09-23). The photo is never shorter
   than the old 2.3:1 band; where each one is centred is in BANNERS (question-type-intro.ts). */
.product-shell:has(.intro-page) { display:flex; flex-direction:column; height:calc(100dvh - var(--safe-top,0px)); }
.product-shell:has(.intro-page) .product-frame { box-sizing:border-box; display:flex; flex:1 1 auto; flex-direction:column; min-height:0; width:100%; }
.intro-page { container-type:inline-size; display:flex; flex:1 1 auto; flex-direction:column; margin:0 auto; max-width:600px; min-height:0; width:100%; }
.intro-banner { border-radius:12px; flex:1 1 auto; margin:0; max-height:45dvh; min-height:calc(100cqw / 2.3); overflow:hidden; position:relative; }
.intro-banner img { display:block; height:100%; inset:0; object-fit:cover; position:absolute; width:100%; }
.intro-body { padding:24px 0 0; }
.intro-kicker { color:var(--product-muted); font-size:.8125rem; font-weight:600; margin:0; }
.intro-body h1 { color:var(--product-ink); font-size:1.4375rem; font-weight:700; margin:4px 0 0; }
.intro-format { color:var(--product-muted); font-size:.9375rem; line-height:1.75; margin:16px 0 0; }
.intro-watch { color:var(--product-ink); font-size:.9375rem; font-weight:600; line-height:1.75; margin:4px 0 0; }
.intro-foot { margin-top:auto; padding:40px 0 0; }
.intro-dots { display:flex; gap:8px; justify-content:center; margin:0 0 20px; }
.intro-dot { background:var(--product-line); border-radius:50%; height:5px; width:5px; }
.intro-dot.is-done { background:var(--product-muted); }
.intro-dot.is-active { background:var(--product-green); }

/* 程度測驗完成: the estimate on top, the per-Part table below it, 開始今天的練習 pushed to the bottom (thumb zone); the frame scrolls
   at the largest text size instead of clipping. rem throughout so 介面文字大小 applies. */
.product-shell[data-fixed-page] .product-frame:has(.ob-done) { box-sizing:border-box; display:flex; flex-direction:column; width:100%; }
.ob-done { padding-top:24px; }
.product-frame .ob-done.ob-col, .product-frame .ob-done + .ob-foot.ob-col { padding-inline:0; }
.ob-done-title { align-items:center; color:var(--product-green); display:flex; font-size:.9375rem; font-weight:700; gap:8px; letter-spacing:.02em; margin:0; }
.ob-done-check { align-items:center; background:var(--product-green); border-radius:50%; color:var(--product-on-fill); display:inline-flex; font-size:.75rem; height:22px; justify-content:center; width:22px; }
.ob-done-score { align-items:baseline; color:var(--product-ink); display:flex; flex-wrap:wrap; font-size:1.375rem; font-weight:600; gap:0 8px; line-height:1; margin:18px 0 0; }
.ob-done-num { font-family:var(--apt-font-figures); font-size:4.5rem; font-variant-numeric:tabular-nums; font-weight:600; letter-spacing:-.02em; }
.ob-done-fig { color:var(--product-ink); font-family:var(--apt-font-figures); font-variant-numeric:tabular-nums; font-weight:600; white-space:nowrap; }
.ob-done-sections { color:var(--product-muted); font-size:.9375rem; margin:18px 0 0; padding-top:14px; border-top:1px solid var(--product-line); }
.ob-done-note { color:var(--product-muted); font-size:.8125rem; line-height:1.7; margin:6px 0 26px; }
.ob-done-step { margin-bottom:4px; }
.ob-done-step h2 { font-size:1.0625rem; }
.ob-done-parts { border-collapse:collapse; font-size:.9375rem; margin:0 0 8px; width:100%; }
.ob-done-parts th, .ob-done-parts td { border-bottom:1px solid var(--product-line); padding:10px 0; vertical-align:middle; }
.ob-done-parts tr:last-child th, .ob-done-parts tr:last-child td { border-bottom:0; }
.ob-done-parts th { color:var(--product-ink); font-weight:500; text-align:start; white-space:nowrap; width:4.5em; }
.ob-done-parts td { color:var(--product-muted); font-family:var(--apt-font-figures); font-variant-numeric:tabular-nums; font-weight:600; text-align:end; white-space:nowrap; width:3em; }
.ob-done-parts td.ob-done-dots { padding-inline:8px; text-align:start; width:auto; }
.ob-done-dots i { border:1.5px solid var(--product-line); border-radius:50%; box-sizing:border-box; display:inline-block; height:14px; margin-right:7px; vertical-align:middle; width:14px; }
.ob-done-dots i.is-right { background:var(--product-green); border-color:var(--product-green); }
.ob-done-hero .ob-done-goal-link, .ob-done > .ob-done-goal-link { margin-top:8px; }
.ob-done-goal { color:var(--product-ink); font-size:.9375rem; font-weight:600; margin:12px 0 0; }
.product-shell a.ob-done-goal-link { align-items:center; border:1px solid var(--product-line); border-radius:12px; color:var(--product-ink); display:inline-flex; font-size:.9375rem; font-weight:600; min-height:44px; padding:0 16px; text-decoration:none; }
.ob-done + .ob-foot { margin-top:auto; padding-bottom:calc(16px + env(safe-area-inset-bottom)); }
.ob-done-more { display:flex; flex-wrap:wrap; gap:4px 20px; justify-content:center; margin-top:12px; }
.product-shell .ob-done-more a { color:var(--product-muted); display:inline-flex; align-items:center; font-size:.875rem; min-height:44px; text-decoration:underline; text-underline-offset:3px; }

@media (max-width:430px) { .ob-col { padding-inline:20px; } }
@media (max-width:374px) { .ob-col { padding-inline:16px; } }
/* A longer language (a four-line headline, wrapping part names) no longer fits 812px without scrolling: take the air out of the list's own
   spacing first, as the shorter-phone block below does for 700px. */
@media (max-height:830px) {
  .ob-body { padding-top:16px; }
  .ob-step { margin-bottom:8px; }
  .ob-row { min-height:1.8rem; }
  .ob-foot { padding-top:12px; }
  .ob-say { margin-bottom:8px; }
  .ob-sound { margin-bottom:12px; }
}
/* The hero's own 200px floor is already the headline's minimum (two lines at max 介面文字大小); on a short
   phone (iPhone SE et al.) at that same max text scale the list below it still doesn't fit, so this trims
   its own spacing — never the hero — to keep 開始 in view without scrolling. Rows and paragraph checked
   against real content at 1.45x first: none of this clips text, it only removes air between it. */
@media (max-height:700px) {
  .ob-body { padding-top:14px; }
  .ob-step { margin-bottom:8px; }
  .ob-ch { padding-bottom:4px; }
  .ob-row { min-height:1.6rem; }
  .ob-foot { padding-top:12px; }
  .ob-say { line-height:1.5; margin-bottom:8px; }
  .ob-sound { margin-bottom:10px; }
}
/* Desktop: the photo sits in the 600 column with rounded top corners, as Home's does in its 760 column. */
@media (min-width:1024px) {
  .product-shell[data-page-kind="fullbleed"] .product-frame { padding-top:40px; }
  .ob-hero { border-radius:16px 16px 0 0; margin-inline:auto; max-width:600px; overflow:hidden; width:100%; }
}


.record { --record-tone:var(--product-green); color:var(--product-ink); min-width:0; }
:where(.record) :where(h1, h2, h3, p, dl, dd) { margin:0; }
.record-head h1 { font-size:1.6875rem; font-weight:700; letter-spacing:-.01em; line-height:1.2; }
.record-range { color:var(--product-muted); font-size:.8125rem; font-variant-numeric:tabular-nums; line-height:1.5; margin-top:8px; }

.record-ledger { border-bottom:1px solid var(--product-line); display:grid; gap:24px 16px; grid-template-columns:repeat(2,minmax(0,1fr)); margin-top:32px; padding:0 0 20px; }
.record-fact { display:grid; gap:8px; align-content:start; min-width:0; }
.record-fact dt { color:var(--product-muted); font-size:.8125rem; line-height:1.5; }
.record-fact dd { align-items:baseline; display:flex; flex-wrap:wrap; gap:0 4px; min-width:0; }
.record-fig { color:var(--product-ink); font-family:var(--apt-font-figures); font-size:2rem; font-variant-numeric:tabular-nums; font-weight:600; line-height:1.1; overflow-wrap:anywhere; }
.record-fig.is-text { font-family:inherit; font-size:1.1875rem; font-weight:650; line-height:1.4; }
.record-unit { color:var(--product-muted); font-size:.875rem; line-height:1.5; }
.record .record-inline-link { align-items:center; color:var(--product-green); display:inline-flex; font-size:1rem; font-weight:600; min-height:44px; text-decoration:underline; text-underline-offset:2px; }
.record-footnote { color:var(--product-muted); font-size:.75rem; line-height:1.8; margin-top:8px; }

.record-section { margin-top:48px; min-width:0; }
.record-section h2 { font-size:1.1875rem; font-weight:650; line-height:1.4; margin-bottom:12px; }

.record-days { margin-top:40px; }
.record-days-head { align-items:baseline; display:flex; flex-wrap:wrap; gap:4px 16px; justify-content:space-between; margin-bottom:16px; }
.record-days-head h2 { margin-bottom:0; }
.record-days-line { color:var(--product-muted); display:flex; flex-wrap:wrap; font-size:.875rem; gap:4px 12px; line-height:1.5; }
.record-streak { color:var(--product-green); font-weight:600; }
.record-strip { display:grid; gap:4px; grid-template-columns:repeat(14,minmax(0,1fr)); list-style:none; margin:0; padding:4px 0; }
.record-stamp { display:flex; justify-content:center; min-width:0; }
.record-stamp span { aspect-ratio:1; background:transparent; border:1px solid var(--product-line); border-radius:50%; box-sizing:border-box; display:block; max-width:40px; width:100%; }
.record-stamp.is-active span { background:var(--product-green); border-color:var(--product-green); }
.record-stamp.is-today span { border:2px solid var(--product-ink); box-shadow:inset 0 0 0 2px var(--product-canvas); }
.record-strip-ends { color:var(--product-muted); display:flex; font-size:.75rem; font-variant-numeric:tabular-nums; justify-content:space-between; line-height:1.5; margin-top:8px; }

.record-groups { display:grid; gap:32px; }
.record-group.is-listening { --record-tone:var(--product-green); }
.record-group.is-reading { --record-tone:var(--product-blue); }
.record-group h3 { align-items:center; color:var(--product-muted); display:flex; font-size:.9375rem; font-weight:600; gap:8px; line-height:1.4; }
.record-group h3 i { background:var(--record-tone); border-radius:50%; display:block; flex:none; height:10px; width:10px; }
.record-parts { list-style:none; margin:0; padding:0; }
.record-part { border-bottom:1px solid var(--product-line); display:grid; gap:8px; padding:16px 0; }
.record-part-head { align-items:baseline; display:flex; flex-wrap:wrap; gap:4px 12px; justify-content:space-between; min-width:0; }
.record-part-name { align-items:baseline; display:flex; flex-wrap:wrap; gap:0 8px; min-width:0; }
.record-part-name b { font-size:1.0625rem; font-weight:600; line-height:1.4; }
.record-part-name span { color:var(--product-muted); font-size:.875rem; line-height:1.5; }
.record-part-pct { font-size:.875rem; font-variant-numeric:tabular-nums; font-weight:600; line-height:1.5; }
.record-part.is-idle .record-part-pct { color:var(--product-muted); font-weight:400; }
.record-bar { background:var(--product-line); border-radius:2px; display:block; height:4px; overflow:hidden; }
.record-bar i { background:var(--record-tone); border-radius:2px; display:block; height:100%; }
.record-part-meta { color:var(--product-muted); display:flex; flex-wrap:wrap; font-size:.8125rem; gap:4px 12px; line-height:1.5; }
.record-pending { color:var(--product-red); }
.record-part-meta > span:first-child::before { content:"○ "; color:var(--product-green); }
.record-pending::before { content:"× "; }
.record-part.is-idle .record-part-pct::before { content:"– "; }

.record-vocab { display:grid; gap:8px; }
.record-vocab h2 { margin-bottom:4px; }
.record-body { font-size:1rem; line-height:1.7; }
.record .record-link { align-items:center; align-self:start; color:var(--product-green); display:inline-flex; font-size:.9375rem; font-weight:600; gap:4px; justify-self:start; min-height:44px; text-decoration:none; }
.record .record-link span { font-size:1.1875rem; line-height:1; }

.record-mocks .strategy-rows { margin-top:0; }

.record-timeline { border-top:1px solid var(--product-line); list-style:none; margin:0; padding:0; }
.record-entry { border-bottom:1px solid var(--product-line); display:grid; gap:4px 16px; grid-template-columns:minmax(0,1fr); padding:12px 0; }
.record-entry-date { font-size:.875rem; font-variant-numeric:tabular-nums; font-weight:600; line-height:1.5; }
.record-entry ul { color:var(--product-muted); display:grid; font-size:.875rem; gap:4px; line-height:1.5; list-style:none; margin:0; padding:0; }

.record-pdf .record-body { color:var(--product-muted); font-size:.875rem; }
.record-secondary { background:transparent; border:1px solid var(--product-green); border-radius:12px; color:var(--product-green); cursor:pointer; font:inherit; font-size:1rem; font-weight:600; line-height:1.4; margin-top:16px; min-height:48px; padding:12px 16px; width:100%; }
@media (hover:hover) { .record-secondary:hover { background:color-mix(in srgb, var(--product-green) 8%, transparent); } }
.record-notice { background:color-mix(in srgb, var(--product-green) 10%, var(--product-canvas)); border-radius:12px; font-size:.875rem; line-height:1.7; margin-top:12px; padding:12px 16px; }
.record-notice.is-error { background:color-mix(in srgb, var(--product-red) 10%, var(--product-canvas)); color:var(--product-red); }

.record-empty { display:flex; flex-direction:column; gap:16px; justify-content:center; min-height:calc(100svh - 15rem); }
.record-empty h1 { font-size:1.4375rem; font-weight:700; line-height:1.4; }
.record-empty p { color:var(--product-muted); font-size:1rem; line-height:1.7; }
.record .record-primary { align-items:center; background:var(--product-green); border-radius:12px; box-sizing:border-box; color:var(--product-on-fill); display:flex; font-size:1rem; font-weight:600; justify-content:center; margin-top:16px; min-height:52px; padding:12px 20px; text-align:center; text-decoration:none; width:100%; }

.record a:focus-visible, .record button:focus-visible { outline:3px solid var(--product-green); outline-offset:2px; }

@media (min-width:600px) {
  .record-ledger[data-count="3"] { grid-template-columns:repeat(3,minmax(0,1fr)); }
  .record-ledger[data-count="4"] { grid-template-columns:repeat(4,minmax(0,1fr)); }
  .record-entry { grid-template-columns:8rem minmax(0,1fr); }
  .record .record-secondary, .record .record-primary { width:auto; }
  .record .record-primary { align-self:flex-start; }
}
@media (min-width:720px) {
  .record-groups { gap:40px; grid-template-columns:repeat(2,minmax(0,1fr)); }
}


.score-ruler { margin:18px 0 6px; padding-top:22px; }
.ruler-track { border-radius:999px; height:10px; position:relative; }
.ruler-band { bottom:0; position:absolute; top:0; }
.ruler-band:first-child { border-radius:999px 0 0 999px; }
.ruler-band:nth-child(5) { border-radius:0 999px 999px 0; }
.ruler-band[data-tone="orange"] { background:color-mix(in srgb, #d9822b 30%, var(--product-canvas)); }
.ruler-band[data-tone="brown"] { background:color-mix(in srgb, #8a5a36 30%, var(--product-canvas)); }
.ruler-band[data-tone="green"] { background:color-mix(in srgb, #3f8a5a 30%, var(--product-canvas)); }
.ruler-band[data-tone="blue"] { background:color-mix(in srgb, #3a6ea5 30%, var(--product-canvas)); }
.ruler-band[data-tone="gold"] { background:color-mix(in srgb, #b8932f 34%, var(--product-canvas)); }
.ruler-fill { background:var(--product-ink); border-radius:999px; bottom:3px; left:0; opacity:.82; position:absolute; top:3px; }
.ruler-now { background:var(--product-canvas); border:3px solid var(--product-ink); border-radius:50%; box-sizing:border-box; height:20px; margin-left:-10px; position:absolute; top:-5px; width:20px; }
.ruler-from { background:var(--product-muted); border-radius:50%; height:8px; margin-left:-4px; opacity:.7; position:absolute; top:1px; width:8px; }
.ruler-target { bottom:-6px; position:absolute; top:-24px; transform:translateX(-50%); }
.ruler-target::after { background:var(--product-green); bottom:0; content:""; left:50%; margin-left:-1px; position:absolute; top:16px; width:2px; }
.ruler-target b { color:var(--product-green); display:block; font-family:var(--apt-font-figures); font-size:.8125rem; font-variant-numeric:tabular-nums; font-weight:700; line-height:1; }
.ruler-scale { color:var(--product-muted); font-family:var(--apt-font-figures); font-size:.6875rem; font-variant-numeric:tabular-nums; height:16px; margin-top:6px; position:relative; }
.ruler-scale span { position:absolute; transform:translateX(-50%); }
.ruler-scale .ruler-end:first-child { transform:none; }
.ruler-scale .ruler-end:last-child { transform:translateX(-100%); }

button, input { font: inherit; }
.practice-shell { min-height: calc(100vh - var(--safe-top)); }
/* Fixed size on purpose (px, not rem): A−/A+ scales the question content, never the app's own chrome,
   so the header fits a 375pt phone at every text size: back · Part n · A− A＋ and, on Part 6/7, the
   閱讀設定 icon, each a 44pt target (TestFlight build 9: they overlapped, and at A＋ pushed the page
   wider than the screen). */
.part-training-topbar { align-items:center; background:var(--apt-surface); border-bottom:1px solid var(--apt-line); column-gap:6px; display:grid; font-size:15px; grid-template-columns:minmax(0,1fr) auto minmax(0,1fr); line-height:1.3; min-height:58px; padding:7px max(10px,env(safe-area-inset-right),calc((100% - 1120px) / 2)) 7px max(10px,env(safe-area-inset-left),calc((100% - 1120px) / 2)); position:sticky; top:var(--safe-top); z-index:20; }
.part-training-back { align-items:center; color:var(--apt-ink); display:inline-flex; font-weight:700; gap:5px; min-height:44px; min-width:0; overflow:hidden; text-decoration:none; white-space:nowrap; }
.part-training-back span { overflow:hidden; text-overflow:ellipsis; }
.part-training-back:first-letter { font-size:1.45em; }
.part-training-back-form { display:contents; }
button.part-training-back { appearance:none; background:none; border:0; cursor:pointer; font:inherit; font-weight:700; padding:0; text-align:left; }
.part-training-identity { align-items:center; display:flex; flex-direction:column; gap:1px; justify-self:center; min-width:0; white-space:nowrap; }
.part-training-identity strong { font-size:15px; }
.part-training-progress { color:var(--apt-muted); font-size:13px; }
.part-training-settings { background:transparent; border:0; color:var(--apt-ink); cursor:pointer; font-size:16px; font-weight:750; justify-self:end; min-height:44px; min-width:44px; }
.part-training-settings-icon { display:grid; height:44px; padding:0; place-items:center; width:44px; }
.part-training-settings:focus-visible { outline:3px solid var(--apt-sage); outline-offset:-3px; border-radius:10px; }
.part-training-controls { align-items:center; display:flex; gap:0; justify-self:end; min-width:0; }
.part-training-text-size { display:flex; gap:0; }
.part-training-text-size[hidden] { display:none; }
.media-zoom-trigger { position:relative; }
.zoom-hint { align-items:center; background:color-mix(in srgb, var(--apt-surface) 88%, transparent); border:1px solid var(--apt-line); border-radius:999px; bottom:8px; color:var(--apt-ink); display:inline-flex; font-size:.75rem; font-weight:700; gap:4px; line-height:1; min-height:28px; padding:4px 9px; pointer-events:none; position:absolute; right:8px; }
html[data-zoom-hint-seen="true"] .zoom-hint-text { display:none; }
html[data-zoom-hint-seen="true"] .zoom-hint { padding:4px 6px; }
.part-training-text-size button { background:transparent; border:0; color:var(--apt-ink); cursor:pointer; font-size:13px; font-weight:750; height:44px; padding:0; position:relative; width:42px; }
.part-training-text-size button::before { border:1px solid var(--apt-line); border-radius:8px; content:""; inset:6px 3px; pointer-events:none; position:absolute; }
.part-training-text-size button:disabled { color:var(--apt-muted); cursor:default; opacity:.45; }
.part-training-text-size button:focus-visible { outline:3px solid var(--apt-sage); outline-offset:-3px; border-radius:10px; }
.part-training-settings-panel { background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:12px; box-shadow:0 10px 28px rgba(24,38,55,.14); display:grid; font-size:15px; gap:12px; padding:14px; position:absolute; right:max(12px,calc((100% - 1120px) / 2)); top:54px; width:min(300px,calc(100% - 24px)); }
.part-training-settings-panel[hidden] { display:none; }
.text-scale-control { color:var(--apt-muted); display:grid; gap:8px; font-size:13px; font-weight:700; }
.text-scale-labels { align-items:center; color:var(--apt-ink); display:grid; gap:8px; grid-template-columns:auto minmax(100px,1fr) auto; }
.reading-mode-options { border:0; display:grid; gap:8px; margin:0; padding:0; }
.reading-mode-options legend { color:var(--apt-muted); font-size:13px; font-weight:700; margin-bottom:2px; padding:0; }
.reading-mode-options label { align-items:center; border:1px solid var(--apt-line); border-radius:8px; display:flex; gap:8px; min-height:44px; padding:8px 10px; }
.reading-mode-options input { accent-color:var(--apt-navy); }
.zoomable-media { margin:0; position:relative; }
.media-zoom-trigger { background:transparent; border:0; cursor:zoom-in; display:block; padding:0; width:100%; }
.media-zoom-trigger img { display:block; height:auto; max-width:100%; width:100%; }
/* The picture viewer fills the screen and zooms the picture itself (part-training-ui: pinch, double-tap,
   drag, wheel, ＋/−, keys; 1× to 4×), since page zoom is off in the app (TestFlight build 9). */
.media-zoom-dialog { background:rgba(15,20,26,.94); border:0; box-sizing:border-box; height:100dvh; margin:0; max-height:none; max-width:none; overflow:hidden; padding:0; width:100vw; }
.media-zoom-dialog[open] { display:block; }
.media-zoom-dialog::backdrop { background:rgba(15,20,26,.94); }
.media-zoom-toolbar { display:flex; gap:8px; position:absolute; right:max(12px,env(safe-area-inset-right)); top:calc(10px + env(safe-area-inset-top)); z-index:2; }
.media-zoom-toolbar button { align-items:center; background:rgba(255,255,255,.92); border:0; border-radius:50%; color:#182637; cursor:pointer; display:inline-flex; font-size:22px; font-weight:600; height:44px; justify-content:center; line-height:1; padding:0; width:44px; }
.media-zoom-toolbar button:disabled { opacity:.4; cursor:default; }
.media-zoom-toolbar button:focus-visible { outline:3px solid #9fc5b2; outline-offset:2px; }
.media-zoom-stage { align-items:center; box-sizing:border-box; display:flex; height:100%; justify-content:center; overflow:hidden; padding:calc(64px + env(safe-area-inset-top)) 8px calc(16px + env(safe-area-inset-bottom)); touch-action:none; -webkit-user-select:none; user-select:none; width:100%; }
.media-zoom-stage:focus { outline:none; }
.media-zoom-stage img { display:block; height:auto; max-height:100%; max-width:100%; object-fit:contain; transform-origin:0 0; -webkit-user-drag:none; width:auto; }
.media-zoom-dialog[data-zoomed="true"] .media-zoom-stage { cursor:grab; }
.practice-bar {
  background: color-mix(in srgb, var(--apt-surface) 94%, transparent);
  border-bottom: 1px solid var(--apt-line);
  padding: 18px max(20px, calc((100% - 760px) / 2));
  position: sticky;
  top: var(--safe-top);
  z-index: 10;
}
.practice-bar-main { align-items: baseline; display: flex; justify-content: space-between; gap: 16px; font-weight: 700; }
.brand { font-size: 0.9375rem; letter-spacing: .13em; }
.progress-track { background: var(--apt-line); border-radius: 999px; height: 4px; margin-top: 12px; overflow: hidden; }
.progress-track span { background: var(--apt-gold); border-radius: inherit; display: block; height: 100%; transition: width 180ms ease-out; }
.practice-content { margin: 0 auto; max-width: 760px; min-width: 0; overflow-wrap: anywhere; padding: 32px 20px calc(40px + env(safe-area-inset-bottom)); }
.flow-error, .listening-flow-error, .reading-flow-error { color: var(--apt-alert); font-weight: 650; line-height: 1.5; margin: 12px 0 0; }
.flow-error:empty, .listening-flow-error:empty, .reading-flow-error:empty { display: none; }
.practice-entry-shell { background: var(--apt-bg); color: var(--apt-ink); }
.practice-entry-header {
  align-items: center;
  display: flex;
  justify-content: space-between;
  margin: 0 auto;
  max-width: 760px;
  padding: 22px 20px 8px;
}
.practice-entry-utility {
  color: var(--apt-muted);
  font-size: 0.875rem;
  font-weight: 650;
}
.practice-entry-back {
  color: var(--apt-muted);
  font-size: 0.9375rem;
  font-weight: 650;
  text-decoration: none;
}
.practice-entry-content {
  margin: 0 auto;
  max-width: 760px;
  padding: 18px 20px calc(42px + env(safe-area-inset-bottom));
}
.practice-entry-content h1 {
  font-size: 2rem;
  letter-spacing: 0;
  line-height: 1.2;
  margin: 0 0 24px;
}
.product-shell .practice-entry-content { padding:0; }
/* 27px, the §5 分頁大標題 role shared with 設定's h1 (app-shell-css.ts .my-page-header h1) — both were
   drifting to their own ad-hoc size (2rem here, a vw-clamp there) and no longer matched each other
   (founder, 2026-09-23: "練習頁的字明顯比其他頁大"). */
.product-shell .practice-entry-content h1 { font-size:1.6875rem; font-weight:710; letter-spacing:-.025em; line-height:1.2; }
/* 題型專練 has its own ‹返回 back link, so it's a secondary page, not a tab landing — it takes the
   頁面標題 23px role (§5) instead of the 32px 分頁大標題 role above, which is for 練習/我的 themselves. */
.product-shell .practice-entry-content h1#part-training-title { font-size:1.4375rem; margin-bottom:16px; }
.product-shell .practice-entry-utility { font-size:.875rem; min-height:44px; align-items:center; display:inline-flex; text-decoration:none; }
.product-shell .practice-entry-copy strong, .product-shell .part-training-copy strong { font-size:1.0625rem; }
.product-shell .practice-entry-copy small, .product-shell .part-training-copy small { font-size:.8125rem; }
.product-shell .part-training-group h2, .product-shell .part-training-label { font-size:.875rem; }
.product-shell .practice-entry-heading { align-items:center; display:flex; justify-content:space-between; gap:12px; }
.product-shell .practice-entry-heading h1 { margin-bottom:0; }
/* Same 22px gap to the content as 策略/設定 (.tab-page-header). */
.product-shell .practice-entry-heading { margin-bottom:22px; }
.product-shell .practice-entry-list, .product-shell .part-training-list { gap:0; }
.product-shell .practice-entry-row, .product-shell .part-training-row { background:transparent; border:0; border-bottom:1px solid var(--product-line); border-radius:0; min-height:72px; padding:14px 2px; }
.product-shell .practice-entry-row:first-child, .product-shell .part-training-row:first-child { border-top:1px solid var(--product-line); }
.product-shell .practice-entry-icon { background:var(--product-surface); border-color:var(--product-line); border-radius:12px; }
.product-shell .practice-entry-icon::before, .product-shell .practice-entry-icon::after { background:var(--product-green); }
.product-shell .practice-entry-chevron { color:var(--product-green); }
.product-shell .practice-entry-row.is-unavailable .practice-entry-copy strong { color:var(--product-muted); }
.practice-entry-list,
.part-training-stack,
.part-training-list {
  display: grid;
  gap: 12px;
}
.practice-entry-row,
.part-training-row {
  align-items: center;
  background: var(--apt-surface);
  border: 1px solid var(--apt-line);
  border-radius: 16px;
  display: grid;
  gap: 14px;
  min-height: 72px;
  padding: 15px 16px;
}
.part-training-row.is-active { color: var(--apt-ink); text-decoration: none; }
.practice-entry-row {
  grid-template-columns: 42px minmax(0, 1fr) auto;
  text-decoration: none;
}
.practice-entry-row.is-active {
  color: var(--apt-ink);
}
.practice-entry-row.is-unavailable {
  color: var(--apt-muted);
}
.practice-entry-row.is-unavailable .practice-entry-copy strong {
  color: var(--apt-muted);
}
.practice-entry-icon {
  background: color-mix(in srgb, var(--apt-gold) 12%, var(--apt-surface));
  border: 1px solid color-mix(in srgb, var(--apt-gold) 42%, var(--apt-line));
  border-radius: 14px;
  height: 42px;
  position: relative;
  width: 42px;
}
.practice-entry-icon::before,
.practice-entry-icon::after {
  background: var(--apt-gold);
  border-radius: 999px;
  content: "";
  left: 11px;
  position: absolute;
  right: 11px;
}
.practice-entry-icon::before { height: 2px; top: 14px; }
.practice-entry-icon::after { height: 2px; top: 24px; }
.practice-entry-icon-train::before {
  box-shadow: 0 5px 0 var(--apt-gold), 0 10px 0 var(--apt-gold);
  top: 12px;
}
.practice-entry-icon-train::after { display: none; }
/* A long word (Comprehension at the largest text size) breaks inside the column instead of running under the chevron. */
.practice-entry-copy,
.part-training-copy {
  display: grid;
  gap: 4px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.practice-entry-copy strong,
.part-training-copy strong { text-wrap: balance; }
.practice-entry-copy strong,
.part-training-copy strong {
  color: var(--apt-ink);
  font-size: 1.0625rem;
  line-height: 1.25;
}
.practice-entry-copy small,
.part-training-copy small {
  color: var(--apt-muted);
  font-size: 0.8125rem;
  font-weight: 650;
  line-height: 1.4;
}
.practice-entry-chevron {
  color: var(--apt-gold);
  font-size: 1.75rem;
  line-height: 1;
}
.part-training-group {
  display: grid;
  gap: 10px;
}
.part-training-group + .part-training-group {
  margin-top: 12px;
}
.part-training-group h2 {
  align-items: center;
  color: var(--apt-muted);
  display: flex;
  font-size: 0.875rem;
  gap: 4px;
  letter-spacing: 0;
  margin: 0;
}
.part-training-group h2 svg { flex: none; height: 16px; width: 16px; }
.part-training-row {
  /* The label column is as wide as "Part 1" / "パート 1" needs, not a flat 68px: the text column beside it is already squeezed by the photo strip. */
  grid-template-columns: max-content minmax(0, 1fr) auto;
}
.part-training-label {
  color: var(--apt-sage);
  font-size: 0.875rem;
  font-weight: 700;
}
.practice-card, .completion-card, .result-card {
  background: var(--apt-surface);
  border: 1px solid var(--apt-line);
  border-radius: 20px;
  box-shadow: 0 12px 32px rgba(24, 38, 55, .07);
}
.practice-card { padding: 28px; }
.completion-card { padding: 40px 28px; text-align: center; }
/* End of a Part session: the card sits inside the page gutter, the review picker (Parts 1-2) is a full-width
   control on its own row and the way forward is the one primary button underneath it. */
.practice-shell > .completion-card, .mobile-practice > .completion-card { margin: 16px; }
.completion-card .completion-review { color: var(--apt-muted); display: grid; font-size: .9375rem; gap: 8px; margin-top: 20px; text-align: left; }
.completion-card .completion-review select { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 12px; color: var(--apt-ink); font: inherit; min-height: 48px; padding: 0 12px; width: 100%; }
.completion-card .completion-review select:disabled { opacity: .55; }
.completion-card a.primary-action, .p12-mobile .completion-card a.primary-action { margin-top: 20px; }
/* 本回合已完成: the round's questions (○ right, × wrong, – not recorded), each opening its review, then the ways on. */
.completion-card .round-score { font-size: 1.125rem; font-weight: 700; margin: 8px 0 0; }
.completion-card .round-wrong-note { color: var(--apt-alert); font-size: .9375rem; margin: 10px 0 0; }
.completion-card .round-list { display: grid; gap: 0; list-style: none; margin: 20px 0 0; padding: 0; text-align: left; border-top: 1px solid var(--apt-line); }
.completion-card .round-list li { border-bottom: 1px solid var(--apt-line); }
.completion-card .round-row { align-items: center; color: var(--apt-ink); display: grid; gap: 12px; grid-template-columns: 28px 1fr auto 12px; min-height: 48px; padding: 4px 4px; text-decoration: none; }
.completion-card .round-mark { font-size: 1.25rem; font-weight: 700; text-align: center; }
.completion-card .round-row.is-correct .round-mark { color: var(--apt-success); }
.completion-card .round-row.is-wrong .round-mark { color: var(--apt-alert); }
.completion-card .round-row.is-unknown .round-mark { color: var(--apt-muted); }
.completion-card .round-row-detail { color: var(--apt-muted); font-size: .875rem; }
.completion-card .round-actions { display: grid; gap: 10px; margin-top: 20px; }
.completion-card .round-actions a.primary-action { margin-top: 0; }
.completion-card a.secondary-action, .round-review-actions a.secondary-action { align-items: center; border: 1px solid var(--apt-line); border-radius: 14px; box-sizing: border-box; color: var(--apt-ink); display: flex; font-weight: 600; justify-content: center; min-height: 48px; padding: 0 16px; text-decoration: none; }
.round-review-actions { display: grid; gap: 10px; grid-template-columns: 1fr 1fr; margin: 24px 0 32px; }
.round-review-actions a.primary-action { margin-top: 0; min-height: 48px; }
.round-review-actions a:only-child { grid-column: 1 / -1; }
/* 考試介紹: a full-height page like the onboarding overview — the explanation at the top, 開始 held low in the thumb
   zone (margin-top:auto) with at least 40px of air above it, never pressed against the last line of text. */
.practice-shell:has(.mock-intro-page) { display: block; }
.practice-shell:has(.mock-intro-page) > .practice-content { box-sizing: border-box; padding-bottom: 0; width: 100%; }
.mock-intro-page { display: block; margin: 0 auto; max-width: 600px; width: 100%; }
.mock-intro-body h1 { color: var(--apt-ink); font-size: 1.4375rem; font-weight: 700; margin: 0; }
.mock-intro-facts { color: var(--apt-ink); font-size: 1.0625rem; font-weight: 650; line-height: 1.6; margin: 12px 0 0; }
.mock-intro-notes { border-top: 1px solid var(--apt-line); display: grid; gap: 10px; list-style: none; margin: 20px 0 0; padding: 16px 0 0; }
.mock-intro-notes li { color: var(--apt-muted); font-size: .9375rem; line-height: 1.75; padding-left: 14px; position: relative; }
.mock-intro-notes li::before { background: var(--apt-sage); border-radius: 50%; content: ""; height: 5px; left: 0; position: absolute; top: .78em; width: 5px; }
.mock-intro-foot { margin-top: 32px; padding: 0 0 calc(24px + env(safe-area-inset-bottom)); position: static; }
.mock-intro-foot .primary-action { box-sizing: border-box; display: flex; align-items: center; justify-content: center; margin-top: 0; min-height: 52px; width: 100%; }
.sr-only {
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}
.question-meta { color: var(--apt-muted); display: flex; font-size: 0.875rem; font-weight: 650; justify-content: space-between; gap: 16px; }
.practice-question h1 { font-size: clamp(21px, 3vw, 28px); letter-spacing: -.018em; line-height: 1.45; margin: 26px 0 24px; white-space: pre-wrap; }
.shared-material { background: var(--apt-tint); border: 1px solid var(--apt-line); border-radius: 16px; margin-top: 20px; max-height: 360px; overflow: auto; overscroll-behavior: none; padding: 18px; }
.shared-material p { line-height: 1.65; margin: 0; white-space: pre-wrap; }
.material-label, .eyebrow { color: var(--apt-muted); font-size: 0.8125rem; font-weight: 750; letter-spacing: .06em; margin-bottom: 9px !important; text-transform: uppercase; }
.visual-media { background: var(--apt-tint-strong); border: 1px solid var(--apt-line); border-radius: 16px; margin: 20px 0 0; overflow: hidden; }
.visual-media img { aspect-ratio: 4 / 3; display: block; height: auto; object-fit: contain; width: 100%; }
.listening-media { background: var(--apt-tint); border: 1px solid var(--apt-line); border-radius: 16px; margin-top: 20px; padding: 12px 14px; }
/* The circular play button (P3/P4, 2026-09-23) sits bare on the page like P1/P2's — no card behind it. */
.listening-media.listening-area { background: none; border: 0; padding: 0; }
.listening-media audio { display: none; }
.formal-audio { display: none; }
.answer-options { border: 0; display: grid; gap: 12px; margin: 0; padding: 0; }
.p1-formal-answer-options { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.p1-formal-answer-options .option-choice { display: flex; justify-content: center; padding-inline: 12px; }
.answer-options legend { color: var(--apt-muted); font-size: 0.875rem; font-weight: 650; margin-bottom: 12px; }
.option-choice { align-items: center; background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 16px; cursor: pointer; display: grid; gap: 13px; grid-template-columns: 52px minmax(0, 1fr); min-height: 58px; padding: 11px 16px; }
/* 程度測驗's options are buttons (one tap answers and advances) while the practice screens use labels
   around a radio. A button centres its text by default, so the same class drifted the option's words
   to the middle of their column there and nowhere else. */
button.option-choice { font: inherit; text-align: left; }
@media (hover:hover) { .option-choice:hover { border-color: color-mix(in srgb, var(--apt-gold) 60%, var(--apt-line)); } }
.option-choice:has(input:checked) { background: color-mix(in srgb, var(--apt-gold) 8%, var(--apt-surface)); border-color: var(--apt-gold); box-shadow: inset 0 0 0 1px var(--apt-gold); }
.option-marker { align-items: center; display: flex; gap: 12px; }
.option-marker input { accent-color: var(--apt-gold); flex: 0 0 auto; height: 18px; margin: 0; width: 18px; }
.option-letter { color: var(--apt-navy); font-weight: 750; }
/* No color here fell back to the browser's own button-text default, which read as near-invisible on
   iOS Safari against --apt-surface (only the A/B/C/D letter beside it had an explicit color, so it
   stayed legible) — every listening/reading option's own text was affected, not just diagnostic. */
.option-copy { color: var(--apt-ink); font-size: 1.125rem; line-height: 1.45; overflow-wrap: anywhere; }
/* Also used on links (錯題本 "back to the questions to redo"): a block of its own, so a label that wraps stays inside the pill. */
a.primary-action { align-items: center; box-sizing: border-box; display: flex; justify-content: center; text-align: center; text-decoration: none; }
.primary-action { background: var(--apt-fill); border: 0; border-radius: 16px; color: var(--apt-on-fill); cursor: pointer; font-weight: 750; margin-top: 20px; min-height: 56px; padding: 14px 20px; width: 100%; }
/* Hover only where a real pointer hovers: on touch, :hover sticks after a tap and darkens the button. */
@media (hover:hover) { .primary-action:hover { background: var(--apt-fill-hover); } }
.primary-action:disabled { cursor: default; opacity: .6; }
.formal-listening-master { background: transparent; border: 0; margin: 12px auto 0; max-width: 760px; padding: 0 24px; width: 100%; }
.formal-listening-master audio { display: none; }
.formal-listening-start { margin-top: 0; }
.formal-listening-status { color: var(--apt-muted); font-size: .875rem; margin: 8px 0 0; text-align: center; }
/* Mock Reading: 先跳過 is a quiet text link under the choices, never a second button competing with them. */
.mock-skip { display: flex; justify-content: center; margin-top: 14px; }
.mock-skip form { margin: 0; }
.mock-skip button { background: none; border: 0; color: var(--apt-muted); cursor: pointer; font: inherit; font-size: .9375rem; min-height: 44px; padding: 8px 12px; text-decoration: underline; text-underline-offset: 3px; }
@media (hover:hover) { .mock-skip button:hover { color: var(--apt-ink); } }
.mock-skip button:focus-visible { border-radius: 10px; outline: 3px solid var(--apt-sage); outline-offset: -3px; }
/* The topbar's two centre lines (Part · 未作答 N / 第 X / 100 題 · clock): app chrome, fixed in px like the rest of the topbar. */
.part-training-line { align-items: center; display: inline-flex; gap: 8px; justify-content: center; min-width: 0; }
.part-training-topbar .mock-clock { color: var(--apt-ink); font-size: 13px; font-variant-numeric: tabular-nums; font-weight: 700; }
.part-training-topbar .mock-clock.is-low { color: var(--apt-alert); }
.part-training-topbar .mock-skipped summary { border: 1px solid color-mix(in srgb, var(--apt-alert) 45%, transparent); border-radius: 999px; color: var(--apt-alert); cursor: pointer; font-size: 12px; font-weight: 700; line-height: 1.5; list-style: none; padding: 0 8px; }
.part-training-topbar .mock-skipped summary::-webkit-details-marker { display: none; }
.part-training-topbar .mock-skipped summary:focus-visible { outline: 3px solid var(--apt-sage); outline-offset: 2px; }
.part-training-topbar .mock-skipped-sheet { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 12px; box-shadow: 0 10px 28px rgba(24,38,55,.14); left: 50%; max-height: 50dvh; overflow-y: auto; padding: 14px; position: absolute; top: calc(100% + 6px); transform: translateX(-50%); white-space: normal; width: min(340px, calc(100% - 24px)); }
.part-training-topbar .mock-skipped-sheet p { color: var(--apt-muted); font-size: 13px; margin: 0 0 10px; text-align: left; }
.mock-skipped-list { display: flex; flex-wrap: wrap; gap: 8px; }
.mock-skipped-list a { align-items: center; border: 1px solid var(--apt-line); border-radius: 10px; color: var(--apt-navy); display: inline-flex; font-size: .9375rem; font-variant-numeric: tabular-nums; font-weight: 700; justify-content: center; min-height: 44px; min-width: 56px; text-decoration: none; }
.mock-skipped-list a[aria-current="page"] { background: var(--apt-tint); border-color: var(--apt-navy); }
@media (hover:hover) { .mock-skipped-list a:hover { border-color: var(--apt-navy); } }
.mock-hand-in { text-align: left; }
.mock-hand-in h1 { margin: 0 0 8px; }
.mock-hand-in > p { color: var(--apt-muted); line-height: 1.6; margin: 0 0 16px; }
.mock-hand-in form { margin: 0; }
.mock-hand-in-submit { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 16px; color: var(--apt-ink); cursor: pointer; font: inherit; font-weight: 700; margin-top: 12px; min-height: 56px; padding: 14px 20px; width: 100%; }
@media (hover:hover) { .mock-hand-in-submit:hover { border-color: var(--apt-navy); } }
.mock-hand-in-submit:focus-visible { outline: 3px solid var(--apt-sage); outline-offset: 3px; }
[data-formal-listening-answer] button[aria-pressed="true"] { border-color: var(--apt-gold); box-shadow: inset 0 0 0 2px var(--apt-gold); }
[data-formal-listening-answer] button:disabled { cursor: default; }
[data-formal-listening-answer] button:disabled:not([aria-pressed="true"]) { opacity: .45; }
.primary-action:focus-visible, .option-choice:focus-within { outline: 3px solid var(--apt-sage); outline-offset: 3px; }
.selected-answer { color: var(--apt-muted); font-weight: 650; margin: 16px 0 -4px; }
.result-card { margin-top: 24px; padding: 24px; }
/* A 5px top border on a 16px radius mitres down to 1px at the corners, which read as two red
   horns above the card. The verdict is carried by the whole outline and the tinted heading
   instead, the same way a marked option is. */
/* The verdict and the section label were louder than the question they are about (32/24 against
   an 18px stem); they step back to the 頁面標題/區段標題 sizes the other Parts use. */
.result-card > h1 { font-size:1.375rem; margin:0 0 12px; }
.result-card > h2 { font-size:1.0625rem; margin:0 0 8px; }
.result-card.is-correct { border-color:var(--apt-success); }
.result-card.is-incorrect { border-color:var(--apt-alert); }
.result-card.is-correct > h1 { color:var(--apt-success); }
.result-card.is-incorrect > h1 { color:var(--apt-alert); }
.result-status { font-size: 1.75rem; letter-spacing: -.02em; margin: 4px 0 8px; }
.correct-answer { font-weight: 700; }
.result-explanation { border-top: 1px solid var(--apt-line); margin-top: 20px; padding-top: 18px; }
.result-explanation h3 { font-size: 1.25rem; margin: 0 0 12px; }
.result-explanation p, .result-explanation dd { line-height: 1.65; }
.result-explanation dl { display: grid; gap: 10px; margin: 18px 0; }
.result-explanation dl div { display: grid; gap: 10px; grid-template-columns: 28px 1fr; }
.result-explanation dt { font-weight: 750; }
.result-explanation dd { margin: 0; }
.evidence-locator, .next-time-cue { background: var(--apt-tint); border-radius: 12px; color: var(--apt-muted); padding: 12px 14px; }
.p1-answer-actions { display: grid; gap: 12px; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: 20px; }
.p1-answer-actions form { margin: 0; }
.p1-answer-actions button { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 14px; color: var(--apt-navy); cursor: pointer; font-size: 1.125rem; font-weight: 750; min-height: 56px; width: 100%; }
@media (hover:hover) { .p1-answer-actions button:hover { border-color: var(--apt-gold); } }
.p1-answer-actions button:focus-visible, .feedback summary:focus-visible, .feedback button:focus-visible, .feedback select:focus-visible, .feedback textarea:focus-visible { outline: 3px solid var(--apt-sage); outline-offset: 3px; }
.p1-result-options { display: grid; gap: 10px; margin-top: 18px; }
.p1-option-result { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 14px; padding: 14px 16px; }
.p1-option-result.is-correct { border-color: var(--apt-success); box-shadow: inset 4px 0 0 var(--apt-success); }
.p1-option-result.is-incorrect { border-color: var(--apt-alert); box-shadow: inset 4px 0 0 var(--apt-alert); }
.p1-option-heading { align-items: center; display: flex; gap: 12px; }
.p1-option-status { font-weight: 750; }
.p1-option-result.is-correct .p1-option-status { color: var(--apt-success); }
.p1-option-result.is-incorrect .p1-option-status { color: var(--apt-alert); }
.p1-option-english, .p1-option-translation { line-height: 1.55; margin: 8px 0 0; }
.p1-option-translation { color: var(--apt-muted); }
.p1-explanation { border-top: 1px solid var(--apt-line); margin-top: 20px; padding-top: 16px; }
.p1-explanation summary, .p1-deeper-explanation summary { cursor: pointer; font-weight: 750; min-height: 28px; }
.p1-selected-diagnosis, .p1-correct-reason { line-height: 1.65; }
.p1-why-wrong { margin: 16px 0 0; }
.p1-why-wrong h3, .p1-key-vocabulary h4 { font-size: 1.125rem; margin: 0 0 8px; }
.p1-key-vocabulary { border-top: 1px solid var(--apt-line); margin-top: 16px; padding-top: 14px; }
.p1-key-vocabulary p { display: grid; gap: 4px; margin: 10px 0 0; }
.p1-key-vocabulary span { color: var(--apt-muted); }
.p2-answer-actions { display: grid; gap: 12px; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 20px; }
.p2-answer-actions form { margin: 0; }
.p2-answer-actions button { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 14px; color: var(--apt-ink); cursor: pointer; font-size: 1.25rem; font-weight: 750; min-height: 64px; width: 100%; }
@media (hover:hover) { .p2-answer-actions button:hover { border-color: var(--apt-gold); } }
.p2-pre-audio .formal-audio, .p2-post-audio .formal-audio { display: none; }
.p2-pre-audio .formal-play { margin-top: 0; }
.p2-speed-control { border: 0; margin: 14px 0 0; padding: 0; }
.p2-speed-control legend { color: var(--apt-muted); font-size: 0.875rem; font-weight: 700; margin-bottom: 8px; }
.p2-transcript { border-top: 1px solid var(--apt-line); margin-top: 20px; padding-top: 18px; }
.p2-transcript h2 { font-size: 1.25rem; margin: 0 0 12px; }
.p2-transcript ol { display: grid; gap: 10px; list-style: none; margin: 0; padding: 0; }
.p2-transcript-row { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 14px; padding: 13px 15px; }
.p2-transcript-row.is-correct { border-color: var(--apt-success); box-shadow: inset 4px 0 0 var(--apt-success); }
.p2-transcript-row.is-incorrect { border-color: var(--apt-alert); box-shadow: inset 4px 0 0 var(--apt-alert); }
.p2-option-heading { align-items: center; display: flex; gap: 10px; }
.p2-option-status { color: var(--apt-muted); font-size: 0.8125rem; font-weight: 700; }
.p2-transcript-row.is-correct .p2-option-status { color: var(--apt-success); }
.p2-transcript-row.is-incorrect .p2-option-status { color: var(--apt-alert); }
.p2-transcript-english, .p2-transcript-chinese { line-height: 1.55; margin: 8px 0 0; }
.p2-transcript-chinese { color: var(--apt-muted); }
.p2-question-transcript { border-top: 1px solid var(--apt-line); margin-top: 20px; padding-top: 18px; }
.p2-question-transcript h2 { font-size: 1.25rem; margin: 0 0 10px; }
.p2-question-english, .p2-question-chinese { line-height: 1.6; margin: 7px 0 0; }
.p2-question-chinese { color: var(--apt-muted); }
.p2-result-card { box-shadow: none; margin-top: 0; }
.practice-card:has(.p2-result-card) { padding: 16px; }
.p2-speed-control .speed-options button[aria-pressed="true"] { background: var(--apt-fill); border-color: var(--apt-fill); box-shadow: 0 0 0 2px color-mix(in srgb, var(--apt-fill) 22%, transparent); color: var(--apt-on-fill); }
.p2-explanation-selected, .p2-explanation-correct { border-radius: 12px; margin-top: 12px; padding: 12px 14px; }
.p2-explanation-selected { background: color-mix(in srgb, var(--apt-alert) 8%, var(--apt-surface)); border-left: 4px solid var(--apt-alert); }
.p2-explanation-correct { background: color-mix(in srgb, var(--apt-success) 8%, var(--apt-surface)); border-left: 4px solid var(--apt-success); }
.p2-explanation h3 { font-size: 1rem; margin: 0 0 6px; }
.p2-explanation p { margin: 0; }
.p2-explanation-more { margin-top: 14px; }
.p2-explanation-more summary { cursor: pointer; font-weight: 750; }
.p2-explanation-more ul { line-height: 1.65; padding-left: 22px; }
.p1-correct-answer { display: grid; gap: 8px; line-height: 1.55; margin: 16px 0 0; }
.p1-correct-answer strong { color: var(--apt-success); }
.p1-correct-answer span { display: block; }
.p1-deeper-explanation { background: var(--apt-tint); border-radius: 12px; margin-top: 14px; padding: 12px 14px; }
.speed-control, .feedback label { color: var(--apt-muted); display: grid; font-size: 0.875rem; font-weight: 650; gap: 6px; margin-top: 12px; }
.speed-control { border: 0; padding: 0; }
.speed-control legend { color: var(--apt-muted); font-size: 0.875rem; font-weight: 650; padding: 0; }
.speed-options { display: grid; gap: 8px; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 6px; }
.speed-options button { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 10px; color: var(--apt-ink); cursor: pointer; font-weight: 750; min-height: 44px; }
.speed-options button[aria-pressed="true"] { background: color-mix(in srgb, var(--apt-gold) 14%, var(--apt-surface)); border-color: var(--apt-gold); }
.review-audio-unavailable { color:var(--apt-muted); font-size:0.875rem; margin:14px 0 0; }
.review-audio { align-items:center; display:flex; flex-wrap:wrap; gap:10px; margin-top:14px; padding:0; }
.review-audio .speed-control { border:0; flex:1 1 auto; margin:0; min-width:0; order:0; padding:0; }
.review-audio .speed-options { display:grid; gap:0; grid-template-columns:repeat(3,minmax(0,1fr)); margin:0; width:100%; }
.review-audio .speed-options button { background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:0; color:var(--apt-muted); min-height:44px; min-width:0; width:100%; }
.review-audio .speed-options button:first-child { border-radius:7px 0 0 7px; }
.review-audio .speed-options button:last-child { border-radius:0 7px 7px 0; }
.review-audio .speed-options button[aria-pressed="true"] { background:var(--apt-sage-soft); border-color:var(--apt-navy); box-shadow:none; color:var(--apt-navy); }
.review-audio .replay-action { background:transparent; border:1px solid var(--apt-line); border-radius:8px; color:var(--apt-ink); flex:0 0 auto; font-size:0.8125rem; min-height:48px; min-width:94px; order:1; padding-inline:12px; white-space:nowrap; }
.review-audio .audio-error { flex-basis:100%; margin:0; }
.review-audio .audio-error:empty { display:none; }
.feedback select, .feedback textarea { background: var(--apt-surface); border: 1px solid var(--apt-line); border-radius: 10px; color: var(--apt-ink); min-height: 44px; padding: 8px 10px; }
.feedback { border-top: 1px solid var(--apt-line); margin-top: 20px; padding-top: 16px; }
.feedback summary { color: var(--apt-navy); cursor: pointer; font-weight: 750; min-height: 28px; list-style: none; }
.feedback summary::-webkit-details-marker { display: none; }
.feedback form { display: grid; gap: 12px; margin-top: 12px; }
.feedback textarea { min-height: 96px; resize: vertical; }
.feedback button { background: var(--apt-surface); border: 1px solid var(--apt-navy); border-radius: 12px; color: var(--apt-navy); cursor: pointer; font-weight: 750; min-height: 44px; }
.feedback-message { color: var(--apt-success); font-weight: 700; }
.transcript ol { line-height: 1.65; padding-left: 24px; }
/* Issue #46: bounded P3–P7 learner surfaces on the frozen product palette. */
/* data-p1/data-p2 here are 程度測驗 only: the Part 1 and Part 2 practice screens are .mobile-practice
   and carry their own palette. Without them the test's first four questions stayed on the old navy
   while everything they lead into is sage (founder, 2026-09-24). */
.practice-shell[data-p1],.practice-shell[data-p2],.practice-shell[data-p3],.practice-shell[data-p4],.practice-shell[data-p5],.practice-shell[data-p6],.practice-shell[data-p7] {
  --apt-bg:#FCFAF4; --apt-surface:#FFFEFA; --apt-ink:#20231F; --apt-muted:#636B64;
  --apt-line:#E5E1D8; --apt-navy:#536C57; --apt-success:#3F7B50; --apt-alert:#9B4038;
  --apt-fill:var(--apt-navy); --apt-fill-hover:#3f5643;
}
.practice-shell[data-p3] .practice-card,.practice-shell[data-p4] .practice-card,.practice-shell[data-p5] .practice-card {
  border-radius:12px; box-shadow:none;
}
.listening-group-progress { align-items:baseline; border-bottom:1px solid var(--apt-line); display:flex; justify-content:space-between; margin:18px 0; padding-bottom:10px; }
.listening-group-progress span { color:var(--apt-navy); font-weight:700; }
.listening-group-progress small { color:var(--apt-muted); font-size:0.8125rem; }
/* Five across at 1x; at a larger text size the cell grows with the text (3.2rem) so "Incorrect" and "answered" keep their whole words, and a group of five wraps to a second row. */
.group-question-chips { display:grid; gap:6px; grid-template-columns:repeat(auto-fill,minmax(max(52px,3.2rem),1fr)); margin:12px 0 18px; }
.group-question-chips form { margin:0; }
.question-chip { align-items:center; background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:8px; color:var(--apt-muted); display:flex; flex-direction:column; gap:2px; justify-content:center; min-height:48px; min-width:0; padding:6px 0; width:100%; }
.question-chip.is-current { border-color:var(--apt-navy); box-shadow:inset 0 0 0 1px var(--apt-navy); color:var(--apt-navy); }
/* Solid fill, not the faint tint it had (founder, 2026-09-23: "已作答的用深一點的色，一般聽完不會去改
   了") — an answered sibling reads as settled/locked in, not just "has some activity". Landed on a
   clean Apple-Notes-style yellow ("就是蘋果記事本那種黃") after navy blue and ink both turned out to
   collide with meanings already spoken for here: blue is the 藍色證書 band, green is .is-current/
   .is-correct, gold is the 金色證書 band and --apt-gold/--brand-gold — this yellow is deliberately its
   own family, not the app's muted brand-gold, and dark text (not white) for contrast on a light fill. */
.question-chip.is-answered { background:#F5A623; border-color:#DB9214; color:var(--apt-ink); }
.question-chip.is-correct { background:var(--apt-success-soft); border-color:var(--apt-success); color:var(--apt-success); }
.question-chip.is-wrong { background:var(--apt-alert-soft); border-color:var(--apt-alert); color:var(--apt-alert); }
/* "Not answered" is 73px of English in a 57px chip: it wraps under itself instead of spilling over the neighbours (it was clipped to "ot answered"). */
.question-chip small { font-size:0.6875rem; line-height:1.15; max-width:100%; overflow-wrap:anywhere; text-align:center; }
/* rem, not px, for every question and explanation heading below (founder, 2026-09-26: "字體調大小 題目不會動"): the A-/A+ pair and
   the 設定 preference both scale the root font-size, so only rem text follows them; the px stem stayed put while the choices grew. */
.listening-question h1,.listening-group-result h1 { font-size:clamp(1.25rem,2.4vw,1.625rem); line-height:1.4; margin:16px 0; }
.listening-choices,.reading-choices { display:grid; gap:8px; }
.listening-choices form,.reading-choices form { margin:0; }
.listening-choices button,.reading-choices button { align-items:start; background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:8px; color:var(--apt-ink); display:grid; gap:12px; grid-template-columns:minmax(1.6em,max-content) minmax(0,1fr); line-height:1.5; min-height:52px; padding:12px 14px; text-align:left; width:100%; }
@media (hover:hover) { .listening-choices button:hover,.reading-choices button:hover { border-color:var(--apt-navy); } }
.listening-choices button strong,.reading-choices button strong { color:var(--apt-navy); white-space:nowrap; }
/* 影子計時: how long the learner took, shown on review pages only. Empty when nothing was recorded, and then not shown at all. */
.shadow-time { color:var(--apt-muted); font-size:.875rem; margin:2px 0 10px; }
.shadow-time:empty { display:none; }
.listening-commit { border-top:1px solid var(--apt-line); margin-top:20px; }
.listening-commit p { color:var(--apt-muted); line-height:1.6; }
.listening-group-result .transcript { border-top:1px solid var(--apt-line); margin-top:16px; padding-top:12px; }
.listening-group-result .transcript > summary { color:var(--apt-navy); cursor:pointer; font-weight:650; min-height:44px; }
.listening-group-result .transcript p { line-height:1.7; white-space:pre-wrap; }
.transcript mark { background:#FBEEA9; border-radius:2px; color:inherit; padding:0 1px; }
/* The question number is CSS-generated so it never joins the text it labels: a badge after
   "9.0%" reads as "9.0%3", and copying the transcript would carry the digit along. */
.transcript mark::before { align-items:center; background:var(--apt-navy); border-radius:11px; box-sizing:border-box; color:#fff; content:attr(data-evidence); display:inline-flex; font-size:13px; font-weight:750; height:22px; justify-content:center; margin-right:4px; min-width:22px; padding:0 6px; vertical-align:middle; white-space:nowrap; word-spacing:2px; }
.listening-evidence-item { border-bottom:1px solid var(--apt-line); padding:10px 0; }
.listening-evidence-item h2 { font-size:1.0625rem; line-height:1.45; }
.listening-evidence-item ol,.transcript:has(>h2) ol { list-style:none; margin:0; padding:0; }
.listening-evidence-item li,.transcript li { border-left:3px solid transparent; line-height:1.55; margin:5px 0; padding:8px 10px; }
.listening-evidence-item li.is-correct,.transcript li.is-correct { background:var(--apt-success-soft); border-color:var(--apt-success); }
.listening-evidence-item li.is-incorrect,.transcript li.is-incorrect { background:var(--apt-alert-soft); border-color:var(--apt-alert); }
.listening-evidence-item li span,.transcript li span { color:var(--apt-muted); display:inline-block; font-size:.8125rem; margin-left:8px; }
/* Same ○ / × convention as Part 1's result card, so a mark means the same thing everywhere. */
.listening-evidence-item li .option-symbol,.reading-evidence-item li .option-symbol,.transcript li .option-symbol { font-size:1.05rem; line-height:1; margin:0 4px 0 0; }
.listening-evidence-item li.is-correct .option-symbol,.reading-evidence-item li.is-correct .option-symbol,.transcript li.is-correct .option-symbol { color:var(--apt-success); }
.listening-evidence-item li.is-incorrect .option-symbol,.reading-evidence-item li.is-incorrect .option-symbol,.transcript li.is-incorrect .option-symbol { color:var(--apt-alert); }
.listening-group-analysis,.p5-analysis,.reading-analysis { border-top:1px solid var(--apt-line); line-height:1.65; margin-top:16px; padding-top:12px; }
.listening-group-analysis summary,.p5-analysis summary,.reading-analysis summary { color:var(--apt-navy); cursor:pointer; font-weight:650; min-height:44px; }
/* The section heading keeps its jump links on the same line, right-aligned. */
.review-heading { align-items:baseline; display:flex; flex-wrap:wrap; gap:10px; justify-content:space-between; margin-top:8px; }
.review-heading h2 { color:var(--apt-navy); font-size:1.0625rem; font-weight:650; margin:0; }
.review-jump { display:flex; gap:14px; }
.review-jump a { align-items:center; color:var(--apt-navy); display:inline-flex; font-size:0.875rem; font-weight:650; min-height:36px; text-decoration:none; }
.review-jump a::after { content:"›"; margin-left:3px; }
.review-jump a:focus-visible { outline:3px solid var(--apt-sage); outline-offset:2px; }
/* Clear the sticky topbar so a jump lands on the heading rather than under it. */
#review-analysis,#review-transcript { scroll-margin-top:72px; }
/* Same fold-open treatment as 完整解析 (founder, 2026-09-23: "要...中文翻譯，一樣是用摺疊打開"). */
.transcript-zh { border-top:1px solid var(--apt-line); margin-top:12px; padding-top:10px; }
.transcript-zh summary { color:var(--apt-navy); cursor:pointer; font-weight:650; min-height:44px; }
.transcript-zh p { line-height:1.7; margin-top:8px; white-space:pre-wrap; }
/* The toggle sits above the question; its body is empty because each Chinese line is revealed
   in place, directly under the English line it translates. */
.evidence-zh summary { color:var(--apt-navy); cursor:pointer; font-weight:650; min-height:44px; }
.listening-evidence-item .stem-zh,.listening-evidence-item li .option-zh { display:none; }
.listening-evidence-item .evidence-zh[open] ~ .stem-zh,.listening-evidence-item .evidence-zh[open] ~ ol li .option-zh { color:var(--apt-muted); display:block; font-size:1rem; line-height:1.55; margin:4px 0 0; }
.review-transcript { border-top:1px solid var(--apt-line); margin-top:16px; padding-top:12px; }
.review-transcript > summary { color:var(--apt-navy); cursor:pointer; font-weight:650; min-height:44px; }
.review-transcript p { line-height:1.7; white-space:pre-wrap; }
.review-transcript-note { color:var(--apt-muted); font-size:.9375rem; line-height:1.55; margin:14px 0 0; }
.passage-answer { background:var(--apt-success-soft); border-radius:3px; color:inherit; font-weight:650; padding:0 3px; }
.mock-review-part-nav { display:flex; flex-wrap:wrap; gap:8px; margin:14px 0 4px; }
.mock-review-part-nav a { border:1px solid var(--apt-line); border-radius:999px; color:var(--apt-navy); font-size:.875rem; font-weight:650; min-height:36px; padding:7px 14px; text-decoration:none; }
.mock-review-part-nav a[aria-current="page"] { background:var(--apt-navy); border-color:var(--apt-navy); color:#fff; }
.mock-review-unit { border-top:1px solid var(--apt-line); margin-top:22px; padding-top:6px; }
.mock-review-unit-title { color:var(--apt-muted); font-size:.8125rem; font-weight:750; letter-spacing:.06em; margin:12px 0 0; text-transform:uppercase; }
.mock-review-unit .reading-evidence-item h2 { margin-bottom:6px; }
.mock-review-visual { margin:12px 0 0; }
.mock-review-visual img { border-radius:12px; display:block; height:auto; max-width:100%; }
.mock-review-part-summary { color:var(--apt-navy); font-weight:700; margin:12px 0 0; }
.mock-review-history-note { background:var(--apt-tint); border-radius:10px; color:var(--apt-muted); margin:12px 0 0; padding:10px 12px; }
.mock-review-question-result { font-size:.875rem; font-weight:750; margin:6px 0 0; }
.mock-review-question-result.is-correct { color:var(--apt-success); }
.mock-review-question-result.is-incorrect { color:var(--apt-danger); }
.mock-review-question-result.is-unanswered { color:var(--apt-muted); }
/* One marking everywhere (founder, 2026-10-04: 「答對統一用綠圈」): ○ right, × wrong, – unanswered, always beside the words. */
.question-chip.is-correct small::before, .mock-review-question-result.is-correct::before { content:"○ "; }
.question-chip.is-wrong small::before, .mock-review-question-result.is-incorrect::before { content:"× "; }
.mock-review-question-result.is-unanswered::before { content:"– "; }
.diagnostic-review-result.is-correct { color:var(--apt-success); }
.diagnostic-review-result.is-incorrect { color:var(--apt-alert); }
.listening-teaching-item { border-bottom:1px solid var(--apt-line); padding:8px 0; }
/* The same question appears in both sections, so it is set the same in both. */
.listening-teaching-item h3 { font-size:1.0625rem; line-height:1.45; }
.reading-layout { --dock-folded:calc(64px + 5.4rem); display:grid; gap:18px; min-width:0; }
.practice-content:has(.reading-layout) { --dock-folded:calc(64px + 5.4rem); }
.practice-content:has(.reading-layout) { max-width:1440px; padding-top:20px; }
.reading-material-panel,.reading-question-dock { background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:10px; min-width:0; padding:20px; }
.reading-material-panel .shared-passage,.reading-material-panel .shared-material { background:transparent; border:0; border-top:1px solid var(--apt-line); border-radius:0; max-height:none; margin-top:14px; overflow:visible; padding:18px 0 0; }
.reading-material-panel pre { font-family:inherit; font-size:1rem; line-height:1.7; overflow-wrap:anywhere; white-space:pre-wrap; word-break:normal; }
.reading-material-panel h2 { color:var(--apt-navy); font-size:0.9375rem; margin:0 0 10px; }
.question-dock-controls { align-items:center; border-bottom:1px solid var(--apt-line); border-radius:10px; cursor:grab; display:flex; justify-content:space-between; margin-bottom:12px; min-height:48px; padding-bottom:8px; touch-action:none; }
.question-dock-controls:active { cursor:grabbing; }
.question-dock-controls [data-dock-icon] { color:var(--apt-navy); font-size:1.375rem; font-weight:700; line-height:1; }
.question-dock-controls:focus-visible { outline: 3px solid var(--apt-sage); outline-offset: 3px; }
@media (hover:hover) { .question-dock-controls:hover { background:color-mix(in srgb, var(--apt-navy) 8%, transparent); } }
.draft-status { background:var(--apt-tint); border-radius:8px; color:var(--apt-muted); font-size:0.875rem; padding:8px 10px; }
.reading-choices button.is-selected { background:var(--apt-tint); border-color:var(--apt-navy); box-shadow:inset 0 0 0 1px var(--apt-navy); }
.group-finalize { display:grid; gap:6px; margin-top:16px; }
.group-finalize small { color:var(--apt-muted); text-align:center; }
.group-finalize button:disabled { cursor:not-allowed; opacity:.45; }
.reading-group-progress { align-items:baseline; display:flex; justify-content:space-between; }
.reading-group-progress span { color:var(--apt-navy); font-weight:700; }
.reading-group-progress small { color:var(--apt-muted); }
/* The blank is the question, so it is tappable. It sits inline in running text, so the hit area
   is grown with padding rather than a box that would break the line rhythm. */
.passage-blank { background:var(--apt-tint); border:1px solid var(--apt-line); border-radius:5px; color:var(--apt-ink); cursor:pointer; font:inherit; max-width:100%; min-height:44px; min-width:44px; padding:4px 8px; }
.passage-blank .blank-choice { color:var(--apt-muted); }
.passage-blank.is-filled .blank-choice { color:color-mix(in srgb,var(--apt-ink) 62%,transparent); font-style:italic; }
/* The blank carries the same number as its question chip and stem, like the numbered blanks of the real Part 6. */
.passage-blank .blank-number { background:var(--apt-navy); border-radius:999px; color:#fff; display:inline-block; font-size:.75em; font-weight:700; line-height:1; margin-right:6px; min-width:1.5em; padding:.2em .45em; text-align:center; vertical-align:.1em; }
@media (hover:hover) { .passage-blank:hover { border-color:var(--apt-navy); } }
.passage-blank:focus-visible { outline:3px solid var(--apt-sage); outline-offset:2px; }
.passage-blank.is-active { border-color:var(--apt-navy); box-shadow:0 0 0 2px color-mix(in srgb,var(--apt-navy) 18%,transparent); }
.passage-blank.is-filled { background:var(--apt-sage-soft); }
/* A chip brings the passage to its blank and marks it for a moment: where the question is, not a hint. */
@keyframes passage-blank-flash { 0%,60% { box-shadow:0 0 0 4px color-mix(in srgb,var(--apt-sage) 55%,transparent); } 100% { box-shadow:0 0 0 2px color-mix(in srgb,var(--apt-navy) 18%,transparent); } }
.passage-blank.is-flash { animation:passage-blank-flash 1.2s ease-out 1; }
@media (prefers-reduced-motion:reduce) { .passage-blank.is-flash { animation:none; } }
.document-anchors { display:flex; flex-wrap:wrap; gap:8px; margin:10px 0; }
.document-anchors a { border:1px solid var(--apt-line); border-radius:999px; color:var(--apt-navy); font-size:0.8125rem; padding:6px 10px; text-decoration:none; }
.document-tabs { min-width:0; }
/* 文件 1／2／3 (founder, 2026-10-04): fixed above the material, one document at a time, a sideways-scrolling row at the
   largest text size. Sticky under the Part header on a phone, at the top of the material panel side by side. */
.document-tablist { background:var(--apt-surface); display:flex; flex-wrap:nowrap; gap:6px; margin:10px 0 0; overflow-x:auto; overscroll-behavior-x:contain; padding:6px 0; position:sticky; scrollbar-width:none; top:calc(var(--safe-top, 0px) + 52px); z-index:3; }
.reading-material-panel .document-tablist { top:0; }
@media (max-width:767px) and (orientation:portrait) { .reading-material-panel .document-tablist { top:calc(var(--safe-top, 0px) + 52px); } }
.document-tablist::-webkit-scrollbar { display:none; }
.document-tab { align-items:flex-start; background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:10px; color:var(--apt-navy); cursor:pointer; display:flex; flex:0 0 auto; flex-direction:column; font:inherit; font-size:.875rem; font-weight:700; gap:1px; justify-content:center; min-height:44px; padding:5px 12px; text-align:start; }
.document-tab small { color:var(--apt-muted); font-size:.75rem; font-weight:500; max-width:11em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.document-tab[aria-selected="true"] { background:var(--apt-navy); border-color:var(--apt-navy); color:#fff; }
.document-tab[aria-selected="true"] small { color:rgba(255,255,255,.85); }
.document-tab:focus-visible { outline:3px solid var(--apt-sage); outline-offset:2px; }
.document-tabs [data-doc-panel][hidden] { display:none; }
.doc-para.is-evidence-target { background:var(--apt-sage-soft); border-radius:4px; box-shadow:0 0 0 3px var(--apt-sage-soft); }
.evidence-jump { background:none; border:0; color:var(--apt-navy); cursor:pointer; font:inherit; font-size:.875rem; font-weight:650; min-height:44px; padding:0; text-align:start; text-decoration:underline; text-underline-offset:3px; }
.reading-evidence-item { border-bottom:1px solid var(--apt-line); padding:10px 0; }
.reading-evidence-item h2,.reading-teaching-item h3 { font-size:1rem; line-height:1.45; }
.reading-evidence-item ol { list-style:none; margin:0; padding:0; }
.reading-evidence-item li { border-left:3px solid transparent; line-height:1.5; margin:5px 0; padding:8px 10px; }
.reading-evidence-item li.is-correct { background:var(--apt-success-soft); border-color:var(--apt-success); }
.reading-evidence-item li.is-incorrect { background:var(--apt-alert-soft); border-color:var(--apt-alert); }
.reading-evidence-item li span { color:var(--apt-muted); display:inline-block; font-size:.8125rem; margin-left:8px; }
.reading-teaching-item { border-bottom:1px solid var(--apt-line); padding:8px 0; }
.reading-question-panel { min-width:0; }
.reading-question .stem { font-size:1.125rem; line-height:1.55; margin:8px 0 18px; white-space:pre-wrap; }
.reading-question-panel .result-card { border:0; border-radius:0; box-shadow:none; margin:0; padding:0; }
.reading-question-panel .result-card.is-correct,.reading-question-panel .result-card.is-incorrect { border-top:1px solid var(--apt-line); }
.reading-question-panel .result-card h1 { color:var(--apt-navy); font-size:1.375rem; }
.reading-question-panel .result-card.is-incorrect h1 { color:var(--apt-alert); }
.reading-flow-error:empty { display:none; }
/* 768pt and up (iPad portrait included): material and questions side by side, each scrolling on its own, no drawer. */
@media (min-width:768px) {
  .reading-layout { grid-template-columns:minmax(0,1.15fr) minmax(320px,.85fr); align-items:start; }
  .reading-material-panel { max-height:calc(100dvh - 108px); overflow-y:auto; overscroll-behavior:none; }
  .reading-question-dock { max-height:calc(100dvh - 108px); overflow-y:auto; overscroll-behavior:none; }
  .reading-question-dock[data-dock-state="collapsed"] .reading-question-dock-body { display:none; }
  .reading-question-dock[data-dock-state="expanded"] { min-width:320px; }
}
@media (max-width:767px) and (orientation:portrait) {
  /* Room under the material for the folded sheet (its bar, the number row and a line of the question). */
  .practice-content:has(.reading-layout) { padding:10px 12px calc(var(--dock-folded) + 28px + env(safe-area-inset-bottom)); }
  /* An open sheet covers its own height of the page: leave that much room under the material so the end of the
     passage (last blanks, its translation toggle) can be scrolled clear of the sheet without closing it. */
  .practice-content:has(.reading-question-dock[data-dock-state="expanded"]) { padding-bottom:calc(min(var(--dock-height,55dvh), 100dvh - var(--safe-top) - 74px - env(safe-area-inset-bottom)) + 28px + env(safe-area-inset-bottom)); }
  .reading-material-panel { padding:16px; }
  /* The sheet stays where the learner leaves it (reading-flow: drag, arrow keys), between a third and
     most of the screen; --dock-height carries that choice across questions and sets (TestFlight build 9). */
  .reading-question-dock { bottom:calc(8px + env(safe-area-inset-bottom)); box-shadow:0 8px 28px rgba(32,35,31,.12); display:flex; flex-direction:column; left:50%; max-width:560px; overflow:hidden; padding:8px 16px; position:fixed; transform:translateX(-50%); transition:max-height .18s ease; width:calc(100% - 24px); z-index:12; }
  /* Three stops (reading-flow.ts): folded still shows the question-number row and the first line of the question; half (the
     default, 45%) and full (85%) via --dock-height. Folded is measured in rem so it grows with 介面文字大小. */
  .reading-question-dock[data-dock-state="collapsed"] { max-height:var(--dock-folded); }
  .reading-question-dock[data-dock-state="expanded"] { height:var(--dock-height,45dvh); max-height:calc(100dvh - var(--safe-top) - 74px - env(safe-area-inset-bottom)); }
  .reading-question-dock[data-dock-state="collapsed"] .reading-question-dock-body { display:block; overflow:hidden; pointer-events:auto; }
  .reading-question-dock[data-dock-state="collapsed"] .reading-question .stem { display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:1; margin:4px 0 0; overflow:hidden; }
  .reading-question-dock .question-dock-controls { margin-bottom:6px; min-height:44px; padding-bottom:4px; }
  /* One row of numbers, scrolling sideways when it does not fit (largest text size); it also drags the sheet. */
  .reading-question-dock .group-question-chips { grid-auto-columns:minmax(max(52px,3.2rem),1fr); grid-auto-flow:column; grid-template-columns:none; margin:2px 0 8px; overflow-x:auto; overscroll-behavior:none; touch-action:pan-x; }
  .reading-question-dock-body { flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:none; }
  .question-dock-controls { flex:0 0 auto; touch-action:none; }
  .reading-question-panel { padding-bottom:12px; }
}
@media (max-width:767px) and (orientation:landscape) {
  .reading-layout { grid-template-columns:minmax(0,1fr) minmax(300px,.52fr); align-items:start; }
  .reading-material-panel { max-height:calc(100dvh - 108px); overflow-y:auto; overscroll-behavior:none; }
  .reading-question-dock { max-height:calc(100dvh - 108px); overflow-y:auto; overscroll-behavior:none; }
  .reading-question-dock[data-dock-state="collapsed"] .reading-question-dock-body { display:none; }
}
/* The grab handle sits centred above the row, where a draggable sheet always puts it, so the
   bar reads as something you pull rather than only tap. */
.question-dock-controls { align-items:center; column-gap:8px; display:grid; grid-template-columns:1fr auto 48px; grid-template-rows:auto auto; min-height:56px; padding-top:6px; }
[data-dock-icon] { display:inline-grid; place-items:center; transition:transform .18s ease; }
.reading-question-dock[data-dock-state="expanded"] [data-dock-icon] { transform:rotate(180deg); }
.question-dock-handle { background:var(--apt-line); border-radius:999px; grid-column:1 / -1; height:5px; justify-self:center; margin-bottom:4px; width:44px; }
.question-dock-controls:active .question-dock-handle { background:var(--apt-muted); }
@media (hover:hover) { .question-dock-controls:hover .question-dock-handle { background:var(--apt-muted); } }
/* 連續模式: the material on top and the questions under it, always in full — the drawer's folded/half/full stops and its
   handle belong to the drawer only (founder, 2026-10-04: 「連續模式打開時預設問題也是打開的才對」). These outrank the drawer
   rules (0,3,0 / 0,4,0) whatever data-dock-state the sheet carries. Document tabs stay. */
html[data-reading-mode="continuous"] .reading-question-dock[data-dock-state] { height:auto; max-height:none; overflow:visible; }
html[data-reading-mode="continuous"] .reading-question-dock[data-dock-state] .reading-question-dock-body { display:block; max-height:none; overflow:visible; }
html[data-reading-mode="continuous"] .reading-question-dock[data-dock-state] .question-dock-controls { display:none; }
html[data-reading-mode="continuous"] .reading-question-dock[data-dock-state] .reading-question .stem { -webkit-line-clamp:unset; display:block; margin:8px 0 18px; overflow:visible; }
/* 連續模式: material → number row → the question's stem → options. The number row sits right above the stem and sticks
   under the Part header while the questions scroll, so a long option list or review can always switch question. */
html[data-reading-mode="continuous"] .reading-question-dock[data-dock-state] .group-question-chips { background:var(--apt-surface); margin:0 0 8px; padding:6px 0; position:sticky; top:calc(var(--safe-top, 0px) + 52px); z-index:2; }
html[data-reading-mode="continuous"] .practice-content:has(.reading-layout) { padding-bottom:calc(24px + env(safe-area-inset-bottom)); }
/* 連續模式 stacks the page on a phone; from 768pt the material and the questions stay side by side. */
@media (max-width:767px) {
html[data-reading-mode="continuous"] .reading-layout { display:block; }
html[data-reading-mode="continuous"] .reading-question-dock { box-shadow:none; margin-top:18px; max-height:none; overflow:visible; position:static; transform:none; width:auto; }
html[data-reading-mode="continuous"] .reading-question-dock-body { display:block; max-height:none; overflow:visible; }
}
@media (max-width: 560px) {
  .practice-bar { padding: 15px 16px; }
  .practice-content { padding: 18px 12px calc(24px + env(safe-area-inset-bottom)); }
  .practice-entry-header { padding: 18px 16px 6px; }
  .practice-entry-content { padding: 16px 14px calc(28px + env(safe-area-inset-bottom)); }
  .practice-entry-content h1 { font-size: 1.875rem; margin-bottom: 20px; }
  .practice-entry-row { border-radius: 15px; min-height: 70px; padding: 14px; }
  .part-training-row { border-radius: 15px; min-height: 66px; padding: 13px 14px; }
  .practice-card { border-radius: 16px; box-shadow: none; padding: 20px 16px; }
  .question-meta { font-size: 0.8125rem; }
  .practice-question h1 { margin-top: 22px; }
  .option-choice { border-radius: 14px; min-height: 56px; padding-inline: 13px; }
  .option-copy { font-size: 1.0625rem; }
  .p1-formal-answer-options { gap: 8px; }
  .p1-formal-answer-options .option-choice { min-height: 56px; padding-inline: 8px; }
  .result-card { border-radius: 16px; padding: 20px 16px; }
  .visual-media img { aspect-ratio: 4 / 3; object-fit: contain; }
  .p1-answer-actions { gap: 8px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; }
}
/* Row icons: Phosphor Icons (MIT), drawn as masks so they follow the theme colour. */
.product-shell .practice-entry-row .practice-entry-icon::after { display:none; }
.product-shell .practice-entry-row .practice-entry-icon::before { background:var(--product-green); border-radius:0; box-shadow:none; height:24px; inset:9px; left:9px; right:auto; top:9px; width:24px; -webkit-mask:var(--entry-icon) center / contain no-repeat; mask:var(--entry-icon) center / contain no-repeat; }
/* Each entry keeps its OWN colour whether or not it has anything to open right now, so the four rows always
   read as four categories (founder, 2026-09-26, chose option C: "四個一直都是各自的全色"). It used to turn
   flat grey when unavailable, which left 錯題本 and 繼續上次做題 grey on any account with nothing pending. The
   row's title text still turns muted, which is what says "nothing to open". */
.product-shell .practice-entry-row .practice-entry-icon-train::before { background:var(--product-blue); }
.product-shell .practice-entry-row .practice-entry-icon-review::before { background:var(--product-terracotta); }
.product-shell .practice-entry-row .practice-entry-icon-resume::before { background:var(--product-gold); }
.practice-entry-icon-vocab { --entry-icon:url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20256%20256%22%3E%3Cpath%20d=%22M232,48H160a40,40,0,0,0-32,16A40,40,0,0,0,96,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,24,8,8,0,0,0,16,0,24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM96,192H32V64H96a24,24,0,0,1,24,24V200A39.81,39.81,0,0,0,96,192Zm128,0H160a39.81,39.81,0,0,0-24,8V88a24,24,0,0,1,24-24h64ZM160,88h40a8,8,0,0,1,0,16H160a8,8,0,0,1,0-16Zm48,40a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,128Zm0,32a8,8,0,0,1-8,8H160a8,8,0,0,1,0-16h40A8,8,0,0,1,208,160Z%22/%3E%3C/svg%3E"); }
.practice-entry-icon-resume { --entry-icon:url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20256%20256%22%3E%3Cpath%20d=%22M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm48.24-94.78-64-40A8,8,0,0,0,100,88v80a8,8,0,0,0,12.24,6.78l64-40a8,8,0,0,0,0-13.56ZM116,153.57V102.43L156.91,128Z%22/%3E%3C/svg%3E"); }
.practice-entry-icon-review { --entry-icon:url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20256%20256%22%3E%3Cpath%20d=%22M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z%22/%3E%3C/svg%3E"); }
.practice-entry-icon-train { --entry-icon:url("data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20256%20256%22%3E%3Cpath%20d=%22M224,128a8,8,0,0,1-8,8H128a8,8,0,0,1,0-16h88A8,8,0,0,1,224,128ZM128,72h88a8,8,0,0,0,0-16H128a8,8,0,0,0,0,16Zm88,112H128a8,8,0,0,0,0,16h88a8,8,0,0,0,0-16ZM82.34,42.34,56,68.69,45.66,58.34A8,8,0,0,0,34.34,69.66l16,16a8,8,0,0,0,11.32,0l32-32A8,8,0,0,0,82.34,42.34Zm0,64L56,132.69,45.66,122.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Zm0,64L56,196.69,45.66,186.34a8,8,0,0,0-11.32,11.32l16,16a8,8,0,0,0,11.32,0l32-32a8,8,0,0,0-11.32-11.32Z%22/%3E%3C/svg%3E"); }
/* Part rows: contextual photo strip on the right, fading into the row (images cached via /img/v2/). */
.product-shell .part-training-row { overflow:hidden; position:relative; padding-right:clamp(84px,24%,180px); }
.product-shell .part-thumb { background-position:center; background-size:cover; bottom:0; opacity:.9; pointer-events:none; position:absolute; right:0; top:0; width:clamp(84px,24%,170px); -webkit-mask-image:linear-gradient(to right,transparent,#000 55%); mask-image:linear-gradient(to right,transparent,#000 55%); }
.product-shell .part-training-row .practice-entry-chevron { align-items:center; background:var(--product-surface); border-radius:50%; display:inline-flex; height:28px; justify-content:center; position:relative; width:28px; z-index:1; }
.product-shell .part-thumb-1 { background-image:url("/img/v2/part-1-v2.jpg"); }
.product-shell .part-thumb-2 { background-image:url("/img/v2/part-2.jpg"); }
.product-shell .part-thumb-3 { background-image:url("/img/v2/part-3.jpg"); }
.product-shell .part-thumb-4 { background-image:url("/img/v2/part-4.jpg"); }
.product-shell .part-thumb-5 { background-image:url("/img/v2/part-5.jpg"); }
.product-shell .part-thumb-6 { background-image:url("/img/v2/part-6.jpg"); }
.product-shell .part-thumb-7 { background-image:url("/img/v2/part-7.jpg"); }
/* 練習 landing: a very light photo behind the heading, fading into the canvas. */
.product-shell[data-page-kind="primary"][data-active-tab="practice"] { position:relative; }
/* .55 (2026-09-23-a) competed with the title; .18 (2026-09-23-b) then read as no texture at all against
   the lightened canvas (founder, 2026-09-25: page feels flat). Swapped the source photo itself too
   (abstract light-and-shadow -> coffee and an open book, see brand-image-data.ts) and settled on .32:
   the scene reads as ambient warmth behind the heading without a wash under the text.
   practice-bg-v2.jpg, not practice-bg.jpg (2026-09-25): the photo swap above first shipped under the
   OLD filename, so any device that had ever loaded /img/v2/practice-bg.jpg before that swap kept the
   stale bytes forever — this path is served cache-control: immutable, max-age=31536000 (DESIGN.md), so
   no reload or app restart can ever refresh it. Same fix as part-1 -> part-1-v2 (whose CSS reference was only corrected on 2026-09-26: it kept pointing at the old part-1.jpg, which is 404 for anyone new and still showed the old cached photo on devices that had it). */
.product-shell[data-page-kind="primary"][data-active-tab="practice"]::before { background:url("/img/v2/practice-bg-v2.jpg") center / cover no-repeat; content:""; height:260px; left:0; opacity:.32; pointer-events:none; position:absolute; right:0; top:0; -webkit-mask-image:linear-gradient(#000 35%,transparent); mask-image:linear-gradient(#000 35%,transparent); }
.product-shell[data-page-kind="primary"][data-active-tab="practice"] .product-frame { position:relative; z-index:1; }
/* 策略 landing: the same light band as 練習, with a compass on a map (founder, 2026-09-26: the strategy page wanted a background
   with a strategy feel; compass and map = knowing where you are and where you go). The compass (face in view, not a big dark lid: that read as a pocket watch, and a heavy blur that hid the map was too much; founder 2026-09-26) sits right of centre so the title on the
   left is clear at phone width, where cover crops the sides; background-position keeps the compass in view when the band is wide. A new
   name from day one (strategy-bg.jpg): /img/v2/* is immutable-cached. Hidden in dark mode, like Home's photo. */
.product-shell[data-page-kind="primary"][data-active-tab="strategy"] { position:relative; }
.product-shell[data-page-kind="primary"][data-active-tab="strategy"]::before { background:url("/img/v2/strategy-bg.jpg") 45% 40% / cover no-repeat; content:""; height:260px; left:0; opacity:.5; pointer-events:none; position:absolute; right:0; top:0; -webkit-mask-image:linear-gradient(#000 35%,transparent); mask-image:linear-gradient(#000 35%,transparent); }
.product-shell[data-page-kind="primary"][data-active-tab="strategy"] .product-frame { position:relative; z-index:1; }
html[data-theme="dark"] .product-shell[data-page-kind="primary"][data-active-tab="strategy"]::before { display:none; }

.p12-mobile {
 --app-bg:#FCFAF4; --surface:#FFFEFA; --ink:#20231F; --text-muted:#636B64;
 --sage:#536C57; --sage-soft:#EDF2EC; --brand-gold:#B99550; --border:#E5E1D8;
 --success:#3F7B50; --success-soft:#EAF3EA; --error:#9B4038; --error-soft:#F7EAE8;
 --apt-bg:var(--app-bg); --apt-surface:var(--surface); --apt-ink:var(--ink);
 --apt-muted:var(--text-muted); --apt-line:var(--border); --apt-navy:var(--sage);
 --apt-gold:var(--brand-gold); --apt-success:var(--success); --apt-alert:var(--error);
 --apt-tint:#F1EFE8; --apt-tint-strong:#ECE9E1; --apt-success-soft:var(--success-soft); --apt-alert-soft:var(--error-soft); --apt-sage-soft:var(--sage-soft);
 --apt-fill:var(--sage); --apt-on-fill:var(--surface); --apt-fill-hover:var(--sage);
 background:var(--app-bg); color:var(--ink); font-family:var(--apt-font-sans);
 font-size:1rem; line-height:1.6;
}
.p12-mobile button,.p12-mobile a,.p12-mobile summary { -webkit-tap-highlight-color:transparent; touch-action:manipulation; }
.p12-mobile button { cursor:pointer; }
.p12-mobile button:disabled { cursor:default; }
.p12-mobile :focus-visible { outline:3px solid var(--sage); outline-offset:3px; }
.p12-mobile [hidden] { display:none !important; }
.mobile-practice { margin:0 auto; max-width:560px; min-height:calc(100dvh - var(--safe-top,0px)); min-width:0; overflow-wrap:anywhere; }
.question-context { display:flex; flex-direction:column; min-height:inherit; }
.mobile-header { display:flex; align-items:center; gap:10px; padding:8px 20px 8px 12px; border-bottom:1px solid var(--border); }
html { overscroll-behavior:none; }
/* 練習題圖片黑白 (founder, 2026-09-27): only the pictures and charts inside practice and mock questions (.visual-media, including the zoomed view),
   never the app's own photos. Set by html[data-image-mode="mono"], from the preference or this device's own choice. */
html[data-image-mode="mono"] .visual-media img, html[data-image-mode="mono"] .zoomable-media img { filter:grayscale(1); }
.mobile-header a { display:grid; place-items:center; min-width:44px; min-height:44px; color:var(--ink); font-size:28px; text-decoration:none; }
.mobile-header h1 { font-size:1.125rem; margin:0; font-weight:650; line-height:1.3; }
.mobile-header h1 span { display:block; font-size:.8125rem; font-weight:400; color:var(--text-muted); margin-top:3px; }
.mobile-brand { margin-left:auto; font-size:10px; font-weight:600; letter-spacing:.16em; }
/* Centered as one block (founder, 2026-09-23). A fixed gap keeps the play button close to the
   options instead of stretched toward the bottom edge. */
.mobile-question { flex:1; display:flex; flex-direction:column; justify-content:center; padding:12px 20px calc(20px + env(safe-area-inset-bottom)); }
.mobile-question .listening-area { margin-top:64px; }
.p1-photo { margin:0; background:var(--surface); }
.p1-photo img { display:block; width:100%; aspect-ratio:4/3; object-fit:contain; }
.p1-progress { display:flex; justify-content:space-between; margin:8px 20px 0; }
.group-tree { margin:12px 16px 0; }
.group-progress { display:flex; list-style:none; padding:0; margin:0; }
.group-progress li { flex:1; position:relative; min-width:0; }
.group-progress li:not(:last-child)::after { content:''; position:absolute; top:22px; left:65%; width:70%; height:1px; background:var(--border); }
.group-number { position:relative; z-index:1; display:grid; place-items:center; margin:10px auto; width:24px; height:24px; border-radius:50%; background:var(--app-bg); color:var(--text-muted); font-size:12px; }
.group-progress .is-active span { background:var(--sage); color:var(--surface); }
.group-progress .is-complete span { background:var(--sage-soft); color:var(--sage); border:1px solid var(--sage); }
.group-location { display:flex; align-items:center; justify-content:space-between; gap:8px; margin:8px 4px 0; padding:8px 0 12px; border-bottom:1px solid var(--border); }
.group-picker { position:relative; min-height:44px; min-width:82px; font-size:0.875rem; font-weight:650; }
.group-picker select { position:absolute; inset:0; opacity:0; width:100%; cursor:pointer; }
.group-picker:has(select:enabled) label::after { content:"⌄"; position:absolute; right:0; top:0; color:var(--sage); }
.group-picker:focus-within { outline:2px solid var(--sage); outline-offset:2px; }
.group-location small { font-size:.8125rem; color:var(--text-muted); }
.group-questions { display:flex; }
.question-node { background:transparent; border:0; color:var(--text-muted); min-width:44px; min-height:44px; padding:0; display:grid; place-items:center; position:relative; }
.question-node:not(:last-child)::after { content:''; position:absolute; width:20px; height:1px; background:var(--border); left:34px; }
.question-node span { display:grid; place-items:center; width:24px; height:24px; border:1px solid var(--border); border-radius:50%; font-size:12px; }
.question-node.is-active span { border-color:var(--sage); color:var(--sage); box-shadow:inset 0 0 0 2px var(--app-bg); background:var(--sage-soft); font-weight:700; }
.question-node.is-complete span { background:var(--sage-soft); border-color:var(--sage); color:var(--sage); }
.listening-area { display:flex; align-items:center; justify-content:center; flex-wrap:wrap; column-gap:20px; padding:20px 12px 12px; }
.listening-area audio { display:none; }
.listen-line { width:48px; height:1px; background:var(--apt-line); }
/* Enlarged from 56 (founder, 2026-09-23: play now comes after the answer options, in the thumb
   zone, so it reads as the page's primary action — same idea as Home's GO circle, just smaller). */
/* --apt-* tokens, not --sage/--surface: those only exist inside .p12-mobile (P1/P2's own body class,
   aliased to the same colors there), and this button is also used bare on P3/P4's practice-shell page
   (founder, 2026-09-23), which never carries that class — see the file header note above. */
.listen-play { width:88px; height:88px; display:grid; place-items:center; border:1px solid var(--apt-fill); border-radius:50%; background:var(--apt-fill); color:var(--apt-on-fill); }
.listen-play:disabled { font-size:0.75rem; background:var(--apt-sage-soft); color:var(--apt-fill); }
.played-mark { display:grid; place-items:center; width:88px; height:88px; font-size:0.8125rem; color:var(--apt-fill); }
/* Part 2 has no photo, so centering it like Part 1 leaves a tall empty gap above the options that
   reads as a missing image (founder, 2026-09-28). Anchor it near the top instead. */
.p2-question { flex:none; justify-content:flex-start; padding-top:48px; }
.p2-question .listening-area { padding:16px 12px; margin:0 0 20px; }
.p12-mobile .answer-actions { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:10px; margin:10px 0 0; }
.p12-mobile .p2-answer-actions { grid-template-columns:1fr; gap:10px; }
.p12-mobile .answer-actions form { margin:0; }
.p12-mobile .answer-actions button { min-height:54px; width:100%; font-size:1.0625rem; font-weight:600; border:1px solid var(--border); border-radius:8px; background:var(--surface); color:var(--ink); }
.p12-mobile .answer-actions button:active { background:var(--sage-soft); border-color:var(--sage); }
.p12-mobile .answer-actions button:disabled { color:var(--text-muted); background:var(--app-bg); }
.review-current { display:block; margin:12px auto 0; min-height:44px; background:transparent; border:0; color:var(--sage); }
.page-error:empty,.interaction-error:empty,.audio-error:empty { display:none; }
.p12-mobile .feedback { margin-top:18px; padding-top:8px; }
.p12-mobile summary { min-height:44px; display:flex; align-items:center; gap:8px; color:var(--sage); font-size:0.875rem; cursor:pointer; list-style:none; }
.p12-mobile summary::-webkit-details-marker { display:none; }
.review-mode { margin-top:16px; padding-bottom:calc(28px + env(safe-area-inset-bottom)); }
.review-status { border-bottom:1px solid var(--border); padding:0 0 12px; }
.review-status h1 { margin:0 0 12px; font-size:1.375rem; line-height:1.3; font-weight:650; color:var(--success); }
.review-mode.is-incorrect .review-status h1 { color:var(--error); }
.p12-mobile .primary-action { width:100%; min-height:52px; margin:0; border:0; border-radius:8px; background:var(--sage); color:var(--surface); font-weight:600; }
.p12-mobile .primary-action:disabled { opacity:.65; }
.review-mode h2 { font-size:1rem; font-weight:650; margin:18px 0 8px; }
.review-mode h3 { font-size:0.875rem; font-weight:600; margin:12px 0 6px; }
.review-mode p { line-height:1.65; }
.p12-mobile .p1-result-options,.p12-mobile .p2-transcript ol { display:block; padding:0; margin:0; list-style:none; }
.p12-mobile .p1-option-result,.p12-mobile .p2-transcript-row { border:1px solid var(--border); border-radius:8px; box-shadow:none; background:var(--surface); padding:14px; margin:8px 0; }
.p12-mobile .p1-option-result.is-correct,.p12-mobile .p2-transcript-row.is-correct { background:var(--success-soft); border-color:var(--success); }
.p12-mobile .p1-option-result.is-incorrect,.p12-mobile .p2-transcript-row.is-incorrect { background:var(--error-soft); border-color:var(--error); }
.p12-mobile .p1-option-heading,.p12-mobile .p2-option-heading { display:flex; align-items:center; gap:10px; }
.p12-mobile .p1-option-status,.p12-mobile .p2-option-status { font-size:.8125rem; font-weight:500; }
.p12-mobile .p1-option-result.is-correct .p1-option-status,.p12-mobile .p2-transcript-row.is-correct .p2-option-status { color:var(--success); }
.p12-mobile .p1-option-result.is-incorrect .p1-option-status,.p12-mobile .p2-transcript-row.is-incorrect .p2-option-status { color:var(--error); }
.p12-mobile .option-letter,.p12-mobile .p2-option-heading strong { display:inline-flex; align-items:center; gap:6px; color:var(--ink); font-size:1rem; }
.p12-mobile .option-symbol { display:inline-grid; flex:0 0 24px; place-items:center; width:24px; height:24px; line-height:1; text-align:center; font-size:20px; color:var(--text-muted); }
.p12-mobile .p1-option-result.is-correct .option-symbol,.p12-mobile .p2-transcript-row.is-correct .option-symbol { color:var(--success); }
.p12-mobile .p1-option-result.is-incorrect .option-symbol,.p12-mobile .p2-transcript-row.is-incorrect .option-symbol { color:var(--error); }
/* The ○/× slot is 24px wide plus the heading's 10px gap, reserved whether or not this option
   carries a mark. The text lines up under the letter rather than under the empty slot, so the
   symbol reads as a margin marker instead of leaving the letter floating out on its own. */
.p12-mobile .p1-option-english,.p12-mobile .p2-transcript-english { font-size:1rem; margin:8px 0 4px 34px; }
.p12-mobile .p1-option-translation,.p12-mobile .p2-transcript-chinese { font-size:0.875rem; margin:0 0 0 34px; color:var(--text-muted); }
/* An option with no verdict keeps the plain "B. …" shape: nothing to mark, so no marker gutter. */
.p12-mobile .p1-option-result.is-plain .p1-option-english { margin:0 0 4px; }
.p12-mobile .p1-option-result.is-plain .p1-option-translation { margin:0; }
.p12-mobile .p2-question-transcript { border:0; margin:0; padding:0; }
.p12-mobile .p2-question-chinese { font-size:0.875rem; color:var(--text-muted); }
.p12-mobile .p2-transcript { border:0; margin:16px 0 0; padding:0; }
.p12-mobile .p2-explanation,.p12-mobile .p1-explanation { border:0; margin:0; padding:0; }
.p12-mobile .p2-explanation-selected,.p12-mobile .p2-explanation-correct { border:0; background:none; padding:0; margin:12px 0; border-radius:0; }
.p12-mobile .p2-explanation-more,.p12-mobile .p1-deeper-explanation { border:0; border-top:1px solid var(--border); background:transparent; padding:8px 0; border-radius:0; margin-top:16px; }
.p12-mobile .evidence-locator,.p12-mobile .next-time-cue { background:none; border-radius:0; padding:8px 0; }
.review-audio { display:flex; align-items:center; gap:10px; padding:10px 0 0; }
.review-audio .speed-control { order:0; flex:1 1 auto; min-width:0; margin:0; padding:0; border:0; }
.review-audio .replay-action { order:1; flex:0 0 auto; min-width:94px; padding-inline:12px; min-height:48px; background:transparent; border:1px solid var(--border); border-radius:8px; color:var(--ink); font-size:0.8125rem; white-space:nowrap; }
.p12-mobile .speed-options { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:0; width:100%; }
.p12-mobile .speed-options button { min-height:44px; min-width:0; width:100%; color:var(--text-muted); border:1px solid var(--border); border-radius:0; background:var(--surface); }
.p12-mobile .speed-options button:first-child { border-radius:7px 0 0 7px; }
.p12-mobile .speed-options button:last-child { border-radius:0 7px 7px 0; }
.p12-mobile .speed-options button[aria-pressed="true"] { background:var(--sage-soft); color:var(--sage); border-color:var(--sage); box-shadow:none; }
.secondary-action { display:block; min-height:44px; margin:16px auto 0; padding:0 16px; color:var(--sage); background:transparent; border:1px solid var(--border); border-radius:8px; }
@media(min-width:600px) { .mobile-practice { border-inline:1px solid var(--border); } }


/* Practice page: the four entries share the full height between the top bar and the navigation, so no row is a thin strip
   above an empty screen. The height is capped so a very tall window does not stretch rows into slabs. */
.product-shell:has(.practice-entry-list) { display:flex; flex-direction:column; }
.product-shell:has(.practice-entry-list) .product-frame { display:flex; flex:1 1 auto; flex-direction:column; width:100%; }
/* width:100% because the column flex container would otherwise shrink the centred content to its text (about 360 wide on tablet and desktop). */
.product-shell .practice-entry-content:has(.practice-entry-list) { display:flex; flex:1 1 auto; flex-direction:column; width:100%; }
.product-shell .practice-entry-list { flex:1 1 auto; grid-template-rows:repeat(4,minmax(96px,1fr)); max-height:760px; }
.product-shell .practice-entry-list .practice-entry-row { grid-template-columns:56px minmax(0,1fr) auto; gap:16px; min-height:96px; padding:14px 4px; }
.product-shell .practice-entry-list .practice-entry-icon { justify-self:center; transform:scale(1.25); }
.product-shell .practice-entry-list .practice-entry-copy { gap:6px; }
.product-shell .practice-entry-list .practice-entry-copy strong { font-size:1.0625rem; }
.product-shell .practice-entry-list .practice-entry-copy small { font-size:.875rem; line-height:1.7; }
.product-shell .practice-entry-list .practice-entry-chevron { font-size:32px; }
.practice-entry-stat { display:inline-block; white-space:nowrap; }

/* Dark mode for the answering screens: the same palette as the product shell, applied to all three token sets. */
html[data-theme="dark"],
html[data-theme="dark"] .practice-shell[data-p3],html[data-theme="dark"] .practice-shell[data-p4],html[data-theme="dark"] .practice-shell[data-p5],html[data-theme="dark"] .practice-shell[data-p6],html[data-theme="dark"] .practice-shell[data-p7] {
  color-scheme: dark;
  --apt-bg:#182019; --apt-surface:#202a21; --apt-ink:#f2f2ec; --apt-muted:#c1c7c0; --apt-line:#455347;
  --apt-navy:#91c7a3; --apt-gold:#d5b87a; --apt-sage:#91c7a3; --apt-success:#8fd0a3; --apt-alert:#e2a49b;
  --apt-tint:#26322a; --apt-tint-strong:#2d3a30; --apt-success-soft:#20362a; --apt-alert-soft:#3b2624; --apt-sage-soft:#243529;
  --apt-fill:#91c7a3; --apt-on-fill:#182019; --apt-fill-hover:#a8d6b6;
}
html[data-theme="dark"] .p12-mobile {
  --app-bg:#182019; --surface:#202a21; --ink:#f2f2ec; --text-muted:#c1c7c0; --sage:#91c7a3; --sage-soft:#243529;
  --brand-gold:#d5b87a; --border:#455347; --success:#8fd0a3; --success-soft:#20362a; --error:#e2a49b; --error-soft:#3b2624;
  --apt-tint:#26322a; --apt-tint-strong:#2d3a30; --apt-fill:#91c7a3; --apt-on-fill:#182019;
}
/* iOS zooms the page into any text field under 16px when it is focused, and at 文字大小 最小/較小 the
   fields were 14-15px. A floor of 16px keeps the page still (TestFlight build 9); larger sizes still grow. */
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]), select, textarea { font-size:max(16px, 1em) !important; }
/* Locale text (2026-10-02 four-language layout sweep). Korean separates words with spaces but the browser breaks between any two
   syllables by default ("이메/일", "마쳤/어요"): keep-all wraps at the spaces only, and break-word still splits a word that is wider
   than its whole column. 日本語 gets strict kinsoku so a line never opens on a small kana or ー. text-wrap:balance evens the lines of
   short strings so a headline, button or chip never leaves one glyph alone on a second line (中文 成, 日本語 ン/い/る, 한국어 요). */
html[data-writing-system="ko-KR"] body { overflow-wrap:break-word; word-break:keep-all; }
html[data-writing-system="ja-JP"] body { line-break:strict; }
h1, h2, h3, h4, button:not(.option-choice), .goal-chip, .product-detail-row strong, a.primary-action, a.product-primary-link { text-wrap:balance; }
/* ETS trademark notice (src/i18n/html.ts withEtsDisclaimer): the last line inside <main> on every learner page.
   Small and muted, in the content flow (never a fixed bar), wraps at any width / text size. */
.ets-disclaimer { box-sizing:border-box; clear:both; color:var(--product-muted, #5e665f); font-size:0.75rem; font-weight:400; letter-spacing:0; line-height:1.5; margin:32px auto 0; max-width:40rem; min-width:0; overflow-wrap:anywhere; padding:0 16px; text-align:center; width:100%; }
.ets-disclaimer p { color:inherit; font:inherit; margin:0; text-wrap:balance; }
/* Home is full-bleed (no frame side padding) and its frame leaves only the tab bar's own height under it. */
.product-shell[data-active-tab="home"][data-page-kind="primary"] .ets-disclaimer { padding:0 24px 14px; }
/* Question pages have no frame padding under their last block: keep the line off the home indicator. */
.practice-shell > .ets-disclaimer, .mobile-practice > .ets-disclaimer { padding-bottom:calc(20px + env(safe-area-inset-bottom)); }

/* The Mock answer sheet (renderAnswerSheet), modelled on the official sheet: 25 rows a column, a band every five rows. */
.bubble-sheet { margin:16px 0 0; }
.bubble-sheet-columns { display:grid; gap:0 16px; grid-template-columns:1fr; }
@media (min-width:600px) { .bubble-sheet-columns { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (min-width:1024px) { .bubble-sheet-columns { grid-template-columns:repeat(4,minmax(0,1fr)); } }
.bubble-sheet-column { list-style:none; margin:0; padding:0; }
.bubble-sheet-part { color:var(--apt-navy); font-size:.8125rem; font-weight:700; padding:12px 8px 4px; scroll-margin-top:72px; }
.bubble-sheet-row { align-items:center; border-bottom:1px solid var(--apt-line); display:flex; gap:6px; min-height:48px; padding:0 6px; }
.bubble-sheet-row.is-band { background:var(--apt-tint); }
.bubble-sheet-row.is-now { background:var(--apt-sage-soft); box-shadow:inset 4px 0 0 var(--apt-navy); }
.bubble-sheet-row.is-future { opacity:.45; }
.bubble-sheet-no { align-items:center; background:transparent; border:0; color:var(--apt-ink); display:inline-flex; font:inherit; font-variant-numeric:tabular-nums; font-weight:700; justify-content:flex-end; min-height:44px; min-width:44px; padding:0 6px 0 0; text-decoration:none; }
a.bubble-sheet-no, button.bubble-sheet-no { color:var(--apt-navy); cursor:pointer; text-decoration:underline; text-underline-offset:3px; }
.bubble-sheet-jump { margin:0; }
.bubble-sheet-bubbles { display:inline-flex; gap:6px; margin:0; }
.answer-bubble { align-items:center; background:var(--apt-surface); border:1.5px solid var(--apt-muted); border-radius:50%; box-sizing:border-box; color:var(--apt-ink); display:inline-flex; font:inherit; font-size:.875rem; font-weight:700; height:32px; justify-content:center; line-height:1; padding:0; width:32px; }
button.answer-bubble { cursor:pointer; height:44px; width:44px; }
button.answer-bubble:disabled { cursor:default; }
.answer-bubble.is-filled { background:var(--apt-navy); border-color:var(--apt-navy); color:var(--apt-on-fill); }
.answer-bubble.is-filled.is-correct { background:var(--apt-success); border-color:var(--apt-success); }
.answer-bubble.is-filled.is-incorrect { background:var(--apt-alert); border-color:var(--apt-alert); }
.answer-bubble.is-key { border:2.5px solid var(--apt-success); color:var(--apt-success); }
.bubble-sheet-result { font-weight:800; margin-left:auto; min-width:1.2em; text-align:center; }
.bubble-sheet-result.is-correct { color:var(--apt-success); }
.bubble-sheet-result.is-incorrect { color:var(--apt-alert); }
.bubble-sheet-result.is-unanswered { color:var(--apt-muted); }
.bubble-sheet-skip { border:1px solid var(--apt-gold); border-radius:999px; color:var(--apt-gold); font-size:.6875rem; font-weight:700; margin-left:auto; padding:1px 6px; }
.bubble-sheet-inbook { color:var(--apt-muted); font-size:.6875rem; margin-left:4px; }
.bubble-sheet-summary { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 4px; }
.bubble-sheet-summary a, .bubble-sheet-summary span { border:1px solid var(--apt-line); border-radius:999px; color:var(--apt-ink); font-size:.8125rem; font-weight:650; min-height:36px; display:inline-flex; align-items:center; padding:0 12px; text-decoration:none; }
.bubble-sheet-summary .has-open { border-color:var(--apt-alert); color:var(--apt-alert); }
.bubble-sheet-dialog { background:var(--apt-surface); border:0; box-sizing:border-box; color:var(--apt-ink); height:100dvh; margin:0; max-height:none; max-width:none; overflow:auto; padding:calc(12px + env(safe-area-inset-top)) 16px calc(24px + env(safe-area-inset-bottom)); width:100vw; }
.bubble-sheet-dialog::backdrop { background:rgba(15,20,26,.4); }
.bubble-sheet-dialog-head { align-items:center; background:var(--apt-surface); display:flex; justify-content:space-between; position:sticky; top:0; z-index:1; }
.bubble-sheet-dialog-head h2 { font-size:1.125rem; margin:0; }
.bubble-sheet-close { background:transparent; border:0; color:var(--apt-ink); cursor:pointer; font-size:1.75rem; height:44px; width:44px; }
.bubble-sheet-hint { color:var(--apt-muted); font-size:.875rem; margin:4px 0 8px; }
/* Mock Reading: 先跳過 and 答題卡 side by side under the options, both as large as an option. */
.mock-actions { display:grid; gap:10px; grid-template-columns:1fr 1fr; margin:14px 0 0; }
.mock-actions form { margin:0; }
.mock-actions button { background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:12px; box-sizing:border-box; color:var(--apt-ink); cursor:pointer; font:inherit; font-size:var(--reading-text, 1rem); font-weight:650; min-height:48px; padding:8px 10px; width:100%; }
.mock-actions .bubble-sheet-open { color:var(--apt-navy); word-break:keep-all; } /* 「答題卡 ·」/「未作答 95」, never mid-word */
.mock-hand-in-parts { color:var(--apt-muted); display:flex; flex-wrap:wrap; gap:6px 12px; font-size:.875rem; margin:6px 0 0; }
.mock-hand-in-parts .has-open { color:var(--apt-alert); font-weight:650; }
/* Mock header (2026-10-04; every part-training header is opaque now, so rows never show through it): A−/A+ live in the settings panel (文字大小),
   so back · Part · count · clock · the settings icon fit a 375pt phone at every text size. */
.mock-text-size-setting { display:grid; gap:8px; }
.mock-text-size-setting:has(> .part-training-text-size[hidden]) { display:none; }
.part-training-settings-panel .mock-setting-label { color:var(--apt-muted); font-size:13px; font-weight:700; }
.part-training-settings-panel .part-training-text-size { gap:8px; }
.part-training-settings-panel .part-training-text-size button { flex:1 1 0; font-size:15px; width:auto; }
/* 回報此題 after each question: right-aligned on its own line under the options, and at the end of the row now playing on
   the Listening Part 1/2 sheet. A quiet 44pt icon in the muted ink, so it never competes with the answer. */
.mock-quick-report { display:flex; margin:0; }
.mock-report-after { display:flex; justify-content:flex-end; margin:6px 0 0; }
.bubble-sheet-row > .mock-quick-report { margin-left:auto; }
.practice-shell .mock-report-icon { align-items:center; background:transparent; border:0; border-radius:10px; box-shadow:none; color:var(--apt-muted); cursor:pointer; display:grid; height:44px; justify-items:center; min-height:44px; padding:0; width:44px; }
.mock-report-icon { align-items:center; background:transparent; border:0; border-radius:10px; color:var(--apt-muted); cursor:pointer; display:grid; height:44px; justify-items:center; padding:0; width:44px; }
.mock-report-icon:focus-visible { outline:3px solid var(--apt-sage); outline-offset:-3px; }
.mock-report-icon:disabled { cursor:default; opacity:1; }
.mock-report-icon .mock-report-done { display:none; }
.mock-report-icon.is-sent { color:var(--apt-success); }
.mock-report-icon.is-sent .mock-report-bubble { fill:var(--apt-success); }
.mock-report-icon.is-sent .mock-report-idle { display:none; }
.mock-report-icon.is-sent .mock-report-done { display:inline; stroke:var(--apt-on-fill); }
/* A short note just under the header (已回報，謝謝 / 剩 5 分鐘), wherever the header is: never takes focus or a tap. */
.part-training-topbar .mock-toast { background:var(--apt-ink); border-radius:14px; box-shadow:0 8px 24px rgba(24,38,55,.22); box-sizing:border-box; color:var(--apt-on-fill); font-size:15px; font-weight:650; left:50%; line-height:1.45; margin:0; max-width:calc(100vw - 32px); padding:10px 16px; pointer-events:none; position:absolute; text-align:center; top:calc(100% + 8px); transform:translateX(-50%); white-space:normal; width:max-content; z-index:40; animation:mock-toast-in .2s ease-out; }
.mock-toast[hidden] { display:none; }
.part-training-topbar .mock-clock-toast { background:var(--apt-alert); }
@keyframes mock-toast-in { from { opacity:0; transform:translate(-50%,-6px); } to { opacity:1; transform:translate(-50%,0); } }
@media (prefers-reduced-motion: reduce) { .part-training-topbar .mock-toast { animation:none; } }
/* 待檢查 (Mock Reading): a 44pt toggle above the stem; marked, its bookmark fills in gold. Chips carry the bookmark in a corner. */
.mock-question-tools { display:flex; justify-content:flex-end; margin:0 0 6px; }
.mock-review-form { margin:0; }
.practice-shell .mock-review-toggle { align-items:center; background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:999px; box-shadow:none; color:var(--apt-ink); cursor:pointer; display:inline-flex; font:inherit; font-size:.9375rem; font-weight:650; gap:6px; min-height:44px; min-width:0; padding:0 14px 0 12px; width:auto; }
.practice-shell .mock-review-toggle.is-marked { background:color-mix(in srgb, var(--apt-gold) 14%, var(--apt-surface)); border-color:var(--apt-gold); }
.mock-review-toggle .mock-review-glyph { color:var(--apt-muted); }
.mock-review-toggle.is-marked .mock-review-glyph { color:var(--apt-gold); }
.mock-review-toggle.is-marked .mock-review-glyph path { fill:currentColor; }
.mock-review-toggle:focus-visible { outline:3px solid var(--apt-sage); outline-offset:2px; }
.question-chip.is-marked { position:relative; }
.question-chip.is-marked::after { background:var(--apt-gold); clip-path:polygon(0 0,100% 0,100% 100%,50% 72%,0 100%); content:""; height:13px; position:absolute; right:8px; top:-1px; width:10px; }
/* The Mock Reading 答題卡: counts, filters, 下一題未作答 and a number grid by Part (renderReadingGrid). */
.sheet-grid-summary { color:var(--apt-ink); font-size:.9375rem; font-weight:650; margin:4px 0 10px; }
.sheet-grid-summary span, .sheet-grid-filter span, .sheet-grid-part h3 span { font-variant-numeric:tabular-nums; font-weight:800; }
.sheet-grid-filters { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 10px; }
.sheet-grid-filter { background:var(--apt-surface); border:1px solid var(--apt-line); border-radius:999px; color:var(--apt-ink); cursor:pointer; font:inherit; font-size:.875rem; font-weight:650; min-height:44px; padding:0 14px; }
.sheet-grid-filter[aria-pressed="true"] { background:var(--apt-navy); border-color:var(--apt-navy); color:var(--apt-on-fill); }
.sheet-grid-next { background:var(--apt-surface); border:1.5px solid var(--apt-navy); border-radius:12px; color:var(--apt-navy); cursor:pointer; font:inherit; font-size:.9375rem; font-weight:700; min-height:48px; width:100%; }
.sheet-grid-next:disabled { cursor:default; opacity:.4; }
.sheet-grid-part[hidden], .sheet-cell[hidden] { display:none; }
.sheet-grid-part h3 { color:var(--apt-navy); font-size:.875rem; font-weight:700; margin:18px 0 8px; scroll-margin-top:72px; }
.sheet-grid-cells { display:grid; gap:8px; grid-template-columns:repeat(5,minmax(0,1fr)); list-style:none; margin:0; padding:0; }
@media (min-width:600px) { .sheet-grid-cells { grid-template-columns:repeat(10,minmax(0,1fr)); } }
.sheet-cell { margin:0; min-width:0; }
.sheet-cell-form { height:100%; margin:0; }
.sheet-cell-link { align-items:center; background:var(--apt-surface); border:1.5px solid var(--apt-line); border-radius:10px; box-sizing:border-box; color:var(--apt-ink); cursor:pointer; display:flex; flex-direction:column; font:inherit; gap:1px; height:100%; justify-content:center; min-height:56px; padding:4px 0; position:relative; text-decoration:none; width:100%; }
.sheet-cell-no { font-size:.8125rem; font-variant-numeric:tabular-nums; font-weight:700; line-height:1.2; }
.sheet-cell-mark { font-size:1rem; font-weight:800; line-height:1.2; min-height:1.2em; }
.sheet-cell.is-answered .sheet-cell-link { background:var(--apt-navy); border-color:var(--apt-navy); color:var(--apt-on-fill); }
.sheet-cell.is-open .sheet-cell-link { border:2px solid var(--apt-alert); color:var(--apt-alert); }
.sheet-cell.is-ahead .sheet-cell-link { border-style:dashed; border-color:color-mix(in srgb, var(--apt-muted) 60%, transparent); color:var(--apt-muted); }
.sheet-cell.is-current .sheet-cell-link { box-shadow:0 0 0 2px var(--apt-surface), 0 0 0 4.5px var(--apt-sage); }
.sheet-cell-flag { color:var(--apt-gold); display:none; position:absolute; right:3px; top:-3px; }
.sheet-cell.is-review .sheet-cell-flag { display:block; }
.sheet-cell-link:focus-visible { outline:3px solid var(--apt-sage); outline-offset:2px; }
.sheet-grid-empty { color:var(--apt-muted); font-size:.9375rem; margin:16px 0; text-align:center; }
.bubble-sheet-marked { color:var(--apt-gold); display:inline-flex; margin-left:auto; }
.bubble-sheet-marked + .bubble-sheet-result { margin-left:6px; }
.mock-review-marked, .mock-review-marked-summary { align-items:center; color:var(--apt-ink); display:flex; font-size:.875rem; font-weight:650; gap:6px; margin:8px 0 0; }
.mock-review-marked svg, .mock-review-marked-summary svg { color:var(--apt-gold); flex:none; }
.mock-check-marked h2 { font-size:1rem; margin:16px 0 6px; }
.mock-check-marked + .bubble-sheet-hint { margin-top:16px; }
.mock-skipped-list.is-marked a { border-color:var(--apt-gold); }
/* Mock Listening Part 1/2 (the sheet is where the answer is given) and the read-only Part 3/4 sheet: room to tap. Rows
   are 60pt, circles 40pt with 14pt between them, the row now playing has 48pt circles on a highlighted band, and a row
   scrolled to stays clear of the header. */
.bubble-sheet:is([data-mode="live"],[data-mode="overview"]) .bubble-sheet-row { gap:14px; min-height:60px; padding:6px 12px 6px 4px; scroll-margin:96px 0; }
.bubble-sheet:is([data-mode="live"],[data-mode="overview"]) .bubble-sheet-bubbles { gap:14px; }
.bubble-sheet:is([data-mode="live"],[data-mode="overview"]) .answer-bubble { font-size:.9375rem; height:40px; width:40px; }
.bubble-sheet[data-mode="live"] button.answer-bubble { font-size:1.0625rem; height:48px; width:48px; }
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now { background:var(--apt-sage-soft); border-radius:12px; box-shadow:inset 4px 0 0 var(--apt-navy), inset 0 0 0 1px color-mix(in srgb, var(--apt-navy) 30%, transparent); min-height:68px; }
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now .bubble-sheet-no { color:var(--apt-navy); font-size:1.125rem; }
/* The row now playing also carries 回報此題 at its end. Part 2 (Ⓐ Ⓑ Ⓒ) fits as it is; a Part 1 row (Ⓐ Ⓑ Ⓒ Ⓓ) tightens
   there (46pt circles, 8pt apart) so number · four circles · icon fit a 375pt phone. */
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now:has(button.answer-bubble:nth-of-type(4)) { gap:8px; padding:6px 4px; }
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now:has(button.answer-bubble:nth-of-type(4)) .bubble-sheet-no { min-width:40px; }
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now:has(button.answer-bubble:nth-of-type(4)) .bubble-sheet-bubbles { gap:8px; }
.bubble-sheet[data-mode="live"] .bubble-sheet-row.is-now:has(button.answer-bubble:nth-of-type(4)) button.answer-bubble { height:46px; width:46px; }
.bubble-sheet-row > .mock-quick-report { flex:none; }
.bubble-sheet:is([data-mode="live"],[data-mode="overview"]) .bubble-sheet-part { padding:16px 8px 6px; }
/* The Mock review: the sheet beside the content from 768pt. */
.mock-review-sheet .bubble-sheet-columns { grid-template-columns:1fr; }
@media (min-width:768px) {
  .mock-review-layout { align-items:start; display:grid; gap:24px; grid-template-columns:minmax(260px,320px) minmax(0,1fr); }
  .mock-review-sheet { max-height:calc(100dvh - 96px); overflow:auto; position:sticky; top:72px; }
}
.mock-review-anchor { display:block; position:relative; scroll-margin-top:72px; }
.mock-review-in-book { color:var(--apt-muted); font-size:.8125rem; font-weight:650; margin:8px 0 0; }
.mock-review-sheet-toggle > summary { cursor:pointer; font-weight:700; min-height:44px; display:flex; align-items:center; }
.mock-check-return { align-items:center; color:var(--apt-navy); display:inline-flex; font-weight:700; min-height:44px; text-decoration:none; }
.mock-check .bubble-sheet { text-align:left; }

/* Part 3/4 連續模式 (the same stored choice as Part 6/7): the set's three questions stacked under the graphic, any order. */
html[data-reading-mode="continuous"] :is([data-listening-flow] [data-diagnostic-set-question], [data-listening-flow] [data-listening-set-question], [data-formal-listening-workspace] [data-formal-group-question])[hidden] { display:block; }
html[data-reading-mode="continuous"] :is([data-listening-flow] [data-diagnostic-set-question], [data-listening-flow] [data-listening-set-question], [data-formal-listening-workspace] [data-formal-group-question]) + :is([data-diagnostic-set-question], [data-listening-set-question], [data-formal-group-question]) { border-top:1px solid var(--apt-line); margin-top:16px; padding-top:16px; }
html[data-reading-mode="continuous"] :is([data-listening-flow], [data-formal-listening-workspace]) .group-question-chips { background:var(--apt-surface); position:sticky; top:calc(var(--safe-top, 0px) + 52px); z-index:2; }
/* Part 3/4 graphics fill the question column; the Mock's stays in view above the questions. */
.zoomable-media[data-media-kind="graphic"], .zoomable-media[data-media-kind="graphic"] .media-zoom-trigger, .zoomable-media[data-media-kind="graphic"] .media-zoom-trigger img { max-width:100%; width:100%; }
.listening-set-graphic { background:var(--apt-surface); position:sticky; top:calc(var(--safe-top, 0px) + 52px); z-index:3; }
.listening-set-graphic .media-zoom-trigger img { max-height:38vh; object-fit:contain; }
html[data-reading-mode="continuous"] [data-formal-listening-workspace] .group-question-chips { top:calc(var(--safe-top, 0px) + 52px + 38vh); }
.graphic-preview-option { align-items:center; display:flex; gap:8px; min-height:44px; }
.graphic-preview-countdown { color:var(--apt-navy); font-weight:700; margin:8px 0 0; text-align:center; }
/* One reading size (founder, 2026-10-04: 「標題、內文、解析應該都統一，要不然看久眼睛會疲勞」). On the Part 3-7 answering and
   review screens — 題型專練, the 程度測驗, Mock, 錯題本 and the review pages — the passage, document titles, stems, options,
   translations and explanations are all the stem's size (--reading-text, 1.125rem) and scale together with A−/A+ (rem).
   Levels come from weight and spacing only: titles and section heads are bold, never larger or smaller. Body text is one
   ink colour (no light grey for paragraphs). Smaller stays only for navigation and status: the header, the chips'
   已作答/未作答, progress, and the ○/× status tags. Line height: running text 1.6, options 1.5, options at least 48pt. */
.practice-shell { --reading-text:1.125rem; }
.practice-shell :is(.reading-material-panel pre, .shared-material p, .shared-passage p, .reading-question .stem, .listening-question h1, .listening-commit p, .transcript p, .transcript-zh p, .review-transcript p, .listening-group-analysis, .p5-analysis, .reading-analysis, .evidence-locator, .next-time-cue, .stem-zh, .option-zh) { color:var(--apt-ink); font-size:var(--reading-text); line-height:1.6; }
.practice-shell :is(.listening-choices button, .reading-choices button, .p5-answer-actions button, .listening-evidence-item li, .reading-evidence-item li, .transcript li) { font-size:var(--reading-text); line-height:1.5; }
.practice-shell :is(.listening-choices button, .reading-choices button) { min-height:48px; }
.practice-shell .option-copy { font-size:inherit; line-height:inherit; }
.practice-shell :is(.reading-material-panel h2, .shared-material .material-label, .reading-evidence-item h2, .reading-teaching-item h3, .listening-evidence-item h2, .listening-teaching-item h3, .listening-group-analysis summary, .p5-analysis summary, .reading-analysis summary, .transcript > summary, .transcript-zh summary, .evidence-zh summary, .review-transcript > summary, .review-heading h2) { font-size:var(--reading-text); font-weight:700; letter-spacing:normal; line-height:1.5; text-transform:none; }
.practice-shell .shared-material .material-label { color:var(--apt-ink); }
.practice-shell .listening-question h1 { font-weight:600; }
.practice-shell .listening-evidence-item .evidence-zh[open] ~ :is(.stem-zh, ol li .option-zh) { color:var(--apt-ink); font-size:var(--reading-text); line-height:1.6; }
@media (max-width:560px) { .practice-shell .option-copy { font-size:inherit; } }
