/* =============================================================================
   Ival và Cộng Sự — design system
   Bản sắc thương hiệu (xanh mực + đồng, serif cho tiêu đề) giữ nguyên theo brief.
   Kỷ luật hệ thống mượn từ Linear/Vercel: thang spacing 8px, type scale cố định,
   đủ trạng thái, tiết chế hiệu ứng. KHÔNG biến site thành bản sao xám trung tính.
   ========================================================================== */

:root {
    /* --- Màu V1 (bảng gốc — V2 thử ngày 2026-09-14, Ival thích bản gốc hơn, rollback
       cùng ngày). Số liệu tương phản: apps/fisocial/brand/palette.md. --brass-strong
       là token DUY NHẤT giữ lại từ đợt V2 — xem lý do ngay dưới đây. --*/
    --ink:           #16232E;
    --ink-deep:      #0E1821;
    --ink-soft:      #33454F;
    --paper:         #EFECE5;
    --paper-warm:    #E2DDD0;
    /* CHỈ dùng làm NỀN. Muốn chữ sáng trên nền tối cố định thì dùng
       --on-inverse: --paper-pure đảo thành màu tối ở chế độ tối. */
    --paper-pure:    #FFFFFF;
    --rule:          #B8B1A5;
    --brass:         #A37D3D;   /* chỉ nền lớn có chữ tối đè lên — không dùng cho chữ nhỏ */
    /* Token MỚI, giữ lại sau khi rollback V2 — không phải màu mới, CÙNG GIÁ TRỊ với
       --brass ở trên (đổi visual = 0). Lý do giữ: đợt thử V2 suýt làm mù `:focus-visible`
       (viền lấy nét bàn phím) vì --brass khi đó phải gánh CẢ hai vai trò — nền nút lớn
       VÀ viền/nét mảnh (focus ring, gạch dưới link, chấm bullet...) — mà hai vai trò đó
       cần ngưỡng tương phản khác hẳn nhau. V1 "an toàn" chỉ vì tình cờ #A37D3D đủ tối
       cho cả hai, không phải vì kiến trúc tách vai trò. Giờ có token riêng cho
       "phải nhìn thấy như nét mảnh", đổi --brass sau này (đậm/nhạt hơn cho nền nút) sẽ
       không còn vô tình làm mù focus ring nữa — bug thật, sửa dù không đổi màu hiển thị. */
    --brass-strong:  #A37D3D;
    --brass-hover:   #BC9350;   /* nền nút primary khi hover — chữ ink-deep 6.34 */
    --brass-bright:  #E8CE9C;   /* chữ/nét sáng trên nền tối cố định — 10.46 */
    --brass-text:    #755A2B;   /* chữ trên nền sáng  — 5.08 / 4.62 */
    --brass-on-dark: #AA8240;   /* chữ trên nền tối   — 4.55 */
    --link-visited:  #6E5528;   /* liên kết đã xem — ĐẢO theo theme */

    /* --- Hệ màu NHẤN: dùng cho hình vẽ SVG và cho nhãn phân nhóm nội dung ---
       Màu ở đây mang NGHĨA, không trang trí: mỗi nhóm chương trình một sắc, giữ
       nguyên sắc đó ở mọi nơi nhóm ấy xuất hiện.
       Ngũ hành: hổ phách = Thổ sinh Kim; xanh dương trầm = Thủy sinh Mộc; xanh lá = Mộc.
       Cả ba đạt >= 4.5 trên nền giấy ở cả hai chế độ nên dùng được cho CHỮ NHỎ,
       không chỉ cho đồ họa.
       CẤM dùng cặp đỏ/xanh lá làm tín hiệu tăng/giảm: tệp đọc gần như toàn nam, ~8% nam
       giới mù màu đỏ-lục. Tăng = hổ phách, giảm = xanh dương, và luôn kèm nhãn chữ. */
    --acc-amber: #85550C;   /* 4.84 — đủ cho chữ nhỏ trên mọi nền giấy */
    --acc-sage:  #3D684E;   /* 5.06 */
    --acc-sky:   #33607F;   /* 5.56 */
    --acc-wash:  #E2DDD0;   /* mảng nền rất nhạt — theo định nghĩa là NỀN, không đo tương phản */
    --text:          #23303A;
    --muted:         #53616B;
    --on-dark:       #C7D2DA;
    --on-dark-soft:  #8FA0AB;

    /* --- Spacing: thang 8px. Chỉ 4px được phép nhỏ hơn (nửa nhịp). --- */
    --s-1:   4px;
    --s-2:   8px;
    --s-3:  12px;
    --s-4:  16px;
    --s-5:  24px;
    --s-6:  32px;
    --s-7:  48px;
    --s-8:  64px;
    --s-9:  96px;
    --s-10:128px;

    /* --- Type scale --- */
    --fs-2xs: 0.75rem;    /* 12 */
    --fs-xs:  0.8125rem;  /* 13 */
    --fs-sm:  0.875rem;   /* 14 */
    --fs-base:1rem;       /* 16 */
    --fs-md:  1.125rem;   /* 18 */
    --fs-lg:  1.25rem;    /* 20 */
    --fs-xl:  1.5rem;     /* 24 */
    --fs-2xl: 2rem;       /* 32 */
    --fs-3xl: 2.5rem;     /* 40 */
    --fs-4xl: 3rem;       /* 48 */

    --lh-tight: 1.2;
    --lh-snug:  1.35;
    --lh-body:  1.65;

    --ls-caps: .12em;

    /* --- Hình khối --- */
    --r-1: 4px;
    --r-2: 8px;
    --r-full: 999px;
    --bw: 1px;
    --shadow-1: 0 1px 2px rgba(14,24,33,.06);
    --shadow-2: 0 8px 24px rgba(14,24,33,.12);
    --shadow-3: 0 16px 40px rgba(14,24,33,.18);

    --dur: 160ms;
    --ease: cubic-bezier(.2,.6,.3,1);

    --wrap:   1120px;   /* 140 × 8 */
    --narrow:  720px;   /*  90 × 8 */

    --serif: "Lora", "Iowan Old Style", Georgia, "Times New Roman", serif;
    --sans:  "Be Vietnam Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* ═══ HỆ THỐNG THEME ═══════════════════════════════════════════════════════
   Token chia làm hai lớp:
   1. HẰNG SỐ THƯƠNG HIỆU (--c-*) — không bao giờ đổi.
   2. VAI TRÒ (--paper, --text, --heading…) — đảo theo chế độ sáng/tối.

   Header, footer, hero và khối flagship CỐ Ý giữ nền tối ở CẢ HAI chế độ
   (--inverse-bg). Đó là bản sắc thương hiệu, không phải hệ quả của theme.

   Thứ tự ưu tiên: lựa chọn của người dùng > sở thích hệ điều hành > sáng.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
    /* KHUNG (chrome): header, footer, dải thương hiệu. Phải luôn tách khỏi nền
       trang. Ở chế độ sáng nó tối hơn giấy; ở chế độ TỐI nó phải tối HƠN nền
       trang, nếu không #16232E trùng đúng --paper-warm và khung biến mất. */
    --inverse-bg:      #16232E;   /* nền tối cố định — header/footer/flagship */
    --inverse-bg-deep: #0E1821;   /* bậc sâu hơn — dải bằng chứng */
    --on-inverse:  #F7F4ED;   /* chữ trên nền tối cố định */
    --heading:     #16232E;   /* màu tiêu đề — ĐẢO theo theme */
    --rule-strong: #B9AE99;   /* viền phần tử tương tác (ngưỡng 3.0) */
    color-scheme: light;
}

