/* ══════════════════════════════════════════════════════════════════════
   shared/chrome.css — CSS for shared/chrome.js (unified header, LOGIelts)
   Tách ra từ mảng CSS trong shared/chrome.js (A0, HUD Overhaul R3 M0).
   Di chuyển thuần tuý — nội dung CSS không đổi, chỉ đổi cách lưu trữ.
   Nạp bằng <link rel="stylesheet"> ở <head>, injectStyle() trong chrome.js
   chỉ chèn link này nếu trang chưa có sẵn.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Giá trị dự phòng cho biến chiều cao header ───────────────────────────
   publishHeaderHeight() trong chrome.js ghi đè ba biến này bằng chiều cao ĐO
   THẬT (đổi theo safe-area máy tai thỏ và theo việc header có xuống hàng hai ở
   ≤860px hay không). Đặt mặc định ở đây để trang vẫn đúng trong khoảng thời gian
   trước khi header render xong, hoặc khi JS bị chặn — 76px là con số khung ngang
   của rl/index.css đang dùng.
   Thanh sticky của app viết: top: var(--lg-sticky-top, 76px);
   ⛔ Không tự đo lại chiều cao header ở chỗ khác — một nguồn duy nhất.
   Mặc định phải TỰ CỘNG safe-area: /about vốn đã dự phòng bằng
   calc(56px + env(safe-area-inset-top)); đặt một con số phẳng ở đây là lấy mất
   phần bù tai thỏ của nó trong lúc header chưa render. */
:root{
  --chrome-h: calc(56px + env(safe-area-inset-top,0px));
  --lg-header-h: var(--chrome-h);
  --lg-sticky-top: calc(66px + env(safe-area-inset-top,0px));
}

/* P2 (M2b): skeleton dùng chung — <span class="lg-skel" role="status" aria-busy="true" aria-label="Đang tải…"><i></i><i></i><i></i></span>.
       Là CON của ô nội dung nên innerHTML/textContent của app ghi đè là hết aria-busy. */
.lg-skel{display:block;min-height:40px}.lg-skel i{display:block;height:12px;border-radius:6px;margin:9px 0;background:rgba(148,163,184,.18);animation:lgSkel 1.2s ease-in-out infinite}
.lg-skel i:nth-child(1){width:45%;height:16px}.lg-skel i:nth-child(2){width:82%}.lg-skel i:nth-child(3){width:64%}.lg-skel i:nth-child(4){width:73%}
@keyframes lgSkel{0%,100%{opacity:.55}50%{opacity:1}}@media (prefers-reduced-motion:reduce){.lg-skel i{animation:none}}
/* ══ art v3 25/09, bước 4 — màn trống/lỗi/đang tải dùng chung (HUD-R49) ══
   window.lgUI.emptyHtml()/.empty() ở shared/chrome.js. Công thức mẫu
   Empty.dc.html (chủ app khen "đúng ý" 25/09, GIỮ NGUYÊN): LOGI trong vòng
   tròn tím nhạt + 2 lấp lánh, tiêu đề, dòng phụ, nút pill. Dùng --hue-vocab
   cố định (không đổi theo app gọi) — mẫu chỉ có MỘT công thức vòng tròn tím
   cho mọi màn trống, không phân biệt kỹ năng (đây là màn "chưa có gì"/"lỗi",
   không phải bề mặt gắn với một skill cụ thể). */
.lg-empty{
  --ph-hue:var(--hue-vocab); /* m1: hoạ tiết nền đọc biến này qua CSS, không phải JS */
  position:relative;overflow:hidden;isolation:isolate;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:14px;padding:36px 24px;text-align:center;
}
.lg-empty > .lg-ph-pattern{ z-index:0 }
/* Review 26/09 (m4): vài app bọc màn trống trong khung sẵn có padding + viền
   (.lb-empty leaderboard, .bg-empty/.gate-panel badges, .card songs/listening/
   mfi) ⇒ ~70px đệm trên chồng nhau. Trong các khung đó chỉ giữ đệm mỏng. */
.lb-empty > .lg-empty,.bg-empty > .lg-empty,.gate-panel > .lg-empty,.card > .lg-empty{ padding:8px 0 }
.lg-empty-ring,.lg-empty-title,.lg-empty-sub,.lg-empty-cta{ position:relative;z-index:1 }
.lg-empty-ring{
  width:120px;height:120px;border-radius:50%;
  background:color-mix(in srgb, var(--hue-vocab) 22%, transparent);
  display:flex;align-items:center;justify-content:center;
}
.lg-empty-img{ width:104px;height:104px;object-fit:contain;display:block;pointer-events:none }
/* TĨNH, không animation — mẫu sparkles() gốc (parts.py) không nhấp nháy ở
   màn trống, khác hẳn sao trong hoạ tiết nền (constel(), có nhấp 3 nhịp rồi
   dừng). "No infinite animation" (hiến chương art v3) ⇒ giữ nguyên tĩnh. */
.lg-empty-spark{ position:absolute;width:18px;height:18px;color:var(--hue-gold);pointer-events:none }
.lg-empty-spark--a{ left:2px;top:14px }
.lg-empty-spark--b{ right:6px;bottom:22px;width:13px;height:13px }
.lg-empty-title{ margin:6px 0 0;font-size:1.25rem;font-weight:800;color:var(--text);
  font-family:var(--font-display-family,'Bricolage Grotesque'),var(--font-ui-family,'Be Vietnam Pro'),system-ui,sans-serif }