/* Chế độ tối theo hệ điều hành — chỉ khi người dùng CHƯA chọn tay */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --paper:      #0F1A23;
        --paper-warm: #1B2A36;
        --paper-pure: #253847;
        --inverse-bg:      #0A1219;
        --inverse-bg-deep: #050C12;
        --rule:       #455B6C;
        --rule-strong:#4D687E;
        --text:       #E9EEF2;
        --muted:      #9CACB8;
        --ink-soft:   #C3D0DA;
        --heading:    #FFFFFF;
        --brass-text: #D9B978;
        --link-visited: #C9AC72;
        --acc-amber: #F0B457;
        --acc-sage:  #9ACBAA;
        --acc-sky:   #8CBEE0;
        --acc-wash:  #1B2A36;
        --shadow-1: 0 1px 2px rgba(0,0,0,.4);
        --shadow-2: 0 8px 24px rgba(0,0,0,.5);
        --shadow-3: 0 16px 40px rgba(0,0,0,.6);
        color-scheme: dark;
    }
}
/* Người dùng chọn tay — thắng mọi thứ */
:root[data-theme="dark"] {
    --paper:      #0F1A23;
    --paper-warm: #1B2A36;
    --paper-pure: #253847;
    --inverse-bg:      #0A1219;
    --inverse-bg-deep: #050C12;
    --rule:       #455B6C;
    --rule-strong:#4D687E;
    --text:       #E9EEF2;
    --muted:      #9CACB8;
    --ink-soft:   #C3D0DA;
    --heading:    #FFFFFF;
    --brass-text: #D9B978;
    --link-visited: #C9AC72;
    --acc-amber: #F0B457;
    --acc-sage:  #9ACBAA;
    --acc-sky:   #8CBEE0;
    --acc-wash:  #1B2A36;
    --shadow-1: 0 1px 2px rgba(0,0,0,.4);
    --shadow-2: 0 8px 24px rgba(0,0,0,.5);
    --shadow-3: 0 16px 40px rgba(0,0,0,.6);
    color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
/* Thuộc tính hidden LUÔN thắng. Không có dòng này, bất kỳ lớp nào đặt display
   (flex/grid/block) đều âm thầm vô hiệu hóa hidden — hộp tìm kiếm .fs-search
   {display:flex} đã MỞ SẴN phủ lên mọi trang ngay lần deploy đầu (2026-09-11).
   Mặc định an toàn: ẩn là ẩn. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
    margin: 0;
    overflow-wrap: break-word;
    background: var(--paper);
    color: var(--text);
    font-family: var(--sans);
    font-size: var(--fs-base);
    line-height: var(--lh-body);
    -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { font-family: var(--serif); color: var(--heading); line-height: var(--lh-tight); font-weight: 600; margin: 0; }
p { margin: 0; }
/* Reset RỘNG cho mọi thẻ HTML5 có margin/padding mặc định của trình duyệt mà
   trước đây chỉ zero từng thẻ một khi gặp lỗi (h1-h4, p ở trên). `figure` lọt
   lưới đúng kiểu này 2026-09-14: margin-inline 40px mặc định UA, không ai viết
   CSS vì đây là thẻ đầu tiên dùng. Danh sách lấy theo đúng nhóm thẻ Bootstrap
   Reboot (github.com/twbs/bootstrap, 170k+ sao) và Tailwind Preflight
   (github.com/tailwindlabs/tailwindcss, 89k+ sao) đều zero — hai nguồn phổ biến
   nhất cho đúng loại reset này — để lần sau chèn thẻ mới không lặp lại lỗi. */
figure, blockquote, dl, dd, fieldset { margin: 0; padding: 0; border: 0; }
a { color: var(--heading); text-underline-offset: 3px; transition: color var(--dur) var(--ease); }
a:hover { color: var(--brass-text); }
img { max-width: 100%; height: auto; display: block; }
::selection { background: var(--brass); color: var(--ink-deep); }

.wrap        { max-width: var(--wrap);   margin: 0 auto; padding-inline: var(--s-5); }
.wrap-narrow { max-width: var(--narrow); margin: 0 auto; padding-inline: var(--s-5); }
.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* --- Focus: một kiểu duy nhất cho toàn site --- */
:focus-visible { outline: 2px solid var(--brass-strong); outline-offset: 3px; border-radius: var(--r-1); }
.skip-link { position: absolute; left: -9999px; }
.skip-link:focus { left: var(--s-3); top: var(--s-3); background: var(--inverse-bg); color: var(--on-inverse); padding: var(--s-3) var(--s-4); z-index: 100; border-radius: var(--r-1); }

/* =============================== Header ================================== */
.site-head { background: var(--inverse-bg); color: var(--on-inverse); position: sticky; top: 0; z-index: 50; }
/* Header KHÔNG bị .wrap (1120px) chặn: hàng này cần ~1211px cho brand + 7 mục
   menu + cụm cờ, nên ở mọi cỡ màn hình chữ menu đều bị bóp xuống hai dòng.
   Header là một DẢI, không phải cột nội dung — cho nó khung riêng rộng hơn. */
.site-head-inner { display: flex; align-items: center; gap: var(--s-5); min-height: var(--s-8); max-width: 1440px; }
/* Mục menu không được co và không được xuống dòng: thà chuyển sang nút menu
   còn hơn để chữ gãy làm đôi (lỗi gặp 2026-09-10, có ở mọi bề rộng). */
.menu > li { flex: none; }
.menu > li > a { white-space: nowrap; }
.brand { color: var(--on-inverse); text-decoration: none; font-family: var(--serif); font-size: var(--fs-md); font-weight: 600; }
.brand-logo { max-height: var(--s-5); width: auto; }
.site-menu { margin-left: auto; }
.menu { display: flex; align-items: center; gap: var(--s-1); list-style: none; margin: 0; padding: 0; }
.menu > li > a {
    color: var(--on-inverse); text-decoration: none; font-size: var(--fs-sm);
    padding: var(--s-3); display: inline-block; border-radius: var(--r-1);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.menu > li > a:hover { background: rgba(255,255,255,.08); }
.menu > li > a:active { background: rgba(255,255,255,.14); }
/* Mục menu đang xem. Dùng gạch chân dày ở đáy chứ không đổi nền: nền đã dành
   cho trạng thái hover, hai thứ chồng nhau thì không phân biệt được "đang xem"
   với "đang rê chuột". .is-current-branch là mục cấp 1 chứa trang hiện tại
   (đọc bài trong /kien-thuc/ thì mục Kiến thức sáng). */
.menu > li > a.is-current, .menu > li > a.is-current-branch { color: var(--brass-bright); }
.menu > li > a.is-current::after, .menu > li > a.is-current-branch::after {
    content: ""; position: absolute; left: var(--s-3); right: var(--s-3); bottom: 2px;
    height: 2px; background: var(--brass); border-radius: 1px;
}
.menu > li > a { position: relative; }
.submenu a.is-current { color: var(--brass-text); font-weight: 600; }
.menu-cta > a { border: var(--bw) solid var(--brass); color: var(--brass-bright) !important; }
.menu-cta > a:hover { background: var(--brass); color: var(--ink-deep) !important; }

.has-sub { position: relative; }
.sub-toggle { display: none; }
.submenu, .submenu-2 { list-style: none; margin: 0; padding: var(--s-2) 0; }
.submenu {
    position: absolute; left: 0; top: 100%; min-width: 320px;
    background: var(--paper); border: var(--bw) solid var(--rule); border-radius: var(--r-2);
    box-shadow: var(--shadow-3);
    opacity: 0; visibility: hidden; transform: translateY(var(--s-1));
    transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}
.has-sub:hover > .submenu, .has-sub:focus-within > .submenu { opacity: 1; visibility: visible; transform: none; }
/* Menu con "Khóa học" có ~27 dòng (05/10/2026): một cột cao hơn màn hình laptop, mà
   bảng thả xuống không cuộn được -> mục cuối không với tới. Chia 2 cột, ngắt cột
   tường minh ở .col-break thay vì để trình duyệt tự cân (tự cân thì nhãn nhóm có
   thể nằm cuối cột này còn mục của nó sang cột kia). Mobile trả về 1 cột trong
   khối @media (max-width: 1232px). */
.submenu-cols { columns: 2; column-gap: 0; min-width: 640px; }
.submenu-cols > li { break-inside: avoid; }
.submenu-cols > .col-break { break-before: column; }
/* Mục "Courses" bên EN nằm lệch phải: bảng 640px canh trái thì tràn mép màn hình (đo 710..1350
   ở khung 1280). Canh theo mép phải của mục cha. */
.submenu-cols.submenu-end { left: auto; right: 0; }
.submenu-cols > .col-break.submenu-label { margin-top: 0; border-top: 0; }
.submenu a { display: block; padding: var(--s-2) var(--s-4); color: var(--text); text-decoration: none; font-size: var(--fs-sm); }
.submenu a:hover { background: var(--paper-warm); color: var(--heading); }
.submenu a:active { background: var(--rule); }
.submenu-label { padding: var(--s-3) var(--s-4) var(--s-1); font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); }

/* ── Thang thụt đầu dòng của menu con ───────────────────────────────────────
   Trước đây nhãn nhóm và mục thuộc nhóm cùng mép trái 16px, nên nhìn vào không
   thấy cấp nào cả — chỉ là một danh sách phẳng có mấy dòng chữ hoa xen giữa.

   Nhãn nhóm và mục thuộc nhóm là ANH EM trong cùng một <ul> (không có thẻ bọc),
   nên dùng bộ chọn anh em: mọi <li> ĐỨNG SAU một nhãn đều là mục cấp 2. Cách
   này đúng vì nhãn luôn đi trước nhóm của nó, và mục cấp 1 (như "Tất cả bài
   viết") luôn đứng trước nhãn đầu tiên.

   Ba bậc: 16 -> 32 -> 48, đúng thang 8px. */
.submenu > li:not(.submenu-label) > a { padding-left: var(--s-4); }
.submenu-label ~ li:not(.submenu-label):not(.submenu-sep) > a { padding-left: var(--s-6); }
/* .submenu-sep là mục CẤP 1 đứng sau vạch ngăn ("So sánh khóa học"), không phải
   mục con của nhóm ngay trên nó — nên nó về lại mép 16px. */

/* Nhóm thứ hai trở đi có đường kẻ phân cách — thụt đầu dòng cho biết CẤP, đường
   kẻ cho biết chỗ NHÓM MỚI bắt đầu. Hai việc khác nhau. */
.submenu-label ~ .submenu-label { margin-top: var(--s-2); padding-top: var(--s-3); border-top: var(--bw) solid var(--rule); }
.submenu-sep { border-top: var(--bw) solid var(--rule); margin-top: var(--s-2); padding-top: var(--s-1); }

/* Cấp 3: thêm một vạch dọc làm mốc. Ở độ sâu này chỉ thụt đầu dòng thì mắt khó
   nối mục con với mục cha; vạch dọc đặt đúng mép chữ của cấp 2 làm việc đó. */
.has-sub-2 > .submenu-2 {
    padding-left: 0; margin-left: var(--s-6);
    border-left: var(--bw) solid var(--rule);
}
.has-sub-2 > .submenu-2 > li > a { font-size: var(--fs-xs); color: var(--muted); padding: var(--s-1) var(--s-4); }
.has-sub-2 > .submenu-2 > li > a:hover { color: var(--heading); }
.pill { font-size: var(--fs-2xs); letter-spacing: .08em; text-transform: uppercase; border: var(--bw) solid var(--rule); border-radius: var(--r-full); padding: 0 var(--s-2); margin-left: var(--s-2); color: var(--muted); }
.pill-en { border-color: var(--brass-on-dark); color: var(--brass-on-dark); }
.nav-toggle { display: none; }

/* ================================ Nút ==================================== */
.btn {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: var(--s-7); padding: 0 var(--s-5); border-radius: var(--r-1);
    text-decoration: none; font-size: var(--fs-sm); font-weight: 500;
    border: var(--bw) solid transparent; cursor: pointer; font-family: inherit;
    transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
                color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: .5; pointer-events: none; }
.btn-primary { background: var(--brass); color: var(--ink-deep); border-color: var(--brass); }
.btn-primary:hover { background: var(--brass-hover); color: var(--ink-deep); }
/* btn-ghost mặc định cho nền SÁNG — tức nền trang, trường hợp phổ biến.
   Trước đây mặc định là chữ nghịch đảo (sáng), còn các khối nền sáng phải tự
   ghi tên mình vào một danh sách: .page-head, .more-card, .hero. axe-core bắt
   được hậu quả ngày 2026-09-11: khối .secondary-cta thêm sau, không ai nhớ ghi
   tên vào danh sách, nên nút "Trao đổi về lộ trình hợp tác" ở trang Môi giới ra
   chữ #f7f4ed trên nền #efece5 — tương phản 1.07:1, gần như tàng hình.
   Giờ mặc định là phương án AN TOÀN, còn dải nền tối thì tự khai báo bằng biến.
   Thêm khối mới mà quên khai báo thì nút vẫn đọc được, không biến mất. */
.btn-ghost {
    border-color: var(--ghost-border, var(--rule));
    color: var(--ghost-text, var(--heading));
}
.btn-ghost:hover {
    border-color: var(--brass-strong);
    color: var(--ghost-text-hover, var(--brass-text));
}
/* Dải nền tối đặt biến MỘT lần; nút không cần biết nó đang nằm trong khối nào.
   `.site-footer` ở đây từng là selector CHẾT: chân trang thật mang class
   `.site-foot` (xem partials/site-footer.hbs và .site-foot ở dưới). Chưa gây lỗi
   nhìn thấy được vì chân trang chưa có .btn-ghost nào — nhưng thêm một nút vào
   đó là nó lấy màu chế độ sáng trên nền tối. Sửa cho đúng tên. */
.price-block, .site-foot {
    --ghost-border: rgba(255,255,255,.35);
    --ghost-text: var(--on-inverse);
    --ghost-text-hover: var(--brass-bright);
}

/* ============================== Khối trang ================================ */
.eyebrow { font-family: var(--sans); font-size: var(--fs-2xs); letter-spacing: .14em; text-transform: uppercase; color: var(--acc, var(--brass-text)); margin-bottom: var(--s-3); font-weight: 600; }
/* Hero BÁM THEO THEME. Trước đây luôn tối ở cả hai chế độ — nhưng hero chiếm
   gần hết màn hình đầu nên chế độ sáng trông y như chế độ tối cho tới khi cuộn.
   Header/footer vẫn tối cố định: chúng là dải mỏng, đọc ra là "khung" chứ không
   phải "nền trang". Dải bằng chứng ngay dưới hero giữ nền tối -> tạo nhịp mạnh. */
.hero {
    background: var(--paper-warm); color: var(--text);
    position: relative; overflow: hidden;
    padding-block: var(--s-10) var(--s-9);
    --ghost-border: var(--rule-strong);
}
.hero-inner { position: relative; z-index: 2; }
.hero-title {
    color: var(--heading); font-size: clamp(var(--fs-3xl), 7vw, var(--fs-6xl));
    line-height: 1.04; letter-spacing: -.015em; max-width: 15ch;
    margin-bottom: var(--s-5);
}
.hero-lead { font-size: var(--fs-lg); color: var(--muted); max-width: 52ch; margin-bottom: var(--s-6); }
.hero-actions { display: flex; flex-wrap: wrap; gap: var(--s-3); }

/* Khung BÊN TRONG .page-head phải trùng khung của thân bài. Trước đây tiêu đề
   dùng .wrap (1120px) còn thân bài dùng .wrap-narrow (720px) -> hai mép trái
   khác nhau trên cùng một trang, đọc ra là lệch hàng. Dải nền vẫn tràn hết
   bề ngang, chỉ nội dung bên trong mới đổi khung. */
.page-head { padding-block: var(--s-8) var(--s-6); border-bottom: var(--bw) solid var(--rule); background: var(--paper-warm); }
.page-title { font-size: clamp(var(--fs-xl), 3.4vw, var(--fs-3xl)); margin-bottom: var(--s-3); }
.page-lead { font-size: var(--fs-md); color: var(--muted); max-width: 62ch; margin-bottom: var(--s-5); }
.section-lead { color: var(--muted); max-width: 62ch; margin-top: var(--s-2); }

.paths, .skills, .flagship, .course-more, .course-group, .skill-foot, .compare-link, .home-content { padding-block: var(--s-8); }
.skills { background: var(--paper-warm); border-block: var(--bw) solid var(--rule); }
/* Tiêu đề mục: KHÔNG để bằng nhau hết ở fs-xl. Hero 72px rồi rơi thẳng xuống
   24px là mất hẳn một bậc thang — trang đọc ra đều đều, không có chỗ cho mắt
   dừng. Cho co giãn lên fs-2xl ở màn rộng. */
.paths h2, .skills h2, .flagship h2, .course-group h2, .skill-foot h2,
.paths-block h2, .levels h2, .course-faq h2 { font-size: clamp(var(--fs-xl), 2.6vw, var(--fs-2xl)); }

.grid { display: grid; gap: var(--s-5); margin-top: var(--s-6); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(304px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(256px, 1fr)); }
/* .grid-2 KHÔNG phải "2 cột": auto-fit + minmax(304px) vừa 3 cột trong khung 1120px.
   Bốn thẻ cần 2×2 thì dùng lớp này — số cột viết tường minh, không để thẻ thứ tư đứng lẻ. */
.grid-2x2 { grid-template-columns: 1fr; }
@media (min-width: 640px) { .grid-2x2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* Ba thẻ: một cột, rồi ba cột từ 896px. auto-fit sẽ ra 2+1 ở khổ giữa. */
.grid-3x { grid-template-columns: 1fr; }
@media (min-width: 896px) { .grid-3x { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.card {
    /* --paper-pure, KHÔNG phải --paper: --paper chính là nền trang, nên thẻ đặt
       trên nền trang sẽ chìm hẳn, chỉ còn viền 1px. Thẻ là bề mặt NỔI ở mọi ngữ
       cảnh — đó là thứ tạo ra tầng lớp thị giác của trang. */
    background: var(--paper-pure); border: var(--bw) solid var(--rule);
    padding: var(--s-5); border-radius: var(--r-2);
    /* Bóng ở trạng thái NGHỈ, không chỉ khi hover. Đo được: thẻ trắng trên nền
       giấy chỉ chênh 1.18:1 — dưới ngưỡng mắt thấy được một bề mặt nếu chỉ có
       màu. Độ nổi phải do bóng + viền gánh, nếu không cả trang đọc ra là phẳng. */
    box-shadow: var(--shadow-1);
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.card:hover { border-color: var(--brass-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
.card-title { font-size: var(--fs-lg); margin-bottom: var(--s-2); }
.card-title a { text-decoration: none; }
/* ── Nhấn theo NHÓM nội dung ────────────────────────────────────────────────
   Mỗi nhóm chương trình mang một sắc và giữ nguyên sắc đó ở mọi nơi nó xuất
   hiện: flagship = hổ phách, theo thị trường = xanh dương, kỹ năng cốt lõi =
   xanh lá. Màu ở đây MANG NGHĨA — nhìn sắc là biết đang ở nhóm nào — chứ không
   phải tô cho vui. Ba sắc đều hợp mệnh: hổ phách Thổ sinh Kim, xanh dương Thủy
   sinh Mộc, xanh lá là Mộc. Đỏ loại từ đầu (khắc Kim, và trùng tín hiệu giảm).
   KHÔNG dùng --acc trong vùng nền tối cố định — ba sắc đảo theo chế độ nên sẽ
   tàng hình; preflight chặn việc đó. */
.acc-amber { --acc: var(--acc-amber); }
.acc-sky   { --acc: var(--acc-sky); }
.acc-sage  { --acc: var(--acc-sage); }
.card-tag { font-size: var(--fs-2xs); letter-spacing: .09em; text-transform: uppercase; color: var(--acc, var(--brass-text)); margin-bottom: var(--s-3); }
.card-desc { color: var(--muted); margin-bottom: var(--s-4); font-size: var(--fs-sm); }
.card-link { font-size: var(--fs-sm); text-decoration: none; color: var(--acc, var(--brass-text)); font-weight: 500; }

/* ── Thẻ khóa học: lối chỉ mục sách ─────────────────────────────────────────
   Trang danh sách trước đây là ba khối chữ giống hệt nhau xếp cạnh nhau, không
   có điểm neo mắt. Ba thứ tạo ra khác biệt, không thứ nào là trang trí rỗng:
   số chỉ mục lớn (mắt bám vào ngay), dòng dữ kiện cấu trúc, và chân thẻ có học
   phí — người đọc cần đúng ba thông tin đó để chọn. */
.course-card { position: relative; display: flex; flex-direction: column; overflow: hidden; }
.course-card::before {
    content: ""; position: absolute; inset: 0 0 auto 0; height: 2px;
    background: var(--acc, var(--brass-strong)); transform: scaleX(0); transform-origin: left;
    transition: transform var(--dur) var(--ease);
}
.course-card:hover::before, .course-card:focus-within::before { transform: scaleX(1); }
.card-n {
    position: absolute; top: var(--s-4); right: var(--s-5);
    font-family: var(--serif); font-size: var(--fs-3xl); line-height: 1;
    color: var(--acc, var(--brass-strong)); opacity: .22; pointer-events: none;
}
.course-card .card-title { padding-right: var(--s-8); }
/* Thẻ đối tác (en-home.hbs): cùng khuôn thẻ, logo thay cho số thứ tự. */
.partner-card { display: flex; flex-direction: column; }
.partner-logo { display: block; width: var(--s-8); height: var(--s-8); margin-bottom: var(--s-4); }
.course-card .card-desc { margin-bottom: var(--s-5); }
.card-foot {
    margin-top: auto; padding-top: var(--s-4);
    border-top: var(--bw) solid var(--rule);
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2) var(--s-4);
}
.card-fee { font-family: var(--serif); font-size: var(--fs-lg); color: var(--heading); }

/* ── Thẻ học phí ────────────────────────────────────────────────────────────
   Con số là thứ mắt tìm đầu tiên trên trang này, nên nó được cỡ chữ hiển thị và
   font serif của thương hiệu — không phải một ô trong bảng. Phần còn lại tiết
   chế để con số đứng một mình. */
/* ── Bảng quyết định: tình huống -> chương trình ────────────────────────────
   Hai cột trong một hàng: vế "nếu bạn..." bên trái, đáp án bên phải. Mắt quét
   dọc cột phải để tìm nhanh, đọc cột trái để xác nhận. Ở mobile xếp chồng, đáp
   án tụt xuống dưới cùng nét đồng dẫn mắt. */
.chooser { padding-block: var(--s-8); }
.chooser-intro { font-size: var(--fs-md); max-width: 68ch; margin: 0 0 var(--s-7); }
.chooser-list { list-style: none; margin: var(--s-6) 0 0; padding: 0; }
.ch-row {
    display: grid; grid-template-columns: 1fr; gap: var(--s-1);
    padding-block: var(--s-4); border-top: var(--bw) solid var(--rule);
    text-decoration: none; transition: background var(--dur) var(--ease);
}
.chooser-list li:last-child .ch-row { border-bottom: var(--bw) solid var(--rule); }
@media (min-width: 720px) { .ch-row { grid-template-columns: 1fr auto; gap: var(--s-6); align-items: baseline; } }
.ch-if { color: var(--muted); font-size: var(--fs-sm); }
.ch-then {
    font-family: var(--serif); font-size: var(--fs-md); color: var(--acc-sky);
    white-space: nowrap;
}
.ch-then::after { content: " →"; }
.ch-row:hover { background: var(--paper-warm); }
.ch-row:hover .ch-then { color: var(--heading); }

.pricing { padding-block: var(--s-8); background: var(--paper-warm); border-block: var(--bw) solid var(--rule); }
.price-h { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--acc, var(--muted)); margin-top: var(--s-8); margin-bottom: 0; font-family: var(--sans); font-weight: 600; }
.pricing > .wrap > .price-h:first-of-type { margin-top: var(--s-7); }
.price-card { display: flex; flex-direction: column; }
.pc-name { font-family: var(--serif); font-size: var(--fs-md); color: var(--heading); margin: 0 0 var(--s-3); }
.pc-fee { font-family: var(--serif); font-size: var(--fs-2xl); line-height: 1; color: var(--heading);
    margin: 0 0 var(--s-3); letter-spacing: -.01em; }
.pc-unit { font-size: var(--fs-md); color: var(--muted); margin-left: var(--s-1); letter-spacing: 0; }
.pc-meta { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--muted); margin: 0 0 var(--s-3); }
.pc-note { font-size: var(--fs-sm); color: var(--muted); margin: 0 0 var(--s-4); }
.price-card .card-link { margin-top: auto; }
.price-terms { list-style: none; padding: 0; margin: var(--s-7) 0 0; max-width: 76ch;
    font-size: var(--fs-sm); color: var(--muted); border-top: var(--bw) solid var(--rule); padding-top: var(--s-5); }
.price-terms li + li { margin-top: var(--s-3); }
.price-terms strong { color: var(--heading); }
.card-foot .card-link { margin-left: auto; }

.flagship { background: var(--inverse-bg); color: var(--on-inverse); }
.flagship h2 { color: var(--on-inverse); margin-bottom: var(--s-3); }
.flagship p { color: var(--on-dark); max-width: 58ch; margin-bottom: var(--s-5); }
.cta { background: var(--paper-warm); border-top: var(--bw) solid var(--rule); padding-block: var(--s-8); }
/* Khổ đọc nằm TRONG .wrap, không đặt lên chính .wrap: đặt max-width lên .wrap
   thì margin:0 auto của nó căn giữa cả khối -> lệch khỏi mép trái của lưới
   thẻ phía trên, đọc ra là "trôi giữa trang" chứ không phải một cột. */
/* ── Dải hai cột: lời mời bên trái, nội dung thật bên phải ─────────────────
   Trước đây flagship và CTA là một cột chữ căn trái trong khung 1120px, thừa
   hẳn nửa phải. Cột phải KHÔNG phải khối trang trí cho cân — nó là ba chương
   trình flagship (dải flagship) hoặc ba bước sau khi đăng ký (dải CTA), tức là
   thứ người đọc đang cần biết ở đúng thời điểm đó. */
.split { display: grid; gap: var(--s-7) var(--s-8); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 896px) { .split { grid-template-columns: 1fr 1fr; } }
/* 44ch chỉ áp DƯỚI 896px thì mới đúng nghĩa "khổ đọc": ở MỘT cột, khung .split
   (flagship, CTA — dùng ở hầu hết mọi loại trang qua partials/cta.hbs) vẫn rộng
   hết .wrap trong khi văn bản bị ghim 44ch (~480px). Founder chụp ảnh bắt được
   ở khổ ~880px (chốt 2026-09-13): tiêu đề + đoạn văn + nút dồn hết về nửa trái,
   nửa phải của khối bỏ trắng. Quét lại bằng máy trên toàn site (34 trang × 10
   khổ 640-1180px, chốt lại cùng ngày sau khi founder yêu cầu soát nghiêm hơn)
   xác nhận ĐÂY LÀ TRƯỜNG HỢP DUY NHẤT của lớp lỗi này trên site — mọi khối khác
   bị nghi (course-card, spec-row, level-list, ch-row, post-item-body) đều là
   báo oan do có nhãn ngắn xen với văn bản dài, đã xem ảnh chụp xác nhận không
   hỏng gì. Đo lại: 2 cột vẫn thoải mái từ 896px trở xuống tới khoảng 640px (mỗi
   cột còn ~260px, bằng sàn minmax của .grid-3) — nhưng đổi breakpoint của LƯỚI
   thì lệch khỏi hằng số breakpoint mobile 896px của ui-guidelines.md. Cách đúng:
   chỉ ghim khổ đọc khi ĐÃ hai cột (>=896px, cột kia hẹp nên cần khổ đọc); dưới
   896px một cột thì cho chữ chạy hết bề ngang khối. Điện thoại thật (<500px)
   không đổi gì, vì 44ch (~480px) vốn đã rộng hơn cả khung — chỉ khổ ~640-896px
   là khác. */
@media (min-width: 896px) { .split-main { max-width: 44ch; } }
.split-main .btn { margin-top: var(--s-5); }

.split-list { list-style: none; margin: 0; padding: 0; counter-reset: none; }
.split-list li + li { margin-top: var(--s-1); }
/* Mỗi <li> có ĐÚNG MỘT phần tử bọc .sl-row (thẻ <a> khi là link, <div> khi không).
   Bản đầu nhắm `li > *` nên mục có hai con bị tách thành hai hàng có viền riêng. */
.sl-row { display: flex; gap: var(--s-4); align-items: baseline; text-decoration: none;
    padding-block: var(--s-4); border-top: var(--bw) solid var(--rule); }
.split-list li:last-child .sl-row { border-bottom: var(--bw) solid var(--rule); }
.sl-n { font-family: var(--serif); font-size: var(--fs-lg); color: var(--brass-on-dark); flex: none; }
.sl-body { display: block; }
.sl-body strong { display: block; font-family: var(--serif); font-size: var(--fs-md); font-weight: 600; }
.sl-body em { display: block; font-style: normal; font-size: var(--fs-sm); margin-top: var(--s-1); }

/* Dải flagship nền tối cố định: dùng token KHÔNG đảo theo theme */
.flagship .split-list li > * { border-color: rgba(255,255,255,.16); }
.flagship .sl-body strong { color: var(--on-inverse); }
.flagship .sl-body em { color: var(--on-dark); }
.flagship .split-list a:hover .sl-body strong { color: var(--brass-bright); }

/* Dải CTA nền sáng theo theme */
.cta .sl-n { color: var(--brass-text); }
.cta .sl-body strong { color: var(--heading); }
.cta .sl-body em { color: var(--muted); }
.cta h2 { font-size: var(--fs-xl); margin-bottom: var(--s-3); }
.cta p { color: var(--muted); margin-bottom: var(--s-5); }
.skill-foot p { color: var(--muted); margin-block: var(--s-3) var(--s-5); }

/* ============================ Nội dung dài ================================ */
.single-head { padding-top: var(--s-8); }
.single-title { font-size: clamp(var(--fs-xl), 3.6vw, var(--fs-3xl)); margin-bottom: var(--s-3); }
.single-lead { font-size: var(--fs-md); color: var(--muted); margin-bottom: var(--s-3); }
.single-meta { font-size: var(--fs-sm); color: var(--muted); }
/* Nền + viền riêng: không có khung này thì thân bài chìm vào nền trang,
   đọc ra là chưa xong chứ không phải một khối nội dung. */
.single-content {
    padding-block: var(--s-6) var(--s-8); font-size: var(--fs-md);
    background: var(--paper-pure); border: var(--bw) solid var(--rule); border-radius: var(--r-2);
}
.single-content > * + * { margin-top: var(--s-5); }
.single-content h2 { margin-top: var(--s-8); font-size: var(--fs-xl); }
.single-content h3 { margin-top: var(--s-6); font-size: var(--fs-lg); }
.single-content img { margin-block: var(--s-6); border-radius: var(--r-2); }
/* Ghost <figure> mặc định trình duyệt có margin-inline 40px và figcaption căn
   trái (founder báo 2026-09-14: ảnh thật chèn vào /gioi-thieu/ bị lệch trái,
   chú thích không căn giữa) — đè lại cho khớp khung nội dung, chú thích cùng
   cỡ chữ với văn bản quanh nó (--fs-md, kế thừa từ .single-content) thay vì để
   mặc định trình duyệt tự quyết. */
.single-content .kg-image-card { margin-inline: 0; text-align: center; }
/* Chữ nhỏ + nghiêng, giống hệt quy ước .figure figcaption đã có sẵn cho sơ đồ
   vẽ tay — chú thích ảnh chụp dùng lại đúng ngữ pháp đó thay vì bịa kiểu mới. */
.single-content .kg-image-card figcaption { margin-top: var(--s-3); font-size: var(--fs-sm); font-style: italic; color: var(--muted); text-align: center; }
/* blockquote = KHỐI CHÚ Ý, không phải trích dẫn trang trí. Ghost lột sạch class
   và <div> bọc khi chuyển sang lexical, nên đây là cách DUY NHẤT để biên tập
   viên đặt được một khối nổi bật (cảnh báo pháp lý, lưu ý đối tượng) từ trong
   nội dung. Nền + viền đồng thay cho chữ nghiêng mờ: cảnh báo phải đọc ra là
   cảnh báo, và trang bớt trôi thành một mảng chữ liền. */
/* ── Hình minh họa ──────────────────────────────────────────────────────────
   Nét vẽ + mảng màu phẳng, không đổ bóng. Màu lấy từ token nên hình tự đổi theo
   chế độ sáng/tối — đó là lý do SVG phải viết var(--ill-*) chứ không phải mã hex. */
/* Hình nằm trong CHÍNH khung .wrap-narrow (720px) của thân bài — cùng khổ với
   chữ, không tràn rộng hơn. Từng thử để hình dùng .wrap (1120px) cho "đỡ nhỏ",
   nhưng chữ vẽ trong SVG theo đơn vị viewBox (640) co/giãn CÙNG tỉ lệ với cả
   khung hình: ở 1120px chữ to hẳn ra, còn trên điện thoại (~350-400px) lại co
   ngược lại rất nhỏ — ép hình rộng thêm trên desktop chỉ khiến khoảng co giữa
   hai đầu càng xa, mobile càng khó đọc. Giữ hình đúng khổ thân bài thì tỉ lệ
   co giãn giữa hai đầu gần bằng 1, chữ ổn định hơn ở mọi kích thước, và không
   cần cuộn ngang. Trang chưa có hình thì .figure-wrap rỗng, không chiếm chỗ. */
.figure-wrap:empty { display: none; }
/* margin-inline: 0 là BẮT BUỘC, không phải dọn dẹp cho đẹp. Trình duyệt mặc
   định cho <figure> lề hai bên 40px (UA stylesheet), mà theme chưa bao giờ
   xoá — nên suốt từ lúc dựng, mọi sơ đồ đều hẹp hơn khung thân bài đúng 80px
   và chú thích thụt vào 40px so với chữ. Đo ra: khung 672px nhưng hình chỉ
   592px. Đây chính là phần còn lại của chuyện "chú thích lệch" mà founder
   báo hôm trước — lần đó mới chữa phần căn giữa, chưa chạm tới gốc. */
/* Khối hình CĂN GIỮA cột thân bài (founder chốt 2026-09-11: trên màn hình rộng
   nhìn thuận mắt hơn căn trái). Căn giữa cả KHỐI chứ không riêng thẻ svg — nhờ
   vậy chú thích vẫn căn trái bên trong khối, không bị dàn giữa thành khó đọc.
   margin-inline: auto đồng thời khử lề 40px mà trình duyệt tự thêm cho <figure>
   (UA stylesheet) — chính chỗ từng làm chú thích thụt vào 40px so với chữ. */
.figure { margin-block: var(--s-8); margin-inline: auto; max-width: 28rem; }
/* GIỚI HẠN BỀ NGANG CỦA HÌNH, không phải của khung.
   Chuẩn cỡ chữ mới (font-size/viewBox >= 3.5%) sinh ra từ ràng buộc của ĐIỆN
   THOẠI: cột 342px thì chữ phải >= 12px. Nhưng cùng tỉ lệ đó, nếu để hình kéo
   hết khổ thân bài 672px trên desktop thì chữ trong hình ra 25px — to hơn cả
   tiêu đề mục, đọc ra là hình bị phóng đại. Khoá bề ngang hình ở 28rem: chữ
   trong hình ra ~17px, ngang cỡ chữ thân bài, ở mọi màn hình rộng hơn thế.
   Trên điện thoại 342px < 448px nên luật này không đụng tới, mốc 12.8px giữ
   nguyên. Vẫn margin-inline 0 -> hình và chú thích thẳng mép trái với chữ. */
.figure svg { display: block; width: 100%; height: auto; font-family: var(--sans); }
.figure figcaption { margin-top: var(--s-3); font-size: var(--fs-sm); color: var(--muted); }
/* Ảnh người: dùng lại đúng khung của sơ đồ (cùng khổ, cùng chú thích) nhưng
   thêm viền + bóng như thẻ — ảnh chụp là một BỀ MẶT NỔI, không phải nét vẽ
   nằm trên giấy. */
.portrait img { border-radius: var(--r-2); border: var(--bw) solid var(--rule); box-shadow: var(--shadow-1); }
/* Trên điện thoại 64px trống trên/dưới mỗi hình là quá nhiều — màn hình ngắn,
   hình lại đang nhỏ sẵn, khoảng trắng đó khiến hình trông càng bé. */
/* Hình dẫn nhập đứng đầu thân bài (69 bài blog nhúng hình ở vị trí này): bỏ lề
   trên, nếu không lề của thẻ nội dung cộng lề của hình thành khoảng trống gần
   130px trước khi người đọc thấy bất cứ thứ gì. */
.single-content > .figure:first-child { margin-top: 0; }
@media (max-width: 640px) { .figure { margin-block: var(--s-6); } }

.single-content blockquote {
    border-left: 3px solid var(--brass-strong); background: var(--paper-warm);
    margin-block: var(--s-6); padding: var(--s-5);
    border-radius: 0 var(--r-2) var(--r-2) 0; color: var(--ink-soft);
}
.single-content blockquote p { margin-block: var(--s-3); }
.single-content blockquote > :first-child { margin-top: 0; }
.single-content blockquote > :last-child { margin-bottom: 0; }
.single-content blockquote em { color: var(--muted); }
.single-content ul, .single-content ol { padding-left: var(--s-5); }
.single-content li + li { margin-top: var(--s-2); }
.single-content table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.single-content th, .single-content td { border: var(--bw) solid var(--rule); padding: var(--s-3); text-align: left; }
.single-content th { background: var(--paper-warm); }
/* Bảng do biên tập viên chèn không có khối bọc. KHÔNG dùng white-space: nowrap:
   bảng ở đây là bảng SO SÁNH có văn xuôi trong ô, nowrap biến mỗi ô thành một
   dòng rất dài và bảng rộng gấp mấy lần màn hình. Để chữ tự xuống dòng, chỉ
   siết cỡ chữ và padding; overflow-x giữ lại làm lưới an toàn cho ô dài. */
@media (max-width: 720px) {
    .single-content table { display: block; overflow-x: auto; font-size: var(--fs-xs); }
    .single-content th, .single-content td { padding: var(--s-2); }
}
/* Trang so sánh cần bề rộng 1120px cho bảng 3 cột, nhưng dòng CHỮ dài 1120px
   thì đọc rất mệt. Giới hạn khổ đọc cho mọi thứ trừ bảng. */
.compare-content > :not(table) { max-width: 76ch; }
.compare-content { padding-bottom: var(--s-8); }
/* Dải liên hệ: hai thẻ ngang hàng — kênh liên lạc và quy trình sau khi liên hệ.
   Trang này là đích của mọi CTA nên không được để một cột chữ trơ trọi. */
.contact-band { padding-block: var(--s-8); background: var(--paper-warm); border-block: var(--bw) solid var(--rule); }
.contact-band .grid { margin-top: 0; }
.step-list { margin: var(--s-4) 0 0; padding-left: var(--s-5); color: var(--muted); font-size: var(--fs-sm); }
.step-list li + li { margin-top: var(--s-3); }
.step-list strong { color: var(--heading); }
.plain-list { list-style: none; padding: 0; margin-top: var(--s-4); }
.plain-list li { border-bottom: var(--bw) solid var(--rule); padding-block: var(--s-3); }

/* Kênh liên lạc có biểu tượng (lien-he.hbs, site-footer.hbs). Biểu tượng đơn sắc
   theo currentColor, nên tự đổi màu theo theme sáng/tối và theo nền chân trang. */
.ico { width: var(--s-5); height: var(--s-5); flex: none; }
.channel-list { list-style: none; padding: 0; margin: var(--s-4) 0 0; }
.channel-list li + li { border-top: var(--bw) solid var(--rule); }
.channel { display: flex; align-items: center; gap: var(--s-4); padding-block: var(--s-3); min-height: var(--s-8); text-decoration: none; color: var(--heading); }
/* Ô icon kênh liên lạc (05/10/2026, founder: icon "hiền quá, phèn"): nền xanh mực, hình ĐẶC
   màu đồng sáng (10.46). --ico-cut = màu nền ô, để chữ Zalo trông như khoét trong bong bóng. */
.channel-ico { display: grid; place-items: center; width: var(--s-7); height: var(--s-7); flex: none; border-radius: var(--r-2);
    --ch-bg: var(--inverse-bg); background: var(--ch-bg); color: var(--brass-bright); --ico-cut: var(--ch-bg); transition: background var(--dur) var(--ease); }
.channel-ico .ico { width: var(--s-5); height: var(--s-5); }
.channel-body { display: grid; gap: var(--s-1); min-width: 0; }
.channel-body strong { font-weight: 600; overflow-wrap: anywhere; }
.channel-body span { font-size: var(--fs-sm); color: var(--muted); }
.channel:hover strong { color: var(--brass-text); }
/* Hover chỉ đổi BIẾN của ô icon: selector bắt đầu bằng .channel mà đặt background tối thì
   preflight coi cả dòng .channel là vùng nền tối (sai — chữ nằm trên nền giấy). */
.channel-ico:is(.channel:hover *) { --ch-bg: var(--inverse-bg-deep); }
/* Chân trang nền tối: ô đồng, hình xanh mực đậm (5.02 — cặp chữ trên nút primary). */
.foot-ch { display: inline-flex; align-items: center; gap: var(--s-3); }
.foot-ico { display: grid; place-items: center; width: var(--s-6); height: var(--s-6); flex: none; border-radius: var(--r-1);
    background: var(--brass); color: var(--ink-deep); --ico-cut: var(--brass); transition: background var(--dur) var(--ease); }
.foot-ico .ico { width: calc(var(--s-4) + var(--s-1)); height: calc(var(--s-4) + var(--s-1)); }
.foot-ch:hover .foot-ico { background: var(--brass-hover); --ico-cut: var(--brass-hover); }
/* Hàng icon mạng xã hội: vùng bấm 48px (--s-7) quanh ô icon 32px. */
.foot-social { display: flex; flex-wrap: wrap; gap: var(--s-1); list-style: none; padding: 0; margin: var(--s-3) 0 0 calc(-1 * var(--s-2)); }
.foot-social a { display: grid; place-items: center; width: var(--s-7); height: var(--s-7); }
.foot-social a:hover .foot-ico { background: var(--brass-hover); --ico-cut: var(--brass-hover); }

/* ============================== Thẻ bài =================================== */
/* ── Danh sách bài: dạng HÀNG, không phải lưới thẻ ───────────────────────────
   Founder chốt 2026-09-11. Lưới thẻ hợp khi có vài bài; /kien-thuc/ nay có 78
   bài trong 8 chuỗi, và lưới hai cột thẻ viền làm cả trang thành một đống hộp
   nặng nề, mắt không có đường đi. Dạng hàng cho phép quét tiêu đề theo chiều
   dọc — đúng cách người ta đọc một mục lục.
   Kiểu THẺ vẫn giữ cho .related-grid (chỉ 3 bài, cần khối rõ ràng ở cuối bài). */
.post-list { display: flex; flex-direction: column; padding-block: var(--s-7) 0; }
.post-list .post-item {
    flex-direction: row; align-items: flex-start; gap: var(--s-5);
    border: 0; border-top: var(--bw) solid var(--rule); border-radius: 0;
    background: none; overflow: visible; padding-block: var(--s-5);
}
/* Hàng đầu không cần đường kẻ trên: đã có đường kẻ của dải chủ đề ngay phía trên. */
.post-list .post-item:first-child { border-top: 0; padding-top: 0; }
.post-list .post-item:hover { border-color: var(--rule); box-shadow: none; transform: none; }
.post-list .post-item-img {
    flex: none; width: 200px; aspect-ratio: 3 / 2; border-radius: var(--r-2);
    border: var(--bw) solid var(--rule); overflow: hidden; background: var(--paper-warm);
}
.post-list .post-item-img img { transition: transform 420ms var(--ease); }
.post-list .post-item-body { padding: 0; min-width: 0; }
.post-list .post-item-title { font-size: var(--fs-xl); }
.post-list .post-item-title a { transition: color var(--dur) var(--ease); }
.post-list .post-item:hover .post-item-title a { color: var(--brass-text); }
/* Tóm tắt gọn hai dòng: mọi hàng cao bằng nhau thì mắt quét được thành cột. */
.post-list .post-item-excerpt {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
@media (prefers-reduced-motion: no-preference) {
    .post-list .post-item:hover .post-item-img img { transform: scale(1.04); }
}
@media (max-width: 560px) {
    /* Điện thoại: ô ảnh nhỏ lại và bỏ tóm tắt. Cột chỉ rộng ~342px — giữ cả ảnh
       lớn lẫn hai dòng tóm tắt thì tiêu đề bị bóp còn vài chữ mỗi dòng. */
    .post-list .post-item { gap: var(--s-4); padding-block: var(--s-4); }
    .post-list .post-item-img { width: 96px; }
    .post-list .post-item-title { font-size: var(--fs-base); }
    .post-list .post-item-excerpt { display: none; }
}
.post-item {
    border: var(--bw) solid var(--rule); background: var(--paper-pure);
    border-radius: var(--r-2); overflow: hidden; display: flex; flex-direction: column;
    transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.post-item:hover { border-color: var(--brass-strong); box-shadow: var(--shadow-2); transform: translateY(-2px); }
/* aspect-ratio: giữ chỗ cho ảnh -> không nhảy layout khi ảnh tải xong (CLS) */
.post-item-img { display: block; aspect-ratio: 16 / 9; background: var(--paper-warm); }
.post-item-img img { width: 100%; height: 100%; object-fit: cover; }
.post-item-body { padding: var(--s-5); display: flex; flex-direction: column; gap: var(--s-2); }
.post-item-tag { font-size: var(--fs-2xs); letter-spacing: .09em; text-transform: uppercase; color: var(--brass-text); }
.post-item-tag a { color: inherit; text-decoration: none; }
.post-item-tag a:hover { text-decoration: underline; text-underline-offset: .25em; }

.post-item-title { font-size: var(--fs-lg); }
.post-item-title a { text-decoration: none; }
.post-item-excerpt { color: var(--muted); font-size: var(--fs-sm); }
.post-item-meta { font-size: var(--fs-xs); color: var(--muted); margin-top: auto; padding-top: var(--s-2); }

/* ===================== Trạng thái: rỗng / lỗi / đang tải ================== */
.empty-state {
    border: var(--bw) dashed var(--rule); border-radius: var(--r-2);
    padding: var(--s-8) var(--s-5); text-align: center; color: var(--muted);
    background: var(--paper-warm);
}
.empty-state-mark {
    width: var(--s-7); height: var(--s-7); border-radius: var(--r-full);
    border: var(--bw) solid var(--rule); background: var(--paper);
    display: grid; place-items: center; margin: 0 auto var(--s-4);
    color: var(--brass-text); font-family: var(--serif); font-size: var(--fs-lg);
}
.empty-state-title { font-size: var(--fs-lg); margin-bottom: var(--s-2); }
.empty-state p { max-width: 44ch; margin: 0 auto var(--s-5); font-size: var(--fs-sm); }
.empty { color: var(--muted); font-size: var(--fs-sm); }

.error-page { padding-block: var(--s-10); text-align: center; }
.error-code { font-family: var(--serif); font-size: var(--fs-4xl); color: var(--brass-text); margin-bottom: var(--s-3); }
.error-page .page-title { margin-bottom: var(--s-4); }
.error-page p { color: var(--muted); margin-bottom: var(--s-6); }
.error-actions { display: flex; gap: var(--s-3); justify-content: center; flex-wrap: wrap; }

/* ============================ Thành phần phụ ============================== */
.crumb { padding-top: var(--s-4); }
.crumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0; padding: 0; font-size: var(--fs-xs); color: var(--muted); }
.crumb li + li::before { content: "/"; margin-right: var(--s-2); }
.crumb a { color: var(--muted); text-decoration: none; }
.crumb a:hover { color: var(--brass-text); }
.crumb [aria-current="page"] { color: var(--heading); }

.toc { border: var(--bw) solid var(--rule); background: var(--paper-warm); padding: var(--s-5); margin-top: var(--s-6); border-radius: var(--r-2); }
.toc-title { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); margin-bottom: var(--s-3); font-weight: 600; }
.toc-list { list-style: none; margin: 0; padding: 0; }
.toc-list li + li { margin-top: var(--s-2); }
.toc-list a { color: var(--ink-soft); text-decoration: none; font-size: var(--fs-sm); }
.toc-list a:hover { color: var(--brass-text); }
.toc-h3 { padding-left: var(--s-4); }
.toc-h3 a { font-size: var(--fs-xs); color: var(--muted); }
/* ⚠️ Sticky mục lục trong CÙNG cột đọc đã thử và BỊ RUT LẠI (founder, 2026-09-13):
   nó đè lên chữ khi cuộn qua vì không có "làn" riêng — chỉ đúng khi mục lục
   đứng trong một CỘT PHỤ tách biệt (sidebar thật, lưới 2 cột), không phải nằm
   thẳng trên đầu dòng chảy nội dung. Đừng thêm position:sticky vào .toc lần
   nữa mà không dựng lưới 2 cột thật đi kèm. */

/* ── Rail mục lục trang pillar (14/10/2026) ─────────────────────────────────
   Đây CHÍNH là "lưới 2 cột thật" mà ghi chú trên đòi: mục lục nằm trong cột lề
   trái của riêng nó, không đè lên chữ. Lưới 1fr | 720px | 1fr giữ cột chữ đúng chỗ
   cũ (thẳng mép với đầu trang, học phí, FAQ); rail chiếm lề trái, sát cột chữ.
   1280px là bề ngang nhỏ nhất mà lề trái còn đủ chỗ: (1280 − 720) / 2 = 280px.
   Mặc định (không .has-rail, hoặc màn hẹp) là khối dọc thường: phương án an toàn. */
.toc-list a[aria-current] { color: var(--brass-text); font-weight: 600; }
@media (min-width: 1280px) {
    .pillar-body.has-rail { display: grid; grid-template-columns: 1fr minmax(0, var(--narrow)) 1fr; }
    .has-rail .pillar-rail { grid-column: 1; grid-row: 1; justify-self: end; width: 100%; max-width: calc(var(--s-10) * 2 + var(--s-8)); }
    .has-rail .pillar-main { grid-column: 2; grid-row: 1; min-width: 0; }
    .has-rail .pillar-rail .wrap-narrow { position: sticky; top: calc(var(--s-8) + var(--s-5)); padding-inline: var(--s-5) var(--s-7); }
    .has-rail .toc {
        background: none; border: 0; border-left: var(--bw) solid var(--rule); border-radius: 0;
        padding: 0 0 0 var(--s-4); margin-top: var(--s-6);
        max-height: calc(100vh - var(--s-8) - var(--s-7)); overflow-y: auto;
    }
    /* Mục đang đọc: vạch đồng ở mép trái, cùng ngữ pháp .rail > li.is-here. */
    .has-rail .toc-list li { position: relative; }
    .has-rail .toc-list a[aria-current]::before {
        content: ""; position: absolute; top: 0; bottom: 0;
        left: calc(var(--s-4) * -1 - var(--bw)); width: calc(var(--bw) * 3);
        background: var(--brass-strong);
    }
}

/* Hàng thẻ chủ đề dưới bài. Trước đây chỉ có padding-bottom nên nó dính sát đáy
   thẻ thân bài — đọc ra như phần thừa rơi ra khỏi bài, không phải một hàng riêng.
   Cho nó khoảng thở cân đối ở cả hai phía. */
.post-tags { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-6); }
.post-tag { font-size: var(--fs-xs); border: var(--bw) solid var(--rule); border-radius: var(--r-full); padding: var(--s-1) var(--s-3); text-decoration: none; color: var(--muted); }
.post-tag:hover { border-color: var(--brass); color: var(--brass-text); }

.share { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; padding-block: var(--s-5); border-block: var(--bw) solid var(--rule); margin-top: var(--s-5); }
.share-label { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); font-weight: 600; }
.share-btn {
    font: inherit; font-size: var(--fs-sm); border: var(--bw) solid var(--rule);
    background: var(--paper); color: var(--ink-soft);
    min-height: var(--s-6); padding-inline: var(--s-4); border-radius: var(--r-1);
    text-decoration: none; cursor: pointer; display: inline-flex; align-items: center;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.share-btn:hover { border-color: var(--brass); color: var(--brass-text); }
.share-btn:active { background: var(--paper-warm); }
.share-btn.is-done { border-color: var(--brass); color: var(--brass-text); }

.related { padding-block: var(--s-7) var(--s-2); }
.related-title { font-size: var(--fs-xl); margin-bottom: var(--s-5); }
/* ── Phân trang ───────────────────────────────────────────────────────────── */
.pagination {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: var(--s-4);
    /* Trước đây không có CSS nào cho khối này nên nó dính sát đáy trang. */
    margin-top: var(--s-7); padding-top: var(--s-6); padding-bottom: var(--s-8);
    border-top: var(--bw) solid var(--rule);
}
.pg-btn {
    display: inline-flex; align-items: center; gap: var(--s-2);
    min-height: 48px; padding: var(--s-2) var(--s-4);
    border: var(--bw) solid var(--rule); border-radius: var(--r-full);
    font-size: var(--fs-sm); color: var(--text); text-decoration: none;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pg-btn:hover { border-color: var(--brass); color: var(--brass-text); }
/* Hết trang: vẫn giữ chỗ để hàng không nhảy, nhưng nói rõ là không bấm được.
   Dùng MÀU CHỮ PHỤ chứ không giảm độ mờ: opacity .45 cho tương phản 2.2:1 và
   axe-core bắt ngay. Viền nét đứt mới là thứ nói "không bấm được", không phải
   độ mờ. */
.pg-btn.is-off { color: var(--muted); border-style: dashed; cursor: default; }
.pg-pos { font-size: var(--fs-sm); color: var(--muted); order: 3; width: 100%; text-align: center; }
.pg-list { display: flex; gap: var(--s-1); list-style: none; margin: 0; padding: 0; flex-wrap: wrap; justify-content: center; }
.pg-num {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 48px; min-height: 48px; padding-inline: var(--s-2);
    border-radius: var(--r-1); font-size: var(--fs-sm); color: var(--text);
    text-decoration: none; font-variant-numeric: tabular-nums;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pg-num:hover { background: var(--paper-warm); color: var(--heading); }
/* Trang đang xem dùng CLASS RIÊNG chứ không phải .pg-num[aria-current]: nó đặt
   nền tối cố định, và preflight — đúng — coi mọi thứ mang gốc class đó là vùng
   nền tối, rồi chặn .pg-num:hover vì --heading sẽ tàng hình ở chế độ tối. Tách
   ra thì mỗi class nói đúng một chuyện. */
.pg-cur { background: var(--inverse-bg); color: var(--on-inverse); font-weight: 600; }
.pg-gap { display: inline-flex; align-items: center; min-width: var(--s-5); justify-content: center; color: var(--muted); }
@media (max-width: 640px) {
    .pagination { justify-content: center; }
    .pg-pos { order: 0; }
}

/* ── Mục lục chuỗi bài, cuối bài ──────────────────────────────────────────── */
.series {
    margin-top: var(--s-7); padding: var(--s-6);
    background: var(--paper-warm); border: var(--bw) solid var(--rule);
    border-radius: var(--r-2);
}
.series-head { margin-bottom: var(--s-5); }
.series-eyebrow { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); font-weight: 600; }
.series-title { font-size: var(--fs-lg); margin-top: var(--s-2); }
.series-title a { text-decoration: none; }
.series-title a:hover { color: var(--brass-text); text-decoration: underline; text-underline-offset: .2em; }
/* Hai cột khi đủ rộng: chuỗi dài tới 13 bài, một cột thì khối này cao hơn cả
   phần chia sẻ bên dưới và đọc ra như một bức tường. */
/* Mục lục cuối bài dùng chung ngữ pháp .rail, chỉ thêm việc chia hai cột: chuỗi
   dài tới 13 bài, một cột thì khối này cao hơn cả phần chia sẻ bên dưới. */
.series .rail { columns: 2; column-gap: var(--s-6); }
.series .rail > li { break-inside: avoid; }
/* Cắt còn MỘT dòng: tiêu đề bài dài xuống 2-3 dòng không đều nhau giữa hai cột
   đọc ra rối mắt (founder chỉ ra, chụp ảnh xác nhận). title="" trên thẻ <a>
   giữ tên đầy đủ khi rê chuột. min-width:0 bắt buộc — flex item mặc định
   không co dưới độ dài nội dung, thiếu nó ellipsis không có tác dụng. */
.series .rail-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; }
.series .rail > li > a { min-width: 0; }
@media (max-width: 640px) { .series .rail { columns: 1; } .series { padding: var(--s-5); } }

/* Cột TƯỜNG MINH, không auto-fit/minmax: luôn đúng 3 bài, để CSS tự đoán số cột
   thì cửa sổ chưa chạm .wrap (1120px) là rớt xuống 2+1 thay vì một hàng 3 —
   đúng lớp lỗi "lưới tự quyết cột cho khối cố định số lượng" đã ghi trong
   CLAUDE.md (dải số liệu trang chủ, chân trang). 936px đủ cho 3×280+2×24=888px
   khi trừ padding .wrap, còn dư biên, và đúng thang lưới 8px. */
.related-grid { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 640px) { .related-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 936px) { .related-grid { grid-template-columns: repeat(3, 1fr); } }

.blog-layout { display: grid; gap: var(--s-7); grid-template-columns: minmax(0, 1fr) 264px; align-items: start; padding-bottom: var(--s-7); }
.blog-main .post-list { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.side-index { position: sticky; top: var(--s-9); border-left: var(--bw) solid var(--rule); padding: var(--s-7) 0 0 var(--s-5); }
/* ── THANH RAY: ngữ pháp điều hướng dùng chung ──────────────────────────────
   Cùng một dữ liệu từng được vẽ bốn kiểu khác nhau (thanh bên trang danh sách,
   cuối bài viết, menu con — và trang chủ đề thì không có gì). Gom về MỘT ngữ
   pháp: hàng ngăn bằng đường kẻ mảnh, nhãn trái — số phải, mục đang ở đánh dấu
   bằng vạch đồng và chữ đậm. Học một lần, nhận ra ở mọi nơi.

   Vị trí con số mang NGHĨA nên khác nhau có chủ ý: ở ray chuỗi nó là SỐ BÀI
   (siêu dữ liệu, nằm phải), ở ray bài nó là SỐ THỨ TỰ trong chuỗi (một phần của
   tên, nằm trái). --}} */
/* Nhãn nhóm TREO ở mép ngoài, các mục THỤT VÀO 16px. Trước đây hai thứ cùng một
   mép trái nên không có gì tách chúng ra — màu thì đã khác (--muted với --text)
   nhưng ở cỡ chữ nhỏ khác biệt đó không đủ. Thụt đầu dòng mới là thứ nói ra
   quan hệ "nhãn này chứa những mục kia".
   Mảng nền và vạch đồng của mục đang ở vẫn bắt đầu từ mép ngoài, nên nó thò ra
   bên trái chữ — đọc ra là tấm nền có chủ ý chứ không phải hộp bọc quanh chữ. */
.rail { list-style: none; margin: 0 0 0 calc(var(--s-3) * -1); padding: 0; }
.rail > li + li { border-top: var(--bw) solid var(--rule); }
.rail > li > a {
    display: flex; gap: var(--s-3); align-items: baseline; justify-content: space-between;
    padding-block: var(--s-2); padding-inline: var(--s-3);
    font-size: var(--fs-sm); color: var(--text); text-decoration: none;
    line-height: var(--lh-snug); transition: color var(--dur) var(--ease);
}
.rail > li > a:hover { color: var(--brass-text); }
.rail-n { flex: none; font-size: var(--fs-2xs); color: var(--muted); font-variant-numeric: tabular-nums; }

/* ĐANG Ở: bốn tín hiệu cùng lúc — mảng nền nâng lên, vạch đồng 3px, chữ đổi
   sang màu đồng, và chữ đậm.

   Vì sao không chỉ dùng chữ đậm như bản trước: màu chữ thường (--text #23303A)
   và màu tiêu đề (--heading #16232E) gần như trùng nhau, nên đổi sang --heading
   chẳng tạo khác biệt nào. Ở chế độ tối còn tệ hơn: #E9EEF2 với #FFFFFF. Thành
   ra "đang ở" chỉ còn mỗi nét đậm — founder nói đúng, nhìn không ra.

   --brass-text ĐẢO theo chế độ (#755A2B sáng / #D9B978 tối) nên đổi màu chữ ăn
   ở cả hai. Tương phản trên --paper-pure: 6.47 (sáng) và 6.43 (tối).

   Nền dùng --paper-pure chứ không phải --acc-wash: --acc-wash TRÙNG giá trị với
   --paper-warm, mà --paper-warm chính là nền của khối mục lục cuối bài — dùng
   nó thì ở đó mảng nền tàng hình. --paper-pure nâng lên rõ trên cả hai nền. */
.rail > li.is-here {
    background: var(--paper-pure);
    box-shadow: inset 3px 0 0 var(--brass-strong);
    border-radius: var(--r-1);
}
.rail > li.is-here > a { color: var(--brass-text); font-weight: 600; }
.rail > li.is-here .rail-n, .rail > li.is-here .rail-num { color: var(--brass-text); }

/* Nhãn nhóm: chữ hoa nhỏ CỘNG một đường kẻ — chỉ chữ hoa nhỏ thì nó chìm lẫn
   vào danh sách, không tách được nhóm này với nhóm kia. */
.rail-group {
    font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--muted); font-weight: 600;
    margin: var(--s-6) 0 var(--s-2); padding-bottom: var(--s-2);
    border-bottom: var(--bw) solid var(--rule);
}
/* Nhãn nhóm TREO ra ngoài 12px so với chữ của các mục. Kéo NHÃN ra chứ không
   đẩy MỤC vào: cột thanh bên chỉ rộng 264px, thụt các mục vào 12px là tiêu đề
   chuỗi xuống dòng thêm — đã thử và thấy rõ trên ảnh chụp. */
.rail-group { margin-left: calc(var(--s-3) * -1); }
.rail-group:first-of-type { margin-top: var(--s-4); }
/* Ray CÓ THỨ TỰ (danh sách bài trong chuỗi): số nằm trái vì nó là số thứ tự. */
.rail-ordered > li > a { justify-content: flex-start; }
.rail-num { flex: none; width: var(--s-5); font-size: var(--fs-2xs); color: var(--muted); font-variant-numeric: tabular-nums; }

.side-note { margin-top: var(--s-5); font-size: var(--fs-2xs); color: var(--muted); line-height: 1.5; }
.side-title { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); margin-bottom: var(--s-4); font-weight: 600; }

.to-top {
    position: fixed; right: var(--s-5); bottom: var(--s-5);
    width: var(--s-7); height: var(--s-7); border-radius: var(--r-full);
    border: var(--bw) solid var(--rule); background: var(--paper); color: var(--heading);
    font-size: var(--fs-md); cursor: pointer; box-shadow: var(--shadow-2); z-index: 40;
    transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.to-top:hover { border-color: var(--brass); color: var(--brass-text); }
.to-top:active { transform: translateY(1px); }
/* Nút "Nhắn Zalo" cố định (partials/zalo-fab.hbs, chỉ trang VI). Nền xanh mực cố định, bong bóng
   đồng sáng như ô kênh liên lạc ở chân trang; viền đồng để còn tách khỏi nền tối ở theme tối.
   Nút lên đầu trang nằm ngay trên nó. */
.zalo-fab {
    position: fixed; right: var(--s-5); bottom: var(--s-5); z-index: 40;
    display: inline-flex; align-items: center; gap: var(--s-2);
    min-height: var(--s-7); padding: var(--s-2) var(--s-5) var(--s-2) var(--s-3);
    border: var(--bw) solid var(--brass); border-radius: var(--r-full);
    background: var(--inverse-bg); color: var(--on-inverse); box-shadow: var(--shadow-3);
    font-size: var(--fs-sm); font-weight: 600; text-decoration: none;
    --ico-cut: var(--inverse-bg);
    transition: background var(--dur) var(--ease);
}
.zalo-fab-ico { display: grid; place-items: center; color: var(--brass-bright); }
.zalo-fab-ico .ico { width: var(--s-6); height: var(--s-6); }
.zalo-fab:hover { background: var(--inverse-bg-deep); --ico-cut: var(--inverse-bg-deep); color: var(--on-inverse); }
.zalo-fab:active { transform: translateY(1px); }
.zalo-fab ~ .to-top { bottom: calc(var(--s-5) + var(--s-7) + var(--s-3)); }
@media print { .zalo-fab { display: none; } }

/* =============================== Footer =================================== */
.risk-note {
    background: var(--paper-pure); border: var(--bw) solid var(--rule);
    border-left: 3px solid var(--brass-strong);
    padding: var(--s-5); font-size: var(--fs-sm); color: var(--ink-soft);
    margin-bottom: var(--s-7); border-radius: var(--r-1);
}
.site-foot { background: var(--inverse-bg); color: var(--on-dark); padding-block: var(--s-8) var(--s-6); }
.site-foot .risk-note { background: rgba(255,255,255,.04); border-color: rgba(255,255,255,.14); color: var(--on-dark); }
/* Dưới 720px: cột thương hiệu trải hết bề ngang, HAI cột link đứng cạnh nhau.
   auto-fit/minmax(232px) cũ rớt về một cột ở điện thoại — hai nhóm link vài chữ
   xếp dọc sát trái, bỏ trống nửa phải (layout-check luật 5). */
.foot-grid { display: grid; gap: var(--s-6) var(--s-5); grid-template-columns: 1fr 1fr; }
.foot-brand { grid-column: 1 / -1; }
.foot-links a { overflow-wrap: anywhere; }
/* Cột thương hiệu có mô tả dài, hai cột link chỉ vài dòng -> chia 2fr/1fr/1fr để
   độ lệch trông có chủ đích, thay vì ba cột bằng nhau mà hai cột gần trống. */
@media (min-width: 720px) { .foot-grid { grid-template-columns: 2fr 1fr 1fr; } .foot-brand { grid-column: auto; } }
.foot-h { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--on-dark-soft); margin-bottom: var(--s-4); font-family: var(--sans); font-weight: 600; }
.site-foot .brand-name { font-family: var(--serif); font-size: var(--fs-md); color: var(--on-inverse); margin-bottom: var(--s-3); display: block; }
.foot-desc { font-size: var(--fs-sm); max-width: 44ch; }
.foot-col p + p { margin-top: var(--s-2); }
.foot-links { list-style: none; padding: 0; margin: var(--s-3) 0 0; }
.foot-links li + li { margin-top: var(--s-2); }
.site-foot a { color: var(--on-dark); text-decoration: none; font-size: var(--fs-sm); }
.site-foot a:hover { color: var(--brass-bright); }
.foot-copy { border-top: var(--bw) solid rgba(255,255,255,.12); margin-top: var(--s-7); padding-top: var(--s-5); font-size: var(--fs-xs); color: var(--on-dark-soft); }

/* =============================== Mobile ================================== */
/* 1232px chứ không phải 896 hay 1216.
   896 thì tràn ngang ở toàn dải tablet ngang và laptop nhỏ — đó là lý do nâng
   lên 1216 lần đầu. Nhưng 1216 vẫn sai, và sai theo kiểu chỉ phép ĐO mới thấy:
   Chrome đánh giá @media theo bề rộng khung nhìn CÓ TÍNH thanh cuộn, còn hộp bố
   cục thì không. Ở cửa sổ 1224px, media query thấy 1224 nên bật menu ngang,
   trong khi chỗ thật chỉ còn 1209 — hàng header tràn 7px.
   1232 = 1216 + 16, đủ bù thanh cuộn 15px và vẫn đúng thang 8px (154 × 8). */
@media (max-width: 1232px) {
    /* flex: none — nút biểu tượng KHÔNG được co. Thiếu nó thì khi hàng header
       chật (tên thương hiệu dài + cụm cờ), flex bóp nút xuống dưới 48px. */
    .nav-toggle {
        display: block; margin-left: auto; width: var(--s-7); height: var(--s-7); flex: none;
        background: none; border: var(--bw) solid rgba(255,255,255,.25);
        border-radius: var(--r-1); cursor: pointer; position: relative;
    }
    .nav-toggle-bar, .nav-toggle-bar::before, .nav-toggle-bar::after {
        content: ""; position: absolute; width: var(--s-5); height: 1.5px; background: var(--on-inverse);
    }
    /* Căn giữa bằng inset + margin auto, KHÔNG bằng left cố định: left:12px thì
       vạch đứng yên khi nút đổi bề rộng -> tràn sát lề phải (lỗi gặp trên mobile
       2026-09-10). Nay vạch luôn bám giữa nút dù nút rộng bao nhiêu. */
    .nav-toggle-bar { top: calc(50% - 1px); inset-inline: 0; margin-inline: auto; }
    .nav-toggle-bar::before { top: calc(var(--s-2) * -0.75); left: 0; }
    .nav-toggle-bar::after  { top: calc(var(--s-2) *  0.75); left: 0; }
    .site-menu {
        position: fixed; inset: var(--s-8) 0 0; background: var(--inverse-bg); overflow-y: auto;
        padding: var(--s-4) var(--s-5) var(--s-8); display: none;
    }
    body.nav-open .site-menu { display: block; }
    body.nav-open { overflow: hidden; }
    .menu { flex-direction: column; align-items: stretch; gap: 0; }
    .menu > li { border-bottom: var(--bw) solid rgba(255,255,255,.1); }
    .menu > li > a { padding-block: var(--s-4); font-size: var(--fs-base); }
    .has-sub { display: flex; flex-wrap: wrap; align-items: center; }
    .has-sub > a { flex: 1; }
    .sub-toggle { display: block; width: var(--s-7); height: var(--s-7); background: none; border: 0; cursor: pointer; position: relative; }
    .sub-toggle::after {
        content: ""; position: absolute; top: calc(50% - 6px); left: calc(50% - 5px); width: var(--s-2); height: var(--s-2);
        border-right: 1.5px solid var(--on-inverse); border-bottom: 1.5px solid var(--on-inverse);
        transform: rotate(45deg); transition: transform var(--dur) var(--ease);
    }
    .sub-toggle[aria-expanded="true"]::after { transform: rotate(-135deg); }
    .submenu {
        position: static; opacity: 1; visibility: visible; transform: none; display: none;
        width: 100%; background: transparent; border: 0; box-shadow: none; padding: 0 0 var(--s-3);
    }
    .has-sub:hover > .submenu { display: none; }
    .sub-toggle[aria-expanded="true"] + .submenu { display: block; }
    .submenu-cols { columns: auto; min-width: 0; }
    .submenu-cols > .col-break { break-before: auto; }
    .submenu-cols > .col-break.submenu-label { margin-top: var(--s-2); border-top: var(--bw) solid rgba(255,255,255,.14); }
    .submenu a { color: var(--on-dark); padding: var(--s-3) 0; }
    .submenu a:hover { background: none; color: var(--brass-bright); }
    .submenu-label { padding: var(--s-4) 0 var(--s-1); }
    /* Cùng thang thụt như bản desktop, nhưng gốc là 0 vì bảng menu đã có lề
       riêng: 0 -> 16 -> 32. */
    .submenu > li:not(.submenu-label) > a { padding-left: 0; }
    .submenu-label ~ li:not(.submenu-label):not(.submenu-sep) > a { padding-left: var(--s-4); }
    .submenu-label ~ .submenu-label { border-top-color: rgba(255,255,255,.14); }
    .has-sub-2 > .submenu-2 {
        margin-left: var(--s-4); padding-left: 0;
        border-left: var(--bw) solid rgba(255,255,255,.14);
    }
    .has-sub-2 > .submenu-2 > li > a { color: var(--on-dark-soft); padding-left: var(--s-4); }
    .blog-layout { grid-template-columns: 1fr; }
    /* .side-index (thanh ray chuỗi ở /kien-thuc/) thay .side-recent cũ nhưng
       luật tắt sticky ở khổ hẹp chưa từng cập nhật theo — đo thật thấy nó vẫn
       position:sticky ở 390px dù .blog-layout đã rớt về 1 cột, cùng lớp lỗi
       "sticky đè lên chữ" vừa sửa ở mục lục hôm nay (2026-09-13), chỉ khác chỗ. */
    .side-index { position: static; border-left: 0; border-top: var(--bw) solid var(--rule); padding: var(--s-6) 0 0; }
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* ===================== Thành phần theo Brief 3 (2026-09-10) ===================== */

/* Hộp chú thích: dùng cho lưu ý nội hàm "tri thức gốc" và cho nhánh thuần quant.
   Cố tình KHÔNG dùng màu cảnh báo — đây là làm rõ, không phải báo lỗi. */
.note-box {
    border: var(--bw) solid var(--rule); border-left: 3px solid var(--brass-strong);
    background: var(--paper-warm); border-radius: var(--r-2);
    padding: var(--s-5); margin-block: var(--s-6);
}
.note-box h2 { font-size: var(--fs-lg); margin-bottom: var(--s-3); }
.note-box p { color: var(--ink-soft); font-size: var(--fs-sm); }
.note-box p + p { margin-top: var(--s-3); }

/* Lộ trình 4 cấp độ — chương trình Môi giới */
.levels { padding-block: var(--s-8); background: var(--paper-warm); border-block: var(--bw) solid var(--rule); }
.levels h2 { font-size: var(--fs-xl); margin-bottom: var(--s-3); }
.level-list { list-style: none; margin: var(--s-6) 0 0; padding: 0; display: grid; gap: var(--s-3); }
.level-list li {
    display: grid; gap: var(--s-1) var(--s-5); align-items: baseline;
    grid-template-columns: minmax(72px, auto) minmax(0, 1fr) auto;
    background: var(--paper-pure); border: var(--bw) solid var(--rule);
    border-radius: var(--r-2); padding: var(--s-4) var(--s-5);
}
.level-n { font-family: var(--serif); font-size: var(--fs-md); color: var(--brass-text); font-weight: 600; }
.level-name { font-family: var(--serif); font-size: var(--fs-md); color: var(--heading); }
.level-meta { font-size: var(--fs-xs); color: var(--muted); }
@media (max-width: 640px) {
    .level-list li { grid-template-columns: 1fr; }
    .level-meta { grid-column: 1; }
}

/* CTA phụ — đặt cuối trang Môi giới, sau khi đã trình bày hết nội dung đào tạo */
.secondary-cta { padding-block: var(--s-7); border-top: var(--bw) solid var(--rule); }
.secondary-cta h2 { font-size: var(--fs-lg); margin-bottom: var(--s-3); }
.secondary-cta p { color: var(--muted); font-size: var(--fs-sm); margin-bottom: var(--s-5); }

/* Koenig editor: ảnh rộng và ảnh tràn viền trong nội dung bài.
   gscan bắt buộc có, và thiếu thật thì ảnh biên tập viên chèn sẽ mất định dạng. */
.single-content .kg-width-wide {
    width: 100vw; max-width: calc(var(--narrow) + var(--s-9));
    margin-inline: calc(50% - min(50vw, (var(--narrow) + var(--s-9)) / 2));
}
.single-content .kg-width-full {
    width: 100vw; max-width: 100vw;
    margin-inline: calc(50% - 50vw);
}
.single-content .kg-width-full img { border-radius: 0; }

/* ── FAQ dạng accordion — DÙNG CHUNG hai nơi ──────────────────────────────────
   /faq/ (content/pages/faq.py) và cuối 10 trang khóa (partials/course-faq.hbs,
   sinh bởi bin/course-content.py). Cùng một ngữ pháp, vì cùng một loại nội dung:
   founder chốt 2026-09-13 "áp dụng nhất quán" sau khi chỉ ra 19 câu liệt kê
   phẳng ở /faq/ đọc như một bức tường chữ.

   <details>/<summary> gốc HTML — không JS, có sẵn bàn phím và trạng thái mở/đóng
   cho trình đọc màn hình. <summary> chứa ĐÚNG MỘT h3 làm nội dung (spec cho
   phép), nên câu hỏi vẫn là heading thật và vẫn khớp câu trong JSON-LD FAQPage.
   Nội dung câu trả lời nằm nguyên trong DOM cả khi đóng, nên Google/AEO vẫn đọc
   được — đây là lý do chọn <details> thay vì ẩn/hiện bằng JS.

   Hàng ngăn bằng đường kẻ mảnh, cùng ngôn ngữ thị giác với .rail (hàng ngăn
   cách, không phải thẻ hộp). */
.course-faq { padding-block: var(--s-8); border-top: var(--bw) solid var(--rule); }
.course-faq .faq-more { margin-top: var(--s-7); color: var(--muted); }
/* /en/faq/: nhóm đầu đứng ngay dưới dải tiêu đề. Khoảng thở đặt ở khung ngoài chứ không ở padding
   của nhóm: vạch trên của nhóm (và hộp của nó) sẽ dán sát dải tiêu đề (layout-check luật 4). */
.en-faq { padding-top: var(--s-7); }
.en-faq > .course-faq:first-child { border-top: 0; padding-top: 0; }
.faq-group + .faq-group { margin-top: var(--s-7); }
.faq-group h2, .course-faq h2 { margin-bottom: var(--s-5); }
/* Kẻ DƯỚI từng mục, không kẻ trên: danh sách tự đóng ở mục cuối, và mỗi mục
   chỉ có ĐÚNG MỘT cạnh viền — điều kiện để luật 1 của layout-check.js nhận ra
   đây là "danh sách dùng chung đường kẻ" chứ không phải hai hộp dán vào nhau.
   Bản trước kẻ trên + thêm border-bottom cho mục cuối: mục cuối có 2 cạnh, và
   cổng báo đỏ 0px trên cả 11 trang. */
.faq-item { border-bottom: var(--bw) solid var(--rule); padding-block: var(--s-4); }
.faq-item summary {
    display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-4);
    cursor: pointer; list-style: none;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary h3 { margin: 0; font-size: var(--fs-md); font-weight: 500; }
.faq-item summary:hover h3 { color: var(--brass-text); }
/* Dấu "+" thành "−" khi mở (vạch dọc co về 0) — không cần ảnh/icon riêng, không cần JS. */
.faq-icon { flex: none; position: relative; width: var(--s-5); height: var(--s-5); }
.faq-icon::before, .faq-icon::after {
    content: ""; position: absolute; top: 50%; left: 50%; background: var(--muted);
    transition: transform var(--dur) var(--ease);
}
.faq-icon::before { width: var(--s-4); height: var(--bw); transform: translate(-50%, -50%); }
.faq-icon::after { width: var(--bw); height: var(--s-4); transform: translate(-50%, -50%); }
.faq-item[open] .faq-icon::after { transform: translate(-50%, -50%) rotate(90deg) scale(0); }
.faq-item[open] summary h3 { color: var(--brass-text); }
.faq-a { margin-top: var(--s-3); max-width: 62ch; color: var(--ink-soft); }
.faq-a > :first-child { margin-top: 0; }
.faq-a ul, .faq-a ol { padding-left: var(--s-5); }
.faq-a li + li { margin-top: var(--s-2); }

/* ── "Hình thức học" trên trang khóa (partials/course-format.hbs) ──────────
   Sáu dữ kiện vận hành: nhãn trái, câu trả lời phải, ngăn nhau bằng đường kẻ
   mảnh — CÙNG ngữ pháp với .rail, nên người đọc nhận ra ngay đây là dữ kiện tra
   cứu chứ không phải văn bán hàng. Khác .rail ở một điểm có chủ ý: các hàng này
   KHÔNG phải liên kết, nên không có trạng thái hover cho cả hàng.

   .spec cố ý KHÔNG phải grid; grid nằm ở từng .spec-row (2 ô). Nếu .spec là
   grid một cột với 6 ô thì nó rơi đúng vào luật 5 của layout-check.js — lưới
   một cột, >= 3 ô, chữ ngắn dạt trái — dù ở đây chữ không thật sự dạt trái.
   Đặt grid xuống từng hàng thì mỗi lưới chỉ còn 2 ô, dưới ngưỡng của luật. */
.course-format { padding-block: 0; }
.course-format h2 { font-size: var(--fs-xl); margin-bottom: var(--s-5); }
.spec { margin: 0; }
.spec-row {
    display: grid; grid-template-columns: var(--s-10) 1fr; gap: var(--s-5);
    padding-block: var(--s-4);
}
.spec-row + .spec-row { border-top: var(--bw) solid var(--rule); }
.spec dt {
    font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--muted); padding-top: .2em;
}
.spec dd { margin: 0; max-width: 62ch; }

/* Trang sự kiện (custom-su-kien.hbs). Khối thông tin nằm trong THÂN BÀI (một HTML
   card do bin/event.py sinh) và dùng lại .spec. Chỉ thêm khung giấy ấm để nó đọc
   ra là một thẻ thông tin chứ không phải đoạn văn, và chỗ cho nút đăng ký. */
.event-facts { background: var(--paper-warm); border: var(--bw) solid var(--rule); border-radius: var(--r-2); padding: var(--s-2) var(--s-5) var(--s-5); }
.event-facts .spec-row { grid-template-columns: var(--s-9) 1fr; }
.event-facts .btn { margin-top: var(--s-4); }
.event-cover { margin-top: var(--s-6); }
.event-cover img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-2); border: var(--bw) solid var(--rule); }
.event-cover figcaption { margin-top: var(--s-3); font-size: var(--fs-sm); font-style: italic; color: var(--muted); text-align: center; }
.event-facts-note { font-size: var(--fs-sm); color: var(--muted); margin-top: var(--s-3); }
.event-speaker { display: grid; gap: var(--s-5); margin-top: var(--s-8); padding-top: var(--s-6); border-top: var(--bw) solid var(--rule); }
.event-speaker img { width: 100%; max-width: var(--s-10); aspect-ratio: 1; object-fit: cover; border-radius: var(--r-2); border: var(--bw) solid var(--rule); }
.event-speaker h2 { font-size: var(--fs-xl); margin-bottom: var(--s-3); }
.event-speaker p + a { display: inline-block; margin-top: var(--s-3); }
@media (min-width: 640px) { .event-speaker { grid-template-columns: var(--s-10) 1fr; align-items: start; } }

/* Đơn vị chủ quản trên trang pháp lý (partials/legal-entity.hbs). Dùng lại .spec
   ở trên. margin-top 48px để khối không dán vào dải tiêu đề — luật 4 của
   layout-check.js đòi tối thiểu 24px tính tới mép sơn thật. */
.legal-entity { margin-top: var(--s-7); }
.legal-entity-h {
    font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--muted); margin-bottom: var(--s-3); font-weight: 600;
}
.legal-entity .note-box { margin-block: 0; }
/* Ngày cập nhật của văn bản pháp lý: siêu dữ liệu, không phải câu mở đầu. */
.single-content .legal-date { color: var(--muted); font-size: var(--fs-sm); }

/* Nhãn 128px + câu trả lời không vừa khổ điện thoại -> xếp dọc. Số cột viết
   TƯỜNG MINH cho cả hai khổ, không để auto-fit tự quyết. */
@media (max-width: 640px) {
    .spec-row { grid-template-columns: 1fr; gap: var(--s-1); }
    .spec dt { padding-top: 0; }
}

/* Lộ trình gợi ý (ý tưởng Brief 1). Đánh số bằng counter để không phụ thuộc marker mặc định. */
.paths-block { padding-block: var(--s-8); }
.paths-block h2 { font-size: var(--fs-xl); margin-bottom: var(--s-3); }
.path {
    background: var(--paper-pure); border: var(--bw) solid var(--rule);
    border-radius: var(--r-2); padding: var(--s-5);
}
.path-who { font-family: var(--serif); font-size: var(--fs-md); color: var(--heading); margin-bottom: var(--s-4); }
.path-steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.path-steps li {
    counter-increment: step; position: relative;
    padding-left: var(--s-6); padding-block: var(--s-2);
    display: flex; flex-direction: column; gap: var(--s-1);
}
.path-steps li + li { border-top: var(--bw) solid var(--rule); }
.path-steps li::before {
    content: counter(step); position: absolute; left: 0; top: var(--s-2);
    width: var(--s-5); height: var(--s-5); border-radius: var(--r-full);
    background: var(--paper-warm); color: var(--brass-text);
    display: grid; place-items: center; font-size: var(--fs-2xs); font-weight: 600;
}
.path-steps a { font-weight: 500; text-decoration: none; }
.path-steps span { font-size: var(--fs-xs); color: var(--muted); }
.path-note { font-size: var(--fs-xs); color: var(--muted); margin-top: var(--s-4); font-style: italic; }