.lg-empty-sub{ margin:0;font-size:.88rem;line-height:1.55;color:var(--dim);max-width:360px }
.lg-empty-cta{
  margin-top:4px;height:44px;padding:0 22px;border-radius:999px;border:0;
  background:var(--accent);color:var(--on-accent,#FBFAFE);font-family:inherit;
  font-size:.9rem;font-weight:700;cursor:pointer;
}
.lg-empty-cta:hover{ background:var(--accent-hover,var(--accent)) }
.lg-empty-cta:focus-visible{ outline:3px solid var(--accent);outline-offset:2px }
/* Lề mặc định của trình duyệt cho <body> là 8px. Header chung là
   position:sticky;top:0 nên 8px đó thành một KHE HỞ thấy được giữa header
   và mép cửa sổ — trên và bên trái (bên phải bị scrollbar-gutter che bớt).
   Trước 20/09 mỗi app tự khai body{margin:0} trong CSS riêng; Grammar Gym
   quên, và chỉ mình nó lệch (đo playwright 1280px, 21 trang: grammar
   top=8/left=8, 20 trang còn lại 0/0). Khai ở ĐÂY thì không app nào quên
   được nữa — cùng lý lẽ với §1.1c: hình dạng là quy ước chung, không phải
   việc từng app nhớ. Cổng canh: scripts/header-gap.test.mjs.
   ⚠ Đây là luật NỀN, phải đứng trước mọi luật khác trong file và KHÔNG
   dùng !important: app nào cố tình cần lề riêng vẫn ghi đè được. */
body{margin:0}
/* Header shell ─────────────────────────────────────────────────── */
/* §R09 / QA#5 việc #11 — header PHẢI ở nấc --z-header (200).
   Trước bản này nó viết cứng `z-index:100` (= nấc --z-sticky, dành cho
   'thanh dính kiểu phụ'), trong khi cột phụ dính mép ở nấc --z-rail
   (120). 120 > 100 ⇒ **cột phụ vẽ ĐÈ LÊN header** ngay khi cuộn tới
   chỗ nó dính. Chủ app chụp được ở Draft Doctor: khối 'CHẾ ĐỘ VIẾT'
   phủ kín thanh đầu trang. Một con số viết cứng thắng cả bảng nấc
   z-index của theme.css — và nó làm vậy ở cả 47 trang dùng header này. */
/* 24/09 (QA mobile) — header TỰ mang box-sizing, không trông vào reset của app.
   Đo 390px trên preview đã đăng nhập: chip "🔁 Ôn" cao 57px ở Score Sprint,
   Thông báo và Grammar Gym (40px ở 13 app kia) vì ở đó chip tính content-box
   ⇒ min-height 40 + đệm 16 + viền 2. Cả hàng chỉ số cao theo ⇒ header 140px
   thay vì 123px: đổi app là cả trang nhảy 17px. */
.lg-header-mount,.lg-header-mount *,.lg-header-mount *::before,.lg-header-mount *::after{box-sizing:border-box}
.lg-header-mount{position:sticky;top:0;z-index:var(--z-header,200);background:var(--hdr-bg,rgba(24,21,39,.9));border-bottom:1px solid var(--border,#3A3352);padding:12px 24px;padding-top:calc(12px + env(safe-area-inset-top,0px));padding-left:calc(24px + env(safe-area-inset-left,0px));padding-right:calc(24px + env(safe-area-inset-right,0px));display:flex;align-items:center;justify-content:space-between;gap:12px}
.lg-hdr-l,.lg-hdr-r{display:flex;align-items:center;gap:10px}
/* Hai cụm trong .lg-hdr-r: chip số liệu (stats) vs nút thao tác (tools). */
/* Desktop: nằm chung một hàng, không khác gì trước. Mobile (≤860px, xem */
/* media bên dưới): stats tụt xuống hàng riêng cuộn ngang, tools ở lại */
/* hàng 1 — trước đây cả dãy chip đẩy avatar tài khoản văng khỏi màn hình */
/* (642px nội dung trên viewport 375px). */
.lg-hdr-stats,.lg-hdr-tools{display:flex;align-items:center;gap:10px;min-width:0}
/* Logo ─────────────────────────────────────────────────────────── */
.lg-logo{font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:1.15rem;font-weight:700;letter-spacing:-.02em;text-decoration:none;color:var(--text,#EEEAF6);display:inline-flex;align-items:center;line-height:1}
.lg-logo span{font-weight:400;color:#20B9B9}
.lg-logo:hover{opacity:.85}
/* Due chip ─────────────────────────────────────────────────────── */
.lg-due-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 12px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.8rem;font-weight:600;color:var(--text,#EEEAF6);cursor:pointer;line-height:1;transition:border-color .15s,transform .1s}
.lg-due-chip:hover{border-color:#20B9B9}
.lg-due-chip b{color:#20B9B9;font-weight:800}
.lg-due-chip[hidden]{display:none!important}
/* Streak chip (shown when streak >= 1; vocab app drives it) ─────── */
.lg-streak-chip{display:inline-flex;align-items:center;gap:5px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 10px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;color:var(--text,#EEEAF6);line-height:1}
.lg-streak-chip b{color:#FFB547;font-weight:800}
.lg-streak-chip[hidden]{display:none!important}
.lg-streak-freeze{font-size:.85em;line-height:1;margin-left:-1px}
.lg-streak-freeze[hidden]{display:none!important}
/* Hết vé + hôm nay chưa học → chip đỏ để em thấy chuỗi đang lâm nguy. */
.lg-streak-chip.lg-streak--risk{border-color:rgba(239,68,68,.55);background:rgba(239,68,68,.12)}
.lg-streak-chip.lg-streak--risk b{color:#f87171}
/* AI quota chip (⚡ used/limit + mini progress bar; hidden for admin) ── */
.lg-ai-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 11px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;color:var(--text,#EEEAF6);text-decoration:none;line-height:1;transition:border-color .15s}
.lg-ai-chip:hover{border-color:#20B9B9}
.lg-ai-chip b{color:#20B9B9;font-weight:800}
.lg-ai-sep{opacity:.55}
.lg-ai-bar{width:38px;height:4px;border-radius:2px;background:var(--border,#3A3352);overflow:hidden;flex:none}
.lg-ai-fill{display:block;height:100%;width:0;border-radius:2px;background:#20B9B9;transition:width .3s}
.lg-ai--warn .lg-ai-fill{background:#FFB547}
.lg-ai--warn b{color:#FFB547}
.lg-ai--full .lg-ai-fill{background:#E07856}
.lg-ai--full b{color:#E07856}
.lg-ai-chip[hidden]{display:none!important}
/* Điểm chăm chỉ chip (🏆 tổng hôm nay/trần ngày) — bấm mở LGPoints.explain(). */
/* Trần ở đây KHÔNG phải cảnh báo xấu như AI hết quota — chạm trần nghĩa là */
/* đã học đủ hôm nay, nên tô XANH (tích cực) thay vì đỏ/cam. */
.lg-points-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 11px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;color:var(--text,#EEEAF6);cursor:pointer;line-height:1;transition:border-color .15s}
.lg-points-chip:hover{border-color:#F5A623}
.lg-points-chip b{color:#F5A623;font-weight:800}
.lg-points-chip.lg-points--full b{color:#22c55e}
.lg-points-chip[hidden]{display:none!important}
/* Badge of Honor chip (🏅 huy hiệu tháng — icon + khung do shared/lg-badges.js */
/* cấp qua các lớp lgb-*; file này chỉ định vị trí/kích thước trong header). */
/* Chỉ hiện khi window.LGBadges có dữ liệu current() — degrade im lặng khi */
/* module không được nạp (đa số trang không nạp, xem ghi chú updateBadgeChip). */
.lg-badge-chip{--lgb-size:26px;cursor:pointer;text-decoration:none;transition:transform .15s;flex-shrink:0}
.lg-badge-chip:hover{transform:translateY(-1px) scale(1.04)}
.lg-badge-chip[hidden]{display:none!important}
/* Inline "Gia hạn" chip (only when urgent) ─────────────────────── */
.lg-renew-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 12px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;color:var(--text,#EEEAF6);text-decoration:none;line-height:1;transition:border-color .15s}
.lg-renew-chip:hover{border-color:#F5A623}
.lg-renew-chip.lg-renew--danger{background:rgba(224,120,86,.12);border-color:#E07856;color:#E07856}
.lg-renew-chip[hidden]{display:none!important}
/* Homework nav chip — 29/09/2026 (phản hồi UX: BTVN khó tìm trên trang chủ).
   Chỉ index.html gọi lgChrome.setHomeworkBadge() (dữ liệu hw_feed ĐÃ tải sẵn
   qua hub_bootstrap, xem routine-widget.js renderRailHomeworkBlock()) — mọi
   trang khác không gọi ⇒ chip giữ nguyên [hidden], KHÔNG kéo thêm request. */
.lg-hw-chip{display:inline-flex;align-items:center;gap:6px;background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);border-radius:999px;padding:6px 12px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.8rem;font-weight:600;color:var(--text,#EEEAF6);text-decoration:none;cursor:pointer;line-height:1;transition:border-color .15s}
.lg-hw-chip:hover{border-color:var(--accent)}
.lg-hw-chip b{color:var(--accent-text,#A98EEA);font-weight:800}
.lg-hw-chip.lg-hw--late{background:rgba(224,120,86,.12);border-color:#E07856;color:#E07856}
.lg-hw-chip.lg-hw--late b{color:#E07856}
.lg-hw-chip[hidden]{display:none!important}
/* Upgrade chip — shown for FREE tier students + teachers without active sub */
.lg-upgrade-chip{display:inline-flex;align-items:center;gap:6px;background:var(--accent);color:var(--on-accent,#FBFAFE);text-decoration:none;border:none;border-radius:999px;padding:6px 14px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:700;cursor:pointer;transition:opacity .15s;line-height:1;white-space:nowrap}
.lg-upgrade-chip:hover{opacity:.9}
.lg-upgrade-chip .lg-up-star{color:#F5A623;font-size:13px}
.lg-upgrade-chip[hidden]{display:none!important}
@media (max-width:480px){.lg-upgrade-chip{padding:5px 10px;font-size:.72rem}.lg-upgrade-chip .lg-up-label{display:none}}
/* User pill (new spec) ─────────────────────────────────────────── */
.lg-user-pill{display:inline-flex;align-items:center;gap:6px;background:var(--surf3);border-radius:999px;padding:5px 10px 5px 5px;cursor:pointer;border:1px solid rgba(255,255,255,.05);font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif}
.lg-user-pill:hover{background:#233052}
.lg-user-pill[hidden]{display:none!important}
.lg-tier-avatar{width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}
.lg-tier-avatar.tier-free{background:#7880a0;color:#fff}
.lg-tier-avatar.tier-pro{background:#F5A623;color:#412402}
.lg-tier-avatar.tier-teacher{background:#20B9B9;color:#fff}
.lg-tier-avatar.tier-admin{background:#E85D5D;color:#fff}
.lg-tier-icon{font-size:12px;line-height:1}
/* B3 (24/09) "Bạn đồng hành" — con đã chọn thay icon cấp bậc: ảnh con vật lấp
   đầy vòng tròn, cấp bậc chỉ còn là VIỀN màu quanh nó (không còn nền đặc +
   icon 🛡️/✏️/👑/⚪). free vẫn 0 viền — .has-pet không tự thêm viền, chỉ
   .tier-pro/.tier-teacher/.tier-admin bên dưới mới vẽ. */
.lg-tier-avatar.has-pet{background:none;padding:0}
.lg-tier-avatar.has-pet .lg-tier-pet-img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
.lg-tier-avatar.has-pet.tier-pro{box-shadow:0 0 0 2px var(--surf3),0 0 0 3.5px #F5A623}
.lg-tier-avatar.has-pet.tier-teacher,.lg-tier-avatar.has-pet.tier-admin{box-shadow:0 0 0 2px var(--surf3),0 0 0 3.5px #20B9B9}
.lg-user-name{font-size:12px;font-weight:500;color:var(--text);max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lg-caret{font-size:13px;color:var(--dim);margin-left:2px;transition:transform .15s}
.lg-user-pill.lg-open .lg-caret{transform:rotate(180deg)}
/* Wrap (anchor for dropdown) ───────────────────────────────────── */
.lg-user-wrap{position:relative;display:inline-flex}
/* Right-slot pre-render: hide chips until first profile resolves so */
/* we never flash tier='free' before fetchProfileAsync lands. We use */
/* visibility (not display) so layout doesn't jump. */
.lg-hdr-r.lg-pre{visibility:hidden}
/* Inline theme toggle (always visible, beside user pill) ───────── */
.lg-theme-inline{width:32px;height:32px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:1px solid var(--border,rgba(255,255,255,.12));color:var(--text,#EEEAF6);cursor:pointer;font-size:15px;line-height:1;padding:0;transition:background .15s,transform .12s,border-color .15s}
.lg-theme-inline:hover{background:rgba(255,255,255,.06);transform:scale(1.05);border-color:rgba(255,255,255,.22)}
.lg-theme-inline:focus-visible{outline:2px solid #20B9B9;outline-offset:2px}
[data-theme="light"] .lg-theme-inline{border-color:rgba(0,0,0,.12);color:#1a1a2e}
[data-theme="light"] .lg-theme-inline:hover{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.22)}
/* Help / tour button — B1 (HUD R3 M2). Icon-only nút cũ (🧭 emoji, §1.6 cấm
   emoji trong HUD) đổi sang pill có CHỮ nhìn thấy được ("Trợ giúp") theo yêu
   cầu trực tiếp của chủ app — icon một mình không đủ. Icon là SVG stroke
   inline, không emoji. Thu về hình tròn chỉ-icon ở ≤600px (giữ aria-label). */
.lg-tour-btn{display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px 0 9px;border-radius:999px;background:transparent;border:1px solid var(--border,rgba(255,255,255,.12));color:var(--text,#EEEAF6);cursor:pointer;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;line-height:1;white-space:nowrap;transition:background .15s,transform .12s,border-color .15s}
.lg-tour-btn[hidden]{display:none}
.lg-tour-btn:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.22)}
.lg-tour-btn:focus-visible{outline:2px solid #20B9B9;outline-offset:2px}
.lg-tour-ico{flex-shrink:0}
[data-theme="light"] .lg-tour-btn{border-color:rgba(0,0,0,.12);color:#1a1a2e}
[data-theme="light"] .lg-tour-btn:hover{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.22)}
/* Dropdown ─────────────────────────────────────────────────────── */
/* Backdrop + dropdown are teleported to document.body at mount time so */
/* that position:fixed is anchored to the viewport — not the header's */
/* backdrop-filter stacking context (which clipped them to a 60px strip */
/* on mobile, hiding all items except the last and breaking outside-tap). */
.lg-dd-backdrop{position:fixed;inset:0;background:transparent;z-index:2147483639;display:none}
.lg-dd-backdrop.lg-open{display:block}
.lg-dd{position:fixed;min-width:260px;background:var(--surf,#221D33);border:1px solid var(--border,#3A3352);border-radius:12px;padding:6px;box-shadow:0 16px 48px rgba(0,0,0,.45);z-index:2147483640;display:none;flex-direction:column;gap:2px}
.lg-dd.lg-open{display:flex}
.lg-dd-item{display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:8px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.88rem;font-weight:500;color:var(--text,#EEEAF6);text-decoration:none;background:none;border:none;cursor:pointer;text-align:left;width:100%;line-height:1.2;transition:background .12s,color .12s}
.lg-dd-item:hover,.lg-dd-item:focus-visible{background:var(--surf2,#2A2440);outline:none}
.lg-dd-item .lg-dd-ico{font-size:1rem;flex-shrink:0;width:18px;text-align:center}
.lg-dd-item .lg-dd-meta{margin-left:auto;font-size:.78rem;color:var(--dim,#A79DBE);font-weight:600}
.lg-dd-item--danger{color:#E07856;background:rgba(224,120,86,.08)}
.lg-dd-item--danger:hover{background:rgba(224,120,86,.14)}
.lg-dd-item--danger .lg-dd-meta{color:#E07856}
.lg-dd-item--logout{color:var(--dim,#A79DBE)}
.lg-dd-sep{height:1px;background:var(--border,#3A3352);margin:4px 6px}
/* Highlight ping ───────────────────────────────────────────────── */
.lg-ping{animation:lgPing 1.2s ease 2}
@keyframes lgPing{0%,100%{box-shadow:0 0 0 0 rgba(32,185,185,0)}50%{box-shadow:0 0 0 6px rgba(32,185,185,.35)}}
/* Light theme overrides ────────────────────────────────────────── */
[data-theme="light"] .lg-user-pill{background:#f0f1f5;border-color:rgba(0,0,0,.08)}
[data-theme="light"] .lg-user-pill:hover{background:#e6e8ee}
[data-theme="light"] .lg-user-name{color:#1a1a2e}
[data-theme="light"] .lg-caret{color:#4a5568}
[data-theme="light"] .lg-dd{background:#fff;color:#1a1a2e;border-color:#e0e3eb;box-shadow:0 6px 24px rgba(0,0,0,.12)}
[data-theme="light"] .lg-dd-item{color:#1a1a2e}
[data-theme="light"] .lg-dd-item:hover,[data-theme="light"] .lg-dd-item:focus-visible{background:#f5f5fa}
[data-theme="light"] .lg-dd-sep{background:#e0e3eb}
[data-theme="light"] .lg-dd-item--logout{color:#5a5a6e}
[data-theme="light"] .lg-renew-chip{background:#f0f1f5;border-color:rgba(0,0,0,.08);color:#1a1a2e}
/* Loading overlay ──────────────────────────────────────────────── */
#lgLoadingOverlay{position:fixed;inset:0;background:rgba(8,12,30,.92);display:none;align-items:center;justify-content:center;flex-direction:column;gap:16px;z-index:1000}
[data-theme="light"] #lgLoadingOverlay{background:rgba(240,235,248,.92)}
.lg-loading-msg{font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.9rem;color:var(--dim,#A79DBE)}
/* ══ art v3 26/09, QA item 6 — lgUI.loadingHtml(msg), thay spinner tròn cũ
   (.lg-loading-spinner, xoá — không còn nơi nào tạo ra nữa, xem chrome.js).
   Ống ngắm ~96px TÁI DÙNG đúng công thức .lg-ph-lens (vòng nét đứt +
   .lg-ph-lens-img) nhưng KHÔNG absolute — đây là art độc lập, không nằm
   trong .lg-page-head. Không gắn --ph-hue theo skill (loadingHtml() gọi
   được từ MỌI trang, không biết app đang ở kỹ năng nào) — rơi về --accent
   qua cùng fallback `var(--ph-hue,var(--accent))` đã có sẵn trong các rule
   .lg-ph-lens và .lg-ph-lens-img dùng chung ở trên. Ảnh là LOGI-nimal đang
   chọn ở biểu cảm 'think' — KHÔNG animation CSS vòng ngoài (chỉ ảnh .anim.webp
   tự chạy, tôn trọng prefers-reduced-motion qua chính JS chọn ảnh tĩnh). */
.lg-loading-art{ display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:8px 12px;text-align:center }
.lg-loading-lens{
  position:relative;isolation:isolate;width:96px;height:96px;flex:none;
  display:flex;align-items:center;justify-content:center;
  color:color-mix(in srgb, var(--ph-hue,var(--accent)) 60%, var(--text));
}
.lg-loading-lens::after{
  content:'';position:absolute;width:75px;height:75px;border-radius:50%;z-index:-1;
  background:color-mix(in srgb, var(--ph-hue,var(--accent)) 22%, var(--surf));
}
/* Bell button ──────────────────────────────────────────────────── */
.lg-bell-btn{position:relative;width:32px;height:32px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:1px solid var(--border,rgba(255,255,255,.12));color:var(--text,#EEEAF6);cursor:pointer;font-size:16px;line-height:1;padding:0;transition:background .15s,border-color .15s}
.lg-bell-btn:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.22)}
.lg-bell-btn:focus-visible{outline:2px solid #20B9B9;outline-offset:2px}
[data-theme="light"] .lg-bell-btn{border-color:rgba(0,0,0,.12);color:#1a1a2e}
[data-theme="light"] .lg-bell-btn:hover{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.22)}
.lg-bell-btn[hidden]{display:none!important}
.lg-bell-badge{position:absolute;top:-4px;right:-4px;background:#E07856;color:#fff;font-size:10px;font-weight:800;line-height:1;padding:2px 5px;border-radius:999px;min-width:16px;text-align:center;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif}
.lg-bell-badge[hidden]{display:none!important}
/* Bell panel ───────────────────────────────────────────────────── */
.lg-bell-backdrop{position:fixed;inset:0;background:transparent;z-index:2147483638;display:none}
.lg-bell-backdrop.lg-open{display:block}
/* Shell: header + tabs + ONE scrollable body + footer. The body is the only */
/* scroller (max-height caps the panel at the viewport) so a long feed can */
/* never push the footer off-screen the way the old auto-height panel did. */
.lg-bell-panel{position:fixed;width:min(380px,calc(100vw - 24px));max-height:min(72vh,560px);max-height:min(72vh,72dvh,560px);background:var(--surf,#221D33);border:1px solid var(--border,#3A3352);border-radius:14px;padding:0;box-shadow:0 16px 48px rgba(0,0,0,.45);z-index:2147483639;display:none;flex-direction:column;overflow:hidden}
.lg-bell-panel.lg-open{display:flex}
.lg-bell-panel-hdr{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:4px 8px;padding:11px 12px 9px;border-bottom:1px solid var(--border,#3A3352);flex-shrink:0}
.lg-bell-panel-title{display:flex;align-items:center;gap:6px;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.8rem;font-weight:700;color:var(--dim,#A79DBE);text-transform:uppercase;letter-spacing:.04em}
.lg-bell-count{background:#20B9B9;color:#04121a;font-size:.66rem;font-weight:800;line-height:1;padding:3px 6px;border-radius:999px;letter-spacing:0}
.lg-bell-mark-all{background:none;border:none;color:#20B9B9;font-size:.74rem;font-weight:700;cursor:pointer;padding:2px 0;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;letter-spacing:.01em;flex-shrink:0}
.lg-bell-mark-all:hover{text-decoration:underline}
/* Filter tabs — "Tất cả" vs "Chưa xem" keeps a busy feed usable ─── */
.lg-bell-tabs{display:flex;gap:4px;padding:8px 10px;border-bottom:1px solid var(--border,#3A3352);flex-shrink:0}
.lg-bell-tab{flex:1;background:none;border:none;padding:6px 8px;border-radius:8px;cursor:pointer;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.75rem;font-weight:700;color:var(--dim,#A79DBE);transition:background .12s,color .12s}
.lg-bell-tab:hover{background:var(--surf2,#2A2440)}
.lg-bell-tab.is-active{background:rgba(32,185,185,.14);color:#20B9B9}
/* Scroll body ──────────────────────────────────────────────────── */
.lg-bell-scroll{flex:1 1 auto;min-height:0;overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;padding:2px 8px 8px;scrollbar-width:thin;scrollbar-color:rgba(168,154,204,.35) transparent}
.lg-bell-scroll::-webkit-scrollbar{width:6px}
.lg-bell-scroll::-webkit-scrollbar-track{background:transparent}
.lg-bell-scroll::-webkit-scrollbar-thumb{background:rgba(168,154,204,.3);border-radius:3px}
.lg-bell-scroll::-webkit-scrollbar-thumb:hover{background:rgba(168,154,204,.5)}
/* Row = dòng link + nút ✕ của nó. ROW giữ nền hover/unread để ✕ sáng cùng */
/* dòng nó thuộc về (trước đây nền nằm trên chính .lg-notif-item). */
.lg-notif-row{display:flex;align-items:stretch;border-radius:10px;transition:background .12s}
.lg-notif-row:hover{background:var(--surf2,#2A2440)}
.lg-notif-row--unread{background:rgba(32,185,185,.06)}
.lg-notif-row--unread:hover{background:rgba(32,185,185,.1)}
.lg-notif-item{display:flex;align-items:flex-start;gap:10px;padding:8px;border-radius:10px;text-decoration:none;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;color:var(--text,#EEEAF6);transition:opacity .12s;cursor:pointer;border:none;background:none;flex:1 1 auto;min-width:0;text-align:left}
/* ✕ LUÔN hiện (mờ), không nấp sau hover: đa số học sinh dùng điện thoại và */
/* touch không có hover — docs/UI_COMPONENT_VOCAB.md đã bỏ hẳn lối Tooltip */
/* vì đúng lý do này. Nội dung nằm ở aria-label, không phải title. */
.lg-notif-x{flex:0 0 auto;align-self:center;width:32px;height:32px;margin-right:4px;display:flex;align-items:center;justify-content:center;background:none;border:none;border-radius:8px;color:var(--dim,#A79DBE);font-size:.82rem;line-height:1;cursor:pointer;opacity:.55;transition:opacity .12s,background .12s,color .12s}
.lg-notif-row:hover .lg-notif-x{opacity:1}
.lg-notif-x:hover{background:rgba(224,120,86,.16);color:#E07856;opacity:1}
.lg-notif-x:focus-visible{opacity:1;outline:3px solid var(--accent,#7C5CBF);outline-offset:2px}
/* Dải "Hoàn tác" — ẩn là trạng thái nhớ lâu dài nên phải có đường lùi. */
.lg-notif-undo{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:0;padding:9px 12px;border-top:1px solid var(--border,#3A3352);background:var(--surf2,#2A2440);font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.75rem;color:var(--dim,#A79DBE);flex-shrink:0}
.lg-notif-undo button{background:none;border:none;color:#20B9B9;font-weight:800;font-size:.78rem;cursor:pointer;font-family:inherit;padding:6px 4px;min-height:32px}
.lg-notif-undo button:hover{text-decoration:underline}
.lg-notif-undo button:focus-visible{outline:3px solid var(--accent,#7C5CBF);outline-offset:2px;border-radius:6px}
.lg-bell-hdr-actions{display:flex;align-items:center;gap:10px;flex-shrink:0}
/* "Dọn đã xem": nhạt tới khi hover — hành động phá không được to tiếng hơn */
/* hành động chính ("Đánh dấu tất cả đã xem"). */
.lg-bell-clear{background:none;border:none;color:var(--dim,#A79DBE);font-size:.74rem;font-weight:700;cursor:pointer;padding:2px 0;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;letter-spacing:.01em;flex-shrink:0}
.lg-bell-clear:hover{color:#E07856;text-decoration:underline}
.lg-bell-clear:focus-visible{outline:3px solid var(--accent,#7C5CBF);outline-offset:2px;border-radius:6px}
@media (prefers-reduced-motion:reduce){.lg-notif-row,.lg-notif-x,.lg-notif-item{transition:none}}
.lg-notif-ico{font-size:1rem;flex-shrink:0;margin-top:1px;width:20px;text-align:center}
/* Vá review 28/09 (Opus, vòng 2): item.icon giờ có thể là SVG (lgEmojiIcon()
   khớp, xem shared/chrome.js bellRenderItem) thay vì emoji thô — icon SVG
   dựng cố định width/height="16" (thuộc tính HTML) nên KHÔNG tự co theo
   font-size như emoji text. Ép width/height=1em để đi CÙNG font-size của
   .lg-notif-ico (1rem chưa đọc / .85rem khi đã đọc, xem rule --read dưới) —
   nếu không, icon SVG sẽ đứng yên 16px trong khi khối chữ bên cạnh co lại,
   lệch hàng dọc so với item emoji thô. */
.lg-notif-ico .lg-ic{width:1em;height:1em}
.lg-notif-body{display:block;flex:1;min-width:0}
.lg-notif-title{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:.85rem;font-weight:600;line-height:1.3;color:var(--text,#EEEAF6)}
.lg-notif-sub{display:block;font-size:.74rem;color:var(--dim,#A79DBE);margin-top:2px;line-height:1.3;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lg-notif-dot{width:7px;height:7px;border-radius:50%;background:#20B9B9;flex-shrink:0;margin-top:6px}
/* Read (already clicked) items collapse to a single dim line + ✓ ── */
.lg-notif-item--read{padding:5px 8px;align-items:center;opacity:.55}
.lg-notif-item--read:hover{opacity:1}
.lg-notif-item--read .lg-notif-ico{font-size:.85rem;margin-top:0;width:16px}
.lg-notif-item--read .lg-notif-title{display:block;font-size:.79rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lg-notif-item--read .lg-notif-sub{display:none}
.lg-notif-seen{flex-shrink:0;font-size:.72rem;font-weight:800;color:#20B9B9;line-height:1}
/* "+N mục đã xem" expander per section ─────────────────────────── */
.lg-notif-more{display:block;width:100%;text-align:left;background:none;border:none;padding:5px 8px;border-radius:8px;cursor:pointer;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.72rem;font-weight:700;color:var(--dim,#A79DBE);transition:background .12s,color .12s}
.lg-notif-more:hover{background:var(--surf2,#2A2440);color:#20B9B9}
.lg-notif-empty{padding:24px 8px;text-align:center;color:var(--dim,#A79DBE);font-size:.85rem;line-height:1.6;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif}
.lg-notif-empty-icon{font-size:1.8rem;margin-bottom:4px}
/* Section header inside the bell panel (TASK 9 grouping) ───────── */
/* Sticky so the student always knows which block they're scrolling in. */
.lg-notif-group-hdr{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:6px;padding:9px 6px 5px;background:var(--surf,#221D33);font-size:.68rem;font-weight:700;color:var(--dim,#A79DBE);text-transform:uppercase;letter-spacing:.04em;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif}
.lg-notif-group-hdr:first-child{padding-top:4px}
.lg-notif-group-n{font-size:.66rem;font-weight:700;color:var(--dim,#A79DBE);opacity:.7;letter-spacing:0}
[data-theme="light"] .lg-notif-group-hdr{color:#4a5568;background:#fff}
.lg-bell-footer{padding:7px 4px;border-top:1px solid var(--border,#3A3352);text-align:center;flex-shrink:0}
.lg-bell-footer a{font-size:.75rem;color:var(--dim,#A79DBE);text-decoration:none;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif}
.lg-bell-footer a:hover{color:#20B9B9;text-decoration:underline}
[data-theme="light"] .lg-bell-panel{background:#fff;border-color:#e0e3eb;box-shadow:0 6px 24px rgba(0,0,0,.12)}
[data-theme="light"] .lg-notif-item{color:#1a1a2e}
[data-theme="light"] .lg-notif-row:hover{background:#f5f5fa}
[data-theme="light"] .lg-notif-row--unread{background:rgba(32,185,185,.06)}
[data-theme="light"] .lg-notif-x{color:#4a5568}
[data-theme="light"] .lg-notif-undo{background:#f5f5fa;border-color:#e0e3eb;color:#4a5568}
[data-theme="light"] .lg-bell-clear{color:#4a5568}
[data-theme="light"] .lg-notif-title{color:#1a1a2e}
[data-theme="light"] .lg-notif-sub{color:#4a5568}
[data-theme="light"] .lg-bell-tab{color:#4a5568}
[data-theme="light"] .lg-bell-tab:hover{background:#f5f5fa}
[data-theme="light"] .lg-bell-tab.is-active{background:rgba(32,185,185,.14);color:#12908f}
[data-theme="light"] .lg-notif-more{color:#4a5568}
[data-theme="light"] .lg-notif-more:hover{background:#f5f5fa;color:#12908f}
[data-theme="light"] .lg-bell-tabs{border-color:#e0e3eb}
[data-theme="light"] .lg-bell-panel-hdr{border-color:#e0e3eb}
[data-theme="light"] .lg-bell-footer{border-color:#e0e3eb}
/* Header 2 hàng khi hẹp ────────────────────────────────────────── */
/* Mốc 860 chứ không phải 600: dãy chip đầy đủ (Ôn/🔥/🏆/⚡/chuông/⭐ + */
/* avatar) rộng ~830px — iPad dọc 768px và cửa sổ desktop hẹp cũng tràn. */
/* display:contents thả stats/tools thành flex item của header: hàng 1 = */
/* logo + tools (margin-left:auto), hàng 2 = stats cuộn ngang. Nhánh */
/* :not(:has(…)) chỉ dọn hàng 2 khi mọi chip đều hidden (khách vãng lai); */
/* trình duyệt cũ không có :has thì hàng rỗng cao 0px + 6px margin — vô hại. */
@media (max-width:860px){
.lg-header-mount{flex-wrap:wrap}
.lg-hdr-r{display:contents}
.lg-hdr-tools{margin-left:auto}
.lg-hdr-stats{flex:1 1 100%;order:3;margin-top:6px;overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.lg-hdr-stats::-webkit-scrollbar{display:none}
.lg-hdr-stats > *{flex:0 0 auto}
.lg-hdr-stats:not(:has(> :not([hidden]))){display:none;margin:0}
/* Khách vãng lai: slot .lg-pre vô hình bằng visibility nên vẫn CHIẾM CHỖ — */
/* trước đây vô hại (1 hàng), giờ sẽ thành hàng 2 trống 50px nếu không dọn. */
/* (updatePointsChip bỏ hidden của chip 🏆 kể cả khi chưa đăng nhập, nên */
/* không trông cậy được vào nhánh :has ở trên.) */
.lg-hdr-r.lg-pre .lg-hdr-stats{display:none;margin:0}
}
/* Mobile ───────────────────────────────────────────────────────── */
@media (max-width:600px){
.lg-header-mount{padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));padding-left:calc(14px + env(safe-area-inset-left,0px));padding-right:calc(14px + env(safe-area-inset-right,0px))}
/* gap 8px: hàng 1 (logo + 4 nút 44px) vừa khít 320px (iPhone SE cũ) */
.lg-hdr-tools{gap:8px}
.lg-user-name{display:none}
.lg-renew-chip{display:none!important}
/* Bottom sheets: +env(safe-area-inset-bottom) keeps the last row (logout / */
/* bell footer) above the iPhone home indicator; dvh cap + internal scroll */
/* keeps them inside the *visible* viewport (incl. landscape). */
.lg-dd{left:8px!important;right:8px!important;top:auto!important;bottom:calc(8px + env(safe-area-inset-bottom,0px))!important;min-width:0;border-radius:14px;padding:8px;max-height:calc(100dvh - 24px);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
.lg-dd-item{padding:14px 14px;font-size:.95rem;min-height:44px}
.lg-theme-inline{width:44px;height:44px;font-size:16px}
/* Màn hẹp: thu pill "Trợ giúp" về hình tròn chỉ-icon (§B1 "≤600px được phép
   thu về chỉ icon ?, nhưng phải giữ aria-label" — aria-label/title vẫn còn
   nguyên trên <button>, chỉ ẩn chữ .lg-tour-label). */
.lg-tour-btn{width:44px;height:44px;padding:0;justify-content:center;border-radius:50%}
.lg-tour-label{display:none}
.lg-due-chip,.lg-renew-chip,.lg-upgrade-chip,.lg-points-chip,.lg-hw-chip{min-height:40px;padding:8px 14px}
.lg-streak-chip{min-height:40px;padding:8px 12px}
.lg-user-pill{min-height:40px;padding:6px 10px 6px 5px}
.lg-tier-avatar{width:26px;height:26px}
.lg-bell-btn{width:44px;height:44px;font-size:17px}
.lg-bell-mark-all{padding:10px 4px}
.lg-bell-clear{padding:10px 4px}
/* Vùng bấm ≥ 44px trên mobile (luật mobile-ux-sweep-2026-08). */
.lg-notif-x{width:44px;height:44px}
.lg-bell-panel{left:8px!important;right:8px!important;top:auto!important;bottom:calc(8px + env(safe-area-inset-bottom,0px))!important;width:auto;max-width:none;max-height:min(78vh,640px);max-height:min(78vh,78dvh,640px);border-radius:16px}
.lg-notif-item{padding:10px 8px}
.lg-notif-item--read{padding:7px 8px}
}
/* Cảm ứng, MỌI bề rộng ─────────────────────────────────────────── */
/* ⚠ Khối ≤600px ở trên KHÔNG với tới điện thoại xoay ngang: từ khi bỏ khoá */
/* orientation:portrait, iPhone nằm ngang rộng 667–932px nên rơi ra ngoài, */
/* và mọi nút header tụt về 32px giữa lúc ngón tay vẫn là ngón tay. Không */
/* gộp cả khối trên vào `(pointer:coarse)` được, vì trong đó có cả những thứ */
/* chỉ đúng cho màn HẸP (ẩn tên người dùng, dropdown biến thành bottom */
/* sheet) — tablet không cần. Nên tách riêng đúng phần cỡ chạm + lề an toàn */
/* trái/phải (khi xoay ngang, tai thỏ nằm ở CẠNH BÊN). */
/* `pointer` là con trỏ CHÍNH ⇒ laptop Windows cảm ứng (chuột là chính) */
/* không dính. */
@media (pointer:coarse){
.lg-header-mount{padding-top:calc(10px + env(safe-area-inset-top,0px));padding-left:calc(14px + env(safe-area-inset-left,0px));padding-right:calc(14px + env(safe-area-inset-right,0px))}
.lg-theme-inline{width:44px;height:44px;font-size:16px}
/* Pill "Trợ giúp" giữ chữ ở màn CHẠM RỘNG (tablet, iPhone ngang > 600px) —
   chỉ đổi vùng bấm lên ≥44px cao, không ép về hình tròn chỉ-icon (đó là
   việc riêng của khối ≤600px ở trên). */
.lg-tour-btn{min-height:44px}
.lg-bell-btn{width:44px;height:44px;font-size:17px}
.lg-dd-item{min-height:44px}
.lg-user-pill{min-height:40px}
.lg-due-chip,.lg-upgrade-chip,.lg-points-chip,.lg-hw-chip{min-height:40px}
.lg-streak-chip{min-height:40px}
}

/* ══════════════════════════════════════════════════════════════════════
   B2 (HUD R3 M2) — Utility khung ngang `.lg-shell` / `.lg-rail`.
   Nguồn: rl/index.css:865-904 `.hub-rail` (khuôn mẫu đã sửa đúng ở M1) —
   chép ra đây làm bản DÙNG CHUNG cho M3–M7. ⛔ KHÔNG áp vào app nào ở M2
   (SPEC_HUD_R3_M2.md §B2) — chỉ định nghĩa + demo (scripts/hud-shell-demo.html).

   Luật cột phụ (SPEC_HUD_OVERHAUL_R3_2026_09_19.md §1.1):
     TRÁI (mặc định, .lg-shell)       = em đang CHỌN (điều hướng/lọc/mục lục)
     PHẢI (.lg-shell--right)          = em đang LÀM (ngữ cảnh/tra cứu)
     một màn chỉ có MỘT cột phụ.
   Khung ngang chỉ bật từ 1024px (§1.3 — không thêm breakpoint mới; chỉ 1024
   và 1280 được dùng ở đây). Dưới 1024px: một cột, .lg-rail hết sticky, xếp
   xuống dưới nội dung chính (không bao giờ ép 2 cột trên điện thoại). */
/* Mobile-first mặc định: MỘT cột, dùng flex(column) — không phải display:block
   trần — để `order` ép .lg-rail luôn đứng SAU nội dung chính bất kể nó nằm
   trước hay sau trong DOM (TRÁI hay PHẢI, app đặt <aside class="lg-rail">
   trước hay sau <main> đều ra cùng một kết quả dưới 1024px: "cột phụ xếp
   xuống dưới nội dung chính", §1.1). Nội dung chính (không mang class
   .lg-rail) giữ order mặc định = 0. */
/* ══ §8 HIẾN CHƯƠNG BỐ CỤC (chốt 20/09, vòng QA #4 việc #1 + #6) ══
   Trước ngày này mỗi app tự chọn trần khung: đo thật trên preview ở 1707px
   ra BẢY con số (984 · 1000 · 1100 · 1140 · 1180 · 1200 · 1320 · 1560),
   BỐN bề rộng cột phụ (240 · 278 · 280 · 288) và BẢY bề rộng cột giữa
   (680 · 720 · 860 · 880 · 898 · 1008 · 1248). Đổi app là cả khối nội dung
   nhảy chỗ: mép trái cột phụ dịch tới 168px, cột giữa đổi tới 568px. Chủ app
   nhìn ra ngay ("Grammar Gym khác các app khác").
   Nay chỉ còn HAI biến, và trần khung là HỆ QUẢ của chúng:
     --lg-rail-w  bề rộng cột phụ — MỘT giá trị cho mọi app
     --lg-main-w  bề rộng cột giữa — ĐÚNG HAI nấc (880 / .lg-shell--wide 1160)
   ⛔ App KHÔNG viết `max-width` cho .lg-shell nữa. Cần rộng hơn thì thêm
      class .lg-shell--wide; cần khác nữa thì sửa hiến chương ở ĐÂY, một chỗ.
      Cổng canh: scripts/shell-geometry.test.mjs. */
:root{
  --lg-rail-w: clamp(240px, 18vw, 288px);
  /* §R22b — bề rộng cột phụ mà TRẦN KHUNG tính theo. Mặc định BẰNG
     --lg-rail-w; chỉ HỆ THỐNG đổi nó (thu cột phụ), người dùng kéo thì
     không. Xem chú thích dài ở rule .lg-shell.lg-shell bên dưới. */
  --lg-rail-frame: clamp(240px, 18vw, 288px);
  --lg-main-w: 880px;
  --lg-shell-pad: 16px;
  /* 24/09 — ĐỈNH KHUNG chung (≥1024px): khoảng từ mép dưới header tới đỉnh
     cột phụ và đỉnh cột giữa. Đo 24/09 ở 1280px trước khi có token: đỉnh cột
     phụ của 16 trang rải từ 68 tới 116px vì mỗi app tự đệm (.wrap 18/24/26px,
     main 20px, margin 24px…) ⇒ đổi app là cột phụ nhảy lên xuống.
     Cổng canh: scripts/app-layout-top.test.mjs.
     24/09 (QA mobile) — dưới 1024px token là 16px, từ 1024px là 24px (khối
     @media ngay sau :root). Đo 390px trước khi có bản này: đệm đỉnh 14/16/
     18/24px và lề ngang 12/13/14/16/18px tuỳ app ⇒ đổi app là chữ nhích. */
  --lg-shell-top: 16px;
  /* §R24 (22/09) — lề giữ cho TAY NẮM mở lại cột phụ khi đã thu. BẰNG khe
     24px giữa hai cột ⇒ thu cột phụ thì cột giữa nở ra đúng bằng bề rộng
     cột phụ, và chữ không bao giờ chạy xuống dưới tay nắm. */
  --lg-rail-handle: 24px;
  /* ══ §R08b — CỠ CHỮ TIÊU ĐỀ: MỘT TOKEN CHO MỌI APP (chốt 20/09, QA#6 #1)
     Chủ app: *"Unify cỡ chữ ở các app lại giống nhau luôn"*.
     Vòng #5 tôi mới thống nhất VỊ TRÍ (góc trên trái cột giữa) và ghi lại
     rằng cỡ chữ vẫn lệch, chờ chủ app chốt. Đo 20/09 ở 1440px, tiêu đề
     đứng đầu cột giữa của từng app:
        .dd-title    clamp(2rem,6vw,3rem)     → 48,0px   Draft Doctor
        .hw-title    clamp(1.8rem,5vw,2.6rem) → 41,6px   Score Sprint
        .page-title  1.7rem                   → 27,2px   Speaking · BTVN
        .page-title  1.65rem                  → 26,4px   Pron · Sonic · Songs
        .dd-h2       1.6rem                   → 25,6px   Draft Doctor (màn con)
        .gym-title   1.5rem                   → 24,0px   Grammar Gym
     Chênh 2 LẦN giữa app cao nhất và thấp nhất — đổi app là cỡ chữ nhảy.
     `clamp` ở đây thay cho 4 bản @media `.page-title{font-size:1.35rem}` rải
     rác 3 file: một đường cong liên tục, KHÔNG thêm mốc breakpoint nào (§1.3).
     ⛔ App KHÔNG viết font-size bằng số cho tiêu đề nữa — dùng token.
        Cổng canh: scripts/app-title.test.mjs. */
  --lg-title: clamp(1.4rem, 1.1rem + 1.2vw, 1.7rem);
  --lg-title-sub: .92rem;
}
@media (min-width:1024px){ :root{ --lg-shell-top: 24px } }
/* §R08b — TIÊU ĐỀ CỘT GIỮA là primitive dùng chung, không phải chép 7 lần.
   Trước 20/09 `.page-title`/`.page-sub` được khai LẠI trong 6 file CSS app +
   <style> của Graded Reader, mỗi bản một cỡ. Nay một bản ở đây; app chỉ còn
   giữ rule MÀU của <em> trong tiêu đề (thứ thật sự khác nhau theo app).
   Ghi ở shared/chrome.css nên nó nạp TRƯỚC CSS của app — app vẫn đè được khi
   thật sự cần, nhưng cổng scripts/app-title.test.mjs sẽ bắt. */
/* Font display (A1) KHÔNG áp cho phòng thi: html.lg-exam-frozen tự đặt
   --font-ui-family = --font-display-family = --font-exam-family (theme.css),
   nên nếu .page-title khai font-family ở đây nó lặp font thi (Space + Grotesk)
   2 lần trong computed style — vô hại về hình (cùng 1 font) nhưng exam-freeze.test.mjs
   so ĐÚNG-TỪNG-KÝ-TỰ nên bắt là "phòng thi đổi màu". Tách riêng, loại trừ
   html.lg-exam-frozen, để trang thi giữ NGUYÊN font kế thừa từ body (24/09). */
html:not(.lg-exam-frozen) .page-title{
  font-family: var(--font-display-family,'Bricolage Grotesque'), var(--font-ui-family,'Be Vietnam Pro'), system-ui, sans-serif;
  letter-spacing: -.015em; /* design-v2 A1 — tiêu đề cột giữa dùng font display */
}
.page-title{
  font-size: var(--lg-title); font-weight:700; line-height:1.25;
  text-align:left;            /* §R08 — góc trên TRÁI cột giữa, chốt Đ1 */
  margin:8px 0 6px;
  /* ⛔ KHÔNG THẪ— đo 20/09 trên preview đã đăng nhập, Vocab Vault màn
     "Tiến độ": `.lg-shell-main` ở đó là một LƯỚI 2 cột thẻ, nên tiêu đề rơi
     vào ô 1 còn dòng phụ rơi vào ô 2 — chúng đứng CẠNH NHAU (tiêu đề x=296,
     dòng phụ x=770) thay vì xếp chồng. Primitive dùng chung phải tự đứng
     đúng trong CẢ HAI kiểu cột giữa (khối thường và lưới); `grid-column` là
     thuộc tính bị BỬe QUA khi cha không phải grid, nên không hại gì chỗ khác. */
  grid-column: 1 / -1;
}
.page-title em{ font-style:normal; color:var(--accent-text) }
.page-sub{
  font-size: var(--lg-title-sub); color:var(--text2,#CEC7DD);
  line-height:1.55; margin:0 0 18px;
  grid-column: 1 / -1;        /* xem chú thích ở .page-title */
}
.lg-shell{
  /* ⛔ KHÔNG THỪA. Hai trang (Grammar Gym, Score Sprint) KHÔNG có luật
     `*{box-sizing:border-box}` toàn cục, nên `max-width` ở đó tính theo
     content-box và lề ngang CỘNG THÊM ra ngoài. Đo 20/09: hai trang đó rộng
     1256px và 1536px trong khi công thức nói 1224px và 1504px — lệch đúng
     2×16px, và mép trái cột phụ lệch 16px so với 8 trang còn lại. Chuẩn hoá
     ở đây thì hiến chương đúng bất kể trang có reset toàn cục hay không.
     (Đồng thời chặn một lỗi thật: content-box + width:100% + lề ngang là
     tràn ngang 32px ở mọi bề ngang chưa chạm trần.) */
  box-sizing:border-box;
  display:flex;
  flex-direction:column;
  max-width:1560px;
  /* ⛔ THÊM 19/09 (M5) — `width:100%` KHÔNG thừa, thiếu nó là tràn ngang
     THẬT. Khi .lg-shell là flex item của một cha `display:flex;
     flex-direction:column` (vocab/.screen.active là ca đầu gặp), auto-margin
     trên trục ngang làm trình duyệt dùng shrink-to-fit thay vì stretch. Mà
     shrink-to-fit vẫn cộng max-content của TỪNG item trong các dải tab con
     vào bề rộng ưa thích — kể cả khi dải đó đã có `overflow-x:auto`, vì
     overflow chỉ có tác dụng khi CHA đã có bề rộng xác định. Đo thật ở
     390px trước khi vá: #sHome dư 223px, #sWordList dư 353px.
     Cho một bề rộng XÁC ĐỊNH thì margin:auto chỉ còn việc căn giữa khi màn
     rộng hơn 1560px, đúng ý đồ ban đầu. */
  width:100%;
  margin:0 auto;
}
/* Cột CHÍNH trong khung ngang. min-width:0 KHÔNG thừa: cột 1fr của grid mặc
   định là min-width:auto, nên một bảng/từ dài không ngắt được sẽ đẩy cả cột
   rộng ra và làm tràn ngang cả trang. App nào muốn giới hạn bề rộng đọc-tốt
   thì đặt thêm max-width trên chính .lg-shell-main của nó. */
/* Trần bề rộng đọc-tốt nay lấy từ hiến chương, không phải app tự khai.
   `margin-inline:auto` tự triệt tiêu khi khung vừa khít (trần khung = cột
   giữa + khe + cột phụ, xem trên) và chỉ có tác dụng ở màn .lg-shell--solo
   hoặc khi người dùng kéo cột phụ hẹp lại — đúng chỗ cần căn giữa. */
/* ⛔ `width:100%` KHÔNG thừa (đo 20/09, lỗi do chính tôi vừa tạo ra):
   `margin-inline:auto` trên một GRID ITEM tắt stretch mặc định và cho nó
   shrink-to-fit. Thiếu dòng này thì cột giữa co theo nội dung — đo trên
   Draft Doctor: 12 màn ra 148…844px thay vì 880px đồng loạt. */
.lg-shell-main{ min-width:0; width:100%; max-width:var(--lg-main-w); margin-inline:auto }
/* 24/09 (QA mobile) — LỀ NGANG + ĐỆM ĐỈNH của khung là của hiến chương ở MỌI
   bề ngang, không chỉ ≥1024 (rule cùng tên trong @media bên dưới giữ nguyên
   phần trần khung). (0,2,0) để thắng `.wrap` của app như ở §8.2; app đệm
   bằng #id hoặc bằng phần tử BỌC NGOÀI khung thì phải tự gỡ — cổng
   scripts/app-layout-top.test.mjs đo ở 390px sẽ bắt. */
.lg-shell.lg-shell{ padding-inline:var(--lg-shell-pad); padding-top:var(--lg-shell-top); margin-top:0;
  /* 24/09 (QA tablet) — TRẦN KHUNG dưới 1024px cũng của hiến chương. Trước đó
     `.wrap{max-width}` của từng app còn hiệu lực: đo ở 1000px (iPad ngang) ra 5
     bề rộng — 985 (5 app) · 860 (8 app) · 760 Pron · 720 Thông báo; tiêu đề x=16
     tới x=148. Nay = cột giữa desktop + lề ⇒ ≤912px mọi app tràn mép như điện
     thoại, >912px mọi app căn giữa cùng một bề rộng, lên desktop không đổi độ
     dài dòng. Rule trong @media ≥1024 bên dưới (cùng tên, đứng sau) đè lại.
     ⚠ 880px viết SỐ, không dùng var(--lg-main-w): .lg-shell--wide gán lại biến
     đó = 1160 ⇒ 4 app nấc rộng lại tràn 985px (đo lần đầu của bản vá này). */
  max-width:calc(880px + 2 * var(--lg-shell-pad)); margin-inline:auto }
/* 24/09 (QA mobile) — TIÊU ĐỀ APP LUÔN ĐỨNG ĐẦU, trước dải chọn chế độ.
   §R09d kéo khối [data-lg-nav] lên đầu (order:-1) ⇒ ở 10/16 app tiêu đề bị đẩy
   xuống 121–214px, Bảng xếp hạng 435px; 6 app còn lại tiêu đề ở đầu. Nay:
   tiêu đề -2 · dải chọn -1 · nội dung 0 · phần còn lại của cột phụ 2.
   App bọc tiêu đề + dòng phụ trong `.lg-page-head`, và ở <1024px cho các lớp
   bọc giữa khung và nó `display:contents` (CSS của app — mốc 1023.98 không
   thuộc hiến chương, §1.3). ≥1024px cột giữa là một ô lưới nên `order` của
   phần tử con không có tác dụng gì. */
.lg-shell .lg-page-head{ order:-2 }
/* ══ art v3 25/09, bước 3 — đầu trang app, HUD-R49/R50 ══
   Dựng bởi lgPageHeadDecorate() (shared/chrome.js): thêm class --v3, chèn
   hoạ tiết nền .lg-ph-pattern (SVG DOM thật, KHÔNG phải background-image —
   xem lý do "màu trong <pattern> phải tường minh" ở R50) và ống ngắm
   .lg-ph-lens (art LOGI hoặc icon duotone khi app không có kỹ năng).
   --ph-hue đặt bằng JS = var(--hue-<skill>) (tham chiếu THẲNG token đã có ở
   theme.css, không tự chép hex riêng nữa — vá adversarial review m1: bảng
   hex JS cũ (LG_PH_HUE_HEX) là nguồn lệch tiềm ẩn, giờ không còn tồn tại).
   App không có kỹ năng thì biến này KHÔNG được đặt ⇒ mọi chỗ dùng rơi về
   --accent (phớt accent, không vỡ hình, đúng "Apps không có kỹ năng"). */
/* ⚠ Chọn CẢ HAI lớp (.lg-page-head.lg-page-head--v3 — specificity 0,2,0),
   KHÔNG chỉ --v3 một mình. 3/13 app tự đặt thêm một class riêng cùng hàng
   (writing .dd-hero{padding:0}, leaderboard .lb-title-row{display:flex},
   listening .ss-head) trong CSS của APP, nạp SAU shared/chrome.css — cùng
   specificity (0,1,0) thì luật nạp sau thắng, padding của mẫu bị đè về 0
   (đo trên writing/index.css dòng 65 lúc build). Hai lớp gộp thắng bất kể
   thứ tự nạp, không cần !important.
   ⛔ VÁ adversarial review M1: bản đầu còn ép `display:flex;flex-direction:
   column;justify-content:center` ở CHÍNH rule này — leaderboard đặt SẴN
   `.lb-title-row{display:flex}` (title + link "Bảng vàng" + nút refresh xếp
   HÀNG NGANG, index.css dòng 23), rule --v3 đè flex-direction:column làm ba
   phần tử đó xếp CHỒNG dọc, canh giữa dưới tiêu đề — vỡ layout riêng của
   leaderboard. KHÔNG set display/flex-direction/justify-content ở đây nữa —
   chỉ min-height + padding, để layout riêng của app (nếu có) tự quyết, và
   margin sẵn có của .page-title/.page-sub (8px 0 6px / 0 0 18px) tự xếp
   nhịp dọc như app không có v3. */
/* QA 26/09 (item 2): min-height 190px để trống nửa dưới ở app chữ ngắn
   (Pron, Homework, Account…) — chữ thật chỉ cao ~90-100px. Hạ xuống ~136px
   (đo: 16px lề trên + ống ngắm 96px + 8px hở + 1 dòng huy hiệu ~16px ≈
   136px) — vẫn là SÀN, nội dung dài hơn (2 dòng .page-sub) tự đẩy cao hơn
   qua flow bình thường, không cần sửa gì thêm. */
.lg-page-head.lg-page-head--v3{
  position:relative;overflow:hidden;isolation:isolate;
  border-radius:28px;min-height:136px;box-sizing:border-box;
  padding:24px 140px 24px 24px;
  border:1px solid color-mix(in srgb, var(--ph-hue,var(--accent)) 28%, transparent);
  background:color-mix(in srgb, var(--ph-hue,var(--accent)) 9%, var(--surf));
}
:root[data-theme="dark"] .lg-page-head.lg-page-head--v3,:root:not([data-theme="light"]) .lg-page-head.lg-page-head--v3{
  background:color-mix(in srgb, var(--ph-hue,var(--accent)) 15%, var(--surf));
}
/* m5 — MỌI con trực tiếp (trừ hoạ tiết nền) phải nổi trên pattern, không chỉ
   .page-title/.page-sub: leaderboard có link "Bảng vàng"/nút refresh, Pron
   Coach có #pcPreviewNote… những phần tử này TRƯỚC đây bị vẽ DƯỚI các chấm
   (z-index:auto ⇒ thắng theo THỨ TỰ DOM, mà .lg-ph-pattern chèn đầu tiên
   qua insertAdjacentHTML('afterbegin') nên mọi node CŨ đứng sau nó trong
   cây — nhưng node MỚI/re-render có thể đổi thứ tự, không đáng tin). */
/* ⚠ VÁ tự QA (đo bằng getComputedStyle sau khi viết m5): `.lg-page-head.
   lg-page-head--v3 > :not(...)` có specificity (0,3,0) — THẮNG cả
   `.lg-ph-lens{position:absolute}` (0,1,0) dù rule đó viết SAU trong file,
   nên lens/badge/line bị ép NGƯỢC về position:relative, .lg-ph-badge{width:
   108px} hết tác dụng (width chỉ ăn trên box đã blockify bởi absolute) ⇒
   chữ tràn ra ngoài không xuống dòng. `:where()` ở phần cha đưa specificity
   phần cha về 0, chỉ còn `:not(.lg-ph-pattern)` = một lớp (0,1,0) — HOÀ với
   .lg-ph-lens/-badge/-line, mà ba rule đó viết SAU (bên dưới) nên thắng hoà,
   giữ đúng position:absolute của chúng. */
:where(.lg-page-head.lg-page-head--v3) > :not(.lg-ph-pattern){ position:relative;z-index:1 }
.lg-ph-pattern{ position:absolute;inset:0;width:100%;height:100%;z-index:0;pointer-events:none }
/* Màu hoạ tiết nền: HÌNH dựng bằng JS (lgPhPatternSvg — chỉ gắn class,
   không gắn màu), MÀU đọc từ CSS ở đây. Sửa m1: JS cũ tính sẵn hex theo
   theme rồi ghi CỨNG vào từng phần tử — chỉ "làm mới" khi
   lgPageHeadDecorate() chạy lại (nghe 'themechange'), mà theme.js/
   account/account.js đổi data-theme KHÔNG bắn sự kiện đó ⇒ hoạ tiết kẹt
   màu theme cũ sau khi bấm đổi theme ở /account/, còn .lg-empty thì
   KHÔNG BAO GIỜ tự vẽ lại (chỉ dựng một lần). CSS thì tự áp lại ngay khi
   `data-theme` đổi — không cần nghe sự kiện gì cả. Pattern nằm NGAY TRONG
   .lg-page-head/.lg-empty nên `var(--ph-hue)` kế thừa xuyên DOM bình
   thường tới tận nội dung <pattern> (R50 chỉ cấm `currentColor` VỀ TỪ
   PHÍA phần tử tham chiếu `fill="url(#id)"`, không cấm var() kế thừa dọc
   cây DOM thật — pattern này định nghĩa ngay tại chỗ dùng, không phải
   import từ nơi khác). */
.lg-ph-pattern .lg-ph-p-ring{ fill:none;stroke:var(--ph-hue,var(--accent));stroke-opacity:.26;stroke-width:1.3 }
.lg-ph-pattern .lg-ph-p-dot{ fill:var(--ph-hue,var(--accent));fill-opacity:.32 }
.lg-ph-pattern .lg-ph-p-acc{ fill:var(--ph-hue,var(--accent));fill-opacity:.6 }
.lg-ph-pattern .lg-ph-p-star{ fill:#F2A93B;fill-opacity:.55 }
:root[data-theme="dark"] .lg-ph-pattern .lg-ph-p-ring,:root:not([data-theme="light"]) .lg-ph-pattern .lg-ph-p-ring{
  stroke:color-mix(in srgb, var(--ph-hue,var(--accent)) 55%, white);stroke-opacity:.34;
}
:root[data-theme="dark"] .lg-ph-pattern .lg-ph-p-dot,:root:not([data-theme="light"]) .lg-ph-pattern .lg-ph-p-dot{
  fill:color-mix(in srgb, var(--ph-hue,var(--accent)) 55%, white);fill-opacity:.4;
}
:root[data-theme="dark"] .lg-ph-pattern .lg-ph-p-acc,:root:not([data-theme="light"]) .lg-ph-pattern .lg-ph-p-acc{
  fill:color-mix(in srgb, var(--ph-hue,var(--accent)) 70%, white);fill-opacity:.6;
}
:root[data-theme="dark"] .lg-ph-pattern .lg-ph-p-star,:root:not([data-theme="light"]) .lg-ph-pattern .lg-ph-p-star{
  fill:#FFC857;
}
/* Đường chấm nối "logic" (HUD-R49, vá M3a — bản đầu bỏ sót hẳn). Toạ độ
   pixel THẬT do JS đo (lgPhNodeLineSvg), màu đọc --ph-hue qua `color`. */
.lg-ph-line{ position:absolute;height:12px;z-index:1;pointer-events:none;
  color:color-mix(in srgb, var(--ph-hue,var(--accent)) 55%, var(--text)) }
/* Sửa 26/09: left/right/top do JS đặt (lgPhPlaceNodeLine) — đường NGANG theo
   dòng tiêu đề, không còn chéo qua .page-sub. SVG không viewBox, x2="100%"
   ⇒ co giãn theo bề rộng mà không méo nét; overflow để chấm đầu mút không
   bị cắt nửa. */
.lg-ph-line > svg{ display:block;width:100%;height:12px;overflow:visible }
/* Ống ngắm (sight, HUD-R49): neo CỐ ĐỊNH cách đỉnh 20px (không canh giữa
   dọc nữa — bỏ theo cùng lý do M1: head không còn ép flex, chiều cao thật
   biến thiên theo nội dung app, neo tuyệt đối từ mép trên cho ổn định).
   Vòng nét đứt + ĐỦ 4 vạch chia vẽ bằng SVG thật (.lg-ph-lens-ring, vá
   M3b — bản đầu chỉ 2 vạch trên/dưới bằng ::before/::after, pseudo-element
   không đủ vẽ gọn 4 hướng quanh một vòng tròn). Co lại rồi ẨN dưới ~480px
   (R49 "mobile: lens shrinks/hides below ~480px"). */
/* QA 26/09 (item 2): 108px -> 96px cùng đợt hạ min-height (giữ tỉ lệ vòng
   trong/ảnh như bản cũ: 84/108=.778, 74/108=.685). */
.lg-ph-lens{
  position:absolute;right:16px;top:16px;
  width:96px;height:96px;z-index:1;
  color:color-mix(in srgb, var(--ph-hue,var(--accent)) 60%, var(--text));
  display:flex;align-items:center;justify-content:center;
}
.lg-ph-lens-ring{ position:absolute;inset:0;width:100%;height:100% }
.lg-ph-lens::after{
  content:'';position:absolute;width:75px;height:75px;border-radius:50%;z-index:-1;
  background:color-mix(in srgb, var(--ph-hue,var(--accent)) 22%, var(--surf));
}
.lg-ph-lens-img{ width:66px;height:66px;object-fit:contain;display:block;pointer-events:none;position:relative }
/* Scoped tới `.lg-ic` (icon duotone chèn bằng lgIcon()), KHÔNG phải mọi
   svg trong lens — lens KHÔNG-kỹ-năng chứa CẢ .lg-ph-lens-ring (vòng+vạch,
   phải giữ 108px) LẪN icon 28px; chọn nhầm cả hai bằng selector `svg` trần
   sẽ bóp luôn cái vòng. */
.lg-ph-lens--plain svg.lg-ic{ width:28px;height:28px;position:relative }
/* m2 — badge đứng DƯỚI lens một khoảng cố định (lens neo top:16px, cao
   96px ⇒ đáy ở 112px). QA 26/09 (item 2): min-height hạ còn 136px nên
   KHÔNG còn đủ chỗ cho 2 dòng như bản 190px cũ — ép 1 dòng + ellipsis
   (nhãn dài "Từ vựng · Vocabulary" vẫn đọc được phần đầu, đủ phân biệt
   kỹ năng; đây là khu trang trí phụ, không phải nội dung chính của trang). */
.lg-ph-badge{
  position:absolute;right:4px;top:120px;z-index:1;display:block;
  font-size:.7rem;font-weight:800;letter-spacing:.03em;text-transform:uppercase;
  color:color-mix(in srgb, var(--ph-hue,var(--accent)) 55%, var(--text));
  width:120px;text-align:center;line-height:1.2;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
@media (max-width:480px){
  .lg-ph-lens,.lg-ph-badge,.lg-ph-line{ display:none }
  .lg-page-head.lg-page-head--v3{ padding-right:24px;min-height:0 }
}
/* `.lg-lift` — lớp bọc giữa khung và .lg-page-head: ở <1024px nó nhường hộp
   (display:contents) để tiêu đề và các con khác thành con flex của khung.
   ⚠ NGOẠI LỆ CÓ CHỦ Ý với luật "mobile-first, không max-width:1023.98" (§R09d):
   viết mobile-first thì ở ≥1024 phải TRẢ LẠI display gốc, mà display gốc là
   của app (Tài khoản là flex, Vault #sProgress là grid) — hiến chương không
   biết để trả. Giới hạn bằng max-width thì ≥1024 app không bị đụng tới.
   ⚠ App tự ẩn/hiện lớp bọc bằng class (.dd-screen.active) thì KHÔNG gắn
   .lg-lift lên nó — rule này (0,2,0) thắng `.dd-screen{display:none}` và làm
   HIỆN màn đang ẩn. Viết rule riêng có kèm điều kiện hiện (writing/index.css).
   Ẩn bằng `.lg-pane-off`/`[hidden]` thì an toàn: hai cái đó là !important. */
@media (max-width:1023.98px){ .lg-shell .lg-lift:not([hidden]){ display:contents } }
/* Nấc RỘNG: lưới thẻ, bảng, kho đề, bàn làm việc nhiều cột. */
.lg-shell--wide{ --lg-main-w: 1160px }
/* ⛔ TẮT CỘT PHỤ Ở MỘT MÀN = class này, ĐỪNG tự viết display:none.
   Đo 20/09 trên Draft Doctor Studio: app nới trần khung lên 1140px cho bố
   cục nhiều cột, nhưng trần CỘT GIỮA vẫn 880px chặn trước ⇒ 260px chênh
   lệch không đi đâu cả, rơi hết về bên phải (trái 315px / phải 512px ở
   1707px). Khung rộng mà bố cục bên trong không hề rộng ra — nới trần là
   vô nghĩa, chỉ đẻ ra chỗ chết và làm màn lệch.
   Class này làm CẢ HAI việc cùng lúc nên không thể quên nửa nào. */
.lg-shell--solo > .lg-rail{ display:none }
/* ⛔ `hidden` TRÊN CỘT PHỤ PHẢI THẮNG MỌI `display:` — `!important` CÓ CHỦ Ý.
   `[hidden]{display:none}` của trình duyệt nằm ở stylesheet UA, nên BẤT KỲ
   rule tác giả nào có `display:` đều đè được nó. Đo 22/09 (cổng
   scripts/rail-hidden.test.mjs, 27/57 đỏ trước bản vá): ba rule như vậy làm
   cột phụ đã `hidden` vẫn HIỆN — trang chủ `#hubRail` từ 1280px (JS cố ý
   giữ ẩn), `.lg-shell--rail-off` (§R24 bản QA#5, dải mini `display:flex` —
   kể cả ở 390px; dải mini đã gỡ 22/09), và `.lg-rail--study` trong chế độ tập trung từ 1280px.
   Cùng tinh thần các `.lg-*-chip[hidden]` ở đầu file. */
.lg-rail[hidden]{ display:none !important }
/* QA 24/09 — Grammar Gym: cột phụ hiện chữ Times New Roman. grammar.css chỉ
   đặt font cho .gym-wrap (cột giữa); rail nằm NGOÀI khối đó nên kế thừa font
   mặc định của trình duyệt từ <body>. Rail là chrome dùng chung ⇒ tự mang
   font giao diện, không trông vào body của từng app; nút/ô nhập trong rail
   kế thừa theo (UA stylesheet không cho chúng kế thừa). */
.lg-rail{ font-family:var(--font-ui-family,'Be Vietnam Pro'), system-ui, sans-serif }
.lg-rail :is(button, input, select, textarea){ font-family:inherit }
.lg-shell > .lg-rail{
  order:2;
  /* Dưới 1024px: rail là khối thường, KHÔNG sticky. */
  position:static;
  max-height:none;
  overflow:visible;
  margin-top:24px;
}
@media (min-width:1024px){
  /* Bề rộng cột phụ là BIẾN, không phải số chết. Mặc định giữ nguyên con số
     cũ. App nào có cột phụ chở một bộ lọc thật rộng (vocab #sHome: 6 tab
     nguồn + ô tìm + select topic + danh sách passage) đặt lại `--lg-rail-w`
     trên chính .lg-shell đó — ĐỪNG viết đè `grid-template-columns` ở app, vì
     làm vậy là chép lại cả luật cột và sẽ lệch khi utility này đổi.
     ⚠ Nới cột phụ là LẤY BỚT của cột chính: nhớ nới `max-width` của shell
     tương ứng, nếu không cột chính bị bóp lại đúng bằng phần vừa cho thêm.
     Đo thật 19/09 trước khi có biến này: 6 tab nguồn của vocab mỗi tab rộng
     25px trong khi chữ cần 68–100px — chữ dồn thành một chữ cái + "…". */
  /* ⛔ RÃNH CỘT PHỤ LÀ `auto`, KHÔNG PHẢI MỘT CON SỐ. Đừng "sửa lại cho rõ".
     Trước 20/09 rãnh này khai thẳng clamp(). Khi app ẨN cột phụ bằng
     `display:none` (màn ĐANG LÀM — §1.4), phần tử duy nhất còn lại là cột
     giữa, và lưới xếp nó vào rãnh ĐẦU — tức đúng cái rãnh vừa dành cho cột
     phụ. Cột giữa co về BỀ RỘNG CỦA CỘT PHỤ.
     Đo thật 20/09 trên Draft Doctor (5 màn .dd-rail-off, trong đó có Test
     Room = bài thi thật và Free Writing): 1440px → cột giữa 880px tụt còn
     259px; 1280 và 1024px → 880/709px tụt còn 240px. Mất ~70% mặt bàn viết
     bài, không một lỗi nào báo ra, và cổng dd-shell vẫn xanh vì nó đọc
     max-width chứ không đo lưới.
     Chế độ tập trung ĐÃ tránh được bẫy này từ M5 bằng đúng mẹo dưới đây
     (xem [data-lg-focus="1"] .lg-shell ở cuối file) — nay đem lên rule gốc
     để KHÔNG APP NÀO PHẢI NHỚ: rãnh `auto` ôm đúng `width` tường minh của
     cột phụ khi nó còn đó, và co về 0 khi nó bị ẩn.
     (Đánh đổi đã biết: `gap` của rãnh rỗng vẫn còn ⇒ màn không-cột-phụ hụt
     24px so với bản một cột. 24px, đổi lấy 561px — chấp nhận.) */
  .lg-shell{
    display:grid;
    grid-template-columns: auto minmax(0,1fr);
    gap:24px;
    /* §8.2 — trần khung là HỆ QUẢ, không phải số tự chọn. Viết ở đây (trong
       @media ≥1024) chứ không ở rule nền: dưới 1024px khung là MỘT cột, cộng
       thêm bề rộng cột phụ vào trần là sai nghĩa. */
  }
  .lg-shell--right{ grid-template-columns: minmax(0,1fr) auto; }
  /* ⛔ VIẾT HAI LẦN CÙNG MỘT CLASS LÀ CÓ CHỦ Ý, đừng "rút gọn".
     Bốn app (Speaking · Pron Coach · Sonic Scribe · Vocab) đặt CẢ HAI class
     `wrap lg-shell` lên cùng một phần tử, mà `.wrap{max-width:760–860px}`
     của chúng nằm trong stylesheet nạp SAU file này. Cùng độ ưu tiên (0,1,0)
     ⇒ thứ tự nạp quyết ⇒ `.wrap` thắng. Đó đúng là lý do trước 20/09 mỗi app
     phải viết `#sHome .wrap.lg-shell{max-width:1200px}` (1,2,0) — tức là
     phải chép lại con số ở từng app, tức là việc #1.
     Đo 20/09 ngay sau khi gỡ các dòng đó mà CHƯA có mẹo này: Speaking và
     Sonic Scribe tụt về 860px, Pron Coach 760px (màn luyện âm 560px).
     (0,2,0) đủ thắng `.wrap` mà vẫn thua mọi selector có id, nên app nào
     thật sự cần khác vẫn ghi đè được. */
  /* ⛔ LỀ NGANG CŨNG THUỘC HIẾN CHƯƠNG. `max-width` tính theo border-box,
     nên lề ngang của chính khung ĂN vào cột giữa. Đo 20/09 khi mới có công
     thức mà chưa chuẩn hoá lề: cột giữa ra 844px ở Draft Doctor/Speaking/
     Pron Coach (lề 18px), 848px ở Vocab (16px), 880px ở Grammar Gym (0px)
     — tức là vẫn BA bề rộng khác nhau, đúng cái vừa định dẹp, chỉ nhỏ hơn.
     Chuẩn hoá lề rồi CỘNG nó vào công thức thì cột giữa đúng bằng
     --lg-main-w ở mọi app. `padding-inline` chỉ đụng trái/phải — lề trên/
     dưới vẫn của app. */
  /* ⛔ §R22b — TRẦN KHUNG tính theo `--lg-rail-frame`, KHÔNG theo `--lg-rail-w`.
     ĐỪNG "gọn lại" thành một biến.
     Chủ app 20/09 (QA#6, bổ sung): *"Resize gutter behavior không đồng nhất ở
     các screen, lúc thì kéo phần left panel theo chiều ngang, lúc thì đẩy nó
     hẳn về phía trái"*.
     Đo được, CÙNG một cửa sổ, cùng một cú kéo +60px:
       Draft Doctor #sMode (cột giữa 880) — khung chưa chạm mép màn (1195 <
         1440) nên nó NỞ RA +60. Khung căn giữa ⇒ mép trái lùi 30px: cột phụ
         RỘNG thêm 60 nhưng DỊCH TRÁI 30, cột giữa giữ nguyên 880 và dịch
         phải 30. Mép kéo chỉ đi được 30px trong khi chuột đi 60.
       Vocab #sHome (cột giữa 1160) — khung đã chạm mép màn (1393 ở 1440) nên
         không nở được: cột phụ +60, cột giữa −60, mép kéo đi ĐÚNG 60px.
     Cùng một thao tác, hai kết quả, chỉ khác nhau ở chỗ khung còn chỗ nở hay
     không — mà điều đó đổi theo TỪNG MÀN (880 hay 1160). Đúng là "không đồng
     nhất ở các screen".
     Nay tách làm hai vai:
       --lg-rail-frame  bề rộng cột phụ mà TRẦN KHUNG tính theo — do HỆ THỐNG
                        đặt (mặc định hiến chương, hoặc 52px khi thu).
       --lg-rail-w      bề rộng cột phụ THẬT — người dùng kéo được.
     Kéo chỉ đổi cái thứ hai ⇒ khung đứng yên, mép kéo luôn đi theo chuột,
     giống nhau ở mọi màn. Thu cột phụ đổi CẢ HAI ⇒ khung vẫn co lại 236px và
     cột giữa vẫn đúng 880px như chủ app đã duyệt ở vòng #5. */
  .lg-shell.lg-shell{
    padding-inline: var(--lg-shell-pad);
    /* 24/09 — đệm đỉnh do hiến chương đặt, không phải .wrap của từng app
       (xem --lg-shell-top). margin-top:0 vì Tài khoản từng đẩy khung bằng
       margin còn app khác bằng padding — cộng hai thứ là lệch 24px. */
    padding-top: var(--lg-shell-top);
    margin-top: 0;
    max-width: calc(var(--lg-main-w) + 24px + var(--lg-rail-frame, var(--lg-rail-w)) + 2 * var(--lg-shell-pad));
  }
  /* Không còn cột phụ ⇒ khung co về đúng cột giữa ⇒ cột giữa ở CHÍNH GIỮA.
     Hai đường vào: màn tự tắt bằng .lg-shell--solo, hoặc khung vốn không có
     cột phụ nào (`:has()` — mọi trình duyệt đích đã hỗ trợ, cùng mức với
     `body:has()` mà writing/index.css đã dùng từ M6). */
  /* ⛔ `column-gap:0` KHÔNG thừa. Khe 24px là khe GIỮA HAI CỘT; không còn
     cột phụ thì nó là một rãnh rỗng nằm bên trái cột giữa, và nó ĂN của cột
     giữa đúng 24px: đo 20/09 sau khi thu cột phụ — cột giữa ra 856px trong
     khi màn anh em còn cột phụ là 880px. Bấm nút thu là mặt bàn nhảy 24px,
     đúng thứ §7.3 cấm. (Ghi chú cũ ở khối lưới bên trên gọi đây là "đánh
     đổi đã biết" — nay không phải đánh đổi nữa vì đã có class tường minh
     cho đúng trạng thái này.) */
  /* ⚠ `.lg-shell--rail-off` KHÔNG thuộc nhóm này (và từ 22/09 càng không):
     thu cột phụ thì TRẦN KHUNG ĐỨNG YÊN và cột giữa NỞ RA lấp chỗ — xem
     §R24 (bản 22/09) ở khối §9. Đưa nó vào đây là khung co lại, tức đúng
     cái "không thêm chỗ cho cột giữa" mà chủ app vừa gạt. */
  .lg-shell--solo.lg-shell--solo,
  .lg-shell--rail-hidden.lg-shell--rail-hidden,
  .lg-shell--rail-bare.lg-shell--rail-bare,
  /* `:not([hidden])` (22/09): cột phụ đang `hidden` cũng là "không có cột
     phụ". Thiếu nó thì `:has(> .lg-rail)` khớp cả rail đã ẩn ⇒ khung vẫn chừa
     khe 24px bên trái cột giữa cho tới khi JS (lgRailWatchVisibility) đo xong
     và gắn --rail-hidden — tức một cú nháy 24px, hoặc mãi mãi nếu JS không chạy. */
  .lg-shell.lg-shell:not(:has(> .lg-rail:not([hidden]))){
    max-width: calc(var(--lg-main-w) + 2 * var(--lg-shell-pad));
    column-gap: 0;
  }
  /* Bề rộng chuyển từ RÃNH sang chính cột phụ. App vẫn chỉnh bằng
     --lg-rail-w trên .lg-shell như cũ (writing đặt 280px — vẫn đúng). */
  .lg-shell > .lg-rail{ width: var(--lg-rail-w); }
  /* ⛔ HAI biến người-dùng-kéo, MỘT cho mỗi phía (22/09, việc #13).
     `--lg-rail-wl` / `--lg-rail-wr` do shared/chrome.js ghi nội tuyến khi kéo
     rãnh; `--lg-rail-w` là biến mọi công thức đọc, và ở đây nó CHỌN một
     trong hai theo phía của khung. Vì sao không ghi thẳng `--lg-rail-w`:
     Grammar Gym dùng MỘT phần tử khung cho cả màn chọn (cột TRÁI) lẫn màn
     làm bài (cột PHẢI tra cứu) — setShell() chỉ đổi class. Ghi một biến thì
     bề rộng kéo ở bên trái đi theo sang bên phải, và khoá nhớ bị hai phía
     ghi đè lẫn nhau. Chọn bằng CSS thì đổi phía là đổi bề rộng NGAY, không
     cần JS nghe đổi class. */
  .lg-shell{ --lg-rail-w: var(--lg-rail-wl, clamp(240px, 18vw, 288px)) }
  .lg-shell--right{ --lg-rail-w: var(--lg-rail-wr, clamp(240px, 18vw, 288px)) }

  /* ⛔ CHỈ ĐỊNH RÃNH TƯỜNG MINH, đừng để lưới TỰ XẾP. Đây mới là nửa sau của
     bản vá trên, và là nửa quan trọng hơn.
     Lưới tự xếp đặt phần tử vào rãnh trống ĐẦU TIÊN. Khi cột phụ bị
     `display:none` nó thôi là ô lưới, nên cột GIỮA trôi lên rãnh 1 — rãnh
     dành cho cột phụ. Đổi rãnh 1 thành `auto` KHÔNG cứu được: cột giữa khi
     đó co theo max-content của chính nó (đo 20/09 trên 5 màn .dd-rail-off
     của Draft Doctor: 148px · 156px · 187px · 150px · 268px) còn rãnh `1fr`
     rỗng nuốt sạch chỗ trống. Vẫn hỏng, chỉ hỏng theo kiểu khác.
     Ghim rãnh cho TỪNG VAI thì cột giữa ở yên rãnh co giãn dù cột phụ còn
     hay mất. Viết `*:not(.lg-rail)` chứ không phải `.lg-shell-main` vì
     không phải app nào cũng đặt lớp đó lên cột giữa — luật phải đúng cho
     mọi con, kể cả con app tự thêm sau này.
     (`order` ở dưới nay chỉ còn ý nghĩa cho thứ tự đọc; rãnh do đây quyết.) */
  .lg-shell > .lg-rail{ grid-column:1 }
  /* ⛔ `:not(.lg-rail-gutter)` KHÔNG thừa. Rãnh kéo (§9) là con TUYỆT ĐỐI của
     khung. Với một con position:absolute, nếu nó CÓ vị trí lưới xác định thì
     khối chứa của nó là chính Ô LƯỚI đó, chứ không phải hộp-lề của khung —
     tức `left` được tính từ mép cột GIỮA. Đo 20/09 ở 1440px: rãnh rơi ra
     x=695 trong khi mép trong cột phụ ở x=390. Để nó ngoài phép ghim thì
     khối chứa quay về hộp-lề của khung và công thức `left` ở §9 mới đúng. */
  .lg-shell > *:not(.lg-rail):not(.lg-rail-gutter){ grid-column:2 }
  .lg-shell--right > .lg-rail{ grid-column:2 }
  .lg-shell--right > *:not(.lg-rail):not(.lg-rail-gutter){ grid-column:1 }

  /* `order` (không phải vị trí DOM) quyết định rail nằm cột nào — utility
     không đảo DOM, app đặt <aside class="lg-rail"> ở đâu trong HTML cũng ra
     đúng cột theo class đã chọn. TRÁI (mặc định): rail luôn ở cột ĐẦU (order
     âm, nhỏ hơn mọi order mặc định=0 của nội dung chính). PHẢI: rail luôn ở
     cột CUỐI (order dương, ghi đè lại rule TRÁI — hai selector cùng
     specificity (0,2,0), `.lg-shell--right > .lg-rail` đứng SAU trong file
     nên thắng khi phần tử mang cả hai class .lg-shell.lg-shell--right). */
  .lg-shell > .lg-rail{ order:-1; }
  .lg-shell--right > .lg-rail{ order:2; }

  /* ⚠ Chọn lại selector "`.lg-shell > .lg-rail`" (không phải chỉ `.lg-rail`)
     để giữ ĐÚNG specificity với rule mobile-first phía trên — hai class nối
     bằng combinator vẫn cho specificity (0,2,0) như nhau, nên chỉ THỨ TỰ
     trong file (khối @media này đứng SAU) mới quyết định ai thắng. Viết
     `.lg-rail{...}` ở đây (specificity (0,1,0), thấp hơn) sẽ KHÔNG BAO GIỜ
     thắng được `.lg-shell > .lg-rail{position:static}` ở trên dù nằm trong
     @media — đã đo thật ở 1024px: rail bị kẹt position:static, mất sticky
     hoàn toàn (bug thật, không phải giả thuyết). */
  .lg-shell > .lg-rail{
    position:sticky;
    top: var(--lg-sticky-top, 76px);
    max-height: calc(100dvh - var(--lg-sticky-top,76px) - 20px);
    overflow-y:auto;
    overscroll-behavior:contain;
    /* Nội dung trong cột phụ phản ứng theo BỀ RỘNG CỘT PHỤ, không theo bề
       rộng màn hình — §1.3 "trong cột phụ dùng @container, không dùng media
       query theo viewport". scripts/hud-audit-media-queries.mjs đọc
       scripts/hud-rail-candidates.json để báo rule sẽ vỡ khi app nào đó áp
       .lg-rail lên nội dung có media query theo viewport. */
    container-type: inline-size;
    margin-top:0;
    z-index: var(--z-rail,120);
    /* ⛔ §R09b — LỀ DƯỚI ÂM, KHÔNG THỪA, ĐỪNG "DỌN CHO SẠCH".
       Chủ app QA#6 việc #2: *"Rõ ràng là ở screenshot này thì lỗi left rail
       che Header xuất hiện mà"*. Vòng #5 tôi sửa z-index (header 100 → 200)
       rồi kết luận "không tái hiện được" — SAI, vì chỉ đo ở GIỮA trang.
       Đo lại ở ĐÁY trang (cổng scripts/rail-sticky.test.mjs): 4/7 app, 21 màn
       có mép trên cột phụ nằm TRÊN nấc header.
       Vì sao: hộp dính bị chặn để không vượt ra ngoài CONTENT BOX của ô lưới.
       Cuộn tới đáy trang, đáy content box dừng cao hơn đáy màn hình đúng bằng
       phần nằm DƯỚI nó — `padding-bottom` của app (Draft Doctor 64px, Pron
       Coach 40px, Speaking 18px) và cả thanh nav đáy. Cột phụ bị kéo lên theo
       rồi chui dưới header: Pron Coach #sPath ra railTop=0.
       Lề âm nới đúng cái chặn đó ra 200px. AN TOÀN vì `max-height` ở trên đã
       ghim chiều cao cột phụ ≤ (100dvh − nấc dính − 20px): dù được phép thò
       ra bao nhiêu, đáy nó cũng không xuống quá 20px trên đáy màn hình — nó
       chỉ NGỪNG bị đẩy lên, chứ không tràn xuống che thứ gì. */
    margin-bottom: calc(-1 * var(--lg-rail-overhang, 200px));
  }
  /* Cặp bài trùng của lề âm ở trên: trên trang NGẮN (cột phụ cao hơn cột
     giữa) lề âm làm hàng lưới thấp đi 200px và cột phụ thò ra ngoài khung.
     min-height kéo hàng trở lại — grid `align-content:normal` căng hàng auto
     cho đầy khung, nên ô lưới luôn đủ cao cho cột phụ. */
  .lg-shell{ min-height: calc(100dvh - var(--lg-sticky-top, 76px)) }
}

/* ══ §R09c · CỘT PHỤ BẬT MUỐN HƠN 1024px — `.lg-shell--rail-lg` ══════════
   Hiến chương bật cột phụ từ 1024px. Một số bề mặt không chịu được mốc đó:
   Score Sprint đo thật ở 1100px thì sau khi cắt cột phụ 240px, danh sách bài
   còn HẸP HƠN cả bản một cột ở 1024px — nên nó chọn 1280px từ M1.

   ⬛ ĐÂY LÀ LÝ DO BỀ MẶT ĐÓ TỪ CHỐI `.lg-shell` SUỐT M2→QA#6, và cái giá
      đo được là một lỗi thật: nó chép lại công thức cột phụ vào `rl/index.css`,
      nên bản vá §R09b (lề dưới âm) không tới được — và cổng rail-sticky cũng
      mù luôn vì nó tìm `.lg-shell`. Đo 20/09 trên preview đã đăng nhập:
      cuộn tới đáy `/rl/` thì mép trên cột phụ = 36 trong khi nấc dính = 68.
      "Mốc bật khác" là MỘT dòng khác biệt; chép cả công thức để giữ một dòng
      đó là tự nhận mọi bản vá về sau bằng tay. Class này dọn đúng một dòng đó.

   Dưới 1280px: cột phụ ẨN hẳn (app tự lo giao diện thay thế — Score Sprint
   dùng 7 nút accordion), khung về MỘT cột ở nấc HẸP của hiến chương (880px)
   dù bề mặt có khai `.lg-shell--wide` hay không — vì nấc RỘNG sinh ra cho
   bố cục hai cột, không phải cho một dải chữ 1160px.
   Từ 1280px: không còn gì khác 9 bề mặt kia. */
@media (max-width:1279.98px){
  .lg-shell--rail-lg > .lg-rail{ display:none }
  .lg-shell--rail-lg{ --lg-main-w: 880px }
}
@media (min-width:1024px) and (max-width:1279.98px){
  /* Không đợi `lg-shell--rail-hidden` (shared/chrome.js đặt khi đo cột phụ ra
     0px) làm việc này hộ: cùng kết quả, nhưng ở đây đây là MỘT ĐỀU ĐÃ BIẾT
     TRƯỚC (mốc breakpoint), không phải một phép đo lúc chạy — viết bằng CSS thì
     đúng ngay từ khung hình đầu tiên, không nháy 24px khe rỗng chờ JS.
     (0,2,0) để hòa với `.lg-shell.lg-shell` ở §8.2 rồi thắng bằng thứ tự trong file. */
  .lg-shell--rail-lg.lg-shell--rail-lg{
    max-width: calc(var(--lg-main-w) + 2 * var(--lg-shell-pad));
    column-gap: 0;
  }
}

/* ══ §R09d (22/09, việc #11) · MÀN DỌC: khối ĐIỀU HƯỚNG lên ĐẦU trang ══════
   Chủ app (thử ở /speaking/ trên điện thoại): *"Vertical viewport: các
   navigation/list mode nên hiện ở đầu trang"*.
   Dưới 1024px cả cột phụ xếp XUỐNG DƯỚI cột giữa (§8, chốt 20/09) — đúng cho
   khối tra cứu dài (thước đo, lịch sử), sai cho khối CHỌN MÀN: em phải cuộn
   hết nội dung mới thấy các lối chuyển chế độ. Không kéo CẢ cột phụ lên
   (nó chở khối dài) — chỉ kéo đúng khối điều hướng.
   Cách làm: app đánh dấu khối đó bằng `data-lg-nav` (OPT-IN, không đoán:
   "khối đầu tiên có .lg-pick" sai ở Draft Doctor — khối "của em" cũng là
   .lg-pick — và ở LOGI Drop, nơi khối đầu là bộ chọn độ khó). Dưới 1024px
   cột phụ đó thành `display:contents` ⇒ từng khối là con flex của khung
   (khung là flex cột ở mốc này), rồi xếp bằng `order`: khối điều hướng −1,
   các khối còn lại 2 — y như hiện nay.
   ⚠ Viết MOBILE-FIRST rồi trả lại ở ≥1024, không dùng max-width:1023.98
     (không có trong hiến chương mốc §1.3 — scripts/hud-audit-media-queries).
   ⚠ Đặt TRƯỚC §9: `display:block` ở ≥1024 là (0,3,0), cùng hạng với rule
     solo `.lg-shell--solo.lg-shell--solo > .lg-rail` và rule focus — hai rule
     đó đứng SAU nên vẫn thắng; rule THU (0,3,1) thắng nhờ hạng.
   ⚠ `display:contents` làm cột phụ đo ra 0px ⇒ chrome.js gắn --rail-hidden;
     lớp đó chỉ có tác dụng ở ≥1024 (và ResizeObserver đo lại khi qua mốc). */
.lg-shell > .lg-rail:has(> [data-lg-nav]){ display:contents }
.lg-shell > .lg-rail:has(> [data-lg-nav]) > *{ order:2 }
.lg-shell > .lg-rail > [data-lg-nav]{
  order:-1;
  align-self:stretch; min-width:0; max-width:100%;
  margin:0 0 14px;
}
/* Khối tiếp theo sau cột giữa: bù lại lề 24px trước đây nằm trên cả cột phụ. */
.lg-shell > .lg-rail:has(> [data-lg-nav]) > :nth-child(1 of :not([data-lg-nav])){ margin-top:24px }
.lg-shell > .lg-rail > [data-lg-nav] .lg-rail-block-title{ margin-bottom:6px }
/* Dải chip cuộn NGANG TRONG khối — khối đã có bề rộng xác định (flex item
   stretch + min-width:0) nên dải không bao giờ đẩy TRANG tràn ngang
   (bẫy `.lg-shell width:100%`, QA_HUD_R3_BUG_PATTERNS §6). Cố ý khác luật
   "trong cột phụ thì xuống hàng" (bug patterns §1/§3): ở đây chủ app hỏi
   đúng một dải gọn, và mọi chip vẫn tới được bằng vuốt. */
.lg-shell > .lg-rail > [data-lg-nav] .lg-pick{
  flex-direction:row; flex-wrap:nowrap; gap:6px;
  overflow-x:auto; overscroll-behavior-x:contain; scrollbar-width:thin;
}
.lg-shell > .lg-rail > [data-lg-nav] .lg-pick-item{
  flex:0 0 auto; width:auto; min-height:44px; white-space:nowrap;
}
/* 23/09 (QA mobile) — CON KHÁC của .lg-pick cũng phải khoá co. Thẻ "Em có
   bài thi thử đang dở" (.sp-resume, không phải .lg-pick-item) là flex item
   co được ⇒ trong dải nowrap nó bị bóp còn 54px, chữ vỡ 3 dòng (đo 390px). */
.lg-shell > .lg-rail > [data-lg-nav] .lg-pick > *{ flex:0 0 auto; max-width:82vw }
.lg-shell > .lg-rail > [data-lg-nav] .lg-pick-sub{ display:none }
@media (min-width:1024px){
  .lg-shell > .lg-rail:has(> [data-lg-nav]){ display:block }
  .lg-shell > .lg-rail:has(> [data-lg-nav]) > *{ order:0 }
  .lg-shell > .lg-rail > [data-lg-nav]{ margin:0 0 14px }
  .lg-shell > .lg-rail > [data-lg-nav]:last-child{ margin-bottom:0 }
  .lg-shell > .lg-rail:has(> [data-lg-nav]) > :nth-child(1 of :not([data-lg-nav])){ margin-top:0 }
  .lg-shell > .lg-rail > [data-lg-nav] .lg-rail-block-title{ margin-bottom:10px }
  .lg-shell > .lg-rail > [data-lg-nav] .lg-pick{ flex-direction:column; overflow-x:visible; gap:4px }
  .lg-shell > .lg-rail > [data-lg-nav] .lg-pick-item{
    flex:0 1 auto; width:100%; min-height:40px; white-space:normal;
  }
  .lg-shell > .lg-rail > [data-lg-nav] .lg-pick > *{ flex:0 1 auto; max-width:none }
  .lg-shell > .lg-rail > [data-lg-nav] .lg-pick-sub{ display:block }
}

/* ══════════════════════════════════════════════════════════════════════
   §9 — RÃNH KÉO + NÚT THU CỘT PHỤ (chốt 20/09, vòng QA #4 việc #5)
   "Nên tạo luôn cả resize gutter cho left/right panel trong tất cả các
    surface + nút hide."
   Dựng bởi shared/chrome.js (lgRailMountGutters) trên MỌI khung ngang có
   cột phụ — app không phải làm gì, kể cả app thêm sau này.
   Vì trần khung là HỆ QUẢ của --lg-rail-w (§8.2), kéo cột phụ TỰ nới trần:
   không có chỗ thứ hai phải nhớ sửa.
   Rãnh là con TUYỆT ĐỐI của khung, KHÔNG phải ô lưới: đặt nó thành ô lưới
   thì nó chiếm một rãnh thật và đẩy cột giữa đi. */
.lg-shell{ position:relative }
.lg-rail-gutter{ display:none }
@media (min-width:1024px){
  .lg-shell:not(.lg-shell--solo):not(.lg-shell--rail-hidden):not(.lg-shell--rail-bare) > .lg-rail-gutter,
  /* §R26b — `:not(.lg-shell--solo)` KHÔNG THỪA: dòng này là đường thứ hai cho
     rãnh hiện (để thu rồi vẫn mở lại được), và nó từng bỏ quên vế "màn này
     KHÔNG có cột phụ" ⇒ rãnh + nút mở lại vẫn vẽ đè lên cột giữa ở Studio
     Mode. Đo được ở 1265px: nút x=238 trong khi cột giữa bắt đầu x=237. */
  /* ⛔ Rule HIỆN rãnh này phải KHÔNG có `:has()`: trình duyệt chưa hỗ trợ
     `:has()` bỏ CẢ rule (cả danh sách bộ chọn) ⇒ mất luôn rãnh + nút mở lại.
     Vế "cột phụ đang `hidden` thì không có rãnh" nằm ở rule ẩn RIÊNG ngay
     dưới khối này (scripts/rail-hidden.test.mjs canh cả hai). */
  .lg-shell--rail-off:not(.lg-shell--rail-bare):not(.lg-shell--solo) > .lg-rail-gutter{
    display:block;
    position:absolute;
    top:0; bottom:0;
    cursor:col-resize;
    z-index: var(--z-rail,120);
    /* Không nuốt sự kiện chuột của nội dung hai bên khi người dùng không
       chạm tới nó: chỉ 12px này mới bắt chuột. */
    touch-action:none;
  }
  /* ⛔ VỊ TRÍ + BỀ RỘNG ĐỨNG RIÊNG, specificity THẤP (0,2,0) — đừng gộp lại
     vào rule HIỆN ở trên (22/09). Rule hiện là (0,5,0); khi `left` còn nằm
     trong nó thì `.lg-shell--right > .lg-rail-gutter{left:auto}` (0,2,0)
     KHÔNG BAO GIỜ thắng được: rãnh của khung PHẢI bị ghim cả left lẫn right,
     trình duyệt bỏ `right`, và rãnh rơi về BÊN TRÁI cột giữa — cách cột phụ
     thật cả một cột giữa. Không cổng nào thấy vì rail-gutter chỉ đo khung
     TRÁI. (Số đo trước/sau: docs/HUD_RULEBOOK.md §R26d.) */
  .lg-shell > .lg-rail-gutter{
    width:12px;
    /* Rãnh nằm GIỮA khe 24px: mép trong cột phụ + 6px. */
    left: calc(var(--lg-shell-pad) + var(--lg-rail-w) + 6px);
  }
  .lg-shell--right > .lg-rail-gutter{
    left:auto;
    right: calc(var(--lg-shell-pad) + var(--lg-rail-w) + 6px);
  }
  /* §R23 — vạch phải THẤY ĐƯỢC lúc nghỉ.
     Bản QA#4 để `background:transparent` cho tới khi hover, lý lẽ "đừng thêm
     một đường kẻ dọc vào mọi màn". Chủ app QA#5 việc #3: *"Resize gutter nên
     có viền rõ nét hơn chút, hiện tại là invisible luôn trừ khi hover ngang
     qua"* — một tay nắm vô hình thì không ai biết là kéo được.
     Màu là `--border`, KHÔNG phải `--lg-accent`: lúc nghỉ nó phải đọc ra
     "đường phân cột", chỉ khi chạm tới mới đọc ra "nút". */
  .lg-rail-gutter::before{
    content:""; position:absolute; left:5px; top:0; bottom:0; width:2px;
    border-radius:2px; background:var(--border,#3A3352); transition:background .15s;
  }
  .lg-rail-gutter:hover::before,
  .lg-rail-gutter:focus-visible::before,
  .lg-shell--rail-dragging .lg-rail-gutter::before{ background:var(--lg-accent,#20B9B9) }
  .lg-rail-gutter:focus-visible{ outline:2px solid var(--lg-accent,#20B9B9); outline-offset:1px }
  /* (22/09) Cột phụ đang `hidden` thì không có gì để thu/mở — không có rule
     này rãnh + nút mở lại vẫn vẽ ở mép trái cột giữa của một khung không còn
     cột phụ nào. Rule RIÊNG (không nhét vào rule hiện ở trên): trình duyệt
     không hiểu `:has()` chỉ mất rule ẩn này, rãnh vẫn còn. Lớp nhân đôi để
     thắng specificity (0,5,0) của bộ chọn hiện thứ nhất. */
  .lg-shell.lg-shell:has(> .lg-rail[hidden]) > .lg-rail-gutter.lg-rail-gutter{ display:none }
}
/* Nút thu/mở — dính theo màn hình nên luôn với tới được kể cả ở giữa một
   cột phụ dài. */
.lg-rail-toggle{
  position:sticky;
  top: calc(var(--lg-sticky-top,76px) + 4px);
  display:flex; align-items:center; justify-content:center;
  width:22px; height:38px; margin-left:-5px;
  padding:0; border:1px solid var(--border,#3A3352); border-radius:7px;
  background:var(--surf2,#2A2440); color:var(--dim,#A79DBE);
  font:inherit; font-size:.72rem; line-height:1; cursor:pointer;
  transition:color .15s,border-color .15s;
}
.lg-rail-toggle:hover{ color:var(--text,#EEEAF6); border-color:var(--lg-accent,#20B9B9) }
.lg-rail-toggle:focus-visible{ outline:2px solid var(--lg-accent,#20B9B9); outline-offset:2px }

/* ══ §R24 (bản 23/09) · THU = DẢI ICON 64px, cột giữa nở vừa đủ ══
   Lịch sử: QA#4 thu về 0 + một nút treo; QA#5 (Đ2) DẢI MINI 52px (gạt vì
   không đồng nhất — nội dung tự do của mỗi cột phụ); 22/09 đổi thành ẨN
   HẲN (bản trên, xem git blame). 23/09 — chủ app chốt qua AskUserQuestion,
   SAU khi thấy bản trang chủ (HUD QA8 mục 8, luồng H): *"icon rail 64px ÁP
   CHUNG cho mọi app dùng .lg-rail"* — quyết định MỚI HƠN THẮNG bản 22/09.
   Khác bản QA#5 ở chỗ dải nay KHÔNG sống bằng nội dung tự do của cột phụ:
   mỗi khối `.lg-rail-block` khai `data-rail-icon`/`-label` (hoặc rơi về icon
   mặc định + tiêu đề khối nếu chưa khai — spec mục 8), lgRailBuildIconStrip()
   (cuối chrome.js) dựng NÚT GIỐNG NHAU ở MỌI app, nên "Bảng xếp hạng/Hộp
   thư/Tài khoản thu về dải rỗng" (lý do QA#5 bị gạt) không còn xảy ra.
   Nay, ở MỌI app:
     · cột phụ KHÔNG còn `display:none` — thu về `width:64px`, hiện DUY NHẤT
       `.lg-rail-icon-strip` (mọi khối con khác `display:none`, không Tab
       tới được — chỉ dải icon mới Tab tới được);
     · TRẦN KHUNG ĐỨNG YÊN (không đụng `--lg-rail-frame`, bất biến giữ từ bản
       22/09 — §8.2/shell-geometry.test.mjs vẫn đúng nguyên);
     · rãnh kéo (`.lg-rail-gutter`) vô nghĩa với bề rộng CỐ ĐỊNH 64px ⇒ ẩn;
       "mở lại" nay là bấm MỘT nút icon bất kỳ (LGRail.setOff(shell,false,true)
       + cuộn tới đúng khối, xem lgRailBuildIconStrip) — không còn tay nắm
       kéo-mở riêng, đỡ phải giữ hai cơ chế mở cùng lúc.
   Bề rộng đã kéo (`--lg-rail-wl/-wr`) KHÔNG bị đụng khi thu (chỉ `.lg-rail`
   tự nó bị ép 64px bằng `width!important`, biến nội tuyến nằm nguyên trên
   khung) ⇒ mở lại là về đúng bề rộng cũ — GIỮ NGUYÊN bất biến của bản 22/09.
   ⛔ Mọi rule ở đây nằm trong @media ≥1024: dưới đó cột phụ xếp chồng và
      KHÔNG có dải icon — ẩn/thu ở đó là khoá mất nội dung.
   ⛔ `html:not([data-lg-focus="1"])`: lựa chọn thu được nhớ THEO APP (§R27)
      nên nó theo em vào màn tập trung — nơi cột phụ PHẢI tra cứu phải ở lại
      (§R30) và không có nút thu (§R26). Thiếu vế này là thu cột trái ở màn
      chọn bài thì mất luôn bảng tra cứu lúc làm bài.
   *Cổng:* rail-gutter.test.mjs (đã cập nhật khớp quyết định mới 23/09, xem
   HUD_RULEBOOK.md §R24). */
@media (min-width:1024px){
  /* ⛔ §R26b vẫn đúng nguyên: "TẮT cột phụ" (.lg-shell--solo, .lg-shell--
     rail-bare, .lg-shell--rail-hidden — cột phụ RỖNG hoặc app tự ẩn) PHẢI
     THẮNG "thu cột phụ". Bản 22/09 thắng bằng specificity (0,3,0); bản này
     dùng `!important` nên KHÔNG dựa vào specificity được nữa — phải loại
     BA lớp đó ngay trong SELECTOR (rule không khớp ⇒ không có gì để thắng/
     thua) thay vì để đặc trưng phân xử. Thiếu :not() nào ở đây là dải icon
     sống lại ở màn solo/rail-bare — bắt được lúc tự chạy rail-gutter.test.mjs
     sau khi thêm !important (đo 23/09: r26b đỏ ngay). */
  /* :not([hidden]) THÊM (23/09): cột phụ mang thuộc tính `hidden` (app CHƯA
     có nội dung — đang tải, hoặc cố ý ẩn hẳn cho vai đang xem, khác nghĩa
     "thu") phải ĐỨNG YÊN là display:none của UA. `!important` ở dưới thắng
     MỌI specificity kể cả `[hidden]` nếu thiếu :not() này — dải icon RỖNG
     sẽ hiện ra trước khi có block nào để dựng nút, đúng lỗi "dải mini rỗng"
     mà QA#5 từng bị gạt. rail-hidden.test.mjs bắt đúng ca này (23/09). */
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden) > .lg-rail:not([hidden]){
    display:flex!important; flex-direction:column; align-items:center;
    gap:10px; width:64px!important; padding:6px 0 0; overflow:visible;
  }
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden) > .lg-rail:not([hidden]) > :not(.lg-rail-icon-strip){ display:none }
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden) > .lg-rail:not([hidden]) > .lg-rail-icon-strip{ display:flex }
  /* :has(> .lg-rail:not([hidden])) THÊM (23/09) trên BA rule dưới, cùng lý
     do :not([hidden]) ở trên — rail còn [hidden] (chưa có nội dung) thì
     KHÔNG có 64px dải icon nào để chừa khe/nở cột giữa; rule chung
     `.lg-shell:not(:has(> .lg-rail:not([hidden])))` (§8, phía trên trong
     file) đã lo đúng ca đó (gap:0, main hết cỡ) — thiếu :has() ở đây là
     đè NHẦM lên rule chung, để lại khe 16px "ma" không ai chiếm. */
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden):has(> .lg-rail:not([hidden])){
    column-gap:16px;
  }
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden):has(> .lg-rail:not([hidden])) > .lg-shell-main{
    max-width:none;
  }
  /* Rãnh kéo vô nghĩa với dải icon cố định 64px — ẩn hẳn khi đã thu (nút mở
     lại nay là icon đầu dải, không phải tay nắm mép khung). Rail [hidden]
     thì rãnh vốn đã ẩn theo rule cũ (§9) — không cần :has() thêm ở đây. */
  html:not([data-lg-focus="1"]) .lg-shell--rail-off:not(.lg-shell--solo):not(.lg-shell--rail-bare):not(.lg-shell--rail-hidden) > .lg-rail-gutter{ display:none }
}
.lg-rail-icon-strip{display:none;flex-direction:column;gap:10px;align-items:center}
.lg-rail-icon-btn{
  position:relative;width:44px;height:44px;min-width:44px;border-radius:12px;
  display:flex;align-items:center;justify-content:center;
  background:var(--surf2,rgba(255,255,255,.05));border:1px solid var(--border,rgba(255,255,255,.1));
  color:var(--text,#EEEAF6);cursor:pointer;font-family:inherit;padding:0;
}
.lg-rail-icon-btn:hover{ border-color:var(--item-hue,var(--lg-accent,#20B9B9)) }
.lg-rail-icon-btn:focus-visible{ outline:3px solid var(--lg-accent,#20B9B9); outline-offset:2px }
.lg-rail-icon-btn svg{ width:22px;height:22px }
/* 24/09 — icon lấy từ rail đầy đủ (lgRailBuildIconStrip): emoji của mục/tiêu
   đề hoặc sprite; nút-liên-kết là <a> ⇒ bỏ gạch chân. .is-active = mục đang
   chọn ở rail đầy đủ (.active / aria-*), cùng kiểu nhấn của .lg-pick-item. */
.lg-rail-icon-glyph{ display:flex;align-items:center;justify-content:center;pointer-events:none }
.lg-rail-icon-emoji{ font-size:20px;line-height:1 }
a.lg-rail-icon-btn{ text-decoration:none }
/* art v3 25/09 — --item-hue (bảng :has() ở trên) thay teal cứng #20B9B9. */
.lg-rail-icon-btn.is-active{
  border-color:var(--item-hue,var(--lg-accent,#20B9B9));
  background:color-mix(in srgb, var(--item-hue,var(--lg-accent,#20B9B9)) 16%, transparent);
}
.lg-rail-icon-btn:disabled{ opacity:.5;cursor:not-allowed }
/* LOW (23/09, review phản biện) — cùng vá với .hub-rail-icon-badge
   (index.html): .6rem (9,6px) dưới sàn 12px, nền teal/đỏ SÁNG + chữ trắng
   không đạt 4,5:1. Đổi nền sang teal/đỏ ĐẬM hơn cùng sắc (#0f766e ≈
   5,48:1 · #b91c1c ≈ 6,46:1 với #fff) + font-size lên 12px. */
.lg-rail-icon-badge{
  position:absolute;top:-6px;right:-6px;min-width:20px;height:20px;padding:0 5px;
  border-radius:999px;background:#0f766e;color:#fff;
  font-size:.75rem;font-weight:700;line-height:20px;text-align:center;
  font-variant-numeric:tabular-nums;border:2px solid var(--bg,#181527);
}
/* "chấm đỏ" quá hạn KHÔNG chỉ là màu (HUD-R34) — kèm dấu ! trong chính badge. */
.lg-rail-icon-badge.is-late{ background:#b91c1c }
@media (prefers-reduced-motion:no-preference){ .lg-rail-icon-btn{ transition:border-color .15s } }
/* ⛔ §R26b — "TẮT cột phụ" PHẢI THẮNG "thu cột phụ". ĐỪNG GỠ lớp nhân đôi.
   Chủ app 20/09 (QA#6, bổ sung): *"Phần left rail Minimize xuất hiện trong
   Studio mode lơ lửng"* — khi đó `.lg-shell--rail-off > .lg-rail{display:flex}`
   (dải mini) CÙNG specificity (0,2,0) với rule solo ở §8 và đứng SAU nên
   thắng. Dải mini đã gỡ (22/09), nhưng lựa chọn "thu" vẫn được nhớ theo app
   (§R27) và đi theo em sang màn solo — nên các rule thu ở trên đều mang
   `:not(.lg-shell--solo)` (không chừa lề tay nắm, không bỏ trần cột giữa),
   và rule này giữ (0,3,0) để app nào sau này lỡ viết `display` cho cột phụ
   vẫn không hồi sinh nó ở màn solo. */
.lg-shell--solo.lg-shell--solo > .lg-rail{ display:none }
/* ⛔ CLASS NÀY CHỈ ĐƯỢC ĐỘNG VÀO `max-width`, TUYỆT ĐỐI KHÔNG `display`.
   lgRailWatchVisibility() gắn nó khi cột phụ đo ra 0px (app tự ẩn — ví dụ
   #drillTools của Speaking). Nếu nó cũng ẩn cột phụ như .lg-shell--solo thì
   thành KHOÁ CHẾT: ẩn ⇒ đo 0 ⇒ giữ ẩn ⇒ đo mãi 0, cột phụ không bao giờ
   quay lại được. (Bắt được lúc đọc lại bản vá, trước khi chạy.) */
/* §R26 — cột phụ RỖNG: không có gì để thu, và nó không được chiếm chỗ.
   Pron Coach giữ một <aside class="lg-rail"> trống ở vài màn; trước bản này
   nó vẫn được một rãnh + một nút thu, và nút rơi ra giữa khoảng trống —
   đúng ảnh chủ app gửi ở QA#5 việc #5. */
.lg-shell--rail-bare > .lg-rail{ display:none }

/* Chế độ tập trung (§1.4) tự ẩn cột phụ TRÁI — rãnh + nút thu không được
   phép sót lại. §R26 (QA#5 việc #5): *"Nút hide left rail panel hiện lơ lửng
   giữa màn trong focus mode"*. */
[data-lg-focus="1"] .lg-rail-gutter{ display:none !important }
[data-lg-focus="1"] .lg-rail-toggle{ display:none }
/* ══ §R26d (22/09, việc #13) · Cột phụ PHẢI tra cứu trong focus CÓ rãnh kéo ══
   Chủ app: *"Ở các phần focus mode thì right rail panel (tool panel) nên có
   resize gutter luôn nhé. Làm theo convention của left side gutter"*.
   CÙNG phần tử `.lg-rail-gutter`, cùng JS (tự đảo chiều theo
   `.lg-shell--right`), khoá nhớ RIÊNG `lg_rail_w_r:<app>`. Chỉ có RÃNH —
   KHÔNG nút thu (nút ẩn ở rule ngay trên): focus chưa từng có thu cột phụ,
   và việc này cố ý không đẻ thêm (§R26).
   Điều kiện hiện = đúng điều kiện cột phụ tra cứu HIỆN (≥1280px, khung
   phải, rail mang `.lg-rail--study`, không `hidden`), cộng
   `:not(.lg-shell--rail-hidden)` cho ca app tự ẩn bằng style nội tuyến
   (Speaking `#drillTools style="display:none"` cho tới khi bấm "Công cụ").
   `:has()` ở đây là CỐ Ý: trình duyệt không hiểu `:has()` chỉ mất rule này
   ⇒ quay về đúng hành vi cũ (không rãnh) — hỏng theo hướng AN TOÀN.
   `!important` thắng `!important` ở trên nhờ specificity cao hơn. */
@media (min-width:1280px){
  [data-lg-focus="1"] .lg-shell--right:not(.lg-shell--solo):not(.lg-shell--rail-hidden):not(.lg-shell--rail-bare):has(> .lg-rail.lg-rail--study:not([hidden])) > .lg-rail-gutter{
    display:block !important;
  }
}
/* Đang kéo: cấm bôi đen chữ, nếu không nửa trang xanh lè sau một cú kéo. */
.lg-shell--rail-dragging, .lg-shell--rail-dragging *{ user-select:none !important }

/* Một khối trong cột phụ — app không tự đặt tên riêng nữa. */
.lg-rail-block{
  background:var(--surf,#221D33);
  border:1px solid var(--border,#3A3352);
  border-radius:14px;
  padding:14px 16px;
  margin-bottom:14px;
  box-shadow:var(--lg-rail-block-shadow,none);
}
/* ⚠ Giao diện SÁNG: khối cột phụ biến mất nếu chỉ dựa vào viền.
   Đo 19/09 sau QA của chủ app ("Grammar Gym lightmode phần left rail panel
   chưa có box, viền hoàn chỉnh"): --surf sáng là #FFFFFF còn --bg là var(--bg)
   — hai nền chính lệch nhau ~1,04:1, và viền --border #c8c2d8 trên nền đó
   chỉ ~1,3:1. Nghĩa là ở nền sáng cái "hộp" thực tế KHÔNG có mép nào nhìn
   thấy được, dù CSS có khai border đầy đủ — không phải thiếu luật, mà là
   luật không nhìn thấy được.
   Chính theme.css đã ghi câu trả lời: nền sáng dựng độ nổi bằng BÓNG
   ("leaves enough contrast for white cards (#FFF) to read as elevated"),
   và .lg-rail-block là chỗ duy nhất trong bộ chung quên mất điều đó.
   Nền TỐI giữ nguyên none — ở đó --surf đã tối hơn --bg nên viền đủ đọc,
   thêm bóng chỉ làm bẩn. */
[data-theme="light"] .lg-rail-block{
  --lg-rail-block-shadow:0 1px 2px rgba(26,26,46,.07), 0 6px 18px rgba(26,26,46,.06);
  border-color:var(--border2,#584E76);
}
.lg-rail-block:last-child{ margin-bottom:0 }
.lg-rail-block-title{
  font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;
  font-size:.78rem;font-weight:700;letter-spacing:.02em;text-transform:uppercase;
  color:var(--dim,#A79DBE);margin:0 0 10px;
}
/* ══ RAIL BLOCK HUE (30/09, wt/icon-hue-all-0930) ══════════════════════
   Chủ app: "các phần toolbox bên right rail panels cũng nên có màu nhẹ để nổi
   bật hơn trên nền … cố gắng color các elements để đỡ bị chìm, nhìn mỏi mắt".
   Mỗi khối nhuốm NHẸ đúng hue của icon khối đó ở dải thu gọn — --block-hue/
   --block-ink do lgRailBuildIconStrip() (chrome.js) đặt inline; khối chưa có
   (danh sách lối đi, hoặc trang chủ #hubRailShell tự dựng dải riêng) rơi về
   accent. Nền trộn 7% (tối) / 5% (sáng) vào --surf, viền trộn 30%, tiêu đề
   khối tô bằng ink của hue (tối: ink ≥4,5 trên surf2; sáng: trộn 70% ink với
   --text, xem luật dưới). Chữ thân
   khối KHÔNG đổi màu.
   ⛔ :not(.lg-exam-frozen): reading/index.html & listening-test dùng .lg-rail
   trong phòng thi đóng băng — không được đổi màu (exam-freeze.test). */
html:not(.lg-exam-frozen) .lg-rail-block:not(.lg-rail-block--plain){
  background:color-mix(in srgb, var(--block-hue, var(--accent,#7C5CBF)) 7%, var(--surf,#221D33));
  border-color:color-mix(in srgb, var(--block-hue, var(--accent,#7C5CBF)) 30%, var(--border,#3A3352));
}
html[data-theme="light"]:not(.lg-exam-frozen) .lg-rail-block:not(.lg-rail-block--plain){
  background:color-mix(in srgb, var(--block-hue, var(--accent)) 5%, var(--surf));
  border-color:color-mix(in srgb, var(--block-hue, var(--accent)) 35%, var(--border2));
}
/* Khối tự khai data-rail-icon (trang chủ #hubRailShell dựng dải riêng nên JS
   không đặt --block-hue cho nó) — cùng bảng với RAIL SPRITE HUE bên dưới.
   Inline từ JS (nếu có) vẫn thắng. */
.lg-rail-block[data-rail-icon="today"],
.lg-rail-block[data-rail-icon="badges"]{ --block-hue:var(--hue-gold);--block-ink:var(--hue-gold-ink) }
.lg-rail-block[data-rail-icon="homework"],
.lg-rail-block[data-rail-icon="vocab"],
.lg-rail-block[data-rail-icon="inbox"],
.lg-rail-block[data-rail-icon="more"]{ --block-hue:var(--hue-vocab);--block-ink:var(--hue-vocab-ink) }
.lg-rail-block[data-rail-icon="goal"]{ --block-hue:var(--hue-practice);--block-ink:var(--hue-practice-ink) }
.lg-rail-block[data-rail-icon="history"],
.lg-rail-block[data-rail-icon="settings"]{ --block-hue:var(--hue-listening);--block-ink:var(--hue-listening-ink) }
.lg-rail-block[data-rail-icon="filter"]{ --block-hue:var(--hue-grammar);--block-ink:var(--hue-grammar-ink) }
.lg-rail-block[data-rail-icon="toc"]{ --block-hue:var(--hue-reading);--block-ink:var(--hue-reading-ink) }
html:not(.lg-exam-frozen) .lg-rail-block-title{ color:var(--block-ink, var(--accent-text,#A98EEA)) }
/* Sáng: ink (trần 3:1 cho icon) chỉ 3,4–3,7:1 trên nền nhuốm — chưa đủ cho
   chữ .78rem (4,5). Trộn 70% ink + 30% --text ⇒ 5,35–6,8:1, vẫn giữ sắc. */
html[data-theme="light"]:not(.lg-exam-frozen) .lg-rail-block-title{
  color:color-mix(in srgb, var(--block-ink, var(--accent-text)) 70%, var(--text));
}
html:not(.lg-exam-frozen) .lg-rail-block--aux > summary::after{
  border-color:var(--block-ink, var(--accent-text,#A98EEA));
}
/* Nút "» Mở cột phụ" đầu dải (SVG nội tuyến, không thuộc sprite nào). */
html:not(.lg-exam-frozen) .lg-rail-icon-expand{ color:var(--accent-text,#A98EEA) }
/* ══ RAIL SPRITE HUE (30/09) — icons/rail/sprite.svg là monoline
   stroke=currentColor, KHÔNG có .lg-ic/--ic-ink. `color` đặt trên <use> được
   cây bóng của <use> kế thừa ⇒ tô thẳng theo tên symbol, phủ MỌI chỗ dùng
   (dải icon chung, dải riêng trang chủ, /about) mà không cần JS. Khớp bảng
   LG_RAIL_SPRITE_HUE (chrome.js). */
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#today"],
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#badges"]{ color:var(--hue-gold-ink) }
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#homework"],
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#vocab"],
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#inbox"],
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#more"]{ color:var(--hue-vocab-ink) }
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#goal"]{ color:var(--hue-practice-ink) }
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#history"],
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#settings"]{ color:var(--hue-listening-ink) }
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#filter"]{ color:var(--hue-grammar-ink) }
html:not(.lg-exam-frozen) use[href$="/icons/rail/sprite.svg#toc"]{ color:var(--hue-reading-ink) }
/* Khối phụ trợ, thu gọn sẵn (thứ tự cột phụ §1.1: lọc → đang làm → phụ trợ,
   thu gọn). Dựng bằng <details class="lg-rail-block lg-rail-block--aux">
   để không cần JS toggle riêng. */
.lg-rail-block--aux{ padding:0 }
.lg-rail-block--aux > summary{
  cursor:pointer;list-style:none;padding:14px 16px;
  font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.85rem;font-weight:700;
  color:var(--text,#EEEAF6);display:flex;align-items:center;justify-content:space-between;gap:8px;
}
.lg-rail-block--aux > summary::-webkit-details-marker{ display:none }
.lg-rail-block--aux > summary::after{
  content:"";width:8px;height:8px;border-right:2px solid var(--dim,#A79DBE);border-bottom:2px solid var(--dim,#A79DBE);
  transform:rotate(-45deg);transition:transform .15s;flex-shrink:0;
}
.lg-rail-block--aux[open] > summary::after{ transform:rotate(45deg) }
.lg-rail-block--aux > :not(summary){ padding:0 16px 14px }
/* §R18 — khối phụ trợ chỉ có MỘT mục thì không có <summary> để tạo lề trên. */
.lg-rail-block--bare{ padding-top:14px }

/* ══ §R37 · nút BACK mà cột phụ đã thay thế ════════════════════════════
   Chủ app QA#5 việc #10: *"Vị trí nút collapse left rail hơi trùng và gần
   với nút back → Cần review lại chỗ nào cần nút back, chỗ nào không với HUD
   mới này"*. Luật: màn nào MỞ TỪ cột phụ thì cột phụ chính là đường về —
   nút back ở đó là lối đi thứ hai cho cùng một việc (§R19).
   Nhưng nó KHÔNG được xoá khỏi HTML: dưới 1024px cột phụ xếp xuống dưới nội
   dung, và trong chế độ tập trung cột phụ biến mất hẳn — cả hai lúc đó nút
   back là đường về DUY NHẤT. Nên nó chỉ ẩn đúng lúc cột phụ đang với tới
   được. Dải mini (§R24) vẫn là cột phụ với tới được, nên vẫn ẩn. */
@media (min-width:1024px){
  html:not([data-lg-focus="1"]) .lg-shell:not(.lg-shell--solo):not(.lg-shell--rail-hidden) .lg-back-railed{
    display:none;
  }
}
/* ══════════════════════════════════════════════════════════════════════
   B2b (HUD R3 §1.1b) — BỘ PHẦN TỬ DÙNG CHUNG cho khuôn "chọn bên trái,
   xem ở giữa". Chốt 19/09 sau vòng QA của chủ app:

     · "Panel bên trái UXUI element (button, rounded box) nhìn hơi out of
        place so với phần còn lại → Sweep kĩ và đảm bảo tất cả các UXUI
        elements (button, box, noti…) đều chung 1 style, theme và design."

   Vì sao phải là MỘT lớp dùng chung chứ không vá từng app: mỗi app đã tự
   đặt tên riêng cho cùng một thứ (.vv-mode-btn, .gym-toc-item,
   .pron-btn-ghost, .nav-btn…). Mỗi lần chỉnh một chỗ là ba chỗ còn lại
   trôi ra xa thêm — đúng cái chủ app nhìn thấy ở Grammar Gym: cột phụ
   dùng pill bo tròn 999px + màu hổ phách #F59E0B, trong khi cả app còn
   lại là hộp bo 10–14px màu xanh #10B981.

   HÌNH DẠNG là quy ước, MÀU là biến:
     interactive (nút, mục chọn) = hộp bo 10–11px
     nhãn tĩnh (tag/chip)        = viên 999px
   App chỉ đặt `--lg-accent` + `--lg-accent-soft` trên khối cha của nó
   (.lg-rail, .gym-wrap…) là cả bộ đổi màu theo — KHÔNG viết đè từng
   selector, vì làm vậy là chép lại luật hình dạng lần thứ hai.

   ⛔ ĐỪNG dùng [hidden] để ẩn panel của một lựa chọn: app-tour.js
   (openClosedAncestors) GỠ THẲNG thuộc tính hidden khi đi tìm neo ⇒ hai
   panel hiện cùng lúc. Dùng .lg-pane-off + cặp aria-controls/
   aria-expanded trên nút chọn, khi đó tour BẤM ĐÚNG NÚT và hàm đổi chế
   độ của app chạy đầy đủ.
   ══════════════════════════════════════════════════════════════════════ */
:root{
  --lg-accent:var(--accent,#7C5CBF);
  /* Hai dòng, CỐ Ý: dòng đầu là bản dự phòng cho trình duyệt chưa có
     color-mix (nó bỏ qua dòng sau vì không phân giải được). Dòng sau tự
     bám theo --lg-accent, nên app đổi màu nhấn là nền mờ đổi theo —
     không phải nhớ sửa hai chỗ. */
  --lg-accent-soft:rgba(124,92,191,.14);
  --lg-accent-soft:color-mix(in srgb, var(--lg-accent) 14%, transparent);
}

/* Ô NEO cho thanh điều hướng CỦA APP khi nó được DỜI vào cột phụ ở
   ≥1024px. Luật §1.1b: DỜI đúng một phần tử, không nhân bản — hai thanh
   song song là cách chắc chắn nhất để "đang ở mục nào" lệch nhau. */
.lg-rail-nav{ margin-bottom:14px }
.lg-rail-nav:empty{ display:none }

/* ── Danh sách LỰA CHỌN trong cột phụ ─────────────────────────────────
   Một mục = một thứ để CHỌN, và chọn xong thì CỘT GIỮA đổi (§1.1b).
   Không dùng cho mục chỉ cuộn tới một chỗ khác trong cùng cột chính —
   đó là mục lục, và mục lục trùng lặp chính là thứ chủ app đã bỏ. */
.lg-pick{ display:flex;flex-direction:column;gap:4px }
/* ⬛ `text-decoration:none` KHÔNG THỪA — mục của cột phụ không phải lúc nào
   cũng là <button>. Từ M7 (20/09) có hai bề mặt dùng <a class="lg-pick-item">
   cho mục lục nhảy tới khu (/account/ 13 mục, /badges/ 3 mục) — và chúng ra
   GẠCH CHÂN, kiểu chữ link mặc định, cạnh 9 bề mặt kia không gạch. Viết ở
   đây chứ không ở CSS của hai app: một primitive phải trông như nhau dù app
   dựng nó bằng thẻ gì. */
.lg-pick-item{
  display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  text-decoration:none;
  font-family:inherit;font-size:.86rem;font-weight:600;line-height:1.35;
  color:var(--dim,#A79DBE);background:transparent;
  border:1px solid transparent;border-radius:10px;
  padding:9px 11px;min-height:40px;cursor:pointer;
  transition:background-color .15s,border-color .15s,color .15s;
}
.lg-pick-item:hover{
  background:color-mix(in srgb, var(--item-hue,var(--lg-accent)) 12%, var(--surf2,rgba(255,255,255,.05)));
  color:var(--text,#EEEAF6);
}
.lg-pick-item:focus-visible{ outline:2px solid var(--lg-accent);outline-offset:2px }
.lg-pick-item:disabled{ opacity:.5;cursor:not-allowed }
.lg-pick-item:disabled:hover{ background:transparent;color:var(--dim,#A79DBE) }
/* Trạng thái ĐANG CHỌN. Bắt cả aria-expanded="true" chứ không chỉ .active:
   nút mở panel BẮT BUỘC phải khai aria-expanded (xem cảnh báo tour ở
   trên), nên lấy luôn nó làm nguồn hiển thị thì không có cách nào để
   thuộc tính và lớp CSS nói hai chuyện khác nhau.
   art v3 25/09 (HUD-R48/rail bước 1): active lấy HUE của kỹ năng qua biến
   --item-hue (đặt bằng :has() ngay dưới), không còn MỘT màu teal chung —
   mục không có hue riêng rơi về --lg-accent như cũ (không vỡ hình). */
.lg-pick-item.active,
.lg-pick-item[aria-expanded="true"]{
  background:color-mix(in srgb, var(--item-hue,var(--lg-accent)) 16%, transparent);
  border-color:var(--item-hue,var(--lg-accent));
  color:var(--text,#EEEAF6);
}
.lg-pick-ic{ flex:0 0 auto;width:20px;text-align:center;font-size:1.02rem;line-height:1.2 }
.lg-pick-t{ flex:1 1 auto;min-width:0 }
/* ⚠ TỪ 20/09 dòng phụ XUỐNG DÒNG, không cắt. Trước đó luật này là
   `white-space:nowrap;text-overflow:ellipsis` — hợp lý khi dòng phụ chỉ là
   một nhãn ngắn, nhưng nó cắt ĐÂM phần tử Bên TRONG nên <button> bọc
   ngoài vẫn có scrollWidth == clientWidth ⇒ cổng hud-rail-fit báo XANH.
   Mở rộng cổng ra quét cả .lg-pick-sub (20/09) lộ ra Pron Coach đã cắt
   "Từ Reader · Pulse · Lưu…" từ trước (hộp 156px / cần 162px) ở cả
   1024 và 1280px. Giấu chữ đi không bao giờ là câu trả lời đúng — cho
   xuống dòng thì mục cao thêm một dòng, vậy thôi. */
.lg-pick-sub{
  display:block;font-size:.72rem;font-weight:500;color:var(--dim,#A79DBE);
  margin-top:1px;line-height:1.35;
}
.lg-pick-badge{
  flex:0 0 auto;margin-left:auto;border-radius:999px;padding:1px 8px;
  font-size:.7rem;font-weight:800;background:var(--lg-accent-soft);color:var(--text,#EEEAF6);
}
/* Việc đến hạn / bài giáo viên giao — đỏ, vì đây là thứ có HẠN, khác hẳn
   một con số đếm bình thường. */
.lg-pick-badge--due{ background:var(--red,#F2736B);color:#fff }
/* Viên đếm RỖNG không được để lại một chấm nền lơ lửng: nó có nền +
   padding nên rỗng vẫn THẤY được. Mọi chỗ đếm đều ghi '' khi không có gì
   (homework/renderTabCounts là một), nên luật này thuộc về BỘ CHUNG — để ở
   một app là ba app sau phải tự phát hiện lại (§1.1c). */
.lg-pick-badge:empty{ display:none }
/* ── art v3 25/09, bước 1 (rail hue) — mirror LG_ICON_HUE (shared/chrome.js)
   bằng CSS thuần :has(use[href$="#tên"]), KHÔNG phụ thuộc JS đã chạy: custom
   property không truyền NGƯỢC từ icon con lên .lg-pick-item cha, nên --ic-hue
   đặt trên <svg class="lg-ic"> không tới được đây — phải tra lại theo tên
   fragment icon, y hệt bảng LG_ICON_HUE. Trình duyệt không hiểu :has() thì
   --item-hue không được đặt ⇒ rơi về --lg-accent cũ (hỏng theo hướng AN
   TOÀN, không mất tương phản). CHỈ đổi màu khi .active/:hover — mục thường
   vẫn trung tính (HUD-R48 §5c bước 1: "Inactive stays neutral"). */
.lg-pick-item:has(use[href$="#headphones"]),.lg-pick-item:has(use[href$="#clipboard"]),
.lg-rail-icon-btn:has(use[href$="#headphones"]),.lg-rail-icon-btn:has(use[href$="#clipboard"]){ --item-hue:var(--hue-listening) }
.lg-pick-item:has(use[href$="#reading"]),.lg-pick-item:has(use[href$="#cap"]),
.lg-rail-icon-btn:has(use[href$="#reading"]),.lg-rail-icon-btn:has(use[href$="#cap"]){ --item-hue:var(--hue-reading) }
.lg-pick-item:has(use[href$="#mic"]),.lg-pick-item:has(use[href$="#chat"]),
.lg-rail-icon-btn:has(use[href$="#mic"]),.lg-rail-icon-btn:has(use[href$="#chat"]){ --item-hue:var(--hue-speaking) }
.lg-pick-item:has(use[href$="#pen"]),
.lg-rail-icon-btn:has(use[href$="#pen"]){ --item-hue:var(--hue-writing) }
.lg-pick-item:has(use[href$="#book"]),.lg-pick-item:has(use[href$="#home"]),.lg-pick-item:has(use[href$="#leaf"]),
.lg-rail-icon-btn:has(use[href$="#book"]),.lg-rail-icon-btn:has(use[href$="#home"]),.lg-rail-icon-btn:has(use[href$="#leaf"]){ --item-hue:var(--hue-vocab) }
.lg-pick-item:has(use[href$="#blocks"]),.lg-pick-item:has(use[href$="#brain"]),.lg-pick-item:has(use[href$="#bulb"]),
.lg-rail-icon-btn:has(use[href$="#blocks"]),.lg-rail-icon-btn:has(use[href$="#brain"]),.lg-rail-icon-btn:has(use[href$="#bulb"]){ --item-hue:var(--hue-grammar) }
.lg-pick-item:has(use[href$="#trophy"]),.lg-pick-item:has(use[href$="#crown"]),.lg-pick-item:has(use[href$="#star"]),
.lg-rail-icon-btn:has(use[href$="#trophy"]),.lg-rail-icon-btn:has(use[href$="#crown"]),.lg-rail-icon-btn:has(use[href$="#star"]){ --item-hue:var(--hue-gold) }
.lg-pick-item:has(use[href$="#flame"]),
.lg-rail-icon-btn:has(use[href$="#flame"]){ --item-hue:var(--hue-streak) }
.lg-pick-item:has(use[href$="#target"]),
.lg-rail-icon-btn:has(use[href$="#target"]){ --item-hue:var(--hue-practice) }
/* Biến thể HAI DÒNG: tên + một câu nói mục đó làm gì. Dùng khi các lựa
   chọn không tự giải thích được bằng riêng cái tên (ví dụ 4 kiểu gợi ý của
   Score Sprint). Đặt trên .lg-pick, không phải từng mục. */
.lg-pick--stack .lg-pick-item{ align-items:flex-start }
.lg-pick--stack .lg-pick-ic{ margin-top:1px }
.lg-pick--stack .lg-pick-t{ display:flex;flex-direction:column;gap:2px }

/* Biến thể "chip đếm xuống DÒNG RIÊNG" (26/09, wt/emoji-tail): dùng khi nhãn
   dài + chip đếm ("NN bài cần làm") không đủ chỗ cùng một hàng ở bề rộng
   rail thật (--lg-rail-w tối thiểu 240px) — QA 25/09 bắt rail /rl/ mục "Đề
   giáo viên giao" bị ép 3 dòng, xem docs/handovers/
   DESIGN_V2_HANDOVER_2026_09_25_NIGHT.md §2.2. Đặt lớp này trên chính
   .lg-pick-item (app gắn bằng JS, KHÔNG viết đè ở CSS app riêng — §1.1c).
   Grid 2 cột (icon | chữ): nhãn hàng 1 một dòng, chip hàng 2 CÙNG CỘT chữ
   (thẳng hàng dưới nhãn) và tự co theo nội dung (justify-self:start) thay vì
   kéo dài hết bề ngang. */
.lg-pick-item--badge-below{
  display:grid;grid-template-columns:20px 1fr;column-gap:10px;row-gap:2px;
}
.lg-pick-item--badge-below .lg-pick-ic{ grid-column:1;grid-row:1 }
.lg-pick-item--badge-below .lg-pick-t{
  grid-column:2;grid-row:1;
}
.lg-pick-item--badge-below .lg-pick-badge{
  grid-column:2;grid-row:2;justify-self:start;margin-left:0;
}


/* Panel của lựa chọn đang TẮT. Xem cảnh báo [hidden] ở đầu khối.
   ⚠ !important ở đây KHÔNG phải cho tiện. Đo thật 19/09 trên Pron Coach:
   pronunciation/index.css có `.pc-view{display:block}`, cùng specificity
   (0,1,0) với `.lg-pane-off` nhưng nạp SAU shared/chrome.css ⇒ thắng, và
   cả BẢY panel hiện cùng lúc. Không lỗi console, không tràn layout — chỉ là
   màn hình dài gấp bảy lần, đúng kiểu hỏng mà cổng đọc-HTML không thấy
   được (HTML vẫn ghi đủ class, chỉ CSS không còn tác dụng).
   "Panel này đang tắt" là một khẳng định tuyệt đối, app không có lý do
   chính đáng nào để bác nó — nên nó được quyền !important. */
.lg-pane-off{ display:none !important }

/* ── Nút ──────────────────────────────────────────────────────────── */
.lg-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  font-family:inherit;font-size:.82rem;font-weight:700;line-height:1.2;
  padding:10px 14px;min-height:44px;border-radius:11px;
  border:1px solid var(--border,#3A3352);background:var(--surf,#221D33);
  color:var(--dim,#A79DBE);cursor:pointer;
  transition:background-color .15s,border-color .15s,color .15s;
}
.lg-btn:hover{ border-color:var(--lg-accent);color:var(--text,#EEEAF6) }
.lg-btn:focus-visible{ outline:2px solid var(--lg-accent);outline-offset:2px }
.lg-btn:disabled{ opacity:.55;cursor:not-allowed }
.lg-btn:disabled:hover{ border-color:var(--border,#3A3352);color:var(--dim,#A79DBE) }
/* ⛔ `el.hidden = true` KHÔNG ẨN ĐƯỢC một `.lg-btn`. `[hidden]` của trình
   duyệt chỉ là `display:none` trong UA stylesheet — `display:inline-flex` ở
   trên (của TÁC GIẢ) thắng nó. Đo 20/09 trên preview: Grammar Gym M3 có
   0 sơ đồ "bụng mệnh đề rút gọn" nên syncReadTools() đặt
   `sw.hidden = true` — nhưng công tắc "🔍 Bung mệnh đề rút gọn" VẪN HIỆN
   trong cột phụ và bấm vào thì KHÔNG có gì xảy ra. Đây là cùng cái bẫy đã
   trả giá ở mindmap-class-lockdown ("el.hidden ≠ đã ẩn").
   Chỉ đóng đinh cho `.lg-btn` chứ KHÔNG viết `[hidden]{}` toàn cục: có
   chỗ cố ý bật lại một phần tử đang `hidden`
   (grammar.css `.gym-xref-wrap:hover .gym-xref-box[hidden]{display:flex}`). */
.lg-btn[hidden]{ display:none !important }
/* MỰC trên nền nhấn là BIẾN, không phải #fff chết. Grammar Gym nhấn bằng
   #10B981: chữ trắng trên nền đó chỉ đạt ~2,1:1 — dưới chuẩn AA (4,5:1), nên
   app đó đặt --lg-accent-ink:#04120c (đúng cách .gym-next vẫn làm). Viết
   cứng #fff ở đây là cài sẵn một lỗi tương phản cho app nhấn màu sáng. */
.lg-btn--primary{ background:var(--lg-accent);border-color:var(--lg-accent);color:var(--lg-accent-ink,#fff) }
.lg-btn--primary:hover{ filter:brightness(1.08);color:var(--lg-accent-ink,#fff) }
.lg-btn--block{ display:flex;width:100% }
/* Nút BẬT/TẮT (aria-pressed) — cùng nguyên tắc với .lg-pick-item ở trên:
   trạng thái hiển thị đọc thẳng từ thuộc tính trợ năng. */
.lg-btn[aria-pressed="true"]{ background:var(--lg-accent-soft);border-color:var(--lg-accent);color:var(--text,#EEEAF6) }

/* ── Ô nhập · dropdown · vùng gõ ─────────────────────────────────────
   21/09 (QA#8 §2): bộ chung chuẩn hoá nút, mục chọn, nhãn — nhưng KHÔNG có
   một luật nào cho ô nhập, nên mỗi app tự vẽ ô của mình và 22 chỗ đã trôi
   ra 7 kiểu bo góc (2px…20px), một ô rơi hẳn về widget xám mặc định của
   trình duyệt (/reader/ #urlInp), 12 chỗ bo 0px vì một biến không tồn tại.
   Cùng quy ước với .lg-btn: HÌNH DẠNG chỉ khai ở đây; app muốn khác màu
   viền lúc focus thì đặt --lg-accent. Bo góc = --r2 của theme.css (10px),
   cùng số với .lg-pick-item — ô nhập đứng cạnh mục chọn phải cùng một góc.
   Nền là --surf2 chứ không phải --surf: ô nhập nằm TRÊN một thẻ (--surf),
   cùng màu với thẻ thì mất mép ô. */
.lg-input,.lg-select,.lg-textarea{
  box-sizing:border-box;width:100%;
  font-family:inherit;font-size:.9rem;line-height:1.4;
  padding:10px 12px;min-height:44px;border-radius:var(--r2,10px);
  border:1px solid var(--border,#3A3352);background-color:var(--surf2,#2A2440);
  color:var(--text,#EEEAF6);outline:none;
  transition:border-color .15s,box-shadow .15s;
}
.lg-input::placeholder,.lg-textarea::placeholder{ color:var(--dim,#A79DBE);opacity:.75 }
.lg-input:focus-visible,.lg-select:focus-visible,.lg-textarea:focus-visible,
.lg-input:focus,.lg-textarea:focus{ border-color:var(--lg-accent);box-shadow:0 0 0 3px var(--lg-accent-soft) }
.lg-input:disabled,.lg-select:disabled,.lg-textarea:disabled{ opacity:.55;cursor:not-allowed }
.lg-textarea{ min-height:120px;resize:vertical;line-height:1.6 }
/* Mũi tên của dropdown vẽ bằng hai dải gradient lấy màu từ BIẾN — ảnh SVG
   data-URI không đọc được var(), mà một mũi tên màu cứng thì lệch ở theme
   sáng (đúng bẫy "mực không được lật theo theme" theo chiều ngược lại). */
.lg-select{
  appearance:none;-webkit-appearance:none;cursor:pointer;padding-right:34px;
  background-image:linear-gradient(45deg,transparent 50%,var(--dim,#A79DBE) 50%),
                   linear-gradient(135deg,var(--dim,#A79DBE) 50%,transparent 50%);
  background-position:calc(100% - 17px) 50%,calc(100% - 12px) 50%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}
/* Danh sách xổ xuống do HỆ ĐIỀU HÀNH vẽ; không đặt màu option thì ở theme
   tối nó ra chữ sáng trên nền trắng của hệ thống. */
.lg-select option{ background-color:var(--surf,#221D33);color:var(--text,#EEEAF6) }
/* Cỡ gọn cho thanh công cụ (bộ lọc, sắp xếp) — vẫn cùng góc, cùng màu. */
.lg-input--sm,.lg-select--sm{ min-height:34px;padding-top:6px;padding-bottom:6px;font-size:.8rem }
.lg-select--sm{ padding-left:10px }
.lg-input[hidden],.lg-select[hidden],.lg-textarea[hidden]{ display:none !important }

/* ── Nhãn tĩnh + ghi chú ──────────────────────────────────────────── */
.lg-tag{
  display:inline-flex;align-items:center;gap:5px;
  font-size:.76rem;font-weight:600;color:var(--text,#EEEAF6);
  background:var(--lg-accent-soft);border:1px solid var(--lg-accent);
  border-radius:999px;padding:4px 10px;
}
.lg-tag--muted{ background:transparent;border:1px dashed var(--border,#3A3352);color:var(--dim,#A79DBE) }
.lg-note{
  font-size:.78rem;line-height:1.55;color:var(--dim,#A79DBE);
  background:var(--surf2,#2A2440);border:1px solid var(--border,#3A3352);
  border-radius:10px;padding:9px 12px;
}
.lg-note--accent{ background:var(--lg-accent-soft);border-color:var(--lg-accent);color:var(--text,#EEEAF6) }


/* ══════════════════════════════════════════════════════════════════════
   B3 (HUD R3 M2) — Hợp đồng chế độ tập trung (focus mode).
   API: window.LGFocus.enter/update/exit/active — xem shared/chrome.js.
   Hiệu lực: <html data-lg-focus="1">. ⛔ KHÔNG áp vào app nào ở M2 — chỉ
   hợp đồng + demo (scripts/hud-shell-demo.html).
   ══════════════════════════════════════════════════════════════════════ */
.lg-focus-bar{ display:none }
[data-lg-focus="1"] .lg-header-mount{
  /* Thu còn một thanh 34px — §1.4 "thoát (×) · vị trí · tiến độ · đồng hồ",
     không chip, không chuông, không mascot, không dải thi đua. */
  padding-top:calc(4px + env(safe-area-inset-top,0px));
  padding-bottom:4px;
  min-height:0;
}
[data-lg-focus="1"] .lg-header-mount > *{ display:none!important }
[data-lg-focus="1"] .lg-header-mount .lg-focus-bar{
  display:flex!important;align-items:center;gap:10px;width:100%;height:34px;
}
.lg-focus-exit{
  flex-shrink:0;width:26px;height:26px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;
  background:transparent;border:1px solid var(--border,rgba(255,255,255,.14));color:var(--text,#EEEAF6);cursor:pointer;padding:0;
}
.lg-focus-exit:hover{ background:rgba(255,255,255,.08) }
.lg-focus-exit:focus-visible{ outline:2px solid #20B9B9;outline-offset:2px }
/* M3 (23/09) — nút tắt/bật SFX dùng chung, gắn bởi lgFocusEnsureSfxBtn()
   (shared/chrome.js) khi app gọi LGFocus.enter({sfx:true}). Cùng khuôn thị
   giác 26px với .lg-focus-exit (thanh chỉ cao 34px, không có chỗ cho hộp
   44px THẬT) — vùng CHẠM 44×44 dựng bằng ::before absolute mở rộng ra
   ngoài hộp 26px (9px mỗi cạnh: 26+9*2=44), không đổi kích thước nhìn thấy
   được, không đụng layout thanh. */
.lg-focus-sfx{
  position:relative;flex-shrink:0;width:26px;height:26px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;
  background:transparent;border:1px solid var(--border,rgba(255,255,255,.14));color:var(--text,#EEEAF6);cursor:pointer;padding:0;
}
.lg-focus-sfx::before{ content:'';position:absolute;inset:-9px }
.lg-focus-sfx:hover{ background:rgba(255,255,255,.08) }
.lg-focus-sfx:focus-visible{ outline:2px solid #20B9B9;outline-offset:2px }
.lg-focus-sfx[aria-pressed="true"]{ color:var(--text-dim,rgba(232,224,255,.5)) }
[data-theme="light"] .lg-focus-sfx{ border-color:rgba(0,0,0,.14);color:#1a1a2e }
[data-theme="light"] .lg-focus-sfx[aria-pressed="true"]{ color:rgba(26,26,46,.45) }
/* ⛔ VÁ 23/09 (S/T phát hiện) — `flex-shrink:0` CÙNG `text-overflow:ellipsis`
   là mâu thuẫn: shrink:0 khoá phần tử ở bề rộng NỘI DUNG THẬT (không bao
   giờ co lại), nên trình duyệt không bao giờ có cớ để chạy ellipsis — nhãn
   dài cứ tràn ra ngoài. Đo được: #lgHeader tràn 522px ở viewport 375px.
   Sửa gốc: min-width:0 (bắt buộc để flex item co được dưới content-width
   tự nhiên của nó — không có dòng này flex-shrink:1 vẫn vô tác dụng) +
   flex-shrink:1 (được phép co, ellipsis giờ có cớ để chạy). tabular-nums
   thêm theo chuẩn Hallmark (nhãn có thể mang số thứ tự câu). */
.lg-focus-label{
  font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.82rem;font-weight:700;color:var(--text,#EEEAF6);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex-shrink:1;min-width:0;
  font-variant-numeric:tabular-nums;
}
/* M4 (23/09) — ô số câu RIÊNG, tách khỏi `.lg-focus-label` (xem chú thích ở
   lgFocusApply() opts.count, shared/chrome.js). KHÔNG co (`flex-shrink:0`):
   đây là con số đang ĐỔI liên tục, phải luôn đọc được trọn vẹn; phần co lại
   khi hẹp là tên bài ở `.lg-focus-label` bên trái nó (đã `flex-shrink:1`).
   `tabular-nums` để chữ số không đổi bề rộng qua từng lần cập nhật — tránh
   nhãn "nhảy" ngang mỗi khi số đổi (0-9 cùng bề rộng số). */
.lg-focus-count{
  flex-shrink:0;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.78rem;font-weight:600;
  color:var(--dim,#A79DBE);font-variant-numeric:tabular-nums;white-space:nowrap;
}
.lg-focus-count[hidden]{ display:none }
.lg-focus-progress{
  flex:1 1 auto;min-width:40px;height:6px;border-radius:3px;background:var(--border,#3A3352);overflow:hidden;
}
.lg-focus-progress-fill{ display:block;height:100%;width:0;border-radius:3px;background:#20B9B9;transition:width .25s }
.lg-focus-timer{
  flex-shrink:0;font-family:var(--font-ui-family,"Be Vietnam Pro"),system-ui,sans-serif;font-size:.82rem;font-weight:700;
  color:var(--dim,#A79DBE);font-variant-numeric:tabular-nums;
}
/* ══ 19/09 · QA chủ app, việc #10 (vế sau) ══
   "Ở các màn hình focus mode vẫn cần có phần header với các quick access tool
    (trợ giúp, light/dark mode switch, ...) như ở các HUD khác."
   §1.4 bản cũ chốt thanh tập trung chỉ có "thoát · vị trí · tiến độ · đồng hồ".
   Chủ app mở thêm ĐÚNG HAI thứ: đổi sáng/tối và trợ giúp. Không chuông,
   không mascot, không chip — hai thứ này khác chỗ ở chỗ chúng KHÔNG mang
   thông tin mới để lôi em ra khỏi bài, chúng chỉ làm bài đang làm dễ chịu hơn.
   ⚠ chrome.js DỌN chính hai nút đang có của header vào đây rồi trả về lúc
   thoát — KHÔNG dựng bản sao. Một nút đổi theme thứ hai sẽ có trạng thái
   riêng và hai nút sẽ nói hai chuyện khác nhau. */
.lg-focus-tools{ display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:2px }
.lg-focus-tools:empty{ display:none }
[data-theme="light"] .lg-focus-exit{ border-color:rgba(0,0,0,.14);color:#1a1a2e }

/* Cột phụ TRÁI biến mất HOÀN TOÀN (không phải visibility:hidden — §1.4). */
/* ⛔ SỬA 19/09 (M5) — `minmax(0,1fr)` MỘT MÌNH là một lỗi câm, đã tái hiện
   được: nó để lưới chỉ còn MỘT rãnh explicit, nên `grid-column:2` của cột
   phụ tra cứu bên dưới rơi vào một rãnh IMPLICIT cỡ `auto`. Rãnh auto lấy
   kích thước tối thiểu tự động của phần tử, mà `.lg-rail` có
   `overflow-y:auto` ⇒ tối thiểu tự động là 0, còn `1fr` đã nuốt hết chỗ
   trống ⇒ rãnh 2 ra ĐÚNG 0px. Đo trên speaking/#sDrill ở 1440px:
   `grid-template-columns` = "1144px 0px", rail `display:block` nhưng rộng
   0px. Tức cột phụ tra cứu của Nói đã VÔ HÌNH từ M3 tới giờ mà không có lỗi
   nào báo ra — đúng loại hỏng "theo hướng đóng, không kêu" repo này đã trả
   giá nhiều lần.
   Khai đủ HAI rãnh: rãnh 2 là `auto` nên khi cột phụ bị `display:none`
   (mọi .lg-shell TRÁI, và cả .lg-shell--right dưới 1280px) nó co về 0 —
   không chiếm chỗ thừa. Khi cột phụ được ở lại thì nó có `width` tường minh
   ở rule ≥1280px bên dưới, và rãnh auto ôm đúng bề rộng đó. */
[data-lg-focus="1"] .lg-shell{ grid-template-columns: minmax(0,1fr) auto!important }
/* ⛔ ĐẢO LẠI RÃNH KHI TẬP TRUNG. Lưới tập trung xếp NGƯỢC với lưới thường:
   cột giữa ở rãnh 1, cột phụ tra cứu ở rãnh 2. Mà phía trên đã ghim
   `.lg-shell > *:not(.lg-rail){grid-column:2}` cho lưới thường — không đảo
   lại ở đây thì cột giữa bị ghim sang rãnh 2 = rãnh `auto`, co theo nội
   dung, còn rãnh `1fr` rỗng chiếm hết chỗ.
   Đo 20/09 khi mới ghim mà chưa có ba dòng này: Vault 1008px → 663px,
   Pron Coach 866px → 562px NGAY lúc vào chế độ tập trung. Tức là làm bài
   trong một cột hẹp hơn lúc đang chọn bài — ngược hẳn ý của §1.4. */
[data-lg-focus="1"] .lg-shell > *:not(.lg-rail):not(.lg-rail-gutter){ grid-column:1 }
[data-lg-focus="1"] .lg-shell > .lg-rail{ grid-column:2 }
[data-lg-focus="1"] .lg-shell > .lg-rail{ display:none }
/* Cột phụ PHẢI được PHÉP ở lại — chỉ khi bài cần tra cứu, app tự thêm class
   này lên chính .lg-rail đang hiện (§1.4 "chỉ hiện nếu bài đó cần tra cứu";
   §B3 "cột phụ PHẢI được phép ở lại khi bài cần tra cứu"). Mặc định ẨN
   (mobile-first) — chỉ HIỆN từ 1280px, viết bằng min-width chứ không phải
   max-width:1279.98px để tránh đẻ mốc lạ ngoài hiến chương §1.3 (VALID_PX
   của scripts/hud-audit-media-queries.mjs không có 1279.98, chỉ có 768.02).
   "Dưới 1280px, hoặc người dùng phóng chữ: một cột canh giữa, bỏ cột phụ"
   (§1.4/§B3) ⇒ mặc định display:none NGOÀI media đã tự thoả cả hai vế —
   phóng chữ làm layout viewport hẹp lại tương đương hạ dưới ngưỡng 1280px. */
[data-lg-focus="1"] .lg-shell--right > .lg-rail.lg-rail--study{ display:none }
@media (min-width:1280px){
  [data-lg-focus="1"] .lg-shell--right > .lg-rail.lg-rail--study{
    display:block;
    grid-column:2;
    /* Bề rộng TƯỜNG MINH, cùng công thức với rãnh cột phụ lúc không tập
       trung (xem grid-template-columns ở @media(min-width:1024px) phía trên)
       — rãnh `auto` của lưới tập trung ôm theo số này. Bỏ dòng này là rơi
       lại đúng lỗi 0px đã tả ở trên.
       (22/09, §R26d) Nay là BIẾN chứ không phải clamp() chép tay: rãnh kéo
       trong focus ghi `--lg-rail-wr` lên khung, `.lg-shell--right` chọn nó
       làm `--lg-rail-w` (mặc định vẫn đúng clamp cũ). Rãnh tính vị trí từ
       CÙNG biến này ⇒ mép rãnh và mép cột phụ không thể lệch nhau. Vẫn
       tường minh — chỉ là một số tường minh đọc từ biến. */
    width: var(--lg-rail-w);
  }
}

/* Ẩn Logi + dải thi đua khi tập trung. Hai class này là chỗ M3+ SẼ gắn —
   đặt sẵn selector ở đây để app chỉ cần đặt đúng class, không cần code CSS
   focus riêng lần nữa. */
[data-lg-focus="1"] .lg-companion,
[data-lg-focus="1"] .lg-streak-rail{ display:none!important }
/* Mọi position:fixed KHÔNG thuộc bài phải biến mất — opt-out bằng
   `.lg-focus-keep` (§1.4/B3) cho thứ bắt buộc phải ở lại (ví dụ chính
   .lg-focus-bar, hoặc toast lỗi của app; fixed đang display:none lúc quét thì
   được bỏ qua — ẩn kiểu opacity vẫn cần keep). CSS không đọc được computed style
   của phần tử khác nên chrome.js quét bằng getComputedStyle MỘT LẦN lúc
   enter() (không phải trong vòng lặp render/scroll — §1.7 chỉ cấm gọi trong
   đó) rồi gắn class này; exit() gỡ lại đúng danh sách đã gắn. */
[data-lg-focus="1"] .lg-focus-hide-fixed{ display:none!important }

/* design-v2 A4 (24/09) — icon chrome dùng chung, xem shared/chrome.js lgIcon() */
/* v6 DUOTONE (25/09, HUD-R48; vá adversarial review B1 25/09) — --ic-s/--ic-ta/
   --ic-warm là biến CUSTOM PROPERTY, kế thừa xuyên qua <use href> vào cây
   shadow của <symbol> (đã kiểm trên Chromium — custom-prop đi theo cây DOM
   thường, <use> không cắt kế thừa như Shadow DOM thật). --ic-hue do
   lgIcon()/lgIconSyncHues() đặt theo TỪNG icon; icon không có hue rơi về
   --accent hiện tại.
   ⚠ ĐÃ BỎ color-mix() (bản trước): --ic-t = color-mix(...) làm layer TINT
   trong icons/ui/sprite.svg tự thành "invalid at computed-value time" bất cứ
   khi nào phần tử KHÔNG có class lg-ic (rail strip thiếu class — bug thật,
   xem shared/chrome.js dòng dựng .lg-rail-icon-glyph), hoặc trang không nạp
   theme.css (/parent/, /share/ — không có --accent), hoặc trình duyệt không
   có color-mix (iOS Safari <16.2) — cả ba trường hợp fill tính ra ĐEN ĐẶC
   thay vì rơi về outline-only như icon monotone cũ. Sửa: MỘT nguồn màu
   --ic-s (không color-mix), tint = cùng --ic-s nhưng fill-opacity thấp hơn
   qua --ic-ta — không có phép tính nào có thể IACVT ngoài việc chính --ic-s/
   --ic-ta không tồn tại, mà lúc đó style="fill:var(--ic-s, none)" trong sprite
   rơi đúng về 'none' (var() fallback CHỈ hoạt động khi biến không hề được
   khai báo trong phạm vi đó — đúng tình huống thiếu class/thiếu theme.css). */
.lg-ic{
  width:1em;height:1em;display:inline-block;vertical-align:-0.15em;flex:none;color:currentColor;
  --ic-s:var(--ic-hue, var(--accent));
  --ic-ta:.4;
  --ic-warm:#F59E0B;
}
:root[data-theme="dark"] .lg-ic,:root:not([data-theme="light"]) .lg-ic{
  --ic-ta:.52;
}
/* wt/icon-fix (28/09) — CHỈ giao diện sáng: chủ app chê icon "nhạt vì mỗi
   icon không đủ đậm màu riêng". Gốc: ở sáng stroke=currentColor (chữ
   tím-xám nhạt --dim) + tint --ic-s 40% trên nền gần-trắng ⇒ bợt. Icon CÓ
   hue thật (--ic-ink do lgIcon()/lgIconSyncHues() đặt, xem shared/chrome.js
   + theme.css "ICON HUE INK") giờ tô STROKE bằng chính hue đậm của nó thay
   vì currentColor; icon KHÔNG có hue (--ic-ink chưa từng được set) rơi về
   fallback currentColor — giữ NGUYÊN hình cũ, không vỡ. Nâng tint theo
   (.4→.55) vì thân tô càng cần rõ hơn khi stroke đã có màu riêng. (Bản đầu
   KHÔNG đụng giao diện tối; từ 30/09 có luật tối riêng ngay dưới.)
   ⚠ `:not(.lg-exam-frozen)` TRÊN CHÍNH html — 4 trang phòng thi (reading/
   listening-test/rl/worksheet/rl/teacher/review) mang class này và VẪN có
   data-theme="light" (mặc định sáng-trước, xem theme.js) nên nếu không
   loại trừ, luật này sẽ chạm cả icon trong phòng thi (đồng hồ đếm giờ…) —
   đúng thứ exam-freeze.test.mjs (so màu render bằng Playwright, không phải
   chỉ đọc chuỗi) dựng ra để bắt. lgIcon() cũng tự chừa lg-exam-frozen theo
   cách khác (opts.examEmoji) nhưng đó chỉ áp cho icon CÓ truyền option đó —
   selector ở đây là lưới an toàn THỨ HAI, chặn tại gốc CSS cho mọi icon. */
html[data-theme="light"]:not(.lg-exam-frozen) .lg-ic{
  color:var(--ic-ink, var(--accent-text, currentColor));
  --ic-ta:.55;
}
/* wt/dark-ink-0930 (30/09) — CÙNG luật cho giao diện tối. Góp ý UX 30/09
   "icon muted quá": ở tối stroke=currentColor (chữ xám-tím) nên màu kỹ năng
   chỉ còn trong tint 52% (1,8–2,5:1 trên nền) ⇒ icon xám đục. Ink tối là bản
   SÁNG hơn của hue (theme.css "ICON HUE INK — giao diện TỐI"). --ic-ta giữ .52
   của khối trên: tint vẫn là hue GỐC (--ic-s) nên thân tô tối hơn nét ⇒ còn
   duotone. Chọn tối bằng :not([data-theme="light"]) như khối --ic-ta phía
   trên (trang chưa gắn data-theme vẫn là tối mặc định). Cùng độ đặc thù
   (0,3,1) với luật sáng; danh sách loại trừ bên dưới (0,4,0) thắng cả hai.
   Cùng lưới an toàn :not(.lg-exam-frozen) — phòng thi không đổi màu. */
html:not([data-theme="light"]):not(.lg-exam-frozen) .lg-ic{
  color:var(--ic-ink, var(--accent-text, currentColor));
}
/* Đợt 0 (28/09, wt/emoji-d0) — khoảng cách icon–chữ cho icon INLINE trong một
   dòng chữ (không phải icon đứng một mình trong nút/chip đã có gap riêng).
   Đặt sau CLASS PHỤ .lg-ic--inline, KHÔNG đổi .lg-ic toàn cục — nhiều chỗ
   .lg-ic đang canh bằng flex gap của phần tử cha, thêm margin toàn cục sẽ lệch
   những chỗ đó (PLAN_EMOJI_ICONS §3 dặn). */
.lg-ic.lg-ic--inline{ margin-inline-end:.3em; }

/* window.lgDot(state) — chấm trạng thái CSS thay 🟢🔴🟡 (Đợt 0, wt/emoji-d0).
   Dùng token hue có sẵn (theme.css): reading=xanh lá(ok)/gold=vàng(warn)/
   practice=đỏ(bad)/dim=xám(off) — không thêm hex mới ngoài theme.css. */
.lg-dot{
  display:inline-block;width:.55em;height:.55em;border-radius:50%;flex:none;
  vertical-align:.05em;
}
.lg-dot--ok{ background:var(--hue-reading); }
.lg-dot--warn{ background:var(--hue-gold); }
.lg-dot--bad{ background:var(--hue-practice); }
.lg-dot--off{ background:var(--dim); }
/* Icon nằm TRONG nút/chip tô ĐẶC nền accent + chữ sáng (--on-accent) phải
   GIỮ currentColor — luật tô-đậm ở trên nếu áp nguyên xi sẽ đè lên đúng chữ
   trắng cần giữ, làm icon gần như biến mất trên nền accent đậm (contrast
   ink-trên-accent không được đo/đảm bảo). Danh sách này là MỌI chỗ hiện có
   trong repo nền accent/màu riêng ĐẶC (không phải nền mờ color-mix) mang
   .lg-ic: .lg-btn--primary (nút chính, hiện chưa app nào dùng nhưng khai sẵn
   để không bẫy người thêm sau), .lg-upgrade-chip (icon star, header),
   .lg-renew-chip.lg-renew--danger (icon crown, nền cam nhạt CÓ chữ màu riêng
   #E07856 — icon phải khớp màu chữ, không lệch tông sang ink vàng của
   --hue-gold). ⚠ KHÔNG thêm .lg-pick-item.active / .lg-rail-icon-btn.is-active
   vào đây — nền hai chỗ đó chỉ tô 16% qua color-mix (không phải nền ĐẶC),
   icon lấy --ic-ink ở đó là ĐÚNG hướng cải thiện đang làm, không phải lỗi. */
:root[data-theme="light"] .lg-btn--primary .lg-ic,
:root[data-theme="light"] .lg-upgrade-chip .lg-ic,
:root[data-theme="light"] .lg-renew-chip.lg-renew--danger .lg-ic,
/* Review Opus 28/09 — icon nằm trong khung ĐÃ có màu chủ đích (pill trạng
   thái, nhãn "sắp ra mắt" mờ, vòng page-head theo --ph-hue, tab đang chọn,
   màn thi thử Speaking đóng băng) ⇒ theo màu khung, không lấy --ic-ink. */
:root[data-theme="light"] .tag .lg-ic,
:root[data-theme="light"] .card-status .lg-ic,
:root[data-theme="light"] .lg-ph-lens .lg-ic,
:root[data-theme="light"] .nav-btn.active .lg-ic,
:root[data-theme="light"] body.sp-mock-active .lg-ic,
/* 30/09 review Opus — nền accent ĐẶC còn sót từ 28/09 (ink trên #7C5CBF
   chỉ 1,6–2,3:1): tab kỹ năng/picker vocab, chip app leaderboard, thẻ nâng
   cấp Pro ở hub. Áp cả hai theme. */
:root[data-theme="light"] .skill-pill.active .lg-ic,
:root[data-theme="light"] .picker-tab.active .lg-ic,
:root[data-theme="light"] .lb-chip.active .lg-ic,
:root[data-theme="light"] .app-card.cta-pro .card-icon .lg-ic,
/* 30/09 QA live (quét tương phản icon/nền đặc trên hub, vocab, speaking):
   avatar Pro (vương miện vàng trên nền vàng 1,1:1), nút "Làm tiếp" routine,
   nút tô đặc chữ trắng (.btn-em/.btn-teal/.btn-primary/…). */
:root[data-theme="light"] .lg-tier-avatar .lg-ic,
:root[data-theme="light"] .lgrt-next-btn .lg-ic,
:root[data-theme="light"] .lgrt-up-btn .lg-ic,
:root[data-theme="light"] .btn-em .lg-ic,
:root[data-theme="light"] .btn-teal .lg-ic,
:root[data-theme="light"] .btn-primary .lg-ic,
:root[data-theme="light"] .pw-btn .lg-ic,
:root[data-theme="light"] .save-btn .lg-ic,
/* 30/09 review Opus (icon-hue-all) — mọi glyph nay có hue nên nút tô ĐẶC chữ
   trắng nào chưa có tên ở đây đều thành icon màu trên nền màu (1,1–2,6:1):
   CTA thẻ app hub, đăng nhập, banner, MFI, Pron, admin; .rate-again giữ ✕
   cùng màu đỏ của chữ. */
:root[data-theme="light"] .card-btn .lg-ic,
:root[data-theme="light"] .login-btn .lg-ic,
:root[data-theme="light"] .cta .lg-ic,
:root[data-theme="light"] .lg-cta-solid .lg-ic,
:root[data-theme="light"] .btn-cta .lg-ic,
:root[data-theme="light"] .btn-submit .lg-ic,
:root[data-theme="light"] .congrat-btn .lg-ic,
:root[data-theme="light"] .pron-btn-primary .lg-ic,
:root[data-theme="light"] .streak-reminder-btn .lg-ic,
:root[data-theme="light"] .save-all .lg-ic,
:root[data-theme="light"] .apply .lg-ic,
:root[data-theme="light"] .btn-approve .lg-ic,
:root[data-theme="light"] .rate-again .lg-ic,
/* QA live 30/09 (quét 13 trang): nút tô đặc họ *-btn-primary (dd-/hw-/…),
   .novice-card-btn (Vocab), .drop-btn.primary/.quiz (LOGI Drop; .drop-btn
   thường là nút viền, GIỮ ink) — 1,45–1,6:1. */
:root[data-theme="light"] [class*="btn-primary"] .lg-ic,
:root[data-theme="light"] .novice-card-btn .lg-ic,
:root[data-theme="light"] .drop-btn.primary .lg-ic,
:root[data-theme="light"] .drop-btn.quiz .lg-ic,
/* 30/09 — cùng danh sách cho giao diện tối (luật ink tối ở trên). Giữ hai nửa
   khớp nhau: thêm chỗ loại trừ mới thì thêm vào CẢ HAI nửa. */
:root:not([data-theme="light"]) .lg-btn--primary .lg-ic,
:root:not([data-theme="light"]) .lg-upgrade-chip .lg-ic,
:root:not([data-theme="light"]) .lg-renew-chip.lg-renew--danger .lg-ic,
:root:not([data-theme="light"]) .tag .lg-ic,
:root:not([data-theme="light"]) .card-status .lg-ic,
:root:not([data-theme="light"]) .lg-ph-lens .lg-ic,
:root:not([data-theme="light"]) .nav-btn.active .lg-ic,
:root:not([data-theme="light"]) body.sp-mock-active .lg-ic,
:root:not([data-theme="light"]) .skill-pill.active .lg-ic,
:root:not([data-theme="light"]) .picker-tab.active .lg-ic,
:root:not([data-theme="light"]) .lb-chip.active .lg-ic,
:root:not([data-theme="light"]) .app-card.cta-pro .card-icon .lg-ic,
:root:not([data-theme="light"]) .lg-tier-avatar .lg-ic,
:root:not([data-theme="light"]) .lgrt-next-btn .lg-ic,
:root:not([data-theme="light"]) .lgrt-up-btn .lg-ic,
:root:not([data-theme="light"]) .btn-em .lg-ic,
:root:not([data-theme="light"]) .btn-teal .lg-ic,
:root:not([data-theme="light"]) .btn-primary .lg-ic,
:root:not([data-theme="light"]) .pw-btn .lg-ic,
:root:not([data-theme="light"]) .save-btn .lg-ic,
:root:not([data-theme="light"]) .card-btn .lg-ic,
:root:not([data-theme="light"]) .login-btn .lg-ic,
:root:not([data-theme="light"]) .cta .lg-ic,
:root:not([data-theme="light"]) .lg-cta-solid .lg-ic,
:root:not([data-theme="light"]) .btn-cta .lg-ic,
:root:not([data-theme="light"]) .btn-submit .lg-ic,
:root:not([data-theme="light"]) .congrat-btn .lg-ic,
:root:not([data-theme="light"]) .pron-btn-primary .lg-ic,
:root:not([data-theme="light"]) .streak-reminder-btn .lg-ic,
:root:not([data-theme="light"]) .save-all .lg-ic,
:root:not([data-theme="light"]) .apply .lg-ic,
:root:not([data-theme="light"]) .btn-approve .lg-ic,
:root:not([data-theme="light"]) .rate-again .lg-ic,
:root:not([data-theme="light"]) [class*="btn-primary"] .lg-ic,
:root:not([data-theme="light"]) .novice-card-btn .lg-ic,
:root:not([data-theme="light"]) .drop-btn.primary .lg-ic,
:root:not([data-theme="light"]) .drop-btn.quiz .lg-ic{
  color:currentColor;
}