/* ═══════════════════════════════════════════════════════════════════════════
   "GIÁO TRÌNH MỞ" — lớp nâng cấp thị giác (2026-09-10)

   Chiến lược, không phải thẩm mỹ: tệp khách đã bị chào mời nhiều lần, họ mua
   BẰNG CHỨNG VỀ CHIỀU SÂU chứ không mua lời hứa. Site phải trông như một công
   trình đã xuất bản, không phải trang bán hàng. Tiết chế chính là tín hiệu bán.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
    /* Bậc chữ hiển thị — thứ trước đây thiếu. Cỡ lớn nhất cũ mới 48px,
       nên mọi thứ trên trang gần như cùng trọng lượng -> phẳng. */
    --fs-5xl: 4rem;      /* 64 */
    --fs-6xl: 5.5rem;    /* 88 */
    --fs-7xl: 7rem;      /* 112 */
}

/* ── Kết cấu hạt: gợi giấy in, không phải hào quang tech ─────────────────── */
body::before {
    content: ""; position: fixed; inset: 0; z-index: 1; pointer-events: none;
    opacity: .035; mix-blend-mode: multiply;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* KHÔNG đưa .site-head vào đây: nó đã có position:sticky + z-index:50.
   Ghi đè sẽ vừa làm mất tính năng dính, vừa hạ z-index xuống ngang main
   -> menu con bị hero che (lỗi đã gặp 2026-09-10). */
.site-foot, main { position: relative; z-index: 2; }

/* ── Mô-típ EMA/Ichimoku ─────────────────────────────────────────────────── */
.motif-wrap { position: absolute; inset: auto 0 0 0; height: 62%; overflow: hidden; pointer-events: none; }
/* KHÔNG đặt opacity ở đây: nó buộc cả cây SVG render ra một lớp riêng rồi
   mới hợp thành. Hệ số .9 đã nhân thẳng vào 4 giá trị con bên dưới. */
.motif { width: 100%; height: 100%; color: var(--brass); }
.motif-cloud { fill: currentColor; opacity: .063; }
.motif-line { fill: none; stroke: url(#motif-fade); stroke-width: 1.25; }
.motif-line-1 { opacity: .45; }
.motif-line-2 { opacity: .27; }
.motif-line-3 { opacity: .198; }
/* Điện thoại: mô-típ phủ TRỌN hero thay vì chỉ 62% dưới đáy. Đây mới là gốc
   của lỗi "dải lụa tít dưới màn hình" (founder 2026-09-13) — KHÔNG phải biên độ
   dao động như tôi đoán hai lượt trước. Hero mobile rất cao vì tiêu đề xuống
   nhiều dòng; 62% tính từ đáy đẩy dải mây (nằm ở 45-80% chiều cao viewBox)
   xuống sát mép dưới màn hình. Phủ trọn hero thì dải chạy ngang qua giữa trang,
   sau chữ — an toàn vì mây chỉ opacity .063 và ba đường .198-.45. */
@media (max-width: 640px) {
    .motif-wrap { inset: 0; height: 100%; }
    /* Cho SVG RỘNG HƠN khung rồi kéo lệch sang trái để chỉ nhìn phần giữa.
       Lý do: preserveAspectRatio="none" nhồi viewBox 1200x400 vào khung mobile
       ~390x740 -> nén ngang 0,33x nhưng kéo dọc 1,85x, lệch 5,7 lần. Sóng bị ép
       sát nhau, founder đọc ra là "co hẹp, ép díu, mất mềm mại" (2026-09-13).
       150% sau khi founder báo 180% làm các đường THƯA quá (2026-09-13). Mức này đã SO ẢNH, không phải đoán: 150% còn hai
       nhịp sóng dốc, 340% thì cửa sổ rơi vào đoạn phẳng nên tranh nền biến mất,
       220% quá thoải. 180% cho đúng MỘT đường cong S lớn, mềm, có chỗ thở.
       Cửa sổ nhìn nằm trong vùng gradient #motif-fade đang mờ hoàn toàn nên
       không lộ mép. Cao 120% + lệch lên 10% để dải dày hơn mà vẫn cân khung. */
    .motif { position: absolute; width: 150%; height: 120%; top: -10%; left: -25%; }
    /* Founder 2026-09-14: "dải lụa cách xa nhau quá, rối mắt... giảm độ uốn cong
       và các sợi gần nhau hơn". Nén khối NGHỆ THUẬT (không phải khung cắt .motif
       ở trên) theo trục dọc quanh tâm khối — ba sợi xích lại gần nhau VÀ biên độ
       cong mỗi sợi tự giảm theo cùng tỉ lệ, vì cả hai đều nằm trên trục Y bị nén.
       fill-box + center tự tìm tâm hộp bao của cả khối, không cần tính tay toạ độ.
       So ảnh chụp cục bộ 1.0/0.7/0.55/0.4: 0.55 giữ được dáng "dải lụa" mềm mà đã
       rõ rệt bớt rối so với bản gốc; 0.4 phẳng quá mất chất. */
    .motif-content { transform-box: fill-box; transform-origin: center; transform: scale(1, .3); }
}

/* ── Nền trừu tượng chân trang: "Enso & Dòng" ────────────────────────────── */
/* Cùng họ với mô-típ hero — đơn sắc đồng, độ mờ thấp, không đổ bóng — nhưng
   hero là dòng chảy mở đầu, còn đây là dấu hiệu thương hiệu phóng to để đóng
   trang lại. Nền phải NẰM DƯỚI chữ: .site-foot đã là position:relative nên
   lớp này absolute + z-index 0, còn khối nội dung nâng lên z-index 1. Thiếu
   một trong hai thì phần tử định vị sẽ vẽ đè lên chữ tĩnh. */
.foot-art-wrap { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
.site-foot > .wrap { position: relative; z-index: 1; }
/* KHÔNG đặt opacity lên .foot-art: cả cây SVG sẽ phải render ra một lớp riêng
   rồi mới hợp thành. Hệ số đã nhân thẳng vào từng lớp con — cùng lý do đã ghi
   ở .motif bên trên. */
.foot-art { display: block; width: 100%; height: 100%; color: var(--brass); }
/* Ba tầng đường dòng: càng sát vòng Enso càng rõ, vì đó là chỗ dòng bị bẻ mạnh
   nhất — mắt phải bám vào chỗ có chuyện xảy ra, không phải vào rìa hình. */
.ff-s1, .ff-s2, .ff-s3 { fill: none; stroke: currentColor; }
/* Cả ba tầng hạ còn khoảng NỬA độ mờ gốc (founder 2026-09-13: "màu các hoa văn di
   chuyển quanh hình tròn đậm quá... rối mắt"). Lần đầu chỉ hạ riêng .ff-s1
   (.42->.28) — so ảnh chụp trước/sau thấy KHÔNG đủ khác biệt để đọc ra là bớt
   rối, nên hạ tiếp và hạ CẢ BA tầng chứ không chỉ tầng đang chuyển động: người
   nhìn không phân biệt được lớp nào động, cả trường dòng đọc chung là một
   khối "hoa văn quanh vòng tròn". Vẫn chia theo đúng tỉ lệ gốc 2.8:1.73:1 nên
   thứ bậc "càng sát vòng Enso càng rõ" không đổi. */
.ff-s1 { stroke-width: 1.25; opacity: .21; }
.ff-s2 { stroke-width: 1.1;  opacity: .13; }
.ff-s3 { stroke-width: 1;    opacity: .08; }
.fa-level { stroke: currentColor; stroke-width: 1; opacity: .16; }
.fa-accent { color: var(--brass-bright); }
/* Ba cung vơi nét dần: nét bút Thiền nhả mực, không phải vòng compa */
.fa-enso1, .fa-enso2, .fa-enso3, .fa-halo { fill: none; stroke: currentColor; stroke-linecap: round; }
.fa-enso1 { stroke-width: 3.6; opacity: .62; }
.fa-enso2 { stroke-width: 2.8; opacity: .5; }
.fa-enso3 { stroke-width: 1.8; opacity: .34; }
.fa-halo  { stroke-width: 1;   opacity: .15; }

/* ── Chuyển động nền: hero vẽ vào, chân trang thở (founder duyệt 2026-09-13) ──
   NGUYÊN TẮC: trạng thái TĨNH là trạng thái ĐÚNG. Mọi animation chỉ chạy từ
   "chưa vẽ" về đúng trạng thái tĩnh đó, và mọi thuộc tính LÀM ĐỔI HÌNH
   (stroke-dasharray) đều nằm TRONG @media no-preference. Ba hệ quả:

   1. Không JS / trình duyệt cũ / prefers-reduced-motion -> hình y hệt bản cũ.
   2. Mốc visual-check KHÔNG vỡ: cả visual-check.py lẫn layout-check.js khởi
      động chromium bằng --force-prefers-reduced-motion, nên cổng luôn chụp
      đúng trạng thái tĩnh. (Thêm nền động thường là phá cổng so ảnh — ở đây
      đường thoát có sẵn từ trước, đã kiểm chứ không đoán.)
   3. WCAG 2.3.3: người yêu cầu giảm chuyển động không thấy gì nhúc nhích.

   pathLength="1" trên từng <path> chuẩn hóa độ dài nét về 1, nên dasharray
   viết theo TỈ LỆ chứ không phải px — không phải đo getTotalLength() từng
   đường, và không vỡ khi SVG bị kéo giãn (motif dùng preserveAspectRatio=none). */

/* ⚠️ KHÔNG bọc khối này trong @media (prefers-reduced-motion: no-preference).
   Bản trước bọc như thế và trên iPad mini 6 — CẢ Safari LẪN Chrome, mà Chrome
   trên iOS cũng chạy WebKit — hero và chân trang đứng im hoàn toàn, trong khi
   Chromium trên máy chủ đo thấy chạy bình thường. Từ khóa "no-preference" là
   phần WebKit hỗ trợ muộn và không chắc chắn; khối nào phụ thuộc nó là khối có
   thể biến mất trên cả một họ thiết bị mà cổng tự động không hề thấy.
   Cách an toàn: chạy MẶC ĐỊNH, rồi tắt trong @media (prefers-reduced-motion:
   reduce) — "reduce" thì mọi trình duyệt hỗ trợ từ lâu. screen.css đã có sẵn
   một khối reduce chung ép animation-duration .01ms; ở dưới chỉ cần trả thêm
   stroke-dasharray về none để hình tĩnh giống hệt bản cũ. */
/* B · Hero — ba đường EMA tự vẽ từ trái sang, so le nhau. Đúng ẩn dụ: trên
   biểu đồ thật đường trung bình cũng hiện ra theo thời gian. Chạy MỘT lần
   lúc tải trang rồi thôi — chi phí sau đó bằng 0. */
.motif-line { stroke-dasharray: 1; animation: motif-draw 1500ms var(--ease) both; }
.motif-line-2 { animation-delay: 170ms; }
.motif-line-3 { animation-delay: 340ms; }

/* B+ · Hero vẽ xong thì SÓNG chạy tiếp (founder 2026-09-13: "chạy 1 lần rồi
   đứng im... uốn lượn lên xuống như sóng thì thích hơn").
   Không morph thuộc tính d — đắt, và đã tự cấm. Thay vào đó: mỗi tầng trôi
   theo BIÊN ĐỘ và CHU KỲ riêng. Vì chu kỳ lệch nhau (10/13/16s, mây 19s),
   khoảng cách giữa ba đường co giãn liên tục -> mắt đọc ra là dải băng đang
   uốn, chứ không phải cả khối nhấp lên nhấp xuống cứng đơ.
   Chỉ động transform -> trình duyệt hợp lớp trên GPU, không tính lại bố cục.
   Biên độ tính theo đơn vị user của viewBox 1200x400, nên tự co theo khổ. */
/* Sóng nằm trên <g> BỌC NGOÀI, nét vẽ nằm trên <path> bên trong — hai
   thuộc tính animation tách biệt. Gộp cả hai lên cùng một phần tử thì mỗi
   lần bật/tắt .motif-live là nét vẽ lại từ đầu, đúng lỗi đã tránh ở Enso. */
/* HAI tầng dao động chồng lên nhau, chu kỳ không chia hết cho nhau (17/23/29s
   ngoài, 7/9/11s trong). Tổng của hai sin lệch chu kỳ là một đường KHÔNG bao
   giờ lặp lại y hệt — đó là cách làm ra cảm giác ngẫu nhiên mà không cần JS
   và không cần random thật.
   animation-delay ÂM là mấu chốt: nó bắt mỗi tầng vào cuộc ở MỘT PHA KHÁC
   NHAU ngay từ giây đầu. Bản trước chỉ khác chu kỳ mà cùng xuất phát pha 0,
   nên nhiều vòng sau mới tách ra — founder đọc ra là "cùng lên cùng xuống"
   (2026-09-13). Biên độ cũng nâng lên theo yêu cầu. */
/* CHẠY MẶC ĐỊNH, không chờ JS gắn class. JS chỉ TẠM DỪNG khi khối khuất
   khỏi màn hình, và chỉ khi nó đã gắn được .io-ready lên <html>. Hỏng JS,
   chặn JS, hay không có IntersectionObserver -> chuyển động vẫn chạy thay vì
   đứng im hoàn toàn. */
.motif-sway-1 { animation: mw-sway-1 9s ease-in-out -3s infinite alternate; }
.motif-sway-2 { animation: mw-sway-2 11s ease-in-out -7s infinite alternate; }
.motif-sway-3 { animation: mw-sway-3 13s ease-in-out -5s infinite alternate; }
.motif-drift-1 { animation: mw-drift-1 5s ease-in-out -2s infinite alternate; }
.motif-drift-2 { animation: mw-drift-2 7s ease-in-out -4s infinite alternate; }
.motif-drift-3 { animation: mw-drift-3 8s ease-in-out -6s infinite alternate; }
.io-ready .motif-wrap:not(.motif-live) .motif-sway,
.io-ready .motif-wrap:not(.motif-live) .motif-drift { animation-play-state: paused; }
/* Khổ điện thoại: biên độ dọc giảm một nửa. Biên độ viết theo đơn vị viewBox
   nên nó CO THEO TỈ LỆ khung — mà khung mobile lại cao hơn nửa màn hình
   (đo iPhone 14: .motif-wrap cao 460px / 844px). Giữ nguyên biên độ desktop
   thì dải mây đi xuống 81px, đọc ra là "tụt xuống cạnh dưới màn hình"
   (founder 2026-09-13). Nó KHÔNG bị cắt — lúc thấp nhất vẫn dư 70px — nhưng
   quãng đi quá dài so với màn nhỏ. Chu kỳ giữ nguyên, chỉ hạ biên độ. */
@media (max-width: 640px) {
    .motif-sway-1 { animation-name: mw-sway-1-m; }
    .motif-sway-2 { animation-name: mw-sway-2-m; }
    .motif-sway-3 { animation-name: mw-sway-3-m; }
    .motif-drift-1 { animation-name: mw-drift-1-m; }
    .motif-drift-2 { animation-name: mw-drift-2-m; }
    .motif-drift-3 { animation-name: mw-drift-3-m; }
}
/* Mây CHỈ trôi dọc + phồng nhẹ, KHÔNG trôi ngang: nó là hình đặc kéo hết
   bề ngang, dịch ngang là hở mép. Ba đường thì trôi ngang thoải mái vì hai
   đầu đã mờ về 0 bởi gradient #motif-fade. */
.motif-cloud {
    transform-box: fill-box; transform-origin: center;
}
.motif-cloud { animation: mw-cloud 8s ease-in-out -3s infinite alternate; }
.io-ready .motif-wrap:not(.motif-live) .motif-cloud { animation-play-state: paused; }
/* Phải đặt SAU .motif-cloud, không gộp vào khối @media mobile ở trên: cùng
   độ đặc hiệu thì luật đứng sau thắng — đã đo thấy mây vẫn giữ 32px khi để
   nhầm ở trên. Cùng lớp lỗi với luật "dải @media phải đặt SAU quy tắc nó
   ghi đè" đã ghi trong CLAUDE.md. */
@media (max-width: 640px) {
    .motif-cloud { animation-name: mw-cloud-m; }
}

/* D · Chân trang — ba cung Enso tự vẽ khi cuộn tới, nét đậm trước nét mảnh
   sau: một nét bút Thiền nhả mực, không phải vòng compa.
   .foot-art-seen gắn MỘT lần rồi không gỡ, nên cuộn đi cuộn lại không vẽ lại. */
.foot-art-seen .fa-enso1,
.foot-art-seen .fa-enso2,
.foot-art-seen .fa-enso3 { stroke-dasharray: 1; animation: enso-draw 1100ms var(--ease) both; }
.foot-art-seen .fa-enso2 { animation-delay: 240ms; }
.foot-art-seen .fa-enso3 { animation-delay: 480ms; }

/* C · Chân trang — dòng chảy THẬT. Các đường này vốn là đường dòng của
   nghiệm psi = y(1 - a^2/(x^2+y^2)); cho một khe hở nhỏ trôi dọc theo chúng
   là làm lộ ra thứ hình vẽ đã mã hóa sẵn, không phải dán thêm hiệu ứng.
   Khe hở chỉ 10% chiều dài nét -> nhìn vẫn gần như đường liền, chỉ thấy một
   vệt sáng lướt qua. Tầng sát Enso chảy NHANH hơn: dòng bị trụ bó hẹp thì
   vận tốc tăng — đúng vật lý của chính phương trình đã dùng để vẽ.
   .foot-art-live gắn/gỡ theo tầm nhìn -> cuộn khỏi chân trang là dừng hẳn,
   không quay CPU lúc người ta đang đọc giữa bài. */
/* Bản đầu để .9/.1 = ĐÚNG MỘT khe hở chạy hết đường trong 18s. Trên mobile
   khung hẹp, chỉ thấy một đoạn ngắn của trường dòng -> phải chờ rất lâu mới
   có một vệt lướt qua, founder báo "không thấy chuyển động rõ ràng"
   (2026-09-13). Nay 5 chu kỳ mỗi đường (.16+.04) và chỉ trôi ĐÚNG MỘT chu
   kỳ (-.2) mỗi vòng -> vệt đi qua thường xuyên gấp nhiều lần, mà khe hở vẫn
   chỉ 4% chiều dài nên nhìn vẫn gần như đường liền. */
/* Mỗi đường một PHA và một CHU KỲ riêng, suy từ --i gắn sẵn trên từng <path>.
   Bản trước cho cả tầng cùng chu kỳ và cùng xuất phát -> 17 đường nhấp nháy
   đồng loạt, founder đọc ra là "bó đũa lao vào, không mềm mại", và thấy rõ
   nhất ở 21:9 vì preserveAspectRatio="xMidYMax slice" cắt chỉ còn dải giữa —
   đúng chỗ các đường song song và cách đều nhau nhất (2026-09-13).
   Hệ số 0.23s/0.83s cố ý không tròn: chu kỳ các đường không chia hết cho
   nhau nên chúng KHÔNG BAO GIỜ trở lại đồng pha. Vệt sáng lan qua quạt
   đường như gió lướt, không phải cả bó cùng tiến. */
.ff-s1, .ff-s2, .ff-s3 {
    stroke-dasharray: .16 .04;
    animation: ff-drift linear infinite;
    animation-duration: calc(var(--dur) + var(--i) * 0.23s);
    animation-delay: calc(var(--i) * -0.83s);
}
.ff-s1 { --dur: 6s; }
/* .ff-s2/.ff-s3 KHÔNG cần --dur hay trạng thái tạm-dừng riêng nữa: quy tắc
   phía dưới (`.ff-s2, .ff-s3 { animation: none }`, cùng độ đặc hiệu, đứng
   sau nên thắng) đã tắt hẳn animation của chúng — mọi khai báo duration/pause
   riêng cho hai lớp này là code chết, code-review mức max bắt được
   2026-09-13. Chỉ còn .ff-s1 thật sự chạy nên chỉ nó cần tạm dừng khi khuất
   màn hình. */
.io-ready .foot-art-wrap:not(.foot-art-live) .ff-s1 { animation-play-state: paused; }
/* Khổ điện thoại: trường dòng bị cắt còn một dải hẹp -> cho chạy nhanh hơn
   nữa, nếu không chuyển động rơi dưới ngưỡng nhận biết. */
@media (max-width: 640px) {
    .foot-art-live .ff-s1 { --dur: 4.5s; }
    /* CHỈ tầng rõ nhất được chạy trên điện thoại: 6 đường thay vì 17.
       stroke-dashoffset là animation VẼ LẠI, không phải hợp lớp trên GPU —
       mỗi khung hình trình duyệt phải raster lại nét đứt của TỪNG đường.
       17 đường × DPR 3 của iPhone là quá nặng, founder báo "giật giật, kiểu
       như máy yếu quá" (2026-09-13). Đó là lỗi của bản sửa trước, không phải
       của máy. Hai tầng mờ hơn trả về nét liền tĩnh — gần như không ai nhận
       ra chúng đứng yên, vì opacity chỉ .26 và .15. */
    /* ĐO ĐƯỢC, không phải phỏng đoán: trên khổ iPhone 14 @3x với CPU bóp 4x,
       dash 6 đường vẫn cho khung trung vị 33,3ms (30fps) và rớt 124/170
       khung; tắt hết thì về 16,7ms và rớt 1/170. Giảm 17->6 đường KHÔNG
       cứu được — bản thân stroke-dashoffset mới là thủ phạm, vì nó là
       animation VẼ LẠI trên một vùng SVG lớn, không phải hợp lớp GPU.
       Đã thử phương án thay thế: MỘT phần tử chạy transform (vệt sáng lướt).
       A/B trong CÙNG một trang, CPU bóp 4x: vệt sáng bật 33,3ms/rớt 92/140,
       tắt đi 16,7ms/rớt 0/140 — tức một transform duy nhất cũng tốn
       16,6ms/khung. Lý do: tranh nền có <mask> SVG, nên BẤT KỲ thứ gì động
       phủ lên nó cũng buộc raster lại cả vùng mask ở DPR 3; không có kiểu
       hợp lớp nào né được.
       KẾT LUẬN: điện thoại KHÔNG chạy chuyển động liên tục ở chân trang, chỉ
       giữ nét Enso vẽ một lượt. Đừng thử lại bằng một kiểu animation khác —
       đã đo, nút thắt là mask chứ không phải kiểu chuyển động. */
    /* BỎ MASK trên điện thoại thì chuyển động chạy lại được. Đo A/B ở
       iPhone 14 @3x dọc, CPU bóp 4x: dash 17 đường CÓ mask 33,4ms/rớt
       127/130; BỎ mask còn 16,7ms/rớt 35/130; bỏ mask + chỉ 6 đường
       16,7ms/rớt 8/130 (mốc tắt hết là 0/130). Mask là hai lớp gradient
       làm mờ mép — bỏ đi thì mép hơi gắt hơn, nhưng .foot-art-wrap đã
       overflow:hidden và tranh vốn rất mờ nên gần như không thấy khác. */
}

/* Trả hình TĨNH về đúng bản gốc cho người xin giảm chuyển động — và nhờ đó mốc
   ảnh của visual-check (chạy chromium với --force-prefers-reduced-motion) không
   đổi một pixel nào. Khối reduce chung ở trên đã dừng animation; riêng
   stroke-dasharray là thuộc tính TĨNH nên phải tự trả lại. */
@media (prefers-reduced-motion: reduce) {
    .ff-s1, .ff-s2, .ff-s3, .motif-line { stroke-dasharray: none; }
}

/* Vẽ NGƯỢC về 0: trạng thái cuối trùng trạng thái tĩnh, nên animation hỏng hay
   bị cắt giữa chừng thì hình vẫn đúng, không bao giờ kẹt ở "vẽ dở". */
@keyframes motif-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes enso-draw  { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
/* ── Chân trang: gỡ tải cho máy bảng và điện thoại (chốt 2026-09-13) ────────
   Ngưỡng 1024px chứ KHÔNG phải 640px. Bản trước để 640 nên iPad (744) và
   iPhone xoay ngang (844) rơi ra ngoài, vẫn gánh mask + 17 đường: đo được
   50,0ms/khung, rớt 125/130 — founder báo "iPad đứng im" rồi "quay ngang thì
   chuyển động nhưng hơi giật", cả hai đều từ đây mà ra.

   Hai việc, đo A/B ở iPhone 14 @3x, CPU bóp 4x:
     dash 17 đường CÓ mask      33,4ms  rớt 127/130
     dash 17 đường BỎ mask      16,7ms  rớt  35/130
     dash 6 đường  BỎ mask      16,7ms  rớt   8/130
     tắt hết (mốc)              16,7ms  rớt   0/130
   Mask là thủ phạm chính, số lượng đường là thứ hai. Mask ở đây chỉ là hai lớp
   gradient làm mờ mép; bỏ đi thì mép hơi gắt hơn một chút, nhưng .foot-art-wrap
   đã overflow:hidden và cả bức tranh vốn rất mờ nên gần như không nhận ra.
   ⚠️ Quyết định DƯỚI ĐÂY ghi đè kết luận "giữ mask riêng cho desktop" ở trên —
   đo thêm rồi bỏ mask ở MỌI khổ luôn, không chỉ mobile. Đừng đọc dở đoạn trên
   rồi tưởng còn ngưỡng theo khổ máy (code-review mức max bắt được oan sót này
   2026-09-13: comment cũ có thể khiến người sau "khôi phục" nhầm mask riêng
   cho desktop, kéo lại đúng lỗi giật ở iPad/iPhone-ngang đã sửa). */
/* Bỏ mask ở MỌI khổ, không chỉ mobile: nó là nút thắt hiệu năng duy nhất và
   đã chụp so hai bản ở 744px — không phân biệt được bằng mắt. Giữ nó cho
   riêng desktop chỉ để lại một biến số khác nhau giữa các máy, mà chính
   những biến số đó đã làm iPad và iPhone-xoay-ngang hỏng theo kiểu khác nhau. */
.foot-art g[mask] { mask: none; -webkit-mask: none; }
/* CHỈ tầng rõ nhất (.ff-s1, 6 đường) chuyển động — ở MỌI khổ, không còn ngưỡng.
   Mỗi lần tôi đặt một ngưỡng là lại có một loại máy rơi ra ngoài và hỏng theo
   kiểu riêng: 640px bỏ sót iPad dọc (744) và iPhone ngang (844); 1024px bỏ sót
   iPad NGANG (1133) — đo được 33,3ms/rớt 103/120. Một hành vi cho mọi máy thì
   không còn gì để bỏ sót. Hai tầng kia opacity chỉ .26 và .15 nên đứng yên gần
   như không ai nhận ra. */
.ff-s2, .ff-s3 { animation: none; stroke-dasharray: none; }
@keyframes ff-drift   { from { stroke-dashoffset: 0; } to { stroke-dashoffset: -.2; } }
/* Keyframes viết TƯỜNG MINH cho từng tầng thay vì dùng biến CSS trong
   @keyframes. Biến trong keyframes là chỗ WebKit có lỗi đã biết — và triệu
   chứng khớp: chân trang (keyframes giá trị thẳng) nhúc nhích được trên
   iPad, hero (keyframes có biến) thì đứng im. Dài dòng hơn nhưng hết rủi ro. */
@keyframes mw-sway-1 { from { transform: translate(-32px, -40px); } to { transform: translate(32px, 40px); } }
@keyframes mw-sway-1-m { from { transform: translate(-16px, -16px); } to { transform: translate(16px, 16px); } }
@keyframes mw-sway-2 { from { transform: translate(-24px, -48px); } to { transform: translate(24px, 48px); } }
@keyframes mw-sway-2-m { from { transform: translate(-16px, -24px); } to { transform: translate(16px, 24px); } }
@keyframes mw-sway-3 { from { transform: translate(-40px, -32px); } to { transform: translate(40px, 32px); } }
@keyframes mw-sway-3-m { from { transform: translate(-24px, -16px); } to { transform: translate(24px, 16px); } }
@keyframes mw-drift-1 { from { transform: translate(-16px, -16px); } to { transform: translate(16px, 16px); } }
@keyframes mw-drift-1-m { from { transform: translate(-16px, -8px); } to { transform: translate(16px, 8px); } }
@keyframes mw-drift-2 { from { transform: translate(-16px, -24px); } to { transform: translate(16px, 24px); } }
@keyframes mw-drift-2-m { from { transform: translate(-16px, -8px); } to { transform: translate(16px, 8px); } }
@keyframes mw-drift-3 { from { transform: translate(-24px, -16px); } to { transform: translate(24px, 16px); } }
@keyframes mw-drift-3-m { from { transform: translate(-24px, -8px); } to { transform: translate(24px, 8px); } }
@keyframes mw-cloud { from { transform: translateY(-32px) scaleY(.92); } to { transform: translateY(32px) scaleY(1.08); } }
@keyframes mw-cloud-m { from { transform: translateY(-16px) scaleY(.92); } to { transform: translateY(16px) scaleY(1.08); } }

/* ── Dải bằng chứng ──────────────────────────────────────────────────────── */
.proof { background: var(--inverse-bg-deep); color: var(--on-dark); border-top: var(--bw) solid rgba(255,255,255,.08); }
/* Số TRÊN, nhãn DƯỚI, căn giữa ô: người xem đọc con số trước. Nhãn dài ngắn
   khác nhau (một hay hai dòng) thì chỉ lệch ở phần dưới — hàng số luôn thẳng.
   Thứ tự trong DOM giữ dt rồi dd cho trình đọc màn hình; column-reverse chỉ đổi
   thứ tự nhìn.
   Số cột viết TƯỜNG MINH, không dùng auto-fit/minmax: minmax(176px) rớt về MỘT
   cột ở khổ 390px (hai cột cần 352px, khung còn 342px) — bốn con số xếp dọc
   sát trái, nửa phải bỏ trống. Founder bắt được bằng mắt 2026-09-11; nay có luật
   5 của layout-check.js giữ. */
.proof-grid {
    display: grid; grid-template-columns: repeat(4, 1fr);
    margin: 0; padding-block: var(--s-7);
}
.proof-item {
    display: flex; flex-direction: column-reverse; justify-content: flex-end; align-items: center;
    text-align: center; padding: var(--s-2) var(--s-4);
    border-left: var(--bw) solid rgba(255,255,255,.12);
}
.proof-item:first-child { border-left: 0; }
.proof-item dt {
    font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase;
    color: var(--on-dark-soft); margin-top: var(--s-3); font-weight: 600;
}
.proof-item dd { margin: 0; font-family: var(--serif); font-size: var(--fs-3xl); color: var(--on-inverse); line-height: 1; }
.proof-suffix { color: var(--brass-on-dark); }
/* Điện thoại: lưới 2×2, kẻ chữ thập giữa bốn ô. */
@media (max-width: 640px) {
    .proof-grid { grid-template-columns: repeat(2, 1fr); padding-block: var(--s-5); }
    .proof-item { padding-block: var(--s-5); border-left: 0; }
    .proof-item:nth-child(even) { border-left: var(--bw) solid rgba(255,255,255,.12); }
    .proof-item:nth-child(n+3) { border-top: var(--bw) solid rgba(255,255,255,.12); }
}

/* ── Số giai đoạn thành yếu tố đồ họa ────────────────────────────────────── */

/* ── Gạch chân vẽ dần khi hover ──────────────────────────────────────────── */
.single-content a:not(.btn), .path-steps a, .plain-list a {
    text-decoration: none;
    background-image: linear-gradient(var(--brass-strong), var(--brass-strong));
    background-repeat: no-repeat; background-position: 0 100%; background-size: 0% 1px;
    transition: background-size var(--dur) var(--ease), color var(--dur) var(--ease);
    padding-bottom: 1px;
}
.single-content a:not(.btn):hover, .path-steps a:hover, .plain-list a:hover { background-size: 100% 1px; }

/* ── Hiện dần khi cuộn ───────────────────────────────────────────────────── */
.js .reveal { opacity: 0; transform: translateY(var(--s-2)); }
.js .reveal.is-in { opacity: 1; transform: none; transition: opacity 520ms var(--ease), transform 520ms var(--ease); }
@media (prefers-reduced-motion: reduce) {
    .js .reveal { opacity: 1 !important; transform: none !important; }
}

/* ── Thanh tiến độ đọc ───────────────────────────────────────────────────── */
.read-bar {
    position: fixed; top: 0; left: 0; height: 2px; width: 0;
    background: var(--brass); z-index: 60; pointer-events: none;
}

/* ── Nút chuyển ngôn ngữ dạng cờ, đặt ở header ───────────────────────────── */
/* ── Chuyển ngôn ngữ: thẻ <details> làm nút thả xuống ────────────────────── */
.lang { position: relative; }
.lang-current {
    display: inline-flex; align-items: center; gap: var(--s-1);
    padding: var(--s-1); border-radius: var(--r-1); cursor: pointer;
    color: var(--on-dark-soft); list-style: none;
    border: var(--bw) solid transparent;
    transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
/* Bỏ tam giác mặc định của <summary> ở cả Chromium lẫn Safari */
.lang-current::-webkit-details-marker { display: none; }
.lang-current::marker { content: ""; }
.lang-current:hover { color: var(--on-inverse); border-color: rgba(255,255,255,.28); }
.lang[open] .lang-current { color: var(--on-inverse); border-color: rgba(255,255,255,.28); }
.lang-caret { width: var(--s-3); height: var(--s-3); flex: none; transition: transform var(--dur) var(--ease); }
@media (prefers-reduced-motion: no-preference) {
    .lang[open] .lang-caret { transform: rotate(180deg); }
}
.flag {
    width: var(--s-5); height: 16px; display: block; border-radius: 2px;
    box-shadow: 0 0 0 1px rgba(255,255,255,.18);
}
/* Lá cờ hiện trên nút là lá cờ của ngôn ngữ ĐANG XEM. Mặc định tiếng Việt; JS
   gắn data-lang-cur="en" lên <html> khi đường dẫn bắt đầu bằng /en/. Mặc định
   phải đúng cho phần lớn trang, vì không có JS thì không ai đổi nó. */
.lang-current .lang-ico[data-lang="en"] { display: none; }
html[data-lang-cur="en"] .lang-current .lang-ico[data-lang="vi"] { display: none; }
html[data-lang-cur="en"] .lang-current .lang-ico[data-lang="en"] { display: block; }
/* Trong danh sách, cờ nằm trên nền GIẤY nên viền trắng mờ không thấy gì. */
.lang-menu .flag { box-shadow: 0 0 0 1px rgba(22,35,46,.18); }

.lang-menu {
    position: absolute; right: 0; top: calc(100% + var(--s-2)); z-index: 20;
    min-width: 176px; padding: var(--s-2) 0;
    background: var(--paper); border: var(--bw) solid var(--rule);
    border-radius: var(--r-2); box-shadow: var(--shadow-3);
}
.lang-opt {
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-3) var(--s-4); min-height: 48px;
    color: var(--text); text-decoration: none; font-size: var(--fs-sm);
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.lang-opt:hover { background: var(--paper-warm); color: var(--heading); }
.lang-opt:active { background: var(--acc-wash); }
.lang-opt[aria-current] { color: var(--heading); font-weight: 600; }
/* Dấu ngôn ngữ đang xem: một chấm đồng ở rìa phải, không đổi nền — nền đã dành
   cho hover, chồng hai thứ lên nhau thì không phân biệt được. */
.lang-opt[aria-current]::after {
    content: ""; margin-left: auto; width: var(--s-2); height: var(--s-2);
    border-radius: var(--r-full); background: var(--brass-strong);
}

/* 1216px chứ không phải 896: menu desktop cần ~1211px (7 mục + brand + cụm cờ).
   Để 896 thì tràn ngang ở toàn dải tablet ngang và laptop nhỏ. */
/* 1216–1400px: khung header chưa đủ rộng cho padding đầy đủ -> siết padding
   mục menu thay vì để hàng tràn ngang. */
@media (max-width: 1400px) {
    .menu > li > a { padding-inline: var(--s-2); }
}

/* ═══ Quy ước sắp chữ cho nội dung bài/trang ═══════════════════════════════
   Trước đây link trong nội dung thừa hưởng đúng màu chữ thân bài -> không
   phân biệt được cho tới khi hover. Đó là lỗi dùng được, không chỉ thẩm mỹ. */

.single-content a:not(.btn) {
    color: var(--brass-text);                 /* 5.08 trên giấy · 4.62 nền ấm — đạt AA */
    background-size: 100% 1px;                /* gạch chân LUÔN hiện */
    background-image: linear-gradient(var(--rule), var(--rule));
    font-weight: 500;
}
.single-content a:not(.btn):hover {
    color: var(--heading);
    background-image: linear-gradient(var(--brass-strong), var(--brass-strong));
    background-size: 100% 2px;
}
.single-content a:not(.btn):visited { color: var(--link-visited); }

/* Chữ in đậm: nhích sang màu mực đậm hơn thay vì chỉ dày nét */
.single-content strong { font-weight: 600; color: var(--heading); }

/* ── Cụm tiện ích header: ngôn ngữ + sáng/tối ────────────────────────────── */
.head-utils { display: flex; align-items: center; gap: var(--s-2); }
/* Desktop: đẩy ra RÌA PHẢI, sau menu — không chen giữa logo và menu */
.head-utils { order: 3; margin-left: var(--s-5); padding-left: var(--s-5); position: relative; }
.site-menu  { order: 2; }
.head-utils::before {
    content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
    width: 1px; height: var(--s-5); background: rgba(255,255,255,.16);
}

.theme-toggle, .util-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--s-5); height: var(--s-5); padding: 0;
    background: none; border: 0; cursor: pointer; color: var(--on-dark-soft);
    transition: color var(--dur) var(--ease);
}
.theme-toggle:hover, .util-btn:hover { color: var(--on-inverse); }
.theme-toggle svg, .util-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; }

/* Mục tìm kiếm trong bảng menu mobile: chỉ hiện khi bảng menu tồn tại. */
.menu-search { display: none; }
.menu-search button {
    display: flex; align-items: center; gap: var(--s-3); width: 100%;
    min-height: 48px; padding-block: var(--s-4); font: inherit; font-size: var(--fs-base);
    background: none; border: 0; color: var(--on-inverse); cursor: pointer; text-align: left;
}
.menu-search svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.6; flex: none; }
.icon-moon { display: none; fill: currentColor; stroke: none; }
:root[data-theme="dark"] .icon-sun { display: none; }
:root[data-theme="dark"] .icon-moon { display: block; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .icon-sun { display: none; }
    :root:not([data-theme="light"]) .icon-moon { display: block; }
    :root[data-theme="light"] .icon-sun { display: block; }
    :root[data-theme="light"] .icon-moon { display: none; }
}

@media (max-width: 1232px) {
    /* Mobile: cụm tiện ích nằm ngay trái nút menu */
    .head-utils { order: 1; margin-left: auto; padding-left: 0; margin-right: 0; flex: none; }
    .head-utils::before { display: none; }
    .nav-toggle { order: 2; margin-left: 0; }
    .search-btn { display: none; }   /* bản mobile nằm trong bảng menu */
    .menu-search { display: block; }
    /* Hàng header có BA cụm không co được: thương hiệu (nowrap), cụm cờ, nút menu.
       Ở khổ 390px, hai khoảng cách 24px của flex đẩy nút menu ra ngoài màn hình
       31px — đo được bằng scrollWidth 421 trên viewport 390. Thu khoảng cách
       xuống 8px là đủ chỗ, và không cần đụng tới ba cụm kia. */
    .site-head-inner { gap: var(--s-2); }
}

/* ── Dấu hiệu thương hiệu "Enso & Mức" ───────────────────────────────────── */
/* flex:none + nowrap: ở khổ ~1280px thanh điều hướng chật, flex bóp khối thương
   hiệu lại và "Ival và Cộng Sự" xuống hai dòng ngay trên header. Tên thương hiệu
   không bao giờ được ngắt dòng. */
.brand { display: inline-flex; align-items: center; gap: var(--s-3); flex: none; white-space: nowrap; }
.logo-mark {
    width: var(--s-5); height: var(--s-5); flex: none;
    color: var(--brass-on-dark);
    transition: color var(--dur) var(--ease), transform 420ms var(--ease);
}
.brand:hover .logo-mark { color: var(--brass-bright); }
/* Vòng Enso khép lại một chút khi hover — chuyển động DUY NHẤT trên dấu hiệu */
@media (prefers-reduced-motion: no-preference) {
    .brand:hover .logo-mark { transform: rotate(-8deg); }
}
.site-foot .logo-mark { color: var(--brass-on-dark); }

/* ═══ SẮP CHỮ THEO PHẦN TỬ, KHÔNG THEO CLASS ═══════════════════════════════
   Ghost chuyển HTML sang lexical và XÓA SẠCH class cùng <div> bọc. Mọi quy ước
   gắn bằng class trong nội dung đều bị lột. Nên theme phải làm nội dung đẹp
   dựa trên CHÍNH PHẦN TỬ — biên tập viên gõ chữ bình thường vẫn ra đúng.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Đoạn mở đầu: to hơn, nhạt hơn — không cần gắn gì */
.single-content > p:first-child {
    font-size: var(--fs-lg); line-height: var(--lh-snug); color: var(--ink-soft);
}
/* Chữ hoa thả: CHỈ ở hai trang tự sự dài. Trang khóa học mở đầu bằng ghi chú
   ngắn nên thả chữ vào đó sẽ lố. */
.page-gioi-thieu .single-content > p:first-child::first-letter,
.page-phuong-phap .single-content > p:first-child::first-letter,
.page-gioi-thieu-doi-ngu .single-content > p:first-child::first-letter {
    /* Chữ hoa thả. line-height PHẢI nhỏ hơn nhiều so với 1: hộp dòng của chữ
       cao bằng font-size × line-height, dư ra bao nhiêu thì chữ bị đẩy xuống
       bấy nhiêu — đó là lý do bản đầu nó tụt xuống thay vì nhô lên.
       Lora có chiều cao chữ hoa ~0.72em nên lấy đúng số đó. KHÔNG padding-top. */
    float: left; font-family: var(--serif); font-weight: 600;
    font-size: 3.2em; line-height: .72; padding: 0;
    margin: .02em .14em 0 0;
    color: var(--brass-text);
}


/* Danh sách không thứ tự: chấm đồng thay bullet mặc định */
.single-content ul { list-style: none; padding-left: 0; }
.single-content ul > li { position: relative; padding-left: var(--s-5); }
.single-content ul > li::before {
    content: ""; position: absolute; left: var(--s-1); top: .72em;
    width: var(--s-1); height: var(--s-1); border-radius: var(--r-full); background: var(--brass-strong);
}

/* Danh sách có thứ tự: số bằng serif, màu đồng — dùng cho giai đoạn giáo trình */
.single-content ol { list-style: none; counter-reset: n; padding-left: 0; }
.single-content ol > li { counter-increment: n; position: relative; padding-left: var(--s-7); }
.single-content ol > li::before {
    content: counter(n, decimal-leading-zero); position: absolute; left: 0; top: 0;
    font-family: var(--serif); font-weight: 600; font-size: var(--fs-md);
    color: var(--brass-text); line-height: inherit;
}

/* Tiêu đề mục: vạch đồng ngắn phía trên, thay cho khoảng trống trơn */
.single-content h2 { position: relative; padding-top: var(--s-5); }
.single-content h2::before {
    content: ""; position: absolute; top: 0; left: 0;
    width: var(--s-6); height: 2px; background: var(--brass-strong); opacity: .8;
}

/* Đường phân cách do biên tập viên chèn */
.single-content hr {
    border: 0; height: 1px; background: var(--rule);
    margin-block: var(--s-8); position: relative; overflow: visible;
}
.single-content hr::after {
    content: "◦"; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
    background: var(--paper); color: var(--brass-text); padding-inline: var(--s-3); font-size: var(--fs-base);
}

/* Chữ nghiêng cuối trang (dòng miễn trừ pháp lý) — nhỏ và lùi lại */
.single-content > p > em:only-child { color: var(--muted); font-size: var(--fs-sm); }

/* ── Dải "tìm hiểu thêm" cuối trang khóa học ─────────────────────────────── */
.course-more .grid { margin-top: 0; }
/* Dùng lại .card (nền/viền/padding/hover chung của hệ thống). Chỉ khai báo
   phần thật sự riêng: nền nổi hơn vì dải này nằm trên --paper, và nút bị đẩy
   xuống đáy để hai thẻ cao bằng nhau. */
.more-card {
    background: var(--paper-pure);
    display: flex; flex-direction: column; align-items: center; gap: var(--s-3);
    /* Căn giữa CẢ thẻ, không chỉ nút. Bản trước để align-items:flex-start rồi
       thêm align-self:center riêng cho nút — nút vào giữa còn tiêu đề và mô tả
       vẫn dính lề trái, đọc ra là lệch. Hai thẻ này là khối MỜI ĐI TIẾP (mỗi
       thẻ một câu + một nút), không phải khối đọc dài, nên căn giữa hợp hơn. */
    text-align: center;
}
/* gap của flex lo khoảng cách -> bỏ margin riêng của .card-title/.card-desc */
.more-card .card-title, .more-card .card-desc { margin-bottom: 0; }
.more-card .btn { margin-top: auto; }
.method-note { padding-block: var(--s-8) 0; }

/* ── Khối học phí trên trang khóa ─────────────────────────────────────────
   Dải tối DUY NHẤT trong thân trang khóa: đây là khoảnh khắc quyết định, nên
   nó phải là chỗ nặng nhất trang. Bản cũ là 5 đoạn văn ngang hàng nhau nên con
   số bị chôn giữa điều kiện — founder đọc ra là "hiền lành dễ bắt nạt". Cùng
   chừng ấy sự thật, chỉ xếp lại thứ bậc. */
.price-block {
    background: var(--inverse-bg); color: var(--on-dark);
    /* margin CHỈ ở trên: phía trên là thẻ trắng thân bài, cần khoảng hở để dải
       tối không dán vào thẻ. Phía dưới là dải "Kỹ năng cốt lõi" cũng tràn viền —
       để margin ở đó thì lòi ra một vạch 68px MÀU NỀN TRANG kẹp giữa hai dải,
       đọc ra là đường nối hở chứ không phải khoảng cách có chủ ý. Hai dải tràn
       viền phải chạm nhau. */
    padding-block: var(--s-8); margin-block: var(--s-8) 0;
    border-block: var(--bw) solid rgba(255,255,255,.14);
}
.price-block .eyebrow { color: var(--brass-bright); }
/* Con số là TIÊU ĐỀ của khối, không phải một dòng trong đoạn văn */
.pb-fee {
    font-family: var(--serif); color: var(--on-inverse);
    font-size: clamp(var(--fs-3xl), 7vw, var(--fs-5xl));
    line-height: 1; letter-spacing: -.02em; margin-block: var(--s-3) var(--s-2);
}
.pb-unit { font-size: .42em; margin-left: .12em; color: var(--brass-bright); }
.pb-meta { color: var(--on-dark-soft); font-size: var(--fs-sm); margin-bottom: var(--s-6); }

/* Các cách trả = LỰA CHỌN bày ra, không phải điều kiện kể lể */
.pb-opts { list-style: none; margin: 0 0 var(--s-5); padding: 0; }
.pb-opt {
    display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5);
    align-items: baseline; justify-content: space-between;
    padding-block: var(--s-4); border-top: var(--bw) solid rgba(255,255,255,.14);
}
.pb-opt:last-child { border-bottom: var(--bw) solid rgba(255,255,255,.14); }
.pb-k { color: var(--on-dark); font-size: var(--fs-sm); }
.pb-v { font-family: var(--serif); font-size: var(--fs-lg); color: var(--on-inverse); }
.pb-x { color: var(--on-dark-soft); font-size: var(--fs-sm); font-family: var(--sans); }
.pb-opt-best .pb-v { color: var(--brass-bright); }
.pb-tag {
    display: inline-block; margin-left: var(--s-2); padding: 2px var(--s-2);
    border: var(--bw) solid var(--brass-on-dark); border-radius: var(--r-1);
    color: var(--brass-bright); font-size: var(--fs-2xs);
    letter-spacing: var(--ls-caps); text-transform: uppercase;
}
.pb-cta { margin-bottom: var(--s-5); }
/* Điều kiện rút xuống chữ nhỏ: vẫn nói đủ, nhưng thôi tranh chỗ với con số */
.pb-fine { color: var(--on-dark-soft); font-size: var(--fs-xs); max-width: 62ch; }
/* ⚠️ Liên kết trong dải học phí PHẢI khai màu riêng. Mặc định toàn site là
   a { color: var(--heading) } — màu chữ tiêu đề trên nền giấy. Đặt nó lên
   --inverse-bg (#16232E) thì --heading (#16232E) trùng đúng màu nền: tương phản
   1.0:1, liên kết TÀNG HÌNH. Không cổng nào ngoài a11y.js bắt được, và
   CLAUDE.md đã ghi một nút 1.07:1 từng sống sót nhiều tuần vì đúng lối này.
   --brass-bright đo được 10.46 trên nền tối (xem brand/palette.md).

   ⚠️ PHẢI loại .btn ra. Bản đầu viết `.price-block a` trần và nó đè luôn màu chữ
   của nút "Đăng ký tư vấn" (.btn-primary nền đồng): chữ brass-bright trên nền
   brass -> a11y.js báo color-contrast [serious] trên mọi trang có dải học phí.
   Nút tự quản màu của nó; luật này chỉ dành cho liên kết trong VĂN XUÔI. */
.price-block a:not(.btn) { color: var(--brass-bright); }
.price-block a:not(.btn):hover { color: var(--on-inverse); }

/* ── Trang đọc dài: MỘT mép trái duy nhất ────────────────────────────────
   Lỗi bố cục thật, founder chỉ ra 2026-09-11: trang khóa học chạy khung đọc
   720px từ breadcrumb xuống hết thân bài, rồi ba dải cuối (kỹ năng cốt lõi,
   tìm hiểu thêm, CTA) lại dùng khung 1120px. Trên màn 1200px, mép chữ nhảy từ
   x=264 sang x=64 — mắt dò mép trái xuống tới đó thì hẫng. Nội dung không sai,
   chỉ là trình bày thiếu chủ ý.
   Dải NỀN vẫn tràn hết bề ngang (nền đặt trên <section>, đây chỉ siết .wrap bên
   trong), nên vẫn phân vùng được mà không lệch mép. Lưới grid-2/grid-3 tự rút
   còn 2 cột nhờ auto-fit, không cần đụng tới. */
.flow-narrow .wrap { max-width: var(--narrow); }
/* .split là lưới hai cột theo BỀ NGANG MÀN HÌNH, không theo bề ngang khối chứa
   — để nguyên thì ở khung 672px mỗi cột chỉ còn ~324px, chữ vỡ vụn. */
.flow-narrow .split { grid-template-columns: 1fr; }
/* Khung đọc 672px chỉ đủ 2 cột, nên lưới 3 thẻ thành 2+1 và để hở một ô ở góc.
   Cho thẻ lẻ cuối cùng chiếm trọn hàng: lấp chỗ hở, và đọc ra là có chủ ý. */
.flow-narrow .grid-3 > :last-child:nth-child(odd),
.flow-narrow .grid-2 > :last-child:nth-child(odd) { grid-column: 1 / -1; }

/* ── Đánh giá học viên ────────────────────────────────────────────────────
   Lời của người khác nên phải đọc ra là lời người khác: serif, cỡ lớn hơn thân
   bài, có nét đồng dẫn ở mép trái. KHÔNG dùng dấu ngoặc kép trang trí cỡ đại —
   tệp này đã chán kiểu landing page bán khóa học. */
.testimonials { padding-block: var(--s-7) var(--s-8); }
.ts-source { color: var(--muted); font-size: var(--fs-sm); margin-bottom: var(--s-6); }
.ts { margin: 0 0 var(--s-6); padding-left: var(--s-5); border-left: 2px solid var(--brass-strong); }
.ts blockquote {
    margin: 0; font-family: var(--serif); font-size: var(--fs-md);
    line-height: var(--lh-snug); color: var(--ink-soft);
}
.ts figcaption { margin-top: var(--s-3); font-size: var(--fs-sm); color: var(--heading); font-weight: 600; }
.ts figcaption span { color: var(--muted); font-weight: 400; margin-left: var(--s-2); }
.ts-note { margin-top: var(--s-7); font-size: var(--fs-xs); color: var(--muted); max-width: 62ch; }

/* ── Tiếng nói học viên trên trang chủ ───────────────────────────────────── */
.voices { padding-block: var(--s-8); background: var(--paper-warm); border-block: var(--bw) solid var(--rule); }
.voices .ts { margin-bottom: 0; }
.voices-quotes { display: grid; gap: var(--s-6); }
/* Link "đọc thêm" nay là <p class="wrap voices-more"> đứng SAU .wrap.split (xem
   partials/home-voices.hbs) — DOM = thứ tự đọc = thứ tự nhìn ở MỌI khổ, không
   cần override .voices .split/.split-main/order nữa (bản trước dùng
   display:contents + order để chỉ đẩy link xuống cuối ở khổ hẹp, nhưng DOM vẫn
   link-trước-quotes -> vi phạm WCAG 1.3.2 cho screen reader/Tab, chốt sửa
   2026-09-13). .split dùng nguyên bản chung (1 cột dưới 896px, 2 cột từ 896px)
   — không cần chỉnh riêng cho .voices nữa. */
.voices-more { margin-top: var(--s-6); }
.voices-more a { color: var(--brass-text); text-decoration: none; font-size: var(--fs-sm); font-weight: 600; }
.voices-more a:hover { text-decoration: underline; }
.voices-cred { margin-top: var(--s-5); font-size: var(--fs-sm); color: var(--muted); line-height: 1.6; }
.voices-cred strong { color: var(--ink-soft); }
.voices-note { margin-top: var(--s-3); font-size: var(--fs-xs); color: var(--muted); font-style: italic; }
/* Dải "ai dạy" trang chủ EN (en-home-teacher.hbs): p bị reset margin ở đầu file,
   nên hai đoạn văn dính nhau nếu không khai khoảng cách tại đây. */
.who-teaches .split-main p + p { margin-top: var(--s-4); }
.who-teaches .split-main .btn { margin-top: var(--s-5); }

/* ── Đoạn giới thiệu đầu trang danh mục ──────────────────────────────────
   Lỗi thật founder chỉ ra: hộp intro dán SÁT dải tiêu đề, khoảng cách đo được
   đúng 0px — viền dưới của dải và viền trên của hộp chồng thành một vạch dày.
   Vi phạm hai thứ cùng lúc:
   1. Thang 8px của ui-guidelines: 0 không phải một bậc token nào.
   2. Mép trái: hộp chạy khung 720px trong khi CẢ TRANG chạy 1120px — đúng lỗi
      "lệch mép" đã sửa cho trang khóa, tôi tự tái tạo ở đây khi chuyển intro
      lên đầu trang.
   Cách sửa: intro KHÔNG phải thân bài nên không cần dáng thẻ nổi. Cho nó chạy
   đúng khung .wrap như mọi dải khác của trang, giữ khổ đọc bằng max-width trên
   từng phần tử con — cùng lối .page-lead và .section-lead đang dùng. */
.page-intro { background: none; border: 0; box-shadow: none; padding-block: var(--s-7) 0; }
.page-intro > * { max-width: 70ch; }
/* Khối tiếng nói học viên chạy khung .wrap như mọi dải khác của trang chủ; giữ
   khổ đọc bằng max-width thay vì thu nhỏ cả khung (nếu không thì mép trái lệch
   200px so với lưới thẻ ngay trên — đúng lỗi đã sửa ở intro /khoa-hoc). */

/* ── Thẻ nổi không được dán vào dải nền ───────────────────────────────────
   Lỗi lặp ba lần ở ba trang khác nhau (/khoa-hoc, rồi hub hàng hóa, rồi
   /lien-he): .page-head là dải tràn viền CÓ VIỀN DƯỚI, ngay sau nó là
   .single-content — thẻ trắng CÓ VIỀN TRÊN. Không có margin nào ở giữa nên hai
   viền chồng thành một vạch dày, đọc ra là lỗi render.
   Hai lần trước tôi sửa từng trang một; đây mới là sửa nguyên nhân: bất kỳ thẻ
   thân bài nào đứng ngay sau dải tiêu đề đều tự có khoảng hở. */
/* Dùng ANH EM CHUNG (~) chứ không phải anh em LIỀN KỀ (+). Luật cũ dùng "+" và
   đã âm thầm ngừng tác dụng khi thân bài có thêm khối xen giữa (ô hình, mục
   lục): thẻ nội dung nền trắng dán thẳng vào dòng meta của tiêu đề, 0px. Đó
   đúng là loại lỗi "sát lề" founder đã chỉ ra nhiều lần — nay có cổng giữ
   (layout-check luật 4), nhưng luật CSS cũng phải hết mong manh.
   Trừ .page-intro: nó cố ý liền dải với tiêu đề trên /khoa-hoc/. */
.page-head ~ .single-content:not(.page-intro),
.single-head ~ .single-content:not(.page-intro) { margin-top: var(--s-7); }
/* .page-intro không phải thẻ nổi (đã bỏ nền/viền) nên tự lo khoảng cách bằng
   padding của chính nó — cộng thêm margin nữa là hở gấp đôi. */
.page-head + .page-intro { margin-top: 0; }
/* Dải tiêu đề có nền riêng và viền dưới. Khối ngay sau nó phải có khoảng thở,
   nếu không hai khối đọc ra là dính liền — founder chỉ đúng chỗ này trên
   /kien-thuc/, nơi mục lục chuỗi từng nằm sát ngay dưới dải tiêu đề. */
.page-head + .blog-layout { margin-top: var(--s-7); }
/* /en/insights/ một cột (không có topic-nav): cùng khoảng thở như .blog-layout. */
.page-head + .insights-list { margin-top: var(--s-7); padding-bottom: var(--s-7); }

/* ============================== Tủ sách /sach/ ============================ */
/* Grid bìa 2:3. Bìa chưa có ảnh dựng bằng chữ trên nền xanh mực (cặp brass-bright
   trên ink 10.46), để grid đứng được trước khi có ảnh thật. */
.page-head + .book-page { margin-top: var(--s-7); padding-bottom: var(--s-8); }
.book-intro p { max-width: 62ch; margin: 0 0 var(--s-4); }
.book-group { margin-top: var(--s-8); }
.book-group h2 { margin-bottom: var(--s-3); }
.book-group-lead { max-width: 62ch; color: var(--muted); margin-bottom: var(--s-5); }
.book-grid { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s-6) var(--s-5);
    grid-template-columns: repeat(auto-fill, minmax(calc(var(--s-10) + var(--s-6) + var(--s-2)), 1fr)); }
.book { display: flex; flex-direction: column; min-width: 0; }
.book-cover { display: block; width: 100%; aspect-ratio: 2 / 3; height: auto; object-fit: cover;
    border-radius: var(--r-1); box-shadow: var(--shadow-2); margin-bottom: var(--s-3); }
.book-cover-text { display: flex; flex-direction: column; padding: var(--s-4) var(--s-3);
    background: var(--inverse-bg); color: var(--brass-bright); border-top: var(--s-1) solid var(--brass); }
.bct-title { font-family: var(--serif); font-size: var(--fs-md); line-height: 1.25; font-weight: 600; overflow-wrap: anywhere; }
.bct-orig { margin-top: var(--s-2); font-size: var(--fs-2xs); line-height: 1.4; opacity: .85; }
.bct-by { margin-top: auto; font-size: var(--fs-2xs); letter-spacing: .14em; text-transform: uppercase; }
.book-title { font-size: var(--fs-base); line-height: 1.35; margin: 0 0 var(--s-1); }
.book-orig { font-size: var(--fs-sm); color: var(--muted); margin: 0; }
.book-meta { font-size: var(--fs-xs); color: var(--muted); margin: var(--s-1) 0 0; }
.page-head ~ .event-list-wrap { margin-top: var(--s-7); padding-bottom: var(--s-7); }
/* Thẻ thân bài đứng ngay trên một dải tràn viền cũng dán 0px y hệt (bắt được ở
   /lien-he: .single-content chạm .contact-band). Cùng một nguyên nhân, nên cùng
   một cách chữa. */
.single-content + section { margin-top: var(--s-7); }

/* ── Dải laptop hẹp: menu ngang vừa khít ─────────────────────────────────────
   Đặt Ở CUỐI FILE có chủ đích. Bản đầu để ngay trước khối Mobile và KHÔNG có
   tác dụng: quy tắc .head-utils cho desktop nằm sau nó, cùng độ đặc hiệu nên
   ghi đè ngược lại. Đo mới thấy — scrollWidth vẫn 1318px y như cũ.

   Vì sao cần: thêm nút tìm kiếm (2026-09-11) làm hàng header tràn ở khổ laptop
   1280px (thực tế 1265px sau khi trừ thanh cuộn). Nhãn "EN" lọt ra ngoài dải
   header tối, và axe-core bắt được dưới dạng lỗi tương phản 2.02:1 — vì chữ
   sáng khi đó nằm trên nền giấy của trang chứ không phải nền tối.

   Thu khoảng cách và padding thay vì hạ xuống dạng hamburger: 1280px là khổ
   laptop phổ biến nhất, đổi sang menu ẩn ở đó là bước lùi về điều hướng. */
/* ── Dải laptop hẹp ngay trên ngưỡng hamburger ───────────────────────────────
   Ở 1216px menu ngang bắt đầu hiện. Đo thật: tới khoảng 1225px thì hàng header
   vẫn còn thiếu 7px và tràn ra ngoài. Thu padding mục menu là đủ, không phải
   hạ xuống dạng hamburger.

   ĐẶT Ở CUỐI FILE có chủ đích: quy tắc .head-utils cho desktop nằm sau phần
   giữa file, cùng độ đặc hiệu nên sẽ ghi đè ngược lại. Bản đầu đặt giữa file và
   hoàn toàn vô tác dụng — chỉ phép ĐO mới cho thấy, scrollWidth không đổi. */
@media (min-width: 1232px) and (max-width: 1320px) {
    .menu > li > a { padding-inline: var(--s-2); }
    /* "Hoạt động & Sự kiện" (29/09) dài hơn mục nó thay, tràn 7px ở cửa sổ 1240px.
       Bỏ khe 4px giữa các mục: 6 khe = 24px. Padding của từng mục vẫn giữ vùng bấm. */
    .menu { gap: 0; }
    /* Riêng padding menu còn thiếu 8px ở khổ 1224px — đo được bằng cổng. Thu
       thêm lề của cụm tiện ích. */
    .head-utils { margin-left: var(--s-4); padding-left: var(--s-4); }
}

/* ===================== Form đăng ký + bài đánh giá (05/10/2026) ===================
   partials/lead-form.hbs, bat-dau-tu-dau.hbs, partials/after-course.hbs.
   Chỉ dùng token ĐẢO theo theme (--paper-pure, --paper-warm, --rule-strong, --text,
   --heading, --muted, --brass-text) nên chế độ tối không cần luật riêng.
   Viền ô nhập dùng --rule-strong: ô nhập là thành phần tương tác, ngưỡng 3.0. */
.lead-form-wrap { margin-block: var(--s-8); padding: var(--s-6); background: var(--paper-pure);
    border: var(--bw) solid var(--rule); border-radius: var(--r-2); }
.lead-form-wrap > h2 { margin-top: 0; }
.lead-form-lead { color: var(--muted); margin-top: var(--s-2); }
.lead-form { margin-top: var(--s-5); }
.lf-field { margin-bottom: var(--s-5); }
.lf-field > label { display: block; font-weight: 600; color: var(--heading); margin-bottom: var(--s-2); }
.lf-opt { font-weight: 400; color: var(--muted); }
.lead-form input[type="text"], .lead-form input[type="tel"], .lead-form select, .lead-form textarea {
    display: block; width: 100%; min-height: var(--s-7); padding: var(--s-3) var(--s-4);
    font: inherit; font-size: var(--fs-base); color: var(--text); background: var(--paper);
    border: var(--bw) solid var(--rule-strong); border-radius: var(--r-1); }
.lead-form textarea { min-height: calc(var(--s-9) + var(--s-3)); resize: vertical; }
.lead-form input:hover, .lead-form select:hover, .lead-form textarea:hover { border-color: var(--brass-strong); }
.lf-hint, .lf-fine { font-size: var(--fs-sm); color: var(--muted); margin-top: var(--s-2); }
.lf-check { display: grid; grid-template-columns: var(--s-5) 1fr; gap: var(--s-3); align-items: start; }
.lf-check input { width: var(--s-5); height: var(--s-5); margin: var(--s-1) 0 0; accent-color: var(--brass-strong); }
.lf-check label { font-weight: 400; font-size: var(--fs-sm); color: var(--text); margin: 0; }
.lf-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.lf-error { color: var(--acc-amber); font-weight: 600; min-height: 0; }
.lf-error:empty { display: none; }
.lf-status { margin-top: var(--s-4); padding: var(--s-4); border-radius: var(--r-1);
    border-left: var(--s-1) solid var(--acc-sage); background: var(--paper-warm); }
.lf-status.is-error { border-left-color: var(--acc-amber); }
.lf-thanks { margin-top: var(--s-5); padding: var(--s-5); background: var(--paper-warm); border-left: var(--s-1) solid var(--acc-sage); border-radius: var(--r-1); }
.lf-thanks h3 { margin-top: 0; }
.lf-thanks:focus { outline: none; }
.lf-thanks:focus-visible { outline: 2px solid var(--brass-strong); outline-offset: 3px; }
.lf-route { padding-left: var(--s-5); }

.after-course { margin-block: var(--s-7); }

/* Bài đánh giá: câu hỏi bên trái, khung kết quả bám theo bên phải từ 896px.
   Thứ tự DOM = thứ tự đọc: câu hỏi trước, kết quả sau (trên điện thoại kết quả rơi xuống dưới). */
.quiz-facts { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s-2); padding: 0; margin: var(--s-4) 0 0; }
.quiz-facts li { font-size: var(--fs-sm); color: var(--muted); border: var(--bw) solid var(--rule);
    border-radius: var(--r-full); padding: var(--s-1) var(--s-3); }
.quiz-layout { display: grid; gap: var(--s-7); margin-top: var(--s-6); align-items: start; }
.quiz-part { font-size: var(--fs-lg); margin: var(--s-7) 0 var(--s-4); }
.quiz-part:first-child { margin-top: 0; }
.quiz-q { margin: 0 0 var(--s-5); padding: var(--s-5); background: var(--paper-pure);
    border: var(--bw) solid var(--rule); border-radius: var(--r-2); }
.quiz-q legend { display: flex; gap: var(--s-3); font-weight: 600; color: var(--heading); padding: 0; float: left; width: 100%; margin-bottom: var(--s-4); }
.quiz-q legend + * { clear: both; }
.quiz-n { flex: none; width: var(--s-6); height: var(--s-6); display: inline-grid; place-items: center;
    border-radius: var(--r-full); background: var(--paper-warm); font-size: var(--fs-sm); }
.quiz-opts { display: grid; gap: var(--s-2); }
.quiz-opts label { display: flex; gap: var(--s-3); align-items: center; min-height: var(--s-7);
    padding: var(--s-2) var(--s-4); border: var(--bw) solid var(--rule-strong); border-radius: var(--r-1);
    cursor: pointer; transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease); }
.quiz-opts label:hover { border-color: var(--brass-strong); }
.quiz-opts label:has(input:checked) { background: var(--paper-warm); border-color: var(--brass-strong); }
.quiz-opts input { flex: none; width: var(--s-4); height: var(--s-4); margin: 0; accent-color: var(--brass-strong); }
.quiz-result { padding: var(--s-5); background: var(--paper-pure); border: var(--bw) solid var(--rule);
    border-radius: var(--r-2); box-shadow: var(--shadow-1); }
.quiz-result > h2 { margin-top: 0; font-size: var(--fs-xl); }
.quiz-progress { font-size: var(--fs-sm); color: var(--muted); }
.quiz-sample { font-size: var(--fs-sm); color: var(--muted); font-style: italic; }
.quiz-badges { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.quiz-badge, .quiz-tag { display: inline-block; border-radius: var(--r-full); background: var(--paper-warm);
    color: var(--heading); font-size: var(--fs-sm); padding: 0 var(--s-3); }
.quiz-tag { font-size: var(--fs-2xs); font-weight: 400; }
.quiz-alert, .quiz-steps .is-alert, .quiz-steps .is-note { border-left: var(--s-1) solid var(--acc-amber); }
.quiz-alert { padding: var(--s-3) var(--s-4); background: var(--paper-warm); font-size: var(--fs-sm); }
.quiz-steps { padding-left: var(--s-5); margin-top: var(--s-4); }
.quiz-steps li { padding: var(--s-2) 0 var(--s-2) var(--s-2); }
.quiz-steps li > strong { display: block; color: var(--heading); }
.quiz-steps li > span { display: block; font-size: var(--fs-sm); color: var(--text); }
.quiz-steps .is-alert, .quiz-steps .is-note { padding-left: var(--s-3); }
.quiz-miss-h { font-size: var(--fs-base); margin-top: var(--s-5); }
.quiz-miss { font-size: var(--fs-sm); padding-left: var(--s-5); }
.quiz-note { font-size: var(--fs-sm); color: var(--muted); }
.quiz-to-form { margin-top: var(--s-5); }
.quiz-map-wrap { margin-top: var(--s-8); }
.quiz-map { display: grid; gap: var(--s-4); margin-top: var(--s-5); }
.quiz-lv h3 { font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--muted); margin: 0 0 var(--s-2); }
.quiz-lv ul { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s-2); padding: 0; margin: 0; }
.quiz-chip { font-size: var(--fs-sm); padding: var(--s-1) var(--s-3); border: var(--bw) solid var(--rule);
    border-radius: var(--r-full); color: var(--muted); }
.quiz-chip a { color: inherit; }
.quiz-chip.is-on { border-color: var(--brass-strong); background: var(--paper-warm); color: var(--heading); font-weight: 600; }
.quiz-webinar { margin-block: var(--s-8); }
.quiz-disclaimer { font-size: var(--fs-sm); color: var(--muted); margin-block: var(--s-7); }
@media (min-width: 896px) {
    .quiz-layout { grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); }
    .quiz-side { position: sticky; top: var(--s-5); max-height: calc(100vh - var(--s-6)); overflow-y: auto; }
    .quiz-map { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.quiz-measure { max-width: var(--narrow); }
/* /bat-dau-tu-dau/ là trang đích quảng cáo (Ival 05/10/2026: "bài kiểm tra nên làm landing page
   riêng"): ẩn menu và cụm tiện ích để người đến từ quảng cáo không bị kéo đi giữa chừng. Giữ logo
   (lối về trang chủ) và chân trang (thông tin pháp lý). Ẩn bằng body_class của Ghost thay vì rẽ
   nhánh trong default.hbs. Đặt cuối file: phải thắng .nav-toggle { display:block } trong @media. */
.page-bat-dau-tu-dau .site-menu, .page-bat-dau-tu-dau .nav-toggle, .page-bat-dau-tu-dau .head-utils { display: none !important; }

/* Video nhúng trong bài (YouTube nocookie, bài sự kiện). Khung 16:9 giữ chỗ trước khi iframe tải
   -> không nhảy bố cục. */
.video-embed { position: relative; aspect-ratio: 16 / 9; margin-block: var(--s-5); border-radius: var(--r-2); overflow: hidden; background: var(--paper-warm); }
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
