/* Hallmark · genre: modern-minimal · macrostructure: Index-First (hub) + Workbench (tool) + Long Document (content)
 * theme: Deep Blue (tuned) · paper: #f4f5f7 · ink: navy #172b4d · display: system sans
 * accent: bright blue #0052cc · nav: N3 side-rail · footer: Ft2 inline-rule
 * design-system: design.md · designed-as-app
 * pre-emit critique: P5 H5 E4 S5 R5 V4
 * ==========================================================================
 * AuthBeta — hệ thống thiết kế
 *
 * Ba nguyên tắc của bộ này, để lần sau sửa còn giữ đúng mạch:
 *   1. Đường kẻ 1px làm việc, không dùng bóng đổ. Bóng chỉ còn ở lớp nổi (menu, hộp thoại).
 *   2. Đúng MỘT màu nhấn cobalt, chiếm dưới 5% mỗi màn hình: nút chính, viền focus,
 *      mục đang mở, chip trạng thái. Còn lại là mực trên giấy.
 *   3. Nhãn nhỏ viết hoa, cỡ nhỏ, màu nhạt — đủ để phân tầng mà không ồn ào.
 * ========================================================================== */

/* --------------------------------------------------------------- biến màu */

:root {
    color-scheme: light;

    /* Nền trang xám nhạt, thẻ trắng tinh: thẻ tự nổi lên khỏi mặt giấy mà không
       cần bóng đổ. Nền trắng tinh toàn trang thì thẻ chìm nghỉm. */
    --bg:            #f4f5f7;
    --bg-subtle:     #fafbfc;
    --bg-muted:      #ebecf0;
    --bg-inset:      #dfe1e6;
    --surface:       #ffffff;
    --surface-hover: #fafbfc;
    --overlay:       rgba(9, 30, 66, .54);

    /* Mực xanh navy đậm, không phải xám trung tính — cùng họ với màu nhấn nên
       cả trang trông liền một khối. */
    --text:          #172b4d;
    --text-secondary:#42526e;
    /* Hai bậc này trước đây đo được 4,15:1 và 2,42:1 trên nền #f4f5f7 - dưới
       ngưỡng 4,5:1 của WCAG. Giá trị mới đo trên nền xám đậm nhất mà chúng
       từng nằm lên (#dfe1e6) chứ không phải trên nền trắng: đo trên nền trắng
       rồi yên tâm thì chính các bảng số liệu nền xám sẽ hỏng. */
    --text-muted:    #505f79;   /* thấp nhất 4,93:1 */
    --text-faint:    #596478;   /* thấp nhất 4,56:1 */
    --text-inverse:  #ffffff;

    --border:        #dfe1e6;
    --border-strong: #c1c7d0;
    --border-focus:  #4c9aff;

    /* Xanh dương tươi, bão hoà cao: nút chính phải nhìn ra ngay là bấm được. */
    --accent:        #0052cc;
    --accent-hover:  #0747a6;
    --accent-soft:   #deebff;
    --accent-text:   #0052cc;
    --accent-contrast: #ffffff;

    /* Dải tối duy nhất của trang: bảng mã, thanh bên, khối trình diễn. */
    --graphite:      #0747a6;
    --graphite-2:    #0052cc;
    --graphite-ink:  #deebff;

    --success:       #00875a;
    --success-soft:  #e3fcef;
    --warning:       #ff8b00;
    --warning-soft:  #fffae6;
    --danger:        #de350b;
    --danger-soft:   #ffebe6;
    --info:          #0065ff;
    --info-soft:     #deebff;

    /* Bốn màu trên dùng cho biểu tượng, viền và vạch biểu đồ - những chỗ chỉ
       cần 3:1 nên chúng được giữ nguyên độ tươi. Nhưng CHỮ NHỎ trên nền mềm
       thì cần 4,5:1, và cả bốn đều trượt: cam chỉ đạt 2,24:1. Bốn biến riêng
       dưới đây chỉ dành cho chữ, nhờ vậy nhãn đọc được mà biểu tượng không bị
       xỉn đi theo. */
    --success-text:  #00734c;   /* 5,46:1 trên nền #e3fcef */
    --warning-text:  #a35200;   /* 5,33:1 trên nền #fffae6 */
    --danger-text:   #c92500;   /* 4,88:1 trên nền #ffebe6 */
    --info-text:     #0052cc;   /* 5,66:1 trên nền #deebff */

    /* Chỉ ba mức bóng, dành cho lớp thật sự nổi lên trên mặt giấy. */
    --shadow-sm:  0 1px 1px rgba(9, 30, 66, .06);
    --shadow:     0 1px 3px rgba(9, 30, 66, .12);
    --shadow-md:  0 4px 12px -2px rgba(9, 30, 66, .16);
    --shadow-lg:  0 12px 32px -8px rgba(9, 30, 66, .25);

    /* Bo góc kiểu "vẽ bằng thước": chặt, không bo tròn kiểu viên thuốc. */
    --radius-sm: 4px;
    --radius:    6px;
    --radius-md: 10px;
    --radius-lg: 14px;
    --radius-full: 999px;

    /* Bộ chữ của portal.azure.com: Segoe UI đứng đầu, sau đó mới tới chữ hệ
       thống của máy khác. Máy nào cũng có sẵn nên không phải tải gì.

       Segoe UI chỉ có ba độ đậm thật: 400 thường, 600 nửa đậm, 700 đậm. Cả tệp
       này chỉ dùng đúng ba số đó — viết 560 hay 620 thì trình duyệt vẫn làm
       tròn về 600, chỉ tổ đánh lừa người đọc mã. */
    --font-sans: "Segoe UI", "Segoe UI Web (West European)", -apple-system,
                 BlinkMacSystemFont, Roboto, "Helvetica Neue", "Noto Sans",
                 "Liberation Sans", Arial, sans-serif,
                 "Apple Color Emoji", "Segoe UI Emoji";
    --font-display: var(--font-sans);
    --font-mono: Consolas, "Cascadia Mono", ui-monospace, SFMono-Regular, "SF Mono",
                 Menlo, Monaco, "Liberation Mono", monospace;

    /* Ba mức đậm, gọi bằng tên để chỗ nào cần nhấn thì thấy ngay là đang nhấn. */
    --weight-normal: 400;
    --weight-semibold: 600;
    --weight-bold: 700;

    --header-height: 56px;
    --content-width: 1180px;
    --transition: 150ms cubic-bezier(.4, 0, .2, 1);
    --ease-out: cubic-bezier(.16, 1, .3, 1);

    /* Hệ tương tác — lấy theo Material 3 và eBay:
       trạng thái con trỏ là một lớp màu mờ chồng lên, đậm dần theo mức; focus
       luôn là một vòng nhìn thấy được, không chỉ đổi nền. */
    --state-hover:  color-mix(in srgb, var(--accent) 6%, transparent);
    --state-active: color-mix(in srgb, var(--accent) 12%, transparent);
    --focus-ring:   0 0 0 3px color-mix(in srgb, var(--accent) 35%, transparent);
    --lift: 0 6px 16px -6px rgba(9, 30, 66, .22), 0 2px 5px -3px rgba(9, 30, 66, .16);
}

/* Chế độ tối: nền đen hẳn, thẻ và thanh bên nổi lên bằng một tone sáng hơn.
   Vẫn là Cobalt, cùng dải màu lạnh 250-260, màu nhấn sáng hơn để giữ tương
   phản. Hai khối dưới đây phải luôn giống hệt nhau: một khối cho người để máy
   tự chọn theo hệ điều hành, một khối cho người tự bấm nút đổi giao diện. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        color-scheme: dark;

        /* Nền đen hẳn. Thẻ và thanh bên mang tone sáng hơn để nổi lên khỏi nền
           — trên nền đen, bóng đổ gần như không nhìn thấy nên việc phân lớp
           phải do chính màu nền của thẻ và một viền sáng mờ đảm nhiệm. */
        --bg:            #000000;
        --bg-subtle:     #0e1216;
        --bg-muted:      #232b34;
        --bg-inset:      #2a333d;
        --surface:       #171d24;
        --surface-hover: #1e252d;
        --overlay:       rgba(0, 0, 0, .78);

        /* Chữ sáng hơn bản trước một bậc. Đo trên nền thẻ #171d24:
           thân bài 15,0:1 · chữ phụ 7,3:1 · chữ mờ 5,0:1 — cả ba đều vượt
           ngưỡng 4,5:1 của WCAG 2.2 tiêu chí 1.4.3. */
        --text:          #e8eef4;
        --text-secondary:#c2ccd7;
        --text-muted:    #9aa9b8;
        --text-faint:    #7d8b9a;
        --text-inverse:  #05080b;

        --border:        #262e37;
        --border-strong: #3c4854;
        --border-focus:  #6cb0ff;

        /* Màu nhấn 6,1:1 trên thẻ và 7,4:1 trên nền đen. */
        --accent:        #4c9aff;
        --accent-hover:  #85b8ff;
        --accent-soft:   #0b2f5e;
        --accent-text:   #8fc2ff;
        --accent-contrast: #04070a;

        --success:       #57d9a3;
        --success-soft:  #10402d;
        --warning:       #f8d477;
        --warning-soft:  #40310a;
        --danger:        #ff8f85;
        --danger-soft:   #4d1a16;
        --info:          #6cb0ff;
        --info-soft:     #0b2f5e;

        /*
         * Bốn màu chữ trạng thái. Nhánh này TỪNG THIẾU chúng, và đó là một lỗi
         * thật, không phải chuyện thẩm mỹ.
         *
         * Nhánh `[data-theme="dark"]` bên dưới có đủ, nên ai bấm nút lật sang
         * tối thì đúng màu. Nhưng chế độ mặc định là "theo hệ thống", và ở chế
         * độ đó chỉ nhánh `@media` này chạy: máy để tối mà chưa ai bấm gì thì
         * `--warning-text` vẫn là `#a35200` của bản sáng, đặt trên nền
         * `#40310a` — đo được 1,84:1, trong khi WCAG 2.2 đòi 4,5:1. Ba màu kia
         * cùng cảnh.
         *
         * Cùng giá trị với nhánh `[data-theme="dark"]`: bản tối đo được 5,87:1
         * tới 8,83:1 nên chữ dùng thẳng màu gốc.
         */
        --success-text:  #57d9a3;
        --warning-text:  #f8d477;
        --danger-text:   #ff8f85;
        --info-text:     #6cb0ff;

        /* Khối trình diễn tối phải sáng hơn nền đen, không thì chìm mất. */
        --graphite:      #10203a;
        --graphite-2:    #17365f;
        --graphite-ink:  #cce0ff;

        /* Trên nền đen, bóng đổ vô hình. Thay bằng một viền sáng mờ dày một
           điểm ảnh — đó mới là thứ tách được thẻ ra khỏi nền. */
        --shadow-sm:  0 0 0 1px rgba(255, 255, 255, .04);
        --shadow:     0 1px 3px rgba(0, 0, 0, .9), 0 0 0 1px rgba(255, 255, 255, .05);
        --shadow-md:  0 6px 16px -4px rgba(0, 0, 0, .95), 0 0 0 1px rgba(255, 255, 255, .06);
        --shadow-lg:  0 16px 40px -12px rgba(0, 0, 0, 1), 0 0 0 1px rgba(255, 255, 255, .08);
        --lift: 0 8px 22px -8px rgba(0, 0, 0, 1), 0 0 0 1px rgba(255, 255, 255, .09);

    }
}

:root[data-theme="dark"] {
    color-scheme: dark;

    /* Nền đen hẳn. Thẻ và thanh bên mang tone sáng hơn để nổi lên khỏi nền
       — trên nền đen, bóng đổ gần như không nhìn thấy nên việc phân lớp
       phải do chính màu nền của thẻ và một viền sáng mờ đảm nhiệm. */
    --bg:            #000000;
    --bg-subtle:     #0e1216;
    --bg-muted:      #232b34;
    --bg-inset:      #2a333d;
    --surface:       #171d24;
    --surface-hover: #1e252d;
    --overlay:       rgba(0, 0, 0, .78);

    /* Chữ sáng hơn bản trước một bậc. Đo trên nền thẻ #171d24:
       thân bài 15,0:1 · chữ phụ 7,3:1 · chữ mờ 5,0:1 — cả ba đều vượt
       ngưỡng 4,5:1 của WCAG 2.2 tiêu chí 1.4.3. */
    --text:          #e8eef4;
    --text-secondary:#c2ccd7;
    --text-muted:    #9aa9b8;
    --text-faint:    #7d8b9a;
    --text-inverse:  #05080b;

    --border:        #262e37;
    --border-strong: #3c4854;
    --border-focus:  #6cb0ff;

    /* Màu nhấn 6,1:1 trên thẻ và 7,4:1 trên nền đen. */
    --accent:        #4c9aff;
    --accent-hover:  #85b8ff;
    --accent-soft:   #0b2f5e;
    --accent-text:   #8fc2ff;
    --accent-contrast: #04070a;

    --success:       #57d9a3;
    --success-soft:  #10402d;
    --warning:       #f8d477;
    --warning-soft:  #40310a;
    --danger:        #ff8f85;
    --danger-soft:   #4d1a16;
    --info:          #6cb0ff;
    --info-soft:     #0b2f5e;

    /* Bản tối đo được 5,87:1 tới 8,83:1 nên chữ dùng thẳng màu gốc. */
    --success-text:  #57d9a3;
    --warning-text:  #f8d477;
    --danger-text:   #ff8f85;
    --info-text:     #6cb0ff;

    /* Khối trình diễn tối phải sáng hơn nền đen, không thì chìm mất. */
    --graphite:      #10203a;
    --graphite-2:    #17365f;
    --graphite-ink:  #cce0ff;

    /* Trên nền đen, bóng đổ vô hình. Thay bằng một viền sáng mờ dày một
       điểm ảnh — đó mới là thứ tách được thẻ ra khỏi nền. */
    --shadow-sm:  0 0 0 1px rgba(255, 255, 255, .04);
    --shadow:     0 1px 3px rgba(0, 0, 0, .9), 0 0 0 1px rgba(255, 255, 255, .05);
    --shadow-md:  0 6px 16px -4px rgba(0, 0, 0, .95), 0 0 0 1px rgba(255, 255, 255, .06);
    --shadow-lg:  0 16px 40px -12px rgba(0, 0, 0, 1), 0 0 0 1px rgba(255, 255, 255, .08);
    --lift: 0 8px 22px -8px rgba(0, 0, 0, 1), 0 0 0 1px rgba(255, 255, 255, .09);

}

/* --------------------------------------------------------------- nền tảng */

*, *::before, *::after { box-sizing: border-box; }

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

body {
    margin: 0;
    min-height: 100vh;
    background: var(--bg);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: 15px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/*
 * Tiêu đề: đậm hơn thân bài hẳn một bậc, không phải nhỉnh hơn một chút.
 *
 * Segoe UI vốn hẹp ngang, siết chữ mạnh như trước (-.035em) làm chữ dính vào
 * nhau; đổi sang siết nhẹ và tăng độ đậm — chữ to rõ mà vẫn thoáng.
 */
h1, h2, h3, h4, h5 {
    margin: 0 0 .5em;
    font-family: var(--font-display);
    line-height: 1.2;
    font-weight: var(--weight-bold);
    font-style: normal;
    letter-spacing: -.015em;
    color: var(--text);
    overflow-wrap: anywhere;
    min-width: 0;
}
h1 { font-size: clamp(1.9rem, 1.4rem + 2.1vw, 2.9rem); letter-spacing: -.022em; line-height: 1.14; }
h2 {
    font-size: clamp(1.2rem, 1.1rem + .35vw, 1.4rem);   /* 19px → 22px */
    font-weight: var(--weight-semibold);
    letter-spacing: -.012em;
}
h3 { font-size: 1.06rem; font-weight: var(--weight-semibold); letter-spacing: -.008em; }
h4 { font-size: .95rem; font-weight: var(--weight-semibold); letter-spacing: 0; }

p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a {
    color: var(--accent-text);
    text-decoration: none;
    transition: color var(--transition);
}
a:hover { color: var(--accent-hover); }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: .875em; }

code:not(.hljs) {
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    padding: .12em .38em;
    color: var(--text-secondary);
}

pre {
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    padding: 14px 16px;
    overflow-x: auto;
    margin: 0 0 1em;
    line-height: 1.55;
}
pre code { background: none; border: none; padding: 0; color: var(--text-secondary); }

hr { border: none; border-top: 1px solid;
 border-top-color: var(--border); margin: 28px 0; }

img, svg { max-width: 100%; }

::selection { background: var(--accent); color: var(--accent-contrast); }

:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Thanh cuộn mảnh, hợp với cả hai chế độ màu */
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--border-strong);
    border-radius: var(--radius-full);
    border: 3px solid;
    border-color: var(--bg);
}

/* --------------------------------------------------------------- bố cục */

.container {
    width: 100%;
    max-width: var(--content-width);
    margin-inline: auto;
    padding-inline: 20px;
}
.container-narrow { max-width: 780px; }
.container-wide { max-width: 1420px; }

/* padding-block chứ không phải padding viết tắt: viết tắt sẽ đặt luôn lề trái
   phải về 0, đè mất padding-inline của .container và làm nội dung dính sát mép
   màn hình ở dải 481–1099px. */
.page { padding-block: 32px 72px; }
.page-header { margin-bottom: 28px; }
.page-header h1 { margin-bottom: 8px; }
.page-header .lead { color: var(--text-muted); font-size: 1.03rem; max-width: 70ch; }

.grid { display: grid; gap: 20px; }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-sidebar { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
.grid-sidebar-left { grid-template-columns: 240px minmax(0, 1fr); align-items: start; }

/* Thẻ nằm cùng một hàng thì kéo cao bằng nhau, tránh cảnh so le. */
.grid-2 > .card,
.grid-3 > .card,
.grid-4 > .card,
.feature-grid > .feature,
.grid-2 > .stack,
.grid-3 > .stack { height: 100%; }

/* Cột phụ bám theo khi cuộn, miễn là nó không cao hơn màn hình. */
.grid-sidebar > aside {
    position: sticky;
    top: calc(var(--header-height) + 18px);
}
@media (max-height: 720px) {
    .grid-sidebar > aside { position: static; }
}

/**
 * Bố cục cho các công cụ tra cứu: cột biểu mẫu cố định bên trái, khung kết quả
 * chiếm phần còn lại. Cột trái đủ rộng cho danh sách tuỳ chọn nhiều cột.
 */
.tool-layout {
    display: grid;
    grid-template-columns: 340px minmax(0, 1fr);
    gap: 20px;
    /* Hai cột cao bằng nhau: biểu mẫu dài thì khung kết quả giãn theo, và ngược lại. */
    align-items: stretch;
}
.tool-layout > * { min-width: 0; }
.tool-layout .result-panel { height: 100%; }

@media (max-width: 900px) {
    .tool-layout { grid-template-columns: minmax(0, 1fr); }
    .tool-layout .result-panel { height: auto; }
}

/**
 * Cột phụ trong một thẻ: ngăn cách bằng vạch dọc khi nằm cạnh nhau, và đổi
 * thành vạch ngang khi màn hình hẹp làm hai cột xếp chồng lên nhau.
 */
.info-split {
    border-left: 1px solid;
    border-left-color: var(--border);
    padding-left: 20px;
}
@media (max-width: 1024px) {
    .info-split {
        border-left: none;
        border-top: 1px solid;
        border-top-color: var(--border);
        padding-left: 0;
        padding-top: 14px;
    }
}

/* Danh sách cổng dài thì cuộn trong khung thay vì kéo dài cả trang. */
.option-scroll {
    max-height: 232px;
    overflow-y: auto;
    padding: 10px 12px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    background: var(--bg-subtle);
}

/* Nội dung bên trong khu tài khoản: layout đã lo phần container. */
.account-page { min-width: 0; }
.account-page-narrow { max-width: 820px; }

/* Khoảng cách chuẩn giữa các khối lớn trong một trang. */
.section + .section { margin-top: 40px; }
.section-title { margin-bottom: 18px; }
.section-title h2 { margin-bottom: 4px; }
.section-title p { color: var(--text-muted); font-size: .92rem; margin: 0; }

.stack { display: flex; flex-direction: column; gap: 16px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.row-tight { gap: 8px; }
.row-between { justify-content: space-between; }
.row-end { justify-content: flex-end; }
.spacer { flex: 1 1 auto; }

/* --------------------------------------------------------------- thanh trên */

.site-header {
    position: sticky;
    top: 0;
    z-index: 50;
    height: var(--header-height);
    display: flex;
    align-items: center;
    background: color-mix(in srgb, var(--bg) 85%, transparent);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.site-header .container { display: flex; align-items: center; gap: 20px; }

.brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-weight: var(--weight-bold);
    font-size: 1.02rem;
    letter-spacing: -.02em;
    color: var(--text);
    white-space: nowrap;
}
.brand:hover { color: var(--text); }
/*
 * Ô logo. Trước đây đây là một khối nền chuyển màu với ký tự 🔐 đặt giữa; nay
 * nội dung là tệp SVG thật (`partials/logo.php`), nên KHÔNG còn nền riêng nữa:
 * SVG có góc bo trong suốt, để lại nền chuyển màu thì bốn góc lòi ra bốn mẩu
 * màu tím.
 */
.brand-mark {
    width: 30px; height: 30px;
    display: block;
    border-radius: 9px;
    box-shadow: var(--shadow-sm);
}

.main-nav { display: flex; gap: 2px; align-items: center; }
.main-nav a {
    padding: 7px 12px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: .9rem;
    font-weight: var(--weight-normal);
    transition: background var(--transition), color var(--transition);
}
.main-nav a:hover { background: var(--bg-muted); color: var(--text); }
.main-nav a.is-active { background: var(--bg-muted); color: var(--text); font-weight: var(--weight-semibold); }

.header-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; min-width: 0; }
.header-auth { display: flex; align-items: center; gap: 8px; }

/* Chỉ dùng trong menu bung ra ở màn hình hẹp. */
.nav-auth { display: none; }

.nav-toggle {
    display: none;
    width: 38px; height: 38px;
    align-items: center; justify-content: center;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
}

/* --------------------------------------------------------------- nút */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 9px 16px;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-contrast);
    font-family: inherit;
    font-size: .9rem;
    font-weight: var(--weight-semibold);
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--transition), border-color var(--transition),
                color var(--transition), transform var(--transition), box-shadow var(--transition);
}
.btn:hover { background: var(--accent-hover); color: var(--accent-contrast); }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn.is-loading { opacity: .6; cursor: not-allowed; transform: none; }

.btn-secondary {
    background: var(--surface);
    border-color: var(--border-strong);
    color: var(--text);
    box-shadow: var(--shadow-sm);
}
.btn-secondary:hover { background: var(--bg-muted); color: var(--text); border-color: var(--border-strong); }

.btn-ghost { background: transparent; color: var(--text-secondary); }
.btn-ghost:hover { background: var(--bg-muted); color: var(--text); }

.btn-danger { background: var(--danger); }
.btn-danger:hover { background: color-mix(in srgb, var(--danger) 85%, black); }

.btn-danger-soft { background: var(--danger-soft); color: var(--danger-text); border-color: transparent; }
.btn-danger-soft:hover { background: color-mix(in srgb, var(--danger-soft) 80%, var(--danger)); color: var(--danger); }

.btn-sm { padding: 5px 10px; font-size: .82rem; border-radius: var(--radius-sm); gap: 5px; }
.btn-lg { padding: 12px 22px; font-size: 1rem; border-radius: var(--radius-md); }
.btn-block { width: 100%; }
.btn-icon { padding: 8px; width: 36px; height: 36px; }
.btn-icon.btn-sm { width: 30px; height: 30px; padding: 5px; }

.btn .spinner { display: none; }
.btn.is-loading .spinner { display: inline-block; }
.btn.is-loading .btn-label { opacity: .7; }

.spinner {
    width: 14px; height: 14px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* --------------------------------------------------------------- thẻ */

.card {
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    padding: 22px;
    box-shadow: var(--shadow-sm);
}
.card-flush { padding: 0; overflow: hidden; }
.card-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}
.card-header h2, .card-header h3 { margin: 0; font-size: 1rem; }
.card-body { padding: 20px; }
.card-footer {
    padding: 14px 20px;
    border-top: 1px solid;
    border-top-color: var(--border);
    background: var(--bg-subtle);
}
.card-title { display: flex; align-items: center; gap: 9px; margin-bottom: 4px; font-size: 1.05rem; font-weight: var(--weight-semibold); }
.card-hint { color: var(--text-muted); font-size: .875rem; margin-bottom: 18px; }

/* Thẻ công cụ chính. Trước đây có một thanh gradient cầu vồng chạy trên đỉnh
   khi hover — ba màu ấy phạm nguyên tắc "chỉ một màu nhấn" của bộ này, và giờ
   đã có hiệu ứng nhấc + viền nhấn thống nhất nên bỏ hẳn. */
.tool-card { position: relative; }

/* --------------------------------------------------------------- biểu mẫu */

.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }

label, .label {
    display: block;
    margin-bottom: 6px;
    font-size: .875rem;
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
}
.label-hint { font-weight: var(--weight-normal); color: var(--text-faint); font-size: .82rem; }

input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="search"], input[type="url"], input[type="date"], input[type="datetime-local"],
select, textarea {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid;
    border-color: var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: .92rem;
    line-height: 1.5;
    transition: border-color var(--transition), box-shadow var(--transition);
}
input:focus, select:focus, textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:disabled, select:disabled, textarea:disabled { background: var(--bg-muted); color: var(--text-muted); cursor: not-allowed; }

textarea { resize: vertical; min-height: 110px; font-family: var(--font-mono); font-size: .875rem; }
textarea.textarea-plain { font-family: inherit; font-size: .92rem; }

select {
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2371717a' d='M6 8.5 2.5 5h7z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 11px center;
    padding-right: 32px;
}

.input-group { display: flex; gap: 8px; }
.input-group > input, .input-group > select { flex: 1 1 auto; min-width: 0; }

.checkbox, .radio {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin-bottom: 0;
    font-weight: var(--weight-normal);
    color: var(--text-secondary);
    cursor: pointer;
    font-size: .9rem;
    line-height: 1.45;
}
.checkbox input, .radio input {
    width: 16px; height: 16px;
    margin: 2px 0 0;
    accent-color: var(--accent);
    cursor: pointer;
    flex: none;
}
.checkbox-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 10px 18px; }

input[type="range"] {
    width: 100%;
    height: 5px;
    appearance: none;
    background: var(--bg-inset);
    border-radius: var(--radius-full);
    outline: none;
    cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
    appearance: none;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid;
    border-color: var(--surface);
    box-shadow: var(--shadow);
    cursor: grab;
}
input[type="range"]::-moz-range-thumb {
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid;
    border-color: var(--surface);
    box-shadow: var(--shadow);
    cursor: grab;
}

.field-error { color: var(--danger); font-size: .82rem; margin-top: 5px; }
.field-hint { color: var(--text-muted); font-size: .82rem; margin-top: 5px; }

/* --------------------------------------------------------------- tab */

.tabs {
    display: flex;
    gap: 3px;
    padding: 3px;
    background: var(--bg-muted);
    border-radius: var(--radius);
    margin-bottom: 20px;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
}
.tabs::-webkit-scrollbar { display: none; }

.tab {
    flex: 1 1 auto;
    min-width: 0;
    padding: 8px 14px;
    border: none;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font-family: inherit;
    font-size: .88rem;
    font-weight: var(--weight-semibold);
    line-height: 1.35;
    cursor: pointer;
    white-space: nowrap;
    scroll-snap-align: start;
    transition: background var(--transition), color var(--transition);
}
.tab:hover { color: var(--text); }
.tab.is-active {
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-sm);
}
.tab-panel { display: none; }
.tab-panel.is-active { display: block; animation: fade-in .18s ease; }

@keyframes fade-in { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

/* --------------------------------------------------------------- mã 2FA */

.code-display {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding: 26px 20px;
    background: linear-gradient(160deg, var(--accent-soft), transparent 70%), var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    text-align: center;
}

.code-value {
    display: flex;
    align-items: center;
    gap: 4px;
    font-family: var(--font-mono);
    font-size: clamp(2.1rem, 1.4rem + 3.4vw, 3.4rem);
    font-weight: var(--weight-bold);
    letter-spacing: .06em;
    line-height: 1;
    color: var(--text);
    cursor: pointer;
    user-select: all;
    transition: color var(--transition);
}
.code-value:hover { color: var(--accent-text); }
.code-value .digit-group { display: inline-flex; }
.code-value .digit-gap { width: .35em; }

.code-meta { display: flex; align-items: center; gap: 14px; font-size: .85rem; color: var(--text-muted); flex-wrap: wrap; justify-content: center; }

.code-timer { display: inline-flex; align-items: center; gap: 8px; }
.timer-ring { width: 30px; height: 30px; transform: rotate(-90deg); }
.timer-ring circle { fill: none; stroke-width: 3; }
.timer-ring .track { stroke: var(--bg-inset); }
.timer-ring .progress {
    stroke: var(--accent);
    stroke-linecap: round;
    transition: stroke-dashoffset 1s linear, stroke .3s;
}
.timer-ring.is-expiring .progress { stroke: var(--danger); }
.timer-seconds { font-variant-numeric: tabular-nums; font-weight: var(--weight-semibold); min-width: 2.2em; text-align: left; }

.next-code {
    font-family: var(--font-mono);
    font-size: .9rem;
    color: var(--text-faint);
    letter-spacing: .05em;
}

/* --------------------------------------------------------------- vùng kéo thả */

.dropzone {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 210px;
    padding: 28px 20px;
    border: 2px dashed;
    border-color: var(--border-strong);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
    text-align: center;
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition), transform var(--transition);
}
.dropzone:hover { border-color: var(--accent); background: var(--accent-soft); }
.dropzone.is-dragover {
    border-color: var(--accent);
    background: var(--accent-soft);
    transform: scale(1.01);
}
.dropzone-icon { color: var(--text-faint); }
.dropzone:hover .dropzone-icon, .dropzone.is-dragover .dropzone-icon { color: var(--accent); }
.dropzone-title { font-weight: var(--weight-semibold); color: var(--text); }
.dropzone-hint { font-size: .85rem; color: var(--text-muted); max-width: 46ch; }
.dropzone input[type="file"] { display: none; }

.dropzone-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}

/* Đã có ảnh thì khung thả chuyển thành khung xem trước, không hiện lời mời nữa. */
.dropzone.has-image { padding: 14px; border-style: solid; background: var(--bg-muted); }
.dropzone.has-image .dropzone-empty { display: none; }

.image-preview {
    display: none;
    text-align: center;
}
.image-preview.is-visible { display: block; }
.image-preview img {
    max-height: 260px;
    max-width: 100%;
    border-radius: var(--radius-sm);
    background: #fff;
}
.image-preview-note {
    display: block;
    margin-top: 8px;
    font-size: .78rem;
    color: var(--text-muted);
}

/* --------------------------------------------------------------- ba bước ở trang chủ */

.how-step { position: relative; padding-left: 40px; }
.how-step-number {
    position: absolute;
    left: 0;
    top: 1px;
    width: 27px;
    height: 27px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent-text);
    font-size: .82rem;
    font-weight: var(--weight-bold);
}

/* --------------------------------------------------------------- bản đồ vị trí IP */

.map-figure {
    margin: 0;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--bg-muted);
}
.map-image {
    display: block;
    width: 100%;
    height: auto;
    max-height: 340px;
    object-fit: cover;
}
.map-caption {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    align-items: center;
    justify-content: space-between;
    padding: 9px 13px;
    border-top: 1px solid;
    border-top-color: var(--border);
    background: var(--surface);
    font-size: .8rem;
    color: var(--text-muted);
}
.map-links { display: inline-flex; gap: 12px; align-items: center; }

/* Icon nhỏ trước nhãn trong bảng kết quả. */
.th-icon { display: inline-flex; align-items: center; gap: 7px; }
.row-icon { color: var(--text-faint); flex: 0 0 auto; }
.flag { font-size: 1.05rem; line-height: 1; }

/* --------------------------------------------------------------- thông báo */

.alert {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 12px 15px;
    border: 1px solid transparent;
    border-radius: var(--radius);
    font-size: .9rem;
    margin-bottom: 16px;
}
.alert-icon { flex: none; margin-top: 1px; }
.alert-success { background: var(--success-soft); border-color: color-mix(in srgb, var(--success) 30%, transparent); color: color-mix(in srgb, var(--success) 80%, var(--text)); }
.alert-error   { background: var(--danger-soft);  border-color: color-mix(in srgb, var(--danger) 30%, transparent);  color: color-mix(in srgb, var(--danger) 80%, var(--text)); }
.alert-warning { background: var(--warning-soft); border-color: color-mix(in srgb, var(--warning) 30%, transparent); color: color-mix(in srgb, var(--warning) 80%, var(--text)); }
.alert-info    { background: var(--info-soft);    border-color: color-mix(in srgb, var(--info) 30%, transparent);    color: color-mix(in srgb, var(--info) 80%, var(--text)); }

.toast-container {
    position: fixed;
    bottom: 22px;
    right: 22px;
    z-index: 200;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(380px, calc(100vw - 44px));
}
.toast {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 15px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-left: 3px solid;
    border-left-color: var(--accent);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    font-size: .9rem;
    animation: toast-in .22s cubic-bezier(.2, .9, .3, 1.2);
}
.toast.is-leaving { animation: toast-out .18s ease forwards; }
.toast-success { border-left-color: var(--success); }
.toast-error { border-left-color: var(--danger); }
.toast-warning { border-left-color: var(--warning); }

@keyframes toast-in { from { opacity: 0; transform: translateX(24px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateX(24px); } }

/* --------------------------------------------------------------- nhãn nhỏ */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    color: var(--text-secondary);
    font-size: .75rem;
    font-weight: var(--weight-semibold);
    line-height: 1.6;
    white-space: nowrap;
}
.badge-accent  { background: var(--accent-soft);  border-color: transparent; color: var(--accent-text); }
.badge-success { background: var(--success-soft); border-color: transparent; color: var(--success-text); }
.badge-warning { background: var(--warning-soft); border-color: transparent; color: var(--warning-text); }
.badge-danger  { background: var(--danger-soft);  border-color: transparent; color: var(--danger-text); }
.badge-info    { background: var(--info-soft);    border-color: transparent; color: var(--info-text); }
.badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* --------------------------------------------------------------- bảng */

.table-wrap { overflow-x: auto; border-radius: var(--radius); }

table {
    width: 100%;
    border-collapse: collapse;
    font-size: .9rem;
}
th {
    padding: 10px 14px;
    text-align: left;
    font-weight: var(--weight-semibold);
    font-size: .78rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-muted);
    background: var(--bg-subtle);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    white-space: nowrap;
}
td {
    padding: 11px 14px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    color: var(--text-secondary);
    vertical-align: middle;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr { transition: background var(--transition); }
tbody tr:hover { background: var(--surface-hover); }
td.numeric, th.numeric { text-align: right; font-variant-numeric: tabular-nums; }
td.mono { font-family: var(--font-mono); font-size: .85rem; }

/* --------------------------------------------------------------- thống kê */

.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; }
.stat {
    padding: 16px 18px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}
.stat-label { font-size: .8rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: .05em; font-weight: var(--weight-semibold); }
.stat-value { font-size: 1.85rem; font-weight: var(--weight-bold); letter-spacing: -.02em; line-height: 1.15; margin-top: 6px; font-variant-numeric: tabular-nums; }
.stat-hint { font-size: .82rem; color: var(--text-faint); margin-top: 2px; }

.chart-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}

/* Cột giờ là một khối dọc: con số trên đỉnh, thanh ở giữa, ngày dưới chân. */
.bar-chart { display: flex; align-items: flex-end; gap: 5px; height: 168px; padding-top: 10px; }

.bar-col {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    height: 100%;
    min-width: 0;
}

.bar-value {
    font-size: .74rem;
    font-weight: 600;
    color: var(--text-secondary);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

/* Ngày không có lượt nào vẫn in số 0, nhưng in nhạt để mắt lướt qua. */
.bar-value.is-zero { color: var(--text-faint); font-weight: 400; }

.bar-day {
    font-size: .68rem;
    color: var(--text-faint);
    line-height: 1;
    white-space: nowrap;
}
.bar-chart .bar {
    width: 100%;
    min-height: 2px;
    background: linear-gradient(180deg, var(--accent) 0%, color-mix(in srgb, var(--accent) 55%, transparent) 100%);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    transition: opacity var(--transition);
}
.bar-chart .bar:hover { opacity: .8; }


/* --------------------------------------------------------------- độ mạnh mật khẩu */

.strength-meter { display: flex; gap: 4px; margin: 10px 0 6px; }
.strength-meter .segment {
    height: 5px;
    flex: 1;
    background: var(--bg-inset);
    border-radius: var(--radius-full);
    transition: background var(--transition);
}
.strength-0 .segment:nth-child(-n+1) { background: var(--danger); }
.strength-1 .segment:nth-child(-n+2) { background: #f97316; }
.strength-2 .segment:nth-child(-n+3) { background: var(--warning); }
.strength-3 .segment:nth-child(-n+4) { background: #84cc16; }
.strength-4 .segment { background: var(--success); }

.password-output {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: 1.02rem;
    word-break: break-all;
    transition: border-color var(--transition);
}
.password-output:hover { border-color: var(--border-strong); }
.password-output .value { flex: 1 1 auto; min-width: 0; }
.password-list { display: flex; flex-direction: column; gap: 8px; }

/* --------------------------------------------------------------- danh sách kết quả */

.result-list { display: flex; flex-direction: column; gap: 10px; }

.result-item {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 13px 16px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    transition: border-color var(--transition), box-shadow var(--transition);
}
.result-item:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.result-item .item-main { flex: 1 1 auto; min-width: 0; }
.result-item .item-title { font-weight: var(--weight-semibold); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result-item .item-sub { font-size: .82rem; color: var(--text-muted); }
.result-item .item-code {
    font-family: var(--font-mono);
    font-size: 1.25rem;
    font-weight: var(--weight-bold);
    letter-spacing: .05em;
    color: var(--text);
    cursor: pointer;
}
.result-item .item-code:hover { color: var(--accent-text); }

.avatar {
    width: 38px; height: 38px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-text);
    font-weight: var(--weight-bold);
    font-size: .95rem;
    text-transform: uppercase;
}

.empty-state {
    padding: 44px 24px;
    text-align: center;
    color: var(--text-muted);
}
.empty-state .icon { color: var(--text-faint); margin-bottom: 12px; }
.empty-state h3 { color: var(--text-secondary); font-size: 1rem; margin-bottom: 6px; }
.empty-state p { font-size: .9rem; max-width: 40ch; margin-inline: auto; }

/**
 * Khung kết quả cho các công cụ tra cứu: giữ chiều cao tối thiểu để bố cục
 * không nhảy khi đổi tab hay khi kết quả dài ngắn khác nhau.
 */
.result-panel {
    display: flex;
    flex-direction: column;
    min-height: 260px;
    padding: 18px 20px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
}
.result-panel > :first-child { margin-top: 0; }
.result-panel .empty-state { margin: auto; padding: 20px; }
.result-panel pre { background: var(--surface); margin-bottom: 0; }
.result-panel .table-wrap { background: var(--surface); border: 1px solid;
 border-color: var(--border); }

/* --------------------------------------------------------------- cửa sổ nổi */

.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 150;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: var(--overlay);
    backdrop-filter: blur(3px);
}
.modal-backdrop.is-open { display: flex; animation: fade-in .15s ease; }

.modal {
    width: 100%;
    max-width: 520px;
    max-height: calc(100vh - 40px);
    overflow-y: auto;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    animation: modal-in .2s cubic-bezier(.2, .9, .3, 1.1);
}
.modal-lg { max-width: 720px; }
@keyframes modal-in { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }

.modal-header {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 18px 22px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}
.modal-header h3 { margin: 0; flex: 1 1 auto; }
.modal-body { padding: 22px; }
.modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 16px 22px;
    border-top: 1px solid;
    border-top-color: var(--border);
    background: var(--bg-subtle);
}

/* --------------------------------------------------------------- menu thả xuống */

.dropdown { position: relative; }
.dropdown-menu {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 60;
    display: none;
    min-width: 190px;
    padding: 5px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}
.dropdown-menu.is-open { display: block; animation: fade-in .13s ease; }
.dropdown-menu a, .dropdown-menu button {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 7px 10px;
    border: none;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: .88rem;
    text-align: left;
    cursor: pointer;
    transition: background var(--transition), color var(--transition);
}
.dropdown-menu a:hover, .dropdown-menu button:hover { background: var(--bg-muted); color: var(--text); }
.dropdown-menu .divider { height: 1px; background: var(--border); margin: 5px 0; }
.dropdown-menu .is-current { color: var(--accent-text); font-weight: var(--weight-semibold); }

/* --------------------------------------------------------------- trang chủ */

.hero { padding: 40px 0 30px; text-align: center; }
.hero h1 { max-width: 20ch; margin-inline: auto; }
.hero .lead {
    max-width: 58ch;
    margin: 0 auto;
    font-size: 1.05rem;
    color: var(--text-muted);
}
.hero-badges {
    display: flex;
    gap: 7px;
    justify-content: center;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

/*
 * Bốn thẻ công cụ. Số cột đặt tường minh chứ không dùng auto-fit: auto-fit cho ra
 * 3 cột ở khoảng 900–1024px, thành hàng 3 + 1 thẻ lẻ loi. 4 → 2 → 1 cột thì hàng
 * nào cũng chia đều.
 */
.feature-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
@media (max-width: 999px) { .feature-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .feature-grid { grid-template-columns: minmax(0, 1fr); } }
.feature {
    display: block;
    padding: 20px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    color: inherit;
    transition: border-color var(--transition), box-shadow var(--transition), transform var(--transition);
}
.feature:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
    color: inherit;
}
.feature-icon {
    width: 38px; height: 38px;
    display: grid; place-items: center;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-text);
    margin-bottom: 13px;
}
.feature h3 { font-size: 1rem; margin-bottom: 5px; }
.feature p { font-size: .88rem; color: var(--text-muted); margin: 0; }

/* --------------------------------------------------------------- thanh bên quản trị */

.admin-layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 26px; align-items: start; }

.admin-nav {
    position: sticky;
    top: calc(var(--header-height) + 20px);
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}
.admin-nav a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 11px;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    font-size: .89rem;
    font-weight: var(--weight-normal);
    transition: background var(--transition), color var(--transition);
}
.admin-nav a:hover { background: var(--bg-muted); color: var(--text); }
.admin-nav a.is-active { background: var(--accent-soft); color: var(--accent-text); font-weight: var(--weight-semibold); }
.admin-nav .nav-group-title {
    padding: 12px 11px 5px;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-faint);
    font-weight: var(--weight-semibold);
}

/* --------------------------------------------------------------- chân trang */

.site-footer {
    margin-top: auto;
    padding: 34px 0;
    border-top: 1px solid;
    border-top-color: var(--border);
    background: var(--bg-subtle);
    font-size: .875rem;
    color: var(--text-muted);
}
.footer-grid { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 28px; margin-bottom: 26px; }
.footer-grid h2 { font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); margin-bottom: 10px; }
.footer-grid ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.footer-grid a { color: var(--text-muted); }
.footer-grid a:hover { color: var(--text); }
.footer-bottom { display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap; padding-top: 18px; border-top: 1px solid;
 border-top-color: var(--border); }

.page-wrapper { display: flex; flex-direction: column; min-height: 100vh; }
.page-wrapper > main { flex: 1 0 auto; }

/* --------------------------------------------------------------- trang đăng nhập */

.auth-shell {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 30px 20px;
    background:
        radial-gradient(1000px 500px at 50% -10%, var(--accent-soft), transparent 65%),
        var(--bg);
}
.auth-card {
    width: 100%;
    max-width: 420px;
    padding: 30px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
}
.auth-card .brand { justify-content: center; margin-bottom: 20px; font-size: 1.06rem; }
.auth-card h1 { font-size: 1.4rem; text-align: center; margin-bottom: 6px; }
.auth-card .subtitle { text-align: center; color: var(--text-muted); font-size: .9rem; margin-bottom: 24px; }
.auth-footer { text-align: center; margin-top: 20px; font-size: .88rem; color: var(--text-muted); }

/* --------------------------------------------------------------- tiện ích */

.text-muted { color: var(--text-muted); }
.text-faint { color: var(--text-faint); }
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.text-warning { color: var(--warning); }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-sm { font-size: .875rem; }
.text-xs { font-size: .8rem; }
.text-lg { font-size: 1.08rem; }
.mono { font-family: var(--font-mono); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.break-all { word-break: break-all; }
.nowrap { white-space: nowrap; }
.uppercase { text-transform: uppercase; letter-spacing: .05em; }
.font-semibold { font-weight: var(--weight-semibold); }

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 6px; }
.mt-2 { margin-top: 12px; }
.mt-3 { margin-top: 18px; }
.mt-4 { margin-top: 26px; }
.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: 6px; }
.mb-2 { margin-bottom: 12px; }
.mb-3 { margin-bottom: 18px; }
.mb-4 { margin-bottom: 26px; }

.hidden { display: none !important; }
.w-full { width: 100%; }
.flex-1 { flex: 1 1 auto; min-width: 0; }

.divider-label {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 20px 0;
    color: var(--text-faint);
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .06em;
}
.divider-label::before, .divider-label::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border);
}

.copyable { cursor: pointer; position: relative; }
.copyable::after {
    content: attr(data-copied-label);
    position: absolute;
    top: -26px;
    left: 50%;
    transform: translateX(-50%);
    padding: 3px 8px;
    background: var(--text);
    color: var(--bg);
    border-radius: var(--radius-sm);
    font-size: .72rem;
    font-family: var(--font-sans);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition);
    white-space: nowrap;
}
.copyable.just-copied::after { opacity: 1; }

/* Khối vừa được cuộn tới: viền sáng lên rồi tắt, để mắt bắt được chỗ đã đổi. */
.just-revealed {
    animation: reveal-pulse 1.3s ease-out;
}
@keyframes reveal-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(99, 102, 241, .45); }
    35%  { box-shadow: 0 0 0 4px rgba(99, 102, 241, .28); }
    100% { box-shadow: 0 0 0 0 rgba(99, 102, 241, 0); }
}
@media (prefers-reduced-motion: reduce) {
    .just-revealed { animation: none; }
}

.kbd {
    display: inline-block;
    padding: 1px 6px;
    border: 1px solid;
    border-color: var(--border-strong);
    border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--bg-muted);
    font-family: var(--font-mono);
    font-size: .78rem;
    color: var(--text-secondary);
}

.qr-frame {
    display: inline-block;
    padding: 14px;
    background: #ffffff;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    line-height: 0;
}

.scroll-box { max-height: 380px; overflow-y: auto; }

/* --------------------------------------------------------------- màn hình nhỏ */

@media (max-width: 1024px) {
    .grid-sidebar { grid-template-columns: minmax(0, 1fr); }
    .grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid-sidebar > aside { position: static; }
    .footer-grid { grid-template-columns: repeat(2, 1fr); }
    .section + .section { margin-top: 32px; }
}

/*
 * Danh sách dọc bên trái phải sống tới khá hẹp mới thu gọn: cửa sổ 900–1024px
 * vẫn là màn hình máy tính (Windows phóng 125–150% làm 1366px thật chỉ còn
 * ~910–1090px CSS), và người dùng muốn thấy danh sách bên trái ở trình duyệt.
 * Trong khoảng đó chỉ bóp bề rộng thanh bên, không đổi sang hàng ngang.
 */
/*
 * Khoảng 769–1024px: vẫn là bố cục máy tính nhưng hàng điều hướng trên cùng
 * chật (7 mục + đổi giao diện + ngôn ngữ + menu tài khoản), trước đây làm nút
 * tài khoản chìa ra ngoài mép phải. Thu nhỏ chữ và khoảng đệm thay vì gập menu.
 */
/*
 * Khoảng 861–1024px vẫn là bố cục máy tính, nhưng hàng điều hướng trên cùng
 * chật (7 mục + đổi giao diện + ngôn ngữ + menu tài khoản) nên thu nhỏ chữ và
 * khoảng đệm; thanh bên trái chỉ hẹp lại chứ không gập.
 */
@media (max-width: 1024px) and (min-width: 861px) {
    .site-header .container { gap: 12px; }
    .main-nav { min-width: 0; flex-wrap: nowrap; }
    .main-nav a { padding: 7px 8px; font-size: .83rem; white-space: nowrap; }
    .header-actions { gap: 5px; }
    .header-actions .btn-sm { padding: 6px 9px; font-size: .8rem; }
    .header-actions .truncate { max-width: 72px; }

    .grid-sidebar-left,
    .admin-layout { grid-template-columns: 196px minmax(0, 1fr); gap: 18px; }
    .admin-nav { padding: 8px; }
    .admin-nav a { padding: 7px 9px; font-size: .85rem; gap: 8px; }
    .admin-nav .nav-group-title { padding: 10px 9px 4px; }
}

/*
 * Từ 860px trở xuống là bố cục gọn: menu chính thu vào nút ba gạch, thanh bên
 * của khu tài khoản/quản trị nằm ngang. Ngưỡng đặt ở 860 chứ không phải 768 vì
 * bảy mục điều hướng cộng menu tài khoản không vừa một hàng ở 800px.
 */
/* Ngưỡng này phải KHỚP với ngưỡng hiện thanh bên (900px). Lệch nhau là có một
   dải bề ngang không có thanh bên mà cũng chưa thu gọn thanh trên, và mọi mục
   chen nhau tràn ra ngoài: đo được dải 861-899px tràn 42px. */
@media (max-width: 899px) {
    .grid-sidebar-left,
    .admin-layout { grid-template-columns: minmax(0, 1fr); }
    .admin-nav { position: static; flex-direction: row; flex-wrap: wrap; }
    .admin-nav .nav-group-title { display: none; }

    .nav-toggle { display: inline-flex; }
    .main-nav {
        position: fixed;
        inset: var(--header-height) 0 auto;
        flex-direction: column;
        align-items: stretch;
        gap: 2px;
        padding: 12px;
        background: var(--surface);
        border-bottom: 1px solid;
        border-bottom-color: var(--border);
        box-shadow: var(--shadow-lg);
        display: none;
        max-height: calc(100vh - var(--header-height));
        overflow-y: auto;
    }
    .main-nav.is-open { display: flex; }

    /* Mỗi mục có biểu tượng đứng trước, giống hệt thanh bên của màn rộng: hai
       bên đọc chung `site_nav()` thì cũng phải trông như nhau. */
    .main-nav a {
        display: flex;
        align-items: center;
        gap: 9px;
        padding: 10px 12px;
        font-size: .95rem;
    }
    .main-nav a > svg { flex: 0 0 auto; color: var(--text-faint); }
    .main-nav a.is-active > svg { color: var(--accent); }

    .main-nav .nav-group-title { padding: 12px 12px 4px; }
    .main-nav .nav-group-title:first-child { padding-top: 2px; }

    /* Thanh tiêu đề chỉ còn logo, đổi giao diện, ngôn ngữ và nút menu;
       đăng nhập/đăng ký chuyển vào trong menu bung ra. */
    .header-auth { display: none; }
    .nav-auth {
        display: flex;
        flex-direction: column;
        gap: 8px;
        margin-top: 10px;
        padding-top: 12px;
        border-top: 1px solid;
        border-top-color: var(--border);
    }
    .site-header .container { gap: 10px; }
    .header-actions { gap: 4px; }
    .brand { font-size: .96rem; min-width: 0; }
    .brand > span:not(.brand-mark) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* Máy rất hẹp (320px): bỏ chữ mã ngôn ngữ, chỉ để lại cờ. */
@media (max-width: 360px) {
    .header-actions .btn-sm .uppercase { display: none; }
    .header-actions { gap: 2px; }
    .brand-mark { width: 26px; height: 26px; }
}

@media (max-width: 768px) {
    body { font-size: 14.5px; }
    .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0, 1fr); }
    .grid { gap: 16px; }
    .page { padding-block: 20px 48px; }

    .hero { padding: 22px 0 20px; }
    .hero .lead { font-size: .98rem; }
    .hero-badges { gap: 6px; margin-bottom: 14px; }
    .hero-badges .badge { font-size: .7rem; padding: 2px 7px; }

    /* Nhãn tab dài dễ bị cắt ở màn hình hẹp: thu nhỏ chữ và cho cuộn ngang. */
    .tabs { gap: 2px; }
    .tab { flex: 1 0 auto; padding: 7px 10px; font-size: .82rem; }

    .card { padding: 16px; }
    .card-header { padding: 14px 16px; flex-wrap: wrap; gap: 8px; }
    .card-header h2, .card-header h3 { flex: 1 1 auto; min-width: 0; font-size: .95rem; }
    .card-body { padding: 16px; }
    .card-footer { padding: 12px 16px; }

    /* Hàng tiêu đề có nút: cho nút xuống dòng thay vì ép tiêu đề vỡ chữ. */
    .page-header.row { flex-wrap: wrap; }
    .page-header.row > * { min-width: 0; }

    .code-display { padding: 20px 14px; }
    .result-panel { min-height: 200px; padding: 14px 16px; }

    .modal { max-width: 100%; }
    .modal-body { padding: 18px; }
    .modal-header, .modal-footer { padding: 14px 18px; }

    .toast-container { left: 14px; right: 14px; bottom: 14px; max-width: none; }
    .footer-grid { grid-template-columns: 1fr; gap: 20px; }
    .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
    .stat { padding: 13px 14px; }
    .stat-value { font-size: 1.5rem; }

    /* Bảng rộng vẫn cuộn được trong khung của nó, không đẩy cả trang. */
    .table-wrap { margin-inline: -16px; padding-inline: 16px; }
}

@media (max-width: 480px) {
    .container { padding-inline: 16px; }
    .page-header { margin-bottom: 20px; }
    .stat-grid { grid-template-columns: minmax(0, 1fr); }
    .result-item { flex-wrap: wrap; gap: 10px; }
    .result-item .item-main { flex: 1 1 60%; }
    .result-item .item-code { font-size: 1.15rem; }
    .row { gap: 8px; }
    .btn { padding: 9px 14px; }
    .btn-lg { padding: 11px 18px; font-size: .95rem; }
}

@media print {
    .site-header, .site-footer, .btn, .tabs, .toast-container { display: none !important; }
    body { background: #fff; color: #000; }
    .card { border: 1px solid #ccc; box-shadow: none; }
}

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

/* ===================================================================
   Bố cục có thanh điều hướng dọc bên trái (màn hình rộng)

   Từ 1100px trở lên: menu chính nằm cố định bên trái, thanh tiêu đề trên
   cùng ẩn đi. Hẹp hơn thì quay lại thanh trên cùng như cũ, vì cột 258px
   ăn mất quá nhiều bề ngang của nội dung.
   =================================================================== */

.app-sidebar { display: none; }
.dropdown-menu.dropdown-up { top: auto; bottom: calc(100% + 6px); right: auto; left: 0; }

@media (min-width: 900px) {
    .app-shell {
        display: grid;
        grid-template-columns: 258px minmax(0, 1fr);
        align-items: start;
        min-height: 100vh;
    }

    .app-sidebar {
        position: sticky;
        top: 0;
        display: flex;
        flex-direction: column;
        gap: 6px;
        height: 100vh;
        padding: 18px 14px 14px;
        background: var(--surface);
        border-right: 1px solid;
        border-right-color: var(--border);
        overflow-y: auto;
    }
    .app-sidebar .brand { padding: 0 8px 14px; }
    .app-sidebar .brand-name { font-size: 1.02rem; }

    .sidebar-nav { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; }
    .sidebar-nav .nav-group-title {
        padding: 14px 10px 5px;
        font-size: .7rem;
        font-weight: var(--weight-semibold);
        text-transform: uppercase;
        letter-spacing: .08em;
        color: var(--text-faint);
    }
    .sidebar-nav .nav-group-title:first-child { padding-top: 4px; }

    .sidebar-link {
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 8px 10px;
        border-radius: var(--radius-sm);
        color: var(--text-secondary);
        font-size: .9rem;
        font-weight: var(--weight-normal);
        line-height: 1.3;
        transition: background var(--transition), color var(--transition);
    }
    .sidebar-link:hover { background: var(--bg-muted); color: var(--text); }
    .sidebar-link.is-active { background: var(--accent-soft); color: var(--accent-text); font-weight: var(--weight-semibold); }
    .sidebar-link .nav-icon { flex: 0 0 auto; opacity: .85; }
    .sidebar-link.is-active .nav-icon { opacity: 1; }

    .sidebar-foot { border-top: 1px solid;
 border-top-color: var(--border); padding-top: 10px; }
    .sidebar-user {
        display: flex;
        align-items: center;
        gap: 9px;
        padding: 7px 8px;
        border-radius: var(--radius-sm);
        background: var(--bg-subtle);
    }
    .sidebar-user-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }

    /* Thanh trên cùng nhường chỗ cho thanh bên. */
    .app-shell .site-header { display: none; }

    /* Trang tài khoản/quản trị không cần thanh bên thứ hai nữa: thanh bên trái
       đã liệt kê đúng các mục của khu vực đang mở. */
    .app-shell .admin-layout { grid-template-columns: minmax(0, 1fr); }
    .app-shell .admin-layout > .admin-nav { display: none; }

    .app-shell .page-wrapper { min-height: 100vh; }
    .app-shell .container { max-width: 1120px; padding-inline: 32px; }
    .app-shell .container-wide { max-width: 1420px; }
    .app-shell main > .container.page,
    .app-shell main.page { padding-top: 26px; }
}

/* Cờ quốc gia vẽ bằng SVG (emoji cờ không hiển thị được trên Windows). */
.flag-icon {
    flex: 0 0 auto;
    border-radius: 2px;
    box-shadow: 0 0 0 1px rgb(0 0 0 / .12);
    vertical-align: -2px;
}
.flag-fallback {
    font-size: .68rem;
    font-weight: var(--weight-bold);
    letter-spacing: .04em;
    color: var(--text-faint);
}

/* --------------------------------------------------------------- nhóm công cụ (trang IP)

   Bảy công cụ chia làm ba cụm, mỗi cụm có nhãn nhỏ ở trên. Người dùng chọn theo
   thứ mình đang soi (địa chỉ / tên miền / kết nối) thay vì đọc hết bảy nút.      */

.tool-groups {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 22px;
    align-items: flex-end;
    margin-bottom: 20px;
    padding: 12px 14px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}
.tool-group { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.tool-group + .tool-group { padding-left: 22px; border-left: 1px solid;
 border-left-color: var(--border); }
.tool-group-label {
    font-size: .7rem;
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-faint);
    padding-left: 2px;
}
.tool-groups .tabs { margin: 0; border: 0; background: transparent; padding: 0; gap: 4px; }

@media (max-width: 720px) {
    .tool-group + .tool-group { padding-left: 0; border-left: 0; padding-top: 10px; border-top: 1px solid;
 border-top-color: var(--border); }
    .tool-group { width: 100%; }
    .tool-groups { flex-direction: column; align-items: stretch; }
}

/* --------------------------------------------------------------- khối thông tin có tiêu đề */

.info-block + .info-block { margin-top: 16px; }
.info-block-title {
    margin: 0 0 8px;
    font-size: .74rem;
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-faint);
}
.info-table th {
    width: 190px;
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
    white-space: nowrap;
}
.info-table td { font-variant-numeric: tabular-nums; }
@media (max-width: 560px) {
    .info-table th { width: auto; white-space: normal; }
}

/* Danh sách trong trang hướng dẫn: thụt lề và giãn dòng cho dễ đọc. */
.doc-list { padding-left: 20px; line-height: 1.9; margin: 0; }
.doc-list li + li { margin-top: 2px; }

/* ==========================================================================
   Giọng Cobalt — ghi đè có chủ đích lên các thành phần đã có

   Ba thay đổi lớn so với bản trước:
     · Thẻ dựa vào đường kẻ 1px, không dùng bóng đổ. Bóng chỉ còn ở lớp nổi thật.
     · Nhãn nhỏ viết hoa, cỡ nhỏ, màu nhạt.
     · Bo góc siết lại 6px (nút, ô nhập) và 10px (thẻ), bỏ kiểu bo tròn viên thuốc.
   ========================================================================== */

.card {
    border-radius: var(--radius-md);
    box-shadow: none;
}
.card:hover { border-color: var(--border-strong); }

/* Nhãn kiểu bảng đồng hồ: dùng cho tên trường, tiêu đề nhóm, đầu cột. */
.label,
.nav-group-title,
.tool-group-label,
.info-block-title,
.section-kicker {
    font-size: .72rem;
    font-weight: var(--weight-bold);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-faint);
}
label { font-weight: var(--weight-normal); }

.badge {
    border-radius: var(--radius-sm);
    font-size: .7rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .04em;
    padding: 2px 7px;
}
.badge-dot { border-radius: 50%; }

.btn {
    border-radius: var(--radius);
    font-family: var(--font-sans);
    font-weight: var(--weight-semibold);
    letter-spacing: -.005em;
}
.btn-lg { border-radius: var(--radius); }

.tabs { border-radius: var(--radius); }
.tab { border-radius: var(--radius-sm); font-weight: var(--weight-semibold); }

input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="search"], input[type="url"], select, textarea {
    border-radius: var(--radius);
    font-family: var(--font-sans);
}
textarea, .mono, .code-value, table td.mono { font-family: var(--font-mono); }

/* Mã 2FA và các giá trị máy đọc: chữ đơn cách, giãn số đều nhau. */
.code-display, .code-value {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: .02em;
}

/* Thanh bên dùng nền giấy dịu, ngăn với nội dung bằng một đường kẻ.
   Bản trước để nền than chì đen sì — nhìn nặng và lạc lõng với phần còn lại. */
@media (min-width: 900px) {
    .app-sidebar { background: var(--bg-subtle); }
    .app-sidebar .sidebar-link.is-active {
        background: var(--accent-soft);
        color: var(--accent-text);
        box-shadow: inset 2px 0 0 var(--accent);
        border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    }
}

/* --------------------------------------------------------------- khối kết quả có cấu trúc

   Trước đây kết quả tra cứu là một bảng chữ chạy dài. Giờ chia ba lớp:
     · kết luận (verdict) — được / không, đọc một cái là hiểu
     · số liệu (stat-row) — vài con số quan trọng, chữ to
     · chi tiết (data-table / info-block) — bảng đầy đủ cho ai cần đọc kỹ         */

.verdict {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
    margin-bottom: 16px;
    border: 1px solid;
    border-color: var(--border);
    border-left-width: 3px;
    border-radius: var(--radius);
    background: var(--bg-subtle);
}
.verdict strong { font-family: var(--font-display); font-size: 1.02rem; letter-spacing: -.015em; }
.verdict-mark {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    font-family: var(--font-mono);
    font-size: .82rem;
    font-weight: var(--weight-bold);
    line-height: 1;
}
.verdict-good { border-left-color: var(--success); }
.verdict-good .verdict-mark { background: var(--success-soft); color: var(--success-text); }
.verdict-warn { border-left-color: var(--warning); }
.verdict-warn .verdict-mark { background: var(--warning-soft); color: var(--warning-text); }
.verdict-bad  { border-left-color: var(--danger); }
.verdict-bad .verdict-mark { background: var(--danger-soft); color: var(--danger-text); }

.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 1px;
    margin-bottom: 16px;
    background: var(--border);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}
.stat-cell { display: flex; flex-direction: column; gap: 3px; padding: 12px 14px; background: var(--surface); }
.stat-label {
    font-size: .69rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--text-faint);
}
.stat-figure {
    font-family: var(--font-display);
    font-size: 1.32rem;
    font-weight: var(--weight-bold);
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
.stat-cell.is-good .stat-figure { color: var(--success); }
.stat-cell.is-warn .stat-figure { color: var(--warning); }
.stat-cell.is-bad .stat-figure  { color: var(--danger); }

.pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: .71rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .03em;
    white-space: nowrap;
}
.pill-good    { background: var(--success-soft); color: var(--success-text); border-color: color-mix(in oklch, var(--success) 30%, transparent); }
.pill-warn    { background: var(--warning-soft); color: var(--warning-text); border-color: color-mix(in oklch, var(--warning) 30%, transparent); }
.pill-bad     { background: var(--danger-soft);  color: var(--danger-text);  border-color: color-mix(in oklch, var(--danger) 30%, transparent); }
.pill-info    { background: var(--info-soft);    color: var(--info-text);    border-color: color-mix(in oklch, var(--info) 30%, transparent); }
.pill-neutral { background: var(--bg-muted);     color: var(--text-muted); border-color: var(--border); }

.data-table { width: 100%; border-collapse: collapse; }
.data-table th {
    text-align: left;
    padding: 8px 12px;
    font-size: .69rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--text-faint);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    white-space: nowrap;
}
.data-table td {
    padding: 9px 12px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    vertical-align: middle;
}
.data-table tbody tr:last-child td { border-bottom: none; }
.data-table tbody tr:hover { background: var(--bg-subtle); }

/* Nội dung mã quét được: giữ nguyên xuống dòng, cho cuộn ngang nếu quá dài. */
.scan-value {
    margin: 0;
    max-height: 180px;
    overflow: auto;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-size: .8rem;
    background: var(--bg-muted);
}

/* --------------------------------------------------------------- mục lục công cụ (dashboard)

   Bố cục Index-First: mỗi nhóm là một mục có nhãn, mỗi công cụ là một hàng có
   icon, tên và một dòng nói rõ nó làm gì. Đường kẻ ngăn cách thay cho thẻ nổi. */

.dash-section + .dash-section { margin-top: 34px; }
.dash-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 8px;
    margin-bottom: 14px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.index-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px;
    background: var(--border);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
}
@media (max-width: 720px) { .index-grid { grid-template-columns: minmax(0, 1fr); } }

.index-group {
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--surface);
}
.index-group-title {
    display: block;
    padding: 10px 14px;
    background: var(--bg-subtle);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    font-size: .69rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.index-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 13px 15px;
    background: var(--surface);
    color: var(--text);
    transition: background var(--transition);
}
.index-group .index-row + .index-row { border-top: 1px solid;
 border-top-color: var(--border); }
.index-row:hover { background: var(--bg-subtle); color: var(--text); }
.index-row:hover .index-arrow { opacity: 1; transform: translateX(2px); }
.index-row:focus-visible { outline: 2px solid var(--border-focus); outline-offset: -2px; }

.index-icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    background: var(--bg-subtle);
    color: var(--accent);
}
.index-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.index-title { font-weight: var(--weight-semibold); font-size: .93rem; letter-spacing: -.01em; }
.index-note {
    font-size: .78rem;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}
.index-arrow {
    flex: 0 0 auto;
    color: var(--text-faint);
    opacity: 0;
    transition: opacity var(--transition), transform var(--transition);
}

/* Ô số liệu bấm được thì phải cho thấy là bấm được. */
a.stat-cell { color: var(--text); }
a.stat-cell:hover { background: var(--bg-subtle); color: var(--text); }
a.stat-cell:hover .stat-figure { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
    .index-arrow { transition: none; }
}

/* --------------------------------------------------------------- ngăn cách mục ở bảng điều khiển

   Mỗi mục cách nhau bằng khoảng trống rộng và một đường kẻ mảnh chạy hết chiều
   ngang, để mắt biết chỗ nào là hết một mục — trước đây các mục dính vào nhau.  */

.dash-section + .dash-section {
    margin-top: 0;
    padding-top: 30px;
    border-top: 1px solid;
    border-top-color: var(--border);
}
.dash-section { padding-bottom: 30px; }
.dash-section:last-child { padding-bottom: 0; }

.dash-head {
    padding-bottom: 10px;
    margin-bottom: 16px;
    border-bottom: none;
}
.dash-head .section-kicker { color: var(--text-muted); }

/* Mỗi khối lớn tách khỏi khối trên bằng khoảng trống rộng và một đường kẻ.
   Dùng :not(:first-child) chứ không dùng ".section + .section": khối đầu trang
   thường là lưới công cụ chứ không phải .section, nên luật cũ không chạm tới
   khối "Công cụ 2FA khác" và nó dính sát vào phần trên. */
.container > .section:not(:first-child),
.container > section.section:not(:first-child) {
    margin-top: 44px;
    padding-top: 36px;
    border-top: 1px solid;
    border-top-color: var(--border);
}
.section-title { margin-bottom: 20px; }
.section-title h2 { margin-bottom: 4px; }
.section-title p { color: var(--text-muted); font-size: .9rem; margin: 0; }

/* Hai dòng địa chỉ IPv4 / IPv6 trong thẻ kết nối. */
.ip-stack { display: flex; flex-direction: column; gap: 4px; }
.ip-line { display: flex; align-items: baseline; gap: 10px; min-width: 0; }
.ip-family {
    flex: 0 0 auto;
    width: 42px;
    font-size: .68rem;
    font-weight: var(--weight-bold);
    letter-spacing: .05em;
    color: var(--text-faint);
}
.ip-line .code-value {
    font-size: clamp(1.05rem, .85rem + 1vw, 1.5rem);
    overflow-wrap: anywhere;
    min-width: 0;
}
#whoami[data-loading] .code-value { color: var(--text-faint); }

/* Bản ghi thô gập lại: chỉ mở khi người đọc thật sự cần. */
.raw-block { margin-top: 16px; border: 1px solid;
 border-color: var(--border); border-radius: var(--radius); }
.raw-block > summary {
    padding: 10px 14px;
    cursor: pointer;
    font-size: .85rem;
    font-weight: var(--weight-semibold);
    color: var(--text-secondary);
    list-style: none;
}
.raw-block > summary::-webkit-details-marker { display: none; }
.raw-block > summary::before { content: '▸ '; color: var(--text-faint); }
.raw-block[open] > summary::before { content: '▾ '; }
.raw-block > summary:hover { color: var(--text); background: var(--bg-subtle); }
.raw-block pre { margin: 0; border: none; border-top: 1px solid;
 border-top-color: var(--border); border-radius: 0; max-height: 320px; }

/* --------------------------------------------------------------- thanh dẫn phụ

   Dòng dẫn sang công cụ khác: trước đây là một dòng chữ dính sát đáy thẻ phía
   trên, nhìn như bị bỏ quên. Giờ là một thanh có viền, cách hẳn ra.             */

.link-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 16px;
    margin-top: 20px;
    padding: 12px 16px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}
.link-bar-text { font-size: .9rem; color: var(--text-muted); }

/* Phần mở đầu trang chủ canh trái cho thẳng hàng với thẻ công cụ bên dưới —
   canh giữa làm mắt phải nhảy hai lần khi đọc xuống. */
.hero { text-align: left; padding: 36px 0 26px; }
.hero h1 { max-width: 22ch; margin-inline: 0; }
.hero .lead { max-width: 62ch; margin: 0; }
.hero-badges { justify-content: flex-start; }

@media (max-width: 720px) {
    .link-bar { flex-direction: column; align-items: stretch; }
    .link-bar .row { justify-content: stretch; }
    .link-bar .btn { flex: 1 1 auto; justify-content: center; }
}

/* --------------------------------------------------------------- bảng trong khung kết quả

   Mọi bảng kết quả dùng chung một cách trình bày: đầu cột viết hoa nhạt, đường
   kẻ ngang giữa các dòng, dòng dưới con trỏ đổi nền. Trước đây một số bảng không
   có đầu cột và không có đường kẻ nên nhìn như chữ xếp chồng.                    */

.result-panel table:not(.data-table) {
    width: 100%;
    border-collapse: collapse;
}
.result-panel table:not(.data-table) th,
.result-panel table:not(.data-table) td {
    padding: 9px 12px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    vertical-align: middle;
}
.result-panel table:not(.data-table) th {
    text-align: left;
    font-size: .7rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text-faint);
    white-space: nowrap;
}
.result-panel table:not(.data-table) tbody tr:last-child td { border-bottom: none; }
.result-panel table:not(.data-table) tbody tr:hover { background: var(--bg-subtle); }

/* Bảng nằm trong khung có viền thì bỏ kẻ đáy của dòng cuối cho khỏi kẻ đôi. */
.table-wrap { border-radius: var(--radius); overflow: hidden; }
.info-block .table-wrap,
.data-table { border: 1px solid;
 border-color: var(--border); }
.info-block .table-wrap { border-radius: var(--radius); }
.info-block .table-wrap .data-table { border: none; }

/* Icon đứng cạnh chữ trong đầu dòng bảng. */
.th-icon { display: inline-flex; align-items: center; gap: 7px; }
.th-icon .row-icon { flex: 0 0 auto; color: var(--text-faint); }

/* Danh sách máy chủ DNS trong thẻ kết nối: mỗi cái một dòng, không tràn ra ngoài. */
.resolver-line { overflow-wrap: anywhere; line-height: 1.5; }
.resolver-line + .resolver-line { margin-top: 2px; }

/* ==========================================================================
   Phân tầng chữ — làm tiêu đề nổi lên khỏi thân bài

   Trước đây tiêu đề chỉ nhỉnh hơn thân bài một chút về độ đậm nên cả trang
   trông phẳng lì, đọc lướt không biết đâu là đầu mục. Ba việc làm nó nổi:
     · Tiêu đề đậm 700, thân bài 400 — cách nhau hẳn hai bậc, không phải nửa bậc.
     · Nhãn nhỏ viết hoa ở trên tiêu đề (kicker) mang màu nhấn, làm mốc mắt.
     · Tiêu đề phần có gạch chân ngắn màu nhấn thay cho việc chỉ to hơn.
   ========================================================================== */

/* Nhãn nhỏ trên đầu mỗi phần: đây là mốc để mắt bắt được cấu trúc trang. */
.label,
.nav-group-title,
.tool-group-label,
.info-block-title {
    color: var(--text-muted);
}
.section-kicker {
    display: inline-block;
    color: var(--accent-text);
    letter-spacing: .09em;
}

/*
 * Tiêu đề phần (h2 giữa trang) có một vạch nhấn ngắn phía trên.
 * Chỉ vạch, không nền — đủ để đánh dấu mà không thành thanh màu.
 */
.section > h2:first-child,
.section > .section-title h2 {
    position: relative;
    padding-top: 14px;
}
.section > h2:first-child::before,
.section > .section-title h2::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 34px;
    height: 3px;
    border-radius: 2px;
    background: var(--accent);
}
/* Tiêu đề căn giữa thì vạch cũng phải ở giữa. */
.section > h2.text-center::before { left: 50%; transform: translateX(-50%); }

/* Trang chủ: khối mở đầu to hơn hẳn phần còn lại, đúng vai trò của nó. */
.hero { padding: 44px 0 32px; }
.hero h1 { max-width: 19ch; margin-bottom: 14px; }
.hero .lead { font-size: 1.14rem; line-height: 1.55; color: var(--text-secondary); }

@media (max-width: 640px) {
    .hero { padding: 26px 0 22px; }
    .hero .lead { font-size: 1rem; }
    .section > h2:first-child,
    .section > .section-title h2 { padding-top: 12px; }
}

/* Tiêu đề trong thẻ: nhỏ nhưng vẫn phải khác thân bài về độ đậm. */
.card > h2:first-child,
.card-header h2 { letter-spacing: -.012em; }

/* ==========================================================================
   Thẻ công cụ 2FA — ba thẻ bằng nhau, ô nhập rộng hơn
   ========================================================================== */

/*
 * Ba thẻ chia đúng ba phần bằng nhau.
 *
 * flex:1 1 auto chia theo độ dài nhãn, nên "Mã bí mật / liên kết" chiếm gần nửa
 * hàng còn "Ảnh QR" co lại bé tí — nhìn như ba nút khác cấp nhau. Lưới ba cột
 * cho ra ba ô đúng bằng nhau bất kể nhãn dài ngắn hay dịch sang tiếng gì.
 */
.tool-card > .tabs,
.tabs-even {
    display: grid;
    grid-template-columns: repeat(var(--tab-count, 3), 1fr);
    overflow: visible;
}
.tool-card > .tabs .tab,
.tabs-even .tab {
    text-align: center;
    justify-content: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Ô nhập mã bí mật: chỗ làm việc chính của trang, cho nó rộng rãi hơn. */
#totp-input {
    min-height: 132px;
    font-size: .95rem;
    line-height: 1.6;
    padding: 12px 14px;
}

@media (max-width: 560px) {
    .tool-card > .tabs,
    .tabs-even { grid-template-columns: 1fr; }
    #totp-input { min-height: 116px; font-size: .9rem; }
}

/* ==========================================================================
   Thẻ "Địa chỉ IP của bạn"

   Số liệu ở đây phải đi hỏi mạng nên luôn có một quãng chờ. Quãng đó được nói
   ra bằng ba thứ, không để người dùng đoán:
     · khung xương nhấp nháy đúng chỗ chữ sắp hiện,
     · một dòng trạng thái "đang lấy thông tin…" có chấm quay,
     · hỏng thì hiện hẳn lời báo và nút bấm lại.
   ========================================================================== */

.whoami-card { padding: 22px 24px; }

/* Tầng trên: danh tính đường truyền. */
.whoami-top { min-width: 0; }

.whoami-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.whoami-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

/* Dòng trạng thái: chấm quay khi đang chờ, dấu tích khi xong. */
.whoami-status {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: .76rem;
    color: var(--text-muted);
}
.spinner-dot {
    width: 11px;
    height: 11px;
    border-radius: 50%;
    border: 2px solid;
    border-color: var(--border-strong);
    border-top-color: var(--accent);
    animation: spin .7s linear infinite;
}
#whoami[data-state="ready"] .whoami-status { color: var(--success); }
#whoami[data-state="ready"] .spinner-dot {
    border-color: var(--success);
    border-top-color: var(--success);
    animation: none;
}
#whoami[data-state="error"] .whoami-status { color: var(--danger); }
#whoami[data-state="error"] .spinner-dot {
    border-color: var(--danger);
    border-top-color: var(--danger);
    animation: none;
}

/* Khung xương: chiếm đúng chỗ chữ sắp hiện nên chữ hiện ra không làm nhảy trang. */
.skeleton {
    display: inline-block;
    width: 100%;
    max-width: 220px;
    height: .85em;
    border-radius: var(--radius-sm);
    background: linear-gradient(90deg, var(--bg-muted) 25%, var(--bg-inset) 37%, var(--bg-muted) 63%);
    background-size: 400% 100%;
    animation: skeleton-sweep 1.3s ease-in-out infinite;
    vertical-align: middle;
}
.skeleton-lg { max-width: 300px; height: 1em; }
@keyframes skeleton-sweep {
    from { background-position: 100% 50%; }
    to   { background-position: 0 50%; }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton { animation: none; }
    .spinner-dot { animation: none; }
}

/*
 * Vị trí và nhà mạng nằm ngang thành một hàng, không xếp chồng ba dòng: chúng
 * cùng trả lời một câu hỏi ("đường truyền này của ai, ở đâu") nên đọc liền một
 * mạch dễ hơn đọc ba dòng rời.
 */
.whoami-place { display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: .9rem; }
.whoami-place-line { display: flex; align-items: center; gap: 8px; min-width: 0; }
.whoami-place-line .nav-icon,
.whoami-place-line .flag-icon { flex: 0 0 auto; }
.whoami-place-line .nav-icon { color: var(--text-faint); }
.whoami-place-name { font-weight: var(--weight-semibold); }

/*
 * Tầng dưới: bốn ô ngang, mỗi ô icon + nhãn + giá trị, ngăn nhau bằng kẻ dọc.
 *
 * Xếp dọc thành một cột bên phải thì cột đó cao gần gấp đôi phần địa chỉ bên
 * trái, để lại một khoảng trống lớn — đúng chỗ người dùng chê là xấu.
 */
.whoami-side {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) repeat(3, minmax(0, 1fr));
    gap: 0 22px;
    margin-top: 20px;
    padding-top: 18px;
    border-top: 1px solid;
    border-top-color: var(--border);
    min-width: 0;
}
@media (max-width: 1100px) {
    .whoami-side { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 22px; }
}
@media (max-width: 620px) {
    .whoami-side { grid-template-columns: 1fr; gap: 0; }
}
.whoami-row { display: flex; gap: 11px; min-width: 0; }
.whoami-row + .whoami-row { padding-left: 22px; border-left: 1px solid;
 border-left-color: var(--border); }
@media (max-width: 1100px) {
    .whoami-row + .whoami-row { padding-left: 0; border-left: 0; }
    .whoami-row:nth-child(even) { padding-left: 22px; border-left: 1px solid;
 border-left-color: var(--border); }
}
@media (max-width: 620px) {
    .whoami-row { padding: 12px 0; }
    .whoami-row + .whoami-row { border-top: 1px solid;
 border-top-color: var(--border); padding-left: 0; border-left: 0; }
    .whoami-row:nth-child(even) { padding-left: 0; border-left: 0; }
    .whoami-row:first-child { padding-top: 0; }
    .whoami-row:last-child { padding-bottom: 0; }
}
.whoami-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
    background: var(--bg-muted);
    color: var(--text-muted);
}
.whoami-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }

/*
 * Máy chủ DNS: cờ nước, địa chỉ, rồi tên nhà mạng.
 *
 * Tên nhà mạng bị cắt bằng dấu ba chấm chứ không cho xuống dòng: để nó xuống
 * dòng thì bốn máy chủ chiếm tám dòng, cột phải cao gấp đôi cột trái và nửa
 * dưới bên trái trống hoác. Địa chỉ mới là thứ cần đọc chính xác.
 */
.resolver-line {
    display: flex;
    align-items: baseline;
    gap: 7px;
    flex-wrap: nowrap;
    min-width: 0;
}
.resolver-line + .resolver-line { margin-top: 5px; }
.resolver-line .flag-icon,
.resolver-line .nav-icon { flex: 0 0 auto; align-self: center; }
.resolver-line .mono { flex: 0 0 auto; font-size: .82rem; }
.resolver-owner {
    color: var(--text-muted);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lang-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.lang-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 7px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    font-size: .78rem;
}

/* Lời báo hỏng: chỉ hiện khi thật sự hỏng, kèm nút chạy lại. */
.whoami-error {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 18px;
    padding: 12px 14px;
    border: 1px solid;
    border-color: var(--danger);
    border-radius: var(--radius);
    background: var(--danger-soft);
    color: var(--danger);
    font-size: .88rem;
}
.whoami-error[hidden] { display: none; }
.whoami-error .btn { display: inline-flex; align-items: center; gap: 6px; }

/* ==========================================================================
   Dấu hiệu "đang chạy" cho mọi nút

   Các nút trong trang công cụ có sẵn thẻ <span class="spinner">. Nhưng nhiều
   nút ở khu tài khoản và quản trị cũng gọi dom.loading() mà không có thẻ đó,
   nên bấm xong nút chỉ bị mờ đi — không phân biệt được "đang chạy" với "bị
   khoá". Nút nào thiếu vòng quay thì tự mọc ra một cái.
   ========================================================================== */

.btn.is-loading { cursor: progress; }
.btn.is-loading:not(:has(.spinner))::after {
    content: "";
    display: inline-block;
    width: 13px;
    height: 13px;
    margin-left: 8px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: spin .6s linear infinite;
    vertical-align: -2px;
}
@media (prefers-reduced-motion: reduce) {
    .btn.is-loading:not(:has(.spinner))::after { animation: none; opacity: .5; }
}

/* Trạng thái "dùng lại kết quả trước" + nút kiểm tra lại luôn hiện. */
.whoami-actions { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.whoami-refresh { display: inline-flex; align-items: center; gap: 6px; padding: 3px 9px; font-size: .78rem; }
#whoami[data-state="cached"] .whoami-status { color: var(--text-muted); }
#whoami[data-state="cached"] .spinner-dot {
    border-color: var(--border-strong);
    border-top-color: var(--border-strong);
    animation: none;
}

/* ==========================================================================
   Thanh tiến trình cho phép kiểm tra chạy nhiều phần

   Một thao tác giờ được chia thành nhiều lần hỏi. Không có thanh này thì người
   dùng thấy kết quả nhảy ra từng mảng mà không biết còn bao nhiêu phần nữa,
   cũng không biết bảng đã đầy đủ hay chưa.
   ========================================================================== */

.run-progress {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}
.run-progress[hidden] { display: none; }
.run-progress-track {
    flex: 1 1 auto;
    height: 4px;
    border-radius: 2px;
    background: var(--bg-inset);
    overflow: hidden;
}
.run-progress-fill {
    height: 100%;
    border-radius: 2px;
    background: var(--accent);
    transition: width 220ms var(--ease-out);
}
.run-progress-text {
    flex: 0 0 auto;
    font-size: .76rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
}

/* Chip "đang tra": phân biệt rõ với "đã tra, không có". */
.pill-wait {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text-muted);
    background: var(--bg-muted);
    border-color: var(--border);
}
.pill-wait .spinner-dot { width: 9px; height: 9px; border-width: 2px; }

/* ==========================================================================
   Đầu trang

   Cỡ chữ lấy theo đặc tả của các hệ thống thiết kế lớn, không ước lượng:

     Primer (GitHub)   tiêu đề trang mặc định 20px/600. Bản 32px họ dành riêng
                       cho nội dung do người dùng viết (tiêu đề issue, PR).
     Fluent 2 (Azure)  Title 1 = 32px/600 · Title 3 = 24px/600. MỌI cấp tiêu đề
                       dùng semibold 600, không cấp nào dùng bold 700.
     Carbon (IBM)      productive heading-04 = 28px · heading-03 = 20px.

   Bản trước dùng 46px/700 — to hơn chuẩn khoảng 45%, nặng hơn một bậc, nên nó
   át hết phần còn lại của trang. Bản này 28px/600, nằm giữa dải mà cả ba hệ
   thống đều dùng.
   ========================================================================== */

.page-header {
    margin-bottom: 28px;
    padding-bottom: 20px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

/* --- Đường dẫn ------------------------------------------------------------
   Thay cho nhãn chữ hoa của bản trước. Cùng chiếm một dòng, nhưng đường dẫn
   vừa cho biết đang ở đâu vừa bấm quay lại được — nhãn chữ hoa chỉ để đọc. */
.crumbs {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 12px;
    font-size: .82rem;
    line-height: 1.4;
}
.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--accent-text); text-decoration: underline; }
.crumb-sep { color: var(--text-faint); }
.crumb-current { color: var(--text-secondary); font-weight: var(--weight-semibold); }

/* --- Hàng tiêu đề --------------------------------------------------------- */
.page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 20px;
}
.page-head-text { min-width: 0; flex: 1 1 auto; }
.page-head-aside { flex: 0 0 auto; display: flex; align-items: center; gap: 10px; }

/*
 * Icon nằm ngay trước tiêu đề, không có ô nền.
 *
 * Đây là "leading visual" của Primer và "avatar cỡ S" của SAP Fiori: một dấu
 * hiệu nhỏ gắn liền với dòng chữ. Ô nền 52px màu xanh của bản trước là một
 * mảng trang trí đứng tách khỏi chữ, không neo vào dòng nào cả.
 */
.page-title {
    display: flex;
    align-items: baseline;
    gap: 11px;
    margin: 0 0 8px;
    font-size: clamp(1.4rem, 1.15rem + .8vw, 1.75rem);   /* 22px → 28px */
    font-weight: var(--weight-semibold);
    line-height: 1.25;
    letter-spacing: -.012em;
}
.page-title-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-self: center;
    color: var(--accent-text);
}
.page-title-icon .nav-icon { width: 22px; height: 22px; stroke-width: 1.9; }

/*
 * Mô tả đầu trang. Không giới hạn bề rộng riêng: câu dẫn ngắn nên hiếm khi
 * xuống dòng, mà đặt một mép hẹp hơn thẻ nội dung bên dưới thì nhìn như trang
 * chưa căn xong. Bề rộng đã do .container quy định.
 */
.page-desc {
    margin: 0;
    font-size: .97rem;
    line-height: 1.55;
    color: var(--text-muted);
}

@media (max-width: 720px) {
    .page-head { flex-wrap: wrap; }
    .page-head-aside { width: 100%; }
}
@media (max-width: 640px) {
    .page-header { margin-bottom: 20px; padding-bottom: 16px; }
    .crumbs { margin-bottom: 9px; font-size: .78rem; }
    .page-title { gap: 9px; }
    .page-title-icon .nav-icon { width: 19px; height: 19px; }
    .page-desc { font-size: .93rem; }
}

/* ==========================================================================
   Khu quản trị — thanh bên riêng

   Vào /admin là thanh bên đổi hẳn nội dung: chỉ còn mục quản trị, cộng một lối
   ra. Trộn chung thì thanh bên có tới hai mươi mục mà mười ba mục không dính
   dáng gì tới việc đang làm.
   ========================================================================== */

.brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-area {
    font-size: .66rem;
    font-weight: var(--weight-semibold);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--accent-text);
}

/* Vạch màu nhấn dọc mép trái: dấu hiệu "đây là khu khác" thấy được ngay cả khi
   không đọc chữ. */
.app-sidebar.is-admin { box-shadow: inset 3px 0 0 var(--accent); }

/* Nhóm cuối cùng của thanh bên quản trị là lối ra, tách khỏi phần trên. */
.app-sidebar.is-admin .sidebar-nav > .nav-group-title:last-of-type {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid;
    border-top-color: var(--border);
}

/* Lý do vắng địa chỉ: cỡ chữ thân bài, không phải cỡ chữ địa chỉ. */
.ip-note {
    font-family: var(--font-sans);
    font-size: .87rem;
    font-weight: var(--weight-normal);
    line-height: 1.45;
    color: var(--text-muted);
    letter-spacing: 0;
}

/* ==========================================================================
   Hệ tương tác thống nhất

   Nghiên cứu từ Material 3 và eBay Playbook, hai điểm chung:
     · Trạng thái con trỏ là một lớp màu mờ chồng lên, đậm dần theo mức
       (hover ~6% → nhấn ~12%), không phải đổi hẳn màu nền.
     · Focus LUÔN là một vòng nhìn thấy được — bàn phím phải theo dõi được tiêu
       điểm. Trước đây chỉ ô nhập có vòng này, nút/thẻ/tab/liên kết thì không.

   Khối này đặt cuối tệp để ghi đè nhất quán lên mọi thành phần đã có.
   ========================================================================== */

/* --- Một vòng focus cho tất cả -------------------------------------------
   :focus-visible chỉ hiện khi điều hướng bằng bàn phím, nên chuột bấm không
   để lại vòng thừa. */
.btn:focus-visible,
.tab:focus-visible,
.sidebar-link:focus-visible,
.main-nav a:focus-visible,
.admin-nav a:focus-visible,
.crumbs a:focus-visible,
.feature:focus-visible,
.index-row:focus-visible,
a.stat-cell:focus-visible,
.dropzone:focus-visible,
.resolver-line a:focus-visible,
.dropdown-menu a:focus-visible,
.dropdown-menu button:focus-visible,
summary:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--radius-sm);
}
/* Ô nhập đã có viền đổi màu; thêm vòng cho đồng bộ với phần còn lại. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: var(--focus-ring);
}

/* --- Nút: nổi khối hơn, phản hồi bấm rõ hơn ------------------------------ */
.btn { box-shadow: var(--shadow-sm); }
.btn:hover { box-shadow: var(--shadow); }
.btn:active { transform: translateY(1px); box-shadow: var(--shadow-sm); }
.btn-ghost { box-shadow: none; }
.btn-ghost:hover { box-shadow: none; }

/* Nút chính: chuyển nền mượt sang tông đậm hơn khi hover, kèm chút nổi. */
.btn:not(.btn-secondary):not(.btn-ghost):not(.btn-danger):not(.btn-danger-soft):hover {
    box-shadow: 0 2px 8px -2px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* --- Thẻ: nhấc nhẹ và viền đậm lên khi trỏ tới -------------------------- */
.feature,
.tool-card,
.stat-cell,
.index-row {
    transition: border-color var(--transition), box-shadow var(--transition),
                transform var(--transition), background var(--transition);
}
.card { transition: border-color var(--transition), box-shadow var(--transition); }
.card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }

.feature:hover,
.tool-card:hover {
    border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
    box-shadow: var(--lift);
    transform: translateY(-3px);
}

/* --- Hàng bấm được: lớp màu nhấn mờ thay cho nền xám phẳng -------------- */
.index-row:hover,
a.stat-cell:hover,
.dropdown-menu a:hover,
.dropdown-menu button:hover,
.data-table tbody tr.is-clickable:hover {
    background: var(--state-hover);
    color: var(--text);
}
.index-row:active,
a.stat-cell:active { background: var(--state-active); }

/* --- Tab đang mở: gạch nhấn dưới chân, không chỉ đổi nền --------------- */
.tab.is-active { position: relative; }
.tabs-even .tab.is-active::after,
.tool-card > .tabs .tab.is-active::after {
    content: "";
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: -3px;
    height: 2px;
    border-radius: 2px;
    background: var(--accent);
}

/* --- Ô đánh dấu và nhãn: cả nhãn sáng lên khi trỏ tới ------------------ */
.checkbox:hover,
.radio:hover { color: var(--text); }

/* --- Sao chép được: gợi ý bằng con trỏ và một nền mờ khi trỏ tới ------- */
.copyable { cursor: pointer; border-radius: var(--radius-sm); transition: background var(--transition); }
.copyable:hover { background: var(--state-hover); box-shadow: 0 0 0 4px var(--state-hover); }

/* --- Chuyển động: bảng ngắt ngay khi người dùng chọn giảm hiệu ứng ----- */
@media (prefers-reduced-motion: reduce) {
    .feature:hover,
    .tool-card:hover,
    .btn:active,
    .index-row:hover .index-arrow { transform: none; }
    * { transition-duration: .01ms !important; }
}

/* ==========================================================================
   Trung tâm giám sát (/admin/monitor)
   ========================================================================== */

.mon-section { margin-bottom: 30px; }
.mon-section + .mon-section { margin-top: 4px; }

/* Đầu trang: nút tự-làm-mới và mốc thời gian cập nhật. */
.monitor-auto { display: inline-flex; align-items: center; gap: 7px; font-size: .82rem; color: var(--text-secondary); cursor: pointer; }
.monitor-auto input { width: 15px; height: 15px; accent-color: var(--accent); }
.monitor-updated { font-variant-numeric: tabular-nums; }

/* Chấm trạng thái dùng chung: sức khoẻ hệ thống và thăm dò dịch vụ. */
.mon-dot {
    display: inline-block;
    width: 9px; height: 9px;
    border-radius: 50%;
    background: var(--border-strong);
    flex: 0 0 auto;
}
.mon-dot-good { background: var(--success); }
.mon-dot-warn { background: var(--warning); }
.mon-dot-bad  { background: var(--danger); }
.mon-dot-idle { background: var(--accent); animation: mon-pulse 1s ease-in-out infinite; }
@keyframes mon-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .mon-dot-idle { animation: none; } }

/* Thẻ sức khoẻ: lưới thẻ nhỏ, mỗi thẻ một chỉ số lớn. */
.mon-health { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.mon-card {
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    padding: 14px 16px;
}
.mon-card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.mon-card-label { font-size: .72rem; font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.mon-card-value { font-size: 1.5rem; font-weight: var(--weight-bold); line-height: 1.1; font-variant-numeric: tabular-nums; }
.mon-card-value .mon-unit { font-size: .9rem; font-weight: var(--weight-semibold); color: var(--text-muted); }
.mon-card-hint { margin-top: 4px; }

/* Ô số liệu luồng dữ liệu. */
.mon-flow { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; }
.mon-flow-tight { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mon-metric {
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}
.mon-metric-value { font-size: 1.3rem; font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; line-height: 1.15; }
.mon-metric-value .mon-unit { font-size: .8rem; font-weight: var(--weight-semibold); color: var(--text-muted); }
.mon-metric-label { font-size: .74rem; color: var(--text-muted); }

/* Sức khoẻ trong bảng dịch vụ. */
.mon-health-cell { display: inline-flex; align-items: center; gap: 7px; }

/* Loại sự kiện an ninh: chip nhỏ. */
.mon-kinds { display: flex; flex-wrap: wrap; gap: 6px; }
.mon-kind {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 2px 8px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    font-size: .76rem;
    color: var(--text-secondary);
}
.mon-kind-n { font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }

@media (max-width: 560px) {
    .mon-flow-tight { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ==========================================================================
   Thanh người dùng ở góc phải trên cùng

   Từ 1100px trở lên, thanh trên cùng bị ẩn để nhường chỗ cho thanh bên — nên
   trước đây trên máy tính không có chỗ nào ở góc phải cho biết đang đăng nhập
   bằng tài khoản nào; khối tài khoản nằm lọt thỏm dưới chân thanh bên.
   ========================================================================== */

.user-bar { display: none; }

@media (min-width: 900px) {
    .user-bar {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: 8px;
        width: 100%;
        max-width: 1120px;
        margin-inline: auto;
        /* Cùng lề 32px với .app-shell .container, để nút tài khoản thẳng hàng
           với mép phải của nội dung bên dưới. */
        padding: 12px 32px 0;
    }
    /* Nội dung trang lùi lên cho khít với thanh này. */
    .app-shell main > .container.page,
    .app-shell main.page { padding-top: 14px; }
}

/* Nút tài khoản: avatar + tên + mũi tên. */
.user-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: 190px;
    padding: 5px 10px 5px 5px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: .84rem;
    font-weight: var(--weight-semibold);
    cursor: pointer;
    transition: border-color var(--transition), box-shadow var(--transition), background var(--transition);
}
.user-chip:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.user-chip:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.user-chip .avatar { width: 26px; height: 26px; font-size: .78rem; border-radius: 50%; flex: 0 0 auto; }
.user-chip-name { min-width: 0; }
.user-chip-caret { flex: 0 0 auto; color: var(--text-muted); }

/* Menu tài khoản: rộng hơn menu thường vì có đầu menu và icon. */
.dropdown-end { right: 0; left: auto; min-width: 232px; }
.dropdown-menu .menu-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px 4px;
    min-width: 0;
}
.dropdown-menu .menu-head .avatar { width: 32px; height: 32px; border-radius: 50%; flex: 0 0 auto; }
.menu-head-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.dropdown-menu .nav-icon { flex: 0 0 auto; color: var(--text-faint); }

/* Chân thanh bên giờ chỉ còn một liên kết. */
.sidebar-foot-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: .85rem;
    transition: background var(--transition), color var(--transition);
}
.sidebar-foot-link:hover { background: var(--state-hover); color: var(--text); }
.sidebar-foot-link .nav-icon { color: var(--text-faint); }

/* ==========================================================================
   Biểu đồ theo thời gian trong trang giám sát

   Vẽ bằng SVG tự dựng, viewBox 0..100 và preserveAspectRatio="none" nên đồ thị
   tự co giãn theo bề ngang mà không phải đo pixel hay vẽ lại khi đổi cỡ cửa sổ.
   Không tải thư viện biểu đồ nào — CSP của trang vốn chặn script tên miền khác.
   ========================================================================== */

.mon-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; }
.mon-chart {
    margin: 0;
    padding: 12px 14px 10px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    min-width: 0;
}
.mon-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.mon-chart-label { font-size: .74rem; font-weight: var(--weight-semibold); text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.mon-chart-now { font-size: 1.05rem; font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }
.mon-chart-body { position: relative; height: 62px; }
.mon-plot { display: block; width: 100%; height: 100%; overflow: visible; }
.mon-plot-line { fill: none; stroke-width: 1.6; vector-effect: non-scaling-stroke; stroke-linejoin: round; stroke-linecap: round; }
.mon-plot-area { stroke: none; }

/* Bốn tông màu, dùng lại đúng thang màu trạng thái của cả trang. */
.mon-plot-accent  .mon-plot-line { stroke: var(--accent); }
.mon-plot-accent  .mon-plot-from { stop-color: var(--accent); stop-opacity: .22; }
.mon-plot-danger  .mon-plot-line { stroke: var(--danger); }
.mon-plot-danger  .mon-plot-from { stop-color: var(--danger); stop-opacity: .22; }
.mon-plot-warning .mon-plot-line { stroke: var(--warning); }
.mon-plot-warning .mon-plot-from { stop-color: var(--warning); stop-opacity: .22; }
.mon-plot-success .mon-plot-line { stroke: var(--success); }
.mon-plot-success .mon-plot-from { stop-color: var(--success); stop-opacity: .22; }
.mon-plot-from { stop-color: var(--accent); stop-opacity: .22; }
.mon-plot-to   { stop-color: var(--accent); stop-opacity: 0; }

/* Giá trị lớn nhất ghi ở góc, để biết trục đứng đang tới đâu. */
.mon-plot-max {
    position: absolute;
    top: 0; right: 0;
    font-size: .68rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-faint);
    pointer-events: none;
}

/* Bộ chọn cửa sổ thời gian ở đầu trang. */
.mon-window {
    width: auto;
    min-width: 96px;
    padding: 5px 28px 5px 10px;
    font-size: .8rem;
}

/* Chip trạng thái có tông màu (mã HTTP, phần mở rộng PHP). */
.mon-kind-good { border-color: color-mix(in srgb, var(--success) 40%, var(--border)); color: var(--success); }
.mon-kind-warn { border-color: color-mix(in srgb, var(--warning) 45%, var(--border)); color: var(--warning); }
.mon-kind-bad  { border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); color: var(--danger); }

/* Bảng khoá–giá trị trong ba thẻ bộ chạy / CSDL / lưu trữ. */
[data-monitor="runtime"] th,
[data-monitor="db"] th,
[data-monitor="storage"] th { width: 108px; white-space: nowrap; }

@media (max-width: 900px) {
    .mon-charts { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
}

/* ==========================================================================
   Quét mã bằng camera
   ========================================================================== */

.cam-block[hidden] { display: none; }
.cam-stage {
    position: relative;
    border-radius: var(--radius-md);
    overflow: hidden;
    background: #000;
    aspect-ratio: 4 / 3;
}
.cam-video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Khung ngắm ở giữa: cho biết chĩa mã vào đâu thì máy đọc được. */
.cam-frame {
    position: absolute;
    inset: 50% auto auto 50%;
    transform: translate(-50%, -50%);
    width: min(62%, 240px);
    aspect-ratio: 1;
    border: 2px solid rgb(255 255 255 / .85);
    border-radius: var(--radius-md);
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / .35);
    pointer-events: none;
}
.cam-status {
    position: absolute;
    left: 50%;
    bottom: 10px;
    transform: translateX(-50%);
    padding: 4px 12px;
    border-radius: var(--radius-full);
    background: rgb(0 0 0 / .6);
    color: #fff;
    font-size: .78rem;
    white-space: nowrap;
}

/* ==========================================================================
   Điện thoại — ba lỗi thật, có căn cứ đo được

   1. iOS Safari TỰ PHÓNG TO cả trang khi chạm vào ô nhập có cỡ chữ dưới 16px.
      Ô nhập của bộ này đang 14,4px nên chạm vào là bố cục nhảy — đây là chỗ
      khiến giao diện trên điện thoại thấy tệ nhất. Chỉ cần 16px là hết, không
      cần đụng tới thẻ viewport.
   2. Mục tiêu chạm quá nhỏ: tab 32px, nút 35–38px. Apple HIG đòi 44×44pt,
      Material 3 đòi 48dp, WCAG 2.2 SC 2.5.8 tối thiểu 24px (AAA 2.5.5 là 44px).
      Lấy 44px cho mọi thứ bấm được.
   3. Chữ thân bài 14,5px hơi nhỏ khi cầm trên tay.
   ========================================================================== */

@media (max-width: 860px) {
    /* --- 1. Chặn iOS tự phóng to --- */
    input[type="text"], input[type="email"], input[type="password"], input[type="number"],
    input[type="search"], input[type="url"], input[type="tel"], select, textarea {
        font-size: 16px;
    }
    /* Ô mã bí mật vẫn dùng chữ đơn cách, chỉ nâng cỡ. */
    #totp-input { font-size: 16px; }

    /* --- 2. Mục tiêu chạm 44px --- */
    .btn { min-height: 44px; padding-block: 10px; }
    .btn-sm { min-height: 44px; padding-block: 10px; }
    .btn-icon { min-width: 44px; min-height: 44px; }

    /* Hàng tiêu đề là chỗ duy nhất chật tới mức không nhét đủ 44px: nút ba gạch
       đã chiếm 44px, thêm hai nút phụ nữa là tràn khỏi màn hình 375px. Hai nút
       ấy giữ 38px — vẫn trên ngưỡng 24px của WCAG 2.2 SC 2.5.8, và chúng là nút
       phụ (đổi giao diện, đổi ngôn ngữ) chứ không phải việc chính. */
    .header-actions .btn-sm { min-height: 38px; padding-block: 8px; }
    .header-actions .btn-icon.btn-sm { min-width: 38px; min-height: 38px; }
    .tab { min-height: 44px; display: flex; align-items: center; justify-content: center; }
    .nav-toggle { min-width: 44px; min-height: 44px; }

    /* Nút ba gạch to lên 16px làm cả hàng tiêu đề tràn khỏi màn hình 375px.
       Bù lại bằng lề hẹp hơn và khoảng cách nhỏ hơn giữa các nút — mục tiêu
       chạm 44px là thứ không nhường. */
    .site-header .container { gap: 6px; padding-inline: 10px; }
    .header-actions { gap: 2px; }
    .header-actions .btn-sm { padding-inline: 7px; }
    .brand-name, .brand span:not(.brand-mark) { max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .sidebar-link, .main-nav a, .admin-nav a { min-height: 44px; display: flex; align-items: center; }
    .dropdown-menu a, .dropdown-menu button { min-height: 44px; }
    .checkbox, .radio { min-height: 44px; align-items: center; }
    .checkbox input, .radio input { width: 20px; height: 20px; margin-top: 0; }
    .crumbs a { padding-block: 4px; }

    /* Chữ mở một khối gập lại cũng là thứ phải chạm trúng. Chỉ nới đệm dọc chứ
       không đổi display: đổi sang flex sẽ làm mất dấu tam giác mặc định của
       trình duyệt ở những khối chưa tự vẽ mũi tên riêng. */
    summary { min-height: 44px; padding-block: 10px; }

    /* Con trượt: thanh vẫn mảnh 5px cho đẹp, nhưng vùng chạm phải cao 44px và
       núm phải đạt 24px (WCAG 2.2 SC 2.5.8). Trải chiều cao lên chính ô input
       rồi vẽ thanh bằng một dải gradient đặt giữa — cách duy nhất giữ được vẻ
       mảnh mà ngón tay vẫn bắt trúng. */
    input[type="range"] {
        height: 44px;
        border-radius: 0;
        background: linear-gradient(var(--bg-inset), var(--bg-inset)) center / 100% 5px no-repeat;
    }
    input[type="range"]::-webkit-slider-thumb { width: 26px; height: 26px; }
    input[type="range"]::-moz-range-thumb { width: 24px; height: 24px; }

    /* --- 3. Chữ dễ đọc hơn khi cầm tay --- */
    body { font-size: 15px; }
    .text-sm { font-size: .92rem; }
    .text-xs { font-size: .82rem; }
}

/* Bảng nhiều cột trên điện thoại: cuộn ngang trong khung của nó, và nói cho
   người dùng biết là cuộn được — không thì họ tưởng bảng bị cắt mất. */
@media (max-width: 720px) {
    .table-wrap {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        scroll-snap-type: x proximity;
    }
    .table-wrap::after {
        content: "";
        position: sticky;
        right: 0;
        display: block;
        width: 28px;
        margin-left: -28px;
        height: 100%;
        pointer-events: none;
        background: linear-gradient(90deg, transparent, var(--surface));
    }
}

/* ==========================================================================
   Camera: khung bám mã và trạng thái nhìn thấy được

   Người dùng cần biết máy đã "thấy" mã hay chưa. Trước đây chỉ có một dòng chữ
   đứng im, chĩa camera vào mà không biết có ăn không.
   ========================================================================== */

.cam-overlay { position: absolute; inset: 0; pointer-events: none; }
.cam-overlay svg { width: 100%; height: 100%; display: block; }
.cam-overlay polygon {
    fill: color-mix(in srgb, var(--success) 22%, transparent);
    stroke: var(--success);
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
}

/* Khung ngắm đổi màu theo trạng thái: trắng khi đang tìm, xanh khi thấy mã. */
.cam-stage[data-state="found"] .cam-frame,
.cam-stage[data-state="reading"] .cam-frame {
    border-color: var(--success);
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / .45), 0 0 18px color-mix(in srgb, var(--success) 55%, transparent);
}
.cam-stage[data-state="found"] .cam-status,
.cam-stage[data-state="reading"] .cam-status {
    background: var(--success);
    color: #fff;
}

/* Góc khung ngắm rõ hơn: bốn dấu ke thay vì một ô vuông mờ. */
.cam-frame::before,
.cam-frame::after {
    content: "";
    position: absolute;
    width: 22px; height: 22px;
    border: 3px solid currentColor;
    color: inherit;
}
.cam-frame::before { top: -3px; left: -3px; border-right: 0; border-bottom: 0; border-radius: 6px 0 0 0; }
.cam-frame::after { bottom: -3px; right: -3px; border-left: 0; border-top: 0; border-radius: 0 0 6px 0; }

@media (prefers-reduced-motion: reduce) {
    .cam-frame { transition: none; }
}
.cam-frame { transition: border-color var(--transition), box-shadow var(--transition); }

/* Khu dữ liệu cá nhân: vạch nhấn mép trái giống khu quản trị, khác màu để phân biệt. */
.app-sidebar.is-account { box-shadow: inset 3px 0 0 var(--success); }
.app-sidebar.is-account .brand-area { color: var(--success); }
.app-sidebar.is-account .sidebar-nav > .nav-group-title:last-of-type {
    margin-top: 18px;
    padding-top: 16px;
    border-top: 1px solid;
    border-top-color: var(--border);
}


/* =======================================================================
   Danh bạ công cụ và các công cụ tính
   =======================================================================

   Ba khối chính:
     · .tool-tiles  — lưới thẻ ở /tools và ở trang chủ
     · .tool-filter — ô lọc dính trên đầu danh sách
     · .result-panel nội dung — bảng nhãn/giá trị, dải bit, ô màu

   Mọi màu đều lấy từ token trong :root, không đặt màu cứng, để chế độ tối và
   chế độ sáng chỉ có một bản dựng. */

/* --- Lưới thẻ công cụ --------------------------------------------------- */

.tool-tiles {
    display: grid;
    /* 260px là bề rộng đủ cho hai dòng mô tả mà không xuống dòng lẻ một chữ. */
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
}

.tool-tile {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 14px 16px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    color: inherit;
    text-decoration: none;
    transition: border-color var(--transition), box-shadow var(--transition),
                transform var(--transition);
}

.tool-tile:hover {
    border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.tool-tile:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
}

.tool-tile-icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-text);
}

.tool-tile-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.tool-tile-name {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-weight: 600;
    font-size: .95rem;
    color: var(--text);
}

.tool-tile-desc {
    color: var(--text-muted);
    font-size: .85rem;
    line-height: 1.45;
}

.tool-tile-flag { font-size: .68rem; padding: 1px 6px; }

/* --- Thẻ mục cài đặt ---------------------------------------------------- */

/*
 * Cùng hình hài với ô công cụ ở `/tools`, chỉ khác ba chỗ: ô rộng hơn (320px)
 * vì câu mô tả dài hơn, có thêm một dòng đếm số thông số, và không có ô lọc.
 *
 * Dùng lại kiểu dáng thay vì dựng bộ mới: người quản trị đã quen bấm vào những
 * ô như thế này ở trang công cụ, và hai nơi trông giống nhau thì không phải
 * học lại lần thứ hai.
 */
.setting-cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 12px;
}

.setting-card {
    display: flex;
    gap: 12px;
    align-items: flex-start;
    padding: 16px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    color: inherit;
    text-decoration: none;
    transition: border-color var(--transition), box-shadow var(--transition),
                transform var(--transition);
}

.setting-card:hover {
    border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
}

.setting-card:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
}

.setting-card-icon {
    display: grid;
    place-items: center;
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-text);
}

.setting-card-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

.setting-card-name {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-weight: 600;
    font-size: .98rem;
    color: var(--text);
}

.setting-card-desc {
    color: var(--text-muted);
    font-size: .85rem;
    line-height: 1.5;
}

.setting-card-count {
    color: var(--text-faint);
    font-size: .78rem;
}

/* --- Ô lọc ------------------------------------------------------------- */

.tool-filter {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 22px;
}

.tool-filter-field {
    position: relative;
    display: flex;
    align-items: center;
    flex: 1 1 320px;
    max-width: 520px;
}

.tool-filter-field input {
    width: 100%;
    padding-left: 38px;
    padding-right: 38px;
}

.tool-filter-icon {
    position: absolute;
    left: 12px;
    display: grid;
    place-items: center;
    color: var(--text-faint);
    pointer-events: none;
}

.tool-filter-key {
    position: absolute;
    right: 10px;
    padding: 1px 6px;
    font-family: var(--font-mono);
    font-size: .72rem;
    color: var(--text-faint);
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    pointer-events: none;
}

/* --- Tiêu đề nhóm ------------------------------------------------------ */

.group-head {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 1.05rem;
    font-weight: 600;
    margin-bottom: 14px;
    color: var(--text);
}

.group-head-icon { display: grid; place-items: center; color: var(--text-muted); }

.group-head-count {
    font-size: .74rem;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--bg-muted);
    border-radius: var(--radius-full);
    padding: 1px 8px;
}

/* --- Lối đi ngang giữa các công cụ cùng nhóm --------------------------- */

.tool-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.tool-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 7px 12px;
    min-height: 36px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-full);
    color: var(--text-secondary);
    font-size: .86rem;
    text-decoration: none;
    transition: border-color var(--transition), color var(--transition),
                background var(--transition);
}

.tool-chip:hover {
    border-color: var(--accent);
    color: var(--accent-text);
    background: var(--accent-soft);
}

.tool-chip-all { border-style: dashed; }

/* --- Đầu khung kết quả -------------------------------------------------- */

.res-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    padding-bottom: 14px;
    margin-bottom: 16px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.res-head-color { display: flex; align-items: center; gap: 14px; }

.res-eyebrow {
    display: block;
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 4px;
}

.res-title {
    font-size: 1.3rem;
    font-weight: 600;
    margin: 0;
    line-height: 1.25;
    word-break: break-word;
}

.res-title-unit { color: var(--text-muted); font-weight: 400; font-size: .9rem; }

.res-flags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.res-block { margin-top: 20px; }
.res-block > .label { display: block; margin-bottom: 8px; }

/* --- Bảng nhãn / giá trị ------------------------------------------------ */

.kv { margin: 0; }

.kv-row {
    display: grid;
    /* Cột nhãn cố định để mọi giá trị thẳng hàng nhau — mắt dò xuống một cột
       thẳng nhanh hơn hẳn dò xuống một mép răng cưa. */
    grid-template-columns: minmax(120px, 34%) minmax(0, 1fr);
    gap: 10px 16px;
    padding: 7px 0;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.kv-row:last-child { border-bottom: 0; }

.kv dt {
    color: var(--text-muted);
    font-size: .85rem;
    margin: 0;
}

.kv dd {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    min-width: 0;
}

.kv-value {
    font-size: .9rem;
    color: var(--text);
    word-break: break-word;
}

.kv-value.mono { font-family: var(--font-mono); font-size: .85rem; }

.kv-hint { color: var(--text-faint); font-size: .76rem; }

/* --- Ba con số nổi bật -------------------------------------------------- */

.stat-tile {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 14px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    min-width: 0;
}

.stat-tile .stat-label {
    font-size: .74rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
}

.stat-tile .stat-value {
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--text);
    word-break: break-all;
}

.stat-tile .stat-value.mono { font-family: var(--font-mono); font-size: .92rem; }

/* --- Dải bit ------------------------------------------------------------ */

.bits-block { margin-top: 18px; }
.bits-block > .label { display: block; margin-bottom: 6px; }

.bits-line {
    display: block;
    padding: 9px 11px;
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: .82rem;
    letter-spacing: .03em;
    color: var(--text);
    overflow-x: auto;
    white-space: nowrap;
}

.bits-mask { color: var(--accent-text); }

/* Từng bit là một ô vuông: nhìn dọc một cột là thấy phép toán đã làm gì với
   bit đó, thứ mà một chuỗi 0 và 1 liền nhau không nói được. */
.bit-strip {
    display: flex;
    gap: 2px;
    flex-wrap: wrap;
    padding: 8px 0;
}

.bit {
    display: grid;
    place-items: center;
    width: 22px;
    height: 26px;
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    font-family: var(--font-mono);
    font-size: .78rem;
    color: var(--text-faint);
}

.bit-on {
    background: var(--accent-soft);
    border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
    color: var(--accent-text);
    font-weight: 600;
}

/* Khe rộng sau mỗi bốn bit: đọc ra từng nửa byte mà không phải đếm. */
.bit-gap { margin-right: 8px; }

/* --- Bảng đổi đơn vị ---------------------------------------------------- */

.convert-table .convert-col-sym { color: var(--text-muted); width: 1%; white-space: nowrap; }

.data-table tr.is-source {
    background: var(--accent-soft);
}
.data-table tr.is-source td { font-weight: 600; }

/* --- Màu ---------------------------------------------------------------- */

.color-input { display: flex; gap: 8px; align-items: stretch; }
.color-input input[type="text"] { flex: 1 1 auto; min-width: 0; }

.color-swatch {
    flex: 0 0 46px;
    width: 46px;
    padding: 2px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    cursor: pointer;
}

.color-chip {
    display: block;
    width: 52px;
    height: 52px;
    border-radius: var(--radius);
    border: 1px solid;
    border-color: var(--border-strong);
    flex: 0 0 auto;
}

.contrast-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 12px;
}

.contrast-demo,
.contrast-pair {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 16px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
}

.contrast-pair { margin-top: 12px; }

.contrast-ratio { font-size: 1.4rem; font-weight: 700; line-height: 1; }
.contrast-caption { font-size: .8rem; opacity: .85; }
.contrast-levels { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }

/* Đạt hay không đạt phải đọc được cả khi in đen trắng, nên ngoài màu còn có
   dấu tích và dấu gạch chéo. */
.wcag-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    font-size: .74rem;
    font-weight: 600;
    background: rgba(255, 255, 255, .82);
    color: #1b1b1b;
}

.wcag-badge.is-pass { box-shadow: inset 0 0 0 1px rgba(0, 135, 90, .55); }
.wcag-badge.is-fail { box-shadow: inset 0 0 0 1px rgba(222, 53, 11, .55); }
.wcag-badge .wcag-mark { font-size: .8rem; line-height: 1; }
.wcag-badge.is-pass .wcag-mark { color: #00714c; }
.wcag-badge.is-fail .wcag-mark { color: #c92c05; }

.swatch-row { display: flex; flex-wrap: wrap; gap: 6px; }

.swatch {
    display: grid;
    place-items: center;
    width: 56px;
    height: 46px;
    padding: 0;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    cursor: pointer;
    font-size: .7rem;
    font-weight: 600;
}

.swatch-wide { width: 88px; }
.swatch-name { color: #fff; text-shadow: 0 1px 2px rgba(0, 0, 0, .55); font-size: .68rem; }
.swatch-step { text-shadow: 0 1px 2px rgba(0, 0, 0, .25); }

/* --- Danh sách định danh và lịch chạy ---------------------------------- */

.token-list, .run-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.token-item {
    padding: 9px 12px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    font-size: .85rem;
    cursor: pointer;
    word-break: break-all;
    transition: border-color var(--transition), background var(--transition);
}

.token-item:hover { border-color: var(--accent); background: var(--accent-soft); }

.run-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 0;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.run-item:last-child { border-bottom: 0; }
.run-time { font-size: .88rem; }
.run-in { font-size: .78rem; }

/* --- Mã QR -------------------------------------------------------------- */

.qr-stage {
    display: grid;
    place-items: center;
    padding: 20px;
    background: #ffffff;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
}

.qr-image { max-width: 100%; height: auto; image-rendering: pixelated; }

/* --- Khối mã nguồn và ghi chú ------------------------------------------ */

.code-block {
    margin: 0;
    padding: 12px 14px;
    background: var(--bg-muted);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: .82rem;
    line-height: 1.55;
    color: var(--text);
    overflow-x: auto;
    cursor: pointer;
}

.note-box {
    margin-top: 14px;
    padding: 11px 13px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-left: 3px solid;
    border-left-color: var(--border-strong);
    border-radius: var(--radius);
}

.note-info { background: var(--info-soft); border-left-color: var(--info); }
.note-warn { background: var(--warning-soft); border-left-color: var(--warning); }
.note-bad  { background: var(--danger-soft); border-left-color: var(--danger); }

.empty-state-bad { color: var(--danger); }
.empty-state-icon { display: block; margin-bottom: 8px; }

.pill-xs { font-size: .66rem; padding: 0 5px; }

/* --- Biểu mẫu: hàng hai cột và khối gập lại ---------------------------- */

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.field-row > .field { margin-bottom: 16px; }

.field-group {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid;
    border-top-color: var(--border);
}

.field-group > summary {
    cursor: pointer;
    font-size: .86rem;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 12px;
    list-style: none;
}

.field-group > summary::-webkit-details-marker { display: none; }

/* Mũi tên tự vẽ: dấu tam giác mặc định của trình duyệt mỗi nơi một kiểu. */
.field-group > summary::before {
    content: '';
    display: inline-block;
    width: 0;
    height: 0;
    margin-right: 8px;
    border-left: 5px solid currentColor;
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transition: transform var(--transition);
}

.field-group[open] > summary::before { transform: rotate(90deg); }
.field-group > summary:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }

.chip-row { display: flex; flex-wrap: wrap; gap: 6px; }

.chip-btn {
    padding: 6px 10px;
    min-height: 34px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    font-size: .78rem;
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--transition), background var(--transition);
}

.chip-btn:hover { border-color: var(--accent); background: var(--accent-soft); color: var(--accent-text); }

/* --- Điện thoại --------------------------------------------------------- */

@media (max-width: 860px) {

    /* Cột nhãn 34% ở màn hình hẹp chỉ còn chừng trăm điểm ảnh, chữ vỡ thành
       từng chữ một. Xếp chồng thì mỗi dòng được cả bề ngang. */
    .kv-row { grid-template-columns: minmax(0, 1fr); gap: 2px; padding: 9px 0; }
    .kv dt { font-size: .8rem; }

    .contrast-row { grid-template-columns: minmax(0, 1fr); }
    .bit { width: 20px; height: 24px; }
    .tool-filter-key { display: none; }

    /* Mục tiêu chạm 44px theo Apple HIG; ô nhập 16px để iOS Safari không tự
       phóng to cả trang khi chạm vào. */
    .chip-btn, .tool-chip { min-height: 44px; }
    .swatch { width: 52px; height: 44px; }
    .token-item { padding: 12px; }

    /* Hai chỗ đo được dưới 44px ở bề ngang 375px: chữ mở khối gập lại chỉ cao
       22px (dưới cả ngưỡng 24px của WCAG), và ô chọn màu của trình duyệt cao
       27px. Cả hai đều là thứ phải chạm trúng bằng ngón tay. */
    .field-group > summary {
        min-height: 44px;
        display: flex;
        align-items: center;
        margin-bottom: 8px;
    }
    .color-swatch { min-height: 44px; }
}


/* --- Giám sát: việc nào đang chạy bằng nhà cung cấp nào ----------------- */

.cap-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 12px;
    margin-top: 12px;
}

.cap-card {
    padding: 14px 16px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}

.cap-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 10px;
}

.cap-name { font-weight: 600; font-size: .9rem; }

.cap-list { list-style: none; margin: 0 0 8px; padding: 0; }

.cap-row {
    display: grid;
    grid-template-columns: 10px minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 8px;
    padding: 5px 0;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.cap-row:last-child { border-bottom: 0; }
.cap-provider { font-size: .84rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Vai trò in nhạt và nhỏ: nó là chú thích, không phải số liệu. */
.cap-role {
    font-size: .7rem;
    color: var(--text-faint);
    text-transform: uppercase;
    letter-spacing: .03em;
}

.cap-ms { font-family: var(--font-mono); }
.cap-foot { margin-top: 6px; }

/* Chưa gọi lần nào là một trạng thái riêng, không phải "đang chờ": chấm đứng
   im màu xám, không nhấp nháy như .mon-dot-idle. */
.mon-dot-unknown { background: var(--border-strong); }

@media (max-width: 860px) {
    .cap-grid { grid-template-columns: minmax(0, 1fr); }
}


/* --- Nhãn nhỏ mang dữ liệu (dải RFC, khối còn thừa) --------------------- */

.tag-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-full);
    font-size: .78rem;
    color: var(--text-secondary);
}

.tag-chip.copyable { cursor: pointer; }
.tag-chip.copyable:hover { border-color: var(--accent); color: var(--accent-text); }
.tag-chip-sep { color: var(--text-faint); }

/* Bảng dài (bảng tra prefix 129 dòng) cuộn trong khung của nó. */
.table-scroll { max-height: 460px; overflow-y: auto; }

/* --- Bố cục dãy bit của số thực IEEE 754 -------------------------------- */

/*
 * Ba đoạn ba màu. Đây là chỗ duy nhất trong bộ này dùng nhiều hơn một màu nhấn,
 * và có lý do: ba đoạn bit phải phân biệt được bằng mắt trong một hàng liền
 * mạch, mà vị trí thì không nói lên đoạn nào là đoạn nào.
 */
.float-bits {
    display: flex;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: .82rem;
    letter-spacing: .06em;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.float-seg { padding: 9px 4px; word-break: break-all; }
.float-sign { background: var(--danger-soft); color: var(--danger-text); }
.float-exp  { background: var(--warning-soft); color: var(--warning-text); }
.float-mant { background: var(--accent-soft); color: var(--accent-text); flex: 1 1 auto; }

.float-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-top: 8px;
    font-size: .76rem;
    color: var(--text-muted);
}

.float-legend > span { display: inline-flex; align-items: center; gap: 6px; }

.float-key {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    display: inline-block;
}

.float-key.float-sign { background: var(--danger); }
.float-key.float-exp  { background: var(--warning); }
.float-key.float-mant { background: var(--accent); }

@media (max-width: 860px) {
    .table-scroll { max-height: 320px; }
}


/* --- Mô phỏng mù màu --------------------------------------------------- */

.blind-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: 10px;
}

.blind-cell { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.blind-swatch {
    display: block;
    height: 56px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
}

.blind-swatch.copyable { cursor: pointer; }
.blind-name { font-size: .76rem; color: var(--text-secondary); line-height: 1.3; }
.blind-hex { font-size: .7rem; color: var(--text-faint); }


/* =======================================================================
   Khu quản trị — làm cho số liệu nổi lên và bảng dễ dò
   =======================================================================

   Ba việc, không hơn:

     1. Ô số liệu phải đọc được từ xa. Trước đây nhãn, số và ghi chú cùng một
        sắc xám nhạt như nhau, mắt phải dừng lại đọc mới biết đâu là con số.
     2. Bảng dài phải dò theo hàng được. Thêm nền mờ khi rê chuột và một vạch
        màu ở mép trái hàng đang trỏ tới.
     3. Khu nguy hiểm (xoá hàng loạt) phải nhìn ra ngay là khu nguy hiểm, không
        lẫn với thẻ bình thường.

   Không đổi bảng màu, không thêm màu mới: tất cả lấy từ token ở `:root`.
   ======================================================================= */

/* Vạch màu mảnh ở mép trên ô số liệu: đủ để mắt bắt được ranh giới giữa các ô
   khi cả hàng cùng một nền trắng, mà không ồn như viền đậm. */
.stat {
    position: relative;
    overflow: hidden;
    transition: border-color var(--transition), box-shadow var(--transition);
}

.stat::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 3px;
    background: var(--accent);
    opacity: .85;
}

.stat:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}

/* Con số là thứ người ta vào đây để xem, nên nó phải to hơn hẳn phần còn lại. */
.stat-value { font-size: 2.05rem; }
.stat-label { color: var(--text-secondary); }

/* --- Thanh lọc: mỗi ô có nhãn riêng ------------------------------------- */

/*
 * Bản trước là một hàng ngang gồm ô tìm kiếm, ô chọn và nút, không ô nào có
 * nhãn - người dùng phải bấm thử mới biết ô chọn kia lọc theo cái gì. Nay mỗi
 * ô một nhãn nhỏ, và cả thanh tự xuống dòng khi hẹp.
 */
.filter-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
}

.filter-cell { display: flex; flex-direction: column; gap: 4px; min-width: 140px; }
.filter-cell .label { margin: 0; }
.filter-grow { flex: 1 1 220px; min-width: 200px; }
.filter-actions { flex-direction: row; align-items: center; gap: 8px; min-width: 0; }

/* --- Đầu cột bấm được để xếp thứ tự ------------------------------------- */

.sort-link {
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
}

.sort-link:hover { color: var(--accent-text); }
.sort-link.is-current { color: var(--accent-text); font-weight: var(--weight-bold); }

/* --- Bảng dò theo hàng --------------------------------------------------- */

/*
 * Vạch màu vẽ bằng `box-shadow` chứ không bằng `border-left`: viền làm ô đầu
 * tiên rộng thêm 3px nên cả hàng nhích sang phải mỗi lần rê chuột qua.
 */
.table-hover tbody tr { transition: background var(--transition); }

.table-hover tbody tr:hover {
    background: var(--bg-subtle);
    box-shadow: inset 3px 0 0 var(--accent);
}

/* --- Thẻ của khu nguy hiểm ---------------------------------------------- */

.card-warn {
    border-color: var(--warning);
    background: var(--warning-soft);
}

.card-warn .card-title { color: var(--warning-text); }

/* Chữ phụ trong thẻ cảnh báo phải đọc được trên nền vàng nhạt: `--text-muted`
   đo trên nền đó chỉ còn 3,9:1, dưới ngưỡng 4,5:1 của WCAG 2.2. */
.card-warn .text-muted { color: var(--text-secondary); }

.row-wrap { flex-wrap: wrap; }

/* --- Trang hướng dẫn quản trị ------------------------------------------- */

.guide-steps {
    margin: 0;
    padding-left: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    color: var(--text-secondary);
    line-height: 1.6;
}

.guide-steps code,
.guide-task code {
    font-family: var(--font-mono);
    font-size: .86em;
    padding: 1px 5px;
    border-radius: var(--radius-sm);
    background: var(--bg-muted);
    color: var(--text);
}

/* Mỗi nhóm thông số là một khối có đường kẻ trên, để mắt biết chỗ nhóm mới bắt
   đầu mà không cần thêm một thẻ lồng nữa. */
.guide-group { padding: 6px 0 4px; border-top: 1px solid; border-top-color: var(--border); }
.guide-group:first-of-type { border-top: none; }
.guide-group .nav-group-title { display: block; padding: 12px 18px 6px; }

.guide-tasks {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 14px;
}

.guide-task {
    padding: 14px 16px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}

.guide-task-title { font-size: .95rem; margin: 0 0 6px; }
.guide-task p { margin: 0 0 8px; }

/* --- Gõ chữ: đẩy cho mượt, và neo phần dưới lại ------------------------- */

/*
 * Hai việc, cùng một mục đích: mắt không bị giật.
 *
 * 1. CHIỀU CAO CHẠY MƯỢT. Tiêu đề dài thêm một dòng là cả khối bên dưới nhảy
 *    một phát 34px. JavaScript đặt chiều cao thật cho thẻ tiêu đề ở từng bước,
 *    còn luật này biến cái nhảy đó thành một lượt trượt 260ms. Đường cong
 *    `cubic-bezier(.22,1,.36,1)` chậm dần về cuối - chuyển động dừng lại êm chứ
 *    không cắt ngang.
 *
 * 2. PHẦN DƯỚI ĐỨNG YÊN. Trong lúc gõ, cột chữ bị khoá đúng chiều cao cuối
 *    cùng (xem `initTitle()`), nên cho khối nút dính đáy cột là nó nằm sẵn ở
 *    đúng chỗ nó sẽ nằm - không nhúc nhích một lần nào. Gõ xong thì lớp
 *    `dang-go` bị gỡ, mọi thứ trở lại dòng chảy bình thường.
 *
 * Chỉ áp trong lúc gõ: để `margin-top: auto` vĩnh viễn thì ở bề ngang mà cột
 * chữ ngắn hơn khung trình diễn, hai nút sẽ bị đẩy xuống tận đáy và hở một
 * khoảng trống giữa trang.
 */
.hero-copy.dang-go {
    display: flex;
    flex-direction: column;
}

.hero-copy.dang-go .hero-foot { margin-top: auto; }

.hero-title.dang-day {
    transition: height 260ms cubic-bezier(.22, 1, .36, 1);
    /*
     * Cắt phần thừa TRONG LÚC TRƯỢT.
     *
     * Chiều cao đang chạy từ giá trị cũ tới giá trị mới, nên suốt 260ms đó ô
     * chữ thấp hơn chính nội dung của nó: dòng vừa gõ ra tràn xuống và ĐÈ LÊN
     * đoạn mô tả bên dưới. Cắt đi thì dòng mới lộ dần theo ô chữ cao lên, nhìn
     * như nó trượt vào chỗ.
     *
     * Chỉ cắt trong lúc trượt: lớp `dang-day` bị gỡ ngay khi lượt trượt cuối
     * xong, nên ở trạng thái đứng yên không có gì bị cắt - dấu tiếng Việt và
     * đuôi chữ "g", "y" của dòng cuối vẫn nguyên.
     */
    overflow: hidden;
}

/* Không ai muốn chuyển động thì không có lượt trượt nào. */
@media (prefers-reduced-motion: reduce) {
    .hero-title.dang-day { transition: none; }
}

/* --- Ô quảng cáo --------------------------------------------------------- */

/*
 * Ba điều bắt buộc, học từ những trang bán quảng cáo mà vẫn dùng được:
 *
 *   1. CÓ NHÃN. Người đọc phải phân biệt được đâu là nội dung của trang, đâu là
 *      quảng cáo - đây cũng là yêu cầu của hầu hết mạng quảng cáo.
 *   2. CÓ CHỖ SẴN. `min-height` giữ chỗ trước, không thì quảng cáo tải xong sẽ
 *      đẩy nội dung xuống và người đang đọc mất dòng (Cumulative Layout Shift).
 *   3. KHÔNG IN RA GIẤY và không chiếm chỗ khi in.
 */
.ad-slot {
    display: block;
    margin: 20px 0;
    padding: 10px 12px 12px;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    background: var(--bg-subtle);
}

.ad-label {
    display: block;
    margin-bottom: 6px;
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--text-faint);
    font-weight: var(--weight-semibold);
}

/* Giữ chỗ theo hai khổ quảng cáo hay gặp nhất, để trang không nhảy khi mã của
   mạng quảng cáo tải xong. */
.ad-body { min-height: 90px; overflow: hidden; }
.ad-slot-footer .ad-body { min-height: 90px; }
.ad-slot-list .ad-body,
.ad-slot-tool .ad-body { min-height: 100px; }

/* Quảng cáo không bao giờ được rộng hơn cột chứa nó. */
.ad-body :where(img, iframe, ins) { max-width: 100%; }

@media print {
    .ad-slot { display: none !important; }
}

/* --- Lật giao diện sáng/tối -------------------------------------------- */

/*
 * Trong đúng một khung hình lúc lật giao diện, mọi hiệu ứng bị tắt. Xem chú
 * thích trong Theme.apply() ở app.js: đây không phải chuyện thẩm mỹ mà là cách
 * chữa một lỗi thật — thuộc tính đang trong hiệu ứng chuyển màu giữ nguyên giá
 * trị cũ khi biến màu đổi, nên viền thẻ mắc kẹt ở màu của chế độ sáng.
 */
.theme-switching,
.theme-switching *,
.theme-switching *::before,
.theme-switching *::after {
    transition: none !important;
    animation-duration: 0s !important;
}

/*
 * Biểu tượng của nút đổi giao diện phải nói đúng cái nền đang có: nền sáng thì
 * mặt trăng, nền tối thì mặt trời. Trước đây gắn cứng một hình mặt trăng nên
 * ở chế độ tối nút vẫn mời "bật chế độ tối".
 *
 * Làm bằng CSS vì chế độ "theo hệ thống" không để lại thuộc tính nào trên thẻ
 * <html>: chỉ `prefers-color-scheme` phân biệt được sáng với tối, và nó đổi
 * ngay khi người dùng đổi cài đặt máy, không cần ai nghe ngóng hộ.
 *
 * Ba nhánh, đúng ba trạng thái mà Theme.apply() tạo ra:
 *   · không có data-theme  -> theo hệ thống, hỏi prefers-color-scheme
 *   · data-theme="light"   -> sáng, kể cả khi máy đang để tối
 *   · data-theme="dark"    -> tối, kể cả khi máy đang để sáng
 * Nhánh giữa không cần viết ra: khối @media đã tự loại nó bằng :not().
 */
.icon-theme-sang { display: none; }

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .icon-theme-toi  { display: none; }
    :root:not([data-theme="light"]) .icon-theme-sang { display: block; }
}

:root[data-theme="dark"] .icon-theme-toi  { display: none; }
:root[data-theme="dark"] .icon-theme-sang { display: block; }


/* =======================================================================
   Trang chủ
   =======================================================================

   Ba khối: phần mở đầu có nền động, dải số liệu, và các mục nội dung.

   Chuyển động ở đây có ba mức, tất cả đều tắt khi hệ điều hành báo người dùng
   không muốn chuyển động:
     · lớp sáng nền — 24 giây một vòng, gần như không nhận ra
     · con trỏ nhấp nháy trong khung trình diễn
     · dòng kết quả trượt vào khi vừa tính xong
*/

.landing-hero {
    position: relative;
    overflow: hidden;
    padding-block: 56px 64px;
    margin-bottom: 8px;
    /* Kéo tràn ra hai mép để nền phủ hết bề ngang, kể cả khi trang có thanh bên. */
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

/* Lớp sáng nền: hai vệt màu nhấn rất mờ, trôi rất chậm. Đây là thứ duy nhất
   trên trang không mang thông tin — nên nó phải mờ tới mức không tranh chỗ với
   chữ, và phải biến mất hẳn khi người dùng không muốn chuyển động. */
.hero-aurora {
    position: absolute;
    inset: -40% -20% auto -20%;
    height: 130%;
    pointer-events: none;
    background:
        radial-gradient(38% 44% at 22% 32%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 70%),
        radial-gradient(34% 40% at 78% 22%, color-mix(in srgb, var(--info) 18%, transparent), transparent 68%);
    filter: blur(18px);
    animation: hero-drift 24s ease-in-out infinite alternate;
}

@keyframes hero-drift {
    from { transform: translate3d(-2%, -1%, 0) scale(1); }
    to   { transform: translate3d(3%, 2%, 0) scale(1.08); }
}

/* Lưới kỹ thuật rất nhạt, mờ dần về đáy. Gợi cảm giác bản vẽ chứ không phải
   hoạ tiết trang trí. */
.hero-grid-lines {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background-image:
        linear-gradient(to right, color-mix(in srgb, var(--border-strong) 55%, transparent) 1px, transparent 1px),
        linear-gradient(to bottom, color-mix(in srgb, var(--border-strong) 55%, transparent) 1px, transparent 1px);
    background-size: 44px 44px;
    -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,.5), transparent 78%);
    mask-image: linear-gradient(to bottom, rgba(0,0,0,.5), transparent 78%);
    opacity: .5;
}

.landing-hero-inner {
    position: relative;
    display: grid;
    /*
     * Khung trình diễn là thứ người mới vào nhìn đầu tiên, nên cho nó chỗ. Chia
     * theo tỉ lệ chứ không đặt cứng một cột: thanh bên đã ăn 295px, nên đặt
     * cứng 560px cho khung thì cột chữ chỉ còn 348px ở cửa sổ 1280px.
     */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: 40px;
    /* `stretch` chứ không phải `center`: khung trình diễn kéo dài bằng đúng cột
       chữ bên trái, nên hai bên thẳng mép trên và mép dưới. */
    align-items: stretch;
}

.hero-copy { min-width: 0; }

.hero-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px 5px 10px;
    margin-bottom: 18px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-full);
    font-size: .78rem;
    font-weight: 600;
    color: var(--text-secondary);
}

/* Chấm xanh thở nhẹ: dấu hiệu "đang chạy", quen thuộc từ các trang trạng thái. */
.hero-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--success);
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 60%, transparent);
    animation: hero-pulse 2.4s ease-out infinite;
}

@keyframes hero-pulse {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 55%, transparent); }
    70%  { box-shadow: 0 0 0 7px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.hero-title {
    font-size: clamp(1.85rem, 1.2rem + 2.2vw, 2.9rem);
    line-height: 1.15;
    letter-spacing: -.02em;
    margin: 0 0 14px;
    max-width: 17ch;
}

.hero-lead {
    font-size: 1.02rem;
    line-height: 1.6;
    color: var(--text-secondary);
    max-width: 52ch;
    margin: 0 0 24px;
}

.hero-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 22px; }

.btn-arrow { transition: transform var(--transition); }
.btn:hover .btn-arrow { transform: translateX(3px); }

.hero-points {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
}

.hero-points li {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: .86rem;
    color: var(--text-muted);
}

.hero-check {
    display: grid;
    place-items: center;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: var(--success-soft);
    color: var(--success);
    font-size: .68rem;
    font-weight: 700;
}

/* --- Khung trình diễn --------------------------------------------------- */

.hero-demo { min-width: 0; }

.demo-window {
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.demo-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    background: var(--bg-subtle);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.demo-dots { display: inline-flex; gap: 5px; }
.demo-dots i {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--bg-inset);
}

/* Ba chấm đèn của thanh cửa sổ. Màu viết thẳng chứ không lấy từ `--danger`,
   `--warning`, `--success`: ba biến đó đổi giá trị giữa nền sáng và nền tối để
   giữ tương phản với CHỮ đặt trên chúng. Ba chấm này không có chữ nào nằm trên,
   nên chúng phải giữ nguyên một màu ở cả hai chế độ thì mới ra đúng thứ người
   ta quen nhìn ở thanh tiêu đề cửa sổ. */
.demo-dots i:nth-child(1) { background: #ff5f57; }
.demo-dots i:nth-child(2) { background: #febc2e; }
.demo-dots i:nth-child(3) { background: #28c840; }

.demo-title {
    flex: 1 1 auto;
    font-size: .82rem;
    font-weight: 600;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.demo-open {
    font-size: .76rem;
    color: var(--accent-text);
    text-decoration: none;
    white-space: nowrap;
}
.demo-open:hover { text-decoration: underline; }

.demo-body {
    padding: 16px;
    /* Chiều cao cố định: sáu công cụ có số dòng khác nhau, không chốt chiều cao
       thì cả trang nhảy lên nhảy xuống mỗi lần đổi công cụ. */
    min-height: 216px;
}

.demo-prompt {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 0 0 14px;
    font-family: var(--font-mono);
    font-size: .88rem;
    color: var(--text);
    word-break: break-all;
}

.demo-caret { color: var(--accent-text); font-weight: 700; }

.demo-cursor {
    display: inline-block;
    width: 7px;
    height: 1em;
    margin-left: 2px;
    background: var(--accent);
    vertical-align: text-bottom;
    animation: demo-blink 1.05s step-end infinite;
}

@keyframes demo-blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }

.demo-out { margin: 0; display: flex; flex-direction: column; gap: 7px; }

.demo-row {
    display: grid;
    grid-template-columns: minmax(0, 42%) minmax(0, 1fr);
    gap: 12px;
    align-items: baseline;
    padding-bottom: 7px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.demo-row:last-child { border-bottom: 0; padding-bottom: 0; }
.demo-row.is-entering { animation: demo-in .34s ease-out both; }

@keyframes demo-in {
    from { opacity: 0; transform: translateY(5px); }
    to   { opacity: 1; transform: none; }
}

.demo-row dt { margin: 0; font-size: .8rem; color: var(--text-muted); }
.demo-row dd {
    margin: 0;
    font-family: var(--font-mono);
    font-size: .84rem;
    color: var(--text);
    word-break: break-all;
}

.demo-foot {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px;
    background: var(--bg-subtle);
    border-top: 1px solid;
    border-top-color: var(--border);
}

.demo-ticks { display: inline-flex; gap: 5px; }
.demo-ticks i {
    width: 16px;
    height: 3px;
    border-radius: 2px;
    background: var(--bg-inset);
    transition: background var(--transition);
}
.demo-ticks i.is-on { background: var(--accent); }

.demo-note { font-size: .74rem; color: var(--text-faint); }

/* --- Khuôn theo từng công cụ -------------------------------------------- */

/*
 * Cả ba khuôn cùng nằm trong DOM, chỉ một cái được hiện. Dùng `display:none`
 * chứ không `visibility`: hai khuôn kia phải KHÔNG chiếm chỗ, nếu không chiều
 * cao khung là tổng của cả ba.
 *
 * Chiều cao không đổi giữa các lượt vẫn do `.demo-body { min-height }` giữ,
 * nên mỗi khuôn chỉ cần thấp hơn mức đó là trang đứng yên.
 */
/* `.demo-window` đứng trước là BẮT BUỘC, không phải cho gọn mắt: khối
   `.demo-panel { display: grid }` nằm ở cuối tệp, cùng độ ưu tiên một lớp mà
   đứng sau, nên `.demo-skin { display: none }` trơn thua nó và khuôn lưới hiện
   đè lên khuôn đang chọn. Thêm một lớp nữa là đủ thắng. */
.demo-window .demo-skin { display: none; }
.demo-window[data-demo-skin="code"]  .demo-skin-code   { display: block; margin-bottom: 12px; }
.demo-window[data-demo-skin="meter"] .demo-skin-meter  { display: block; margin-bottom: 12px; }
.demo-window[data-demo-skin="proxy"] .demo-skin-proxy  { display: block; margin-bottom: 12px; }

/* --- Khối mời ủng hộ ---------------------------------------------------- */

/*
 * Nằm cuối trang chủ, dưới lời mời dùng công cụ. Cố ý KHÔNG dùng màu nhấn của
 * nút bấm chính: đây là lời mời, không phải việc trang muốn người ta làm. Nút
 * "Xem tất cả công cụ" ngay phía trên mới là việc đó, và hai khối cùng màu thì
 * chúng cạnh tranh nhau.
 */
.donate-box {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    margin-top: 18px;
    padding: 20px 24px;
    background: var(--bg-subtle);
    border: 1px dashed;
    border-color: var(--border-strong);
    border-radius: var(--radius-lg);
}

.donate-main { flex: 1 1 320px; min-width: 0; }
.donate-title { margin: 0 0 6px; font-size: 1.05rem; }
.donate-lead {
    margin: 0;
    font-size: .86rem;
    line-height: 1.6;
    color: var(--text-muted);
}

.donate-ways {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 16px;
}

.donate-way { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.donate-way-key {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: .68rem;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--text-faint);
}
.donate-way-val {
    font-size: .92rem;
    color: var(--text);
    cursor: pointer;
    /* Địa chỉ ví USDT dài 34-42 ký tự và không có chỗ nào để ngắt dòng tự
       nhiên, nên phải cho bẻ giữa chuỗi — không thì nó đẩy cả khối tràn ra. */
    word-break: break-all;
}
.donate-way-val:hover { color: var(--accent-text); }

/* Mạng của ví USDT, đứng ngay cạnh nhãn: gửi nhầm mạng là mất tiền, nên nó
   không được nằm rời ra một chỗ khác. */
.donate-way-net {
    font-size: .62rem;
    padding: 0 5px;
    letter-spacing: .04em;
    text-transform: none;
}

/*
 * Khối do chủ trang tự viết.
 *
 * Không áp kiểu dáng gì lên phần bên trong: người ta chọn tự viết HTML chính là
 * để tự quyết cách trình bày, mà mình đặt luật lên đó thì thành ra giành phần.
 * Chỉ chốt đúng một điều — không được tràn ra ngoài khung.
 */
.donate-ways-html {
    display: block;
    flex: 1 1 320px;
    min-width: 0;
    max-width: 100%;
    overflow-wrap: anywhere;
}
.donate-ways-html img,
.donate-ways-html iframe { max-width: 100%; }

@media (max-width: 640px) {
    .donate-box { padding: 16px; }
    .donate-ways { width: 100%; }
    .donate-link { width: 100%; justify-content: center; }
    /* Nhiều cách nhận thì xếp dọc cho dễ đọc, thay vì cuốn dòng lộn xộn. */
    .donate-way { width: 100%; }
}

/* --- Bản sao khoá cứng của biểu mẫu ------------------------------------- */

/* Hai công cụ nhận biểu mẫu chứ không nhận một dòng chữ. Khối này thay dòng
   gõ, và chỉ hiện đúng lượt của công cụ đó. */
.demo-form { display: none; margin: 0 0 12px; }
.demo-window[data-demo-form="password"] .demo-form-password,
.demo-window[data-demo-form="ports"]    .demo-form-ports { display: block; }

/* Có biểu mẫu rồi thì dòng gõ là thừa: hai thứ cùng nói một nội dung, mà dòng
   gõ lại nói bằng câu chữ - đúng cái đang muốn bỏ đi. */
.demo-window[data-demo-form="password"] .demo-prompt,
.demo-window[data-demo-form="ports"]    .demo-prompt { display: none; }

.demo-f-line {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 6px;
    font-size: .78rem;
}
.demo-f-key { color: var(--text-muted); }
.demo-f-val { color: var(--text); font-size: .9rem; }
.demo-f-host { color: var(--text); font-size: .82rem; }

/* Thanh trượt dùng nguyên `input[type=range]` của trang thật, chỉ chặn tương
   tác. `pointer-events: none` chứ không chỉ `disabled`: con trỏ chuột không
   được đổi thành hình "cấm" khi rê qua - đây là hình minh hoạ, không phải một
   ô đang bị khoá tạm. */
.demo-form input[type="range"] {
    width: 100%;
    margin: 0 0 10px;
    pointer-events: none;
    opacity: .85;
}

.demo-f-tabs { display: inline-flex; gap: 4px; margin-bottom: 10px; }
.demo-f-tabs .tab {
    padding: 3px 12px;
    font-size: .74rem;
    pointer-events: none;
}

.demo-f-checks { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.demo-f-check {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin: 0;
    font-size: .76rem;
    color: var(--text-secondary);
    pointer-events: none;
}
.demo-f-check input { pointer-events: none; }

/* Bảng kiểm proxy -- dùng lại `.data-table` và `.proxy-table` của trang /proxy,
   kể cả hai lớp `proxy-live`/`proxy-die` tô nền từng dòng. Chỉ bóp nhỏ cho vừa
   khung trình diễn. */
/* `.proxy-table` khai `min-width: 984px` - đúng cho trang /proxy vì nó có bảy
   cột và chiếm cả bề ngang màn hình, nhưng trong khung 466px thì con số ấy bắt
   bảng cuộn ngang và cột trạng thái biến mất. Gỡ trần đó riêng cho khung demo;
   phải viết hai lớp mới thắng, vì luật gốc nằm sau trong tệp. */
.demo-skin-proxy .proxy-table { min-width: 0; }
.demo-proxy-table { font-size: .76rem; table-layout: fixed; width: 100%; }
.demo-proxy-table th,
.demo-proxy-table td { padding: 5px 8px; }

/* CHỈ cột địa chỉ mới cấm xuống dòng: `203.0.113.45:8080` bị bẻ đôi thì không
   còn đọc ra địa chỉ. Ba cột kia phải xuống dòng được, nếu không câu "Cổng này
   không được phép" kéo bảng rộng 984px trong một khung 466px và cột trạng thái
   bị đẩy khuất hẳn -- đúng thứ đáng xem nhất thì mất. */
.demo-proxy-table th:first-child,
.demo-proxy-table td:first-child { white-space: nowrap; width: 45%; }
.demo-proxy-table td:not(:first-child) { white-space: normal; word-break: break-word; }
.demo-proxy-table .pill {
    font-size: .64rem;
    padding: 1px 5px;
    white-space: normal;
    line-height: 1.35;
}
/* `margin-inline: 0` không phải cho gọn mắt mà chữa một chỗ bị cắt thật.
   Dưới 768px, `.table-wrap` được cho tràn ra hai bên 16px rồi bù lại bằng
   padding — mẹo để bảng chạy hết bề ngang thẻ trên trang thật. Khung trình
   diễn thì `overflow: hidden`, nên 16px tràn ấy bị CẮT, kéo theo mấy pixel
   cuối của cột trạng thái. Đo được ở 375px: `.demo-body` có clientWidth 342
   mà scrollWidth 348. */
.demo-skin-proxy .table-wrap {
    border-radius: var(--radius);
    overflow: hidden;
    margin-inline: 0;
    padding-inline: 0;
}

/* Dưới 480px thì bốn cột không vừa kể cả khi đã cho xuống dòng, và bảng lại
   cuộn ngang. Bỏ cột "Độ trễ" - nó là cột ít đáng xem nhất trong ba cột sau:
   người ta muốn biết proxy SỐNG hay CHẾT trước, nhanh chậm tính sau. */
@media (max-width: 480px) {
    .demo-proxy-table th:nth-child(3),
    .demo-proxy-table td:nth-child(3) { display: none; }
    .demo-proxy-table th:first-child,
    .demo-proxy-table td:first-child { width: 52%; }
}

/*
 * Dưới 420px thì bỏ nốt cột "Loại", chỉ còn địa chỉ và trạng thái.
 *
 * Ba cột vẫn KHÔNG vừa, và chỗ thiếu không chia đều mà dồn hết vào cột địa
 * chỉ: hai cột sau có bề rộng tối thiểu do chữ trong chúng quy định (đo được
 * 76px và 92px), nên cột đầu chỉ còn 140px trong khi "203.0.113.45:8080" cần
 * 147px — đuôi cổng bị thay bằng dấu ba chấm, đúng phần đáng đọc nhất. Tệ hơn,
 * tổng ba cột thành 309px trong một khung 267px, tức bảng thò ra 42px và bị
 * `overflow: hidden` cắt.
 *
 * Bỏ cột "Loại" là đủ: đo lại ở 320px thì bảng rộng đúng 267px, bằng khung, và
 * địa chỉ có 153px nên in trọn. Bỏ cột này chứ không bỏ cột trạng thái vì
 * người ta nhìn bảng proxy để biết SỐNG hay CHẾT trước đã; HTTP hay SOCKS5 là
 * chuyện tính sau, và trang /proxy thật vẫn còn đủ bảy cột.
 */
@media (max-width: 420px) {
    .demo-proxy-table th:nth-child(2),
    .demo-proxy-table td:nth-child(2) { display: none; }
    .demo-proxy-table th:first-child,
    .demo-proxy-table td:first-child { width: 58%; }
}

/* Khuôn lưới LUÔN hiện, kể cả khi đã có khuôn riêng ở trên: trang /totp thật
   cũng bày mã to trước rồi mới tới bảng mã bí mật và chu kỳ bên dưới. Giữ
   `display: grid` chứ không đổi sang `block` -- chính lưới ba hàng của
   `.demo-panel` mới là thứ chốt chiều cao khung, bỏ đi là trang phình xẹp mỗi
   lượt đổi công cụ. */
.demo-window .demo-skin-grid { display: grid; }

/* Nhưng phần ĐẦU và hàng giữa của khuôn lưới thì thừa khi đã có khuôn riêng:
   `.demo-res-head` in lại đúng con số vừa hiện to ở trên, còn dãy ô số liệu đã
   nằm trong khuôn riêng rồi. */
.demo-window[data-demo-skin="code"]  .demo-res-head,
.demo-window[data-demo-skin="meter"] .demo-res-head { display: none; }

/* Riêng lượt mã 2FA thì giấu luôn dãy ô: ô "Còn lại 27s" là số chụp lúc dựng
   trang và đứng yên, trong khi vòng đếm ngay trên nó chạy thật từng giây. Để
   cả hai cạnh nhau là sau một giây chúng nói hai điều khác nhau. */
.demo-window[data-demo-skin="code"] .demo-mid { display: none; }

/* Bỏ phần đầu đi thì khối còn lại dính mép trên và chừa một khoảng trống to ở
   đáy. Khung cao bằng cột chữ bên trái nên KHÔNG thể co lại -- co là trang
   giật, đúng thứ luật A60 cấm. Vậy thì căn giữa theo chiều dọc: chỗ trống chia
   đều trên dưới, nhìn ra chủ ý chứ không ra thiếu sót. */
.demo-window[data-demo-skin="code"]  .demo-skin-grid,
.demo-window[data-demo-skin="meter"] .demo-skin-grid,
.demo-window[data-demo-skin="proxy"] .demo-skin-grid {
    grid-template-rows: min-content;
    align-content: center;
}

/* Khuôn mã 2FA -- dùng lại `.code-display` của trang /totp, chỉ thu nhỏ đệm
   cho vừa khung trình diễn. */
.demo-skin-code .code-display { padding: 18px 14px; margin: 0; }
.demo-skin-code .code-value { font-size: clamp(1.9rem, 6vw, 2.6rem); }
.demo-skin-code .code-meta { margin-top: 10px; font-size: .78rem; }
.demo-next-wrap { display: inline-flex; align-items: center; gap: 5px; }
.demo-next-wrap .next-code { color: var(--text-secondary); }

/* Khuôn mật khẩu -- ô kết quả và thanh đo lấy nguyên của trang /password. */
.demo-skin-meter .password-output { margin: 4px 0 0; }
.demo-skin-meter .strength-meter { margin: 12px 0 8px; }
.demo-pw-foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    font-size: .78rem;
}
.demo-pw-label { color: var(--success-text); font-weight: 600; }

/*
 * Khuôn đọc mã QR -- ảnh sang trái, các trường đọc được sang phải, đúng như
 * trang /scan bày sau khi quét xong. Trước đây ảnh nằm trên còn bảng nằm dưới,
 * tức là không giống trang nào.
 *
 * Chỉ đổi bố cục ở màn rộng: dưới 640px thì xếp dọc, vì hai cột cạnh nhau trên
 * điện thoại làm ảnh QR nhỏ tới mức không nhìn ra hình.
 */
@media (min-width: 641px) {
    .demo-window[data-demo-skin="scan"] .demo-skin-grid {
        display: grid;
        grid-template-columns: 104px minmax(0, 1fr);
        grid-template-areas: "qr head" "qr tiles" "qr rows";
        /* Căn giữa theo chiều dọc vì khung cao hơn nội dung, xem ghi chú ở trên. */
        align-content: center;
        column-gap: 14px;
        row-gap: 9px;
    }
    /* `display: contents` để ảnh QR và dãy ô -- vốn là hai con của `.demo-mid`
       -- trở thành ô lưới thẳng, nhờ vậy xếp được vào hai vùng khác nhau.
       Không có dòng này thì cả hai dính chung một ô và dãy ô bị ép xuống dưới
       ảnh, rộng đúng 104px, chữ vỡ hết. */
    .demo-window[data-demo-skin="scan"] .demo-mid { display: contents; }
    .demo-window[data-demo-skin="scan"] .demo-res-head { grid-area: head; margin: 0; }
    .demo-window[data-demo-skin="scan"] .demo-qr {
        grid-area: qr;
        align-self: start;
        width: 104px;
        height: 104px;
    }
    .demo-window[data-demo-skin="scan"] .demo-stats { grid-area: tiles; }
    .demo-window[data-demo-skin="scan"] .demo-out { grid-area: rows; }
}

/* --- Dải số liệu -------------------------------------------------------- */

.stat-strip {
    padding-block: 26px;
    background: var(--bg-subtle);
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
    margin-bottom: 8px;
}

.stat-strip-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 20px;
}

.stat-cell { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

.stat-number {
    font-size: clamp(1.5rem, 1.1rem + 1.1vw, 2.05rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -.02em;
    color: var(--accent-text);
    font-variant-numeric: tabular-nums;
}

.stat-name { font-size: .88rem; font-weight: 600; color: var(--text); }
.stat-hint { font-size: .78rem; color: var(--text-muted); line-height: 1.45; }

/* --- Tiêu đề mục và các lưới nội dung ---------------------------------- */

.section-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

.section-kicker {
    display: block;
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--text-muted);
    margin-bottom: 5px;
}

.section-heading { font-size: 1.35rem; font-weight: 600; margin: 0; letter-spacing: -.01em; }

.why-grid,
.how-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 14px;
}

.why-card {
    padding: 18px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
    transition: border-color var(--transition), box-shadow var(--transition);
}

.why-card:hover {
    border-color: color-mix(in srgb, var(--accent) 35%, var(--border));
    box-shadow: var(--shadow-md);
}

.why-icon {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    margin-bottom: 12px;
    border-radius: var(--radius);
    background: var(--accent-soft);
    color: var(--accent-text);
}

.why-heading { font-size: .98rem; font-weight: 600; margin: 0 0 6px; }
.why-body { font-size: .86rem; line-height: 1.55; color: var(--text-muted); margin: 0; }

.how-step {
    position: relative;
    padding: 18px 18px 18px 20px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-md);
}

.how-heading { font-size: .98rem; font-weight: 600; margin: 10px 0 6px; }
.how-body { font-size: .86rem; line-height: 1.55; color: var(--text-muted); margin: 0; }

/* --- Lời mời cuối trang ------------------------------------------------- */

.closing-cta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    padding: 26px 28px;
    background: var(--surface);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.closing-title { font-size: 1.2rem; font-weight: 600; margin: 0 0 6px; }
.closing-body { font-size: .9rem; color: var(--text-muted); margin: 0; max-width: 56ch; }
.closing-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* --- Điện thoại --------------------------------------------------------- */

/*
 * Dưới ngưỡng này hai cột đều quá hẹp để đọc. Xếp chồng thì khung trình diễn
 * được cả bề ngang, rộng hơn hẳn so với việc cố giữ hai cột.
 */
/*
   Xếp chồng từ 1280px trở xuống. Thanh bên biến mất ở 1100px, và trong dải
   1100-1280px hai cột chen nhau tới mức cột chữ chỉ còn hơn 400px trong khi
   khung trình diễn cũng chật. Xuống dưới thì cả hai đều được cả bề ngang.
*/
@media (max-width: 1280px) {
    .landing-hero-inner { grid-template-columns: minmax(0, 1fr); gap: 32px; }
    .hero-title { max-width: none; }
}

@media (max-width: 860px) {
    .landing-hero { padding-block: 32px 40px; }
    .hero-title { max-width: none; }
    .hero-lead { font-size: .96rem; margin-bottom: 20px; }

    /* Hai nút xếp dọc và trải hết bề ngang: ngón cái với tới cả hai dễ hơn hẳn
       hai nút hẹp nằm cạnh nhau. */
    .hero-actions { flex-direction: column; align-items: stretch; }
    .hero-actions .btn { width: 100%; }

    .hero-points { flex-direction: column; gap: 8px; }

    .stat-strip-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
    .stat-hint { display: none; }

    .demo-body { min-height: 194px; padding: 14px; }
    .demo-row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .demo-row dt { font-size: .76rem; }

    .section-head { align-items: flex-start; }
    .closing-cta { padding: 20px; }
    .closing-actions { width: 100%; }
    .closing-actions .btn { flex: 1 1 auto; }
}

@media (max-width: 420px) {
    /* Ba chấm trước đây bị giấu hẳn ở bề ngang này, vì lúc đó chúng cùng một
       màu xám và đúng là không mang thông tin gì. Nay chúng có màu và là thứ
       làm khung trông ra một cửa sổ thật, nên bản điện thoại giữ lại cho giống
       bản máy tính -- chỉ thu nhỏ để phần còn lại của thanh vẫn đủ cho tên
       công cụ. */
    .demo-dots { gap: 4px; }
    .demo-dots i { width: 7px; height: 7px; }
    .demo-note { display: none; }
}

/* Người dùng báo không muốn chuyển động thì tắt hẳn, không giảm bớt. */
@media (prefers-reduced-motion: reduce) {
    .hero-aurora,
    .hero-dot,
    .demo-cursor,
    .demo-row.is-entering { animation: none !important; }
    .hero-aurora { opacity: .7; }
}


/* --- Trang pháp lý ------------------------------------------------------ */

/*
 * Văn bản pháp lý dài và ít ai đọc hết, nên nó phải dễ quét bằng mắt: số điều
 * to rõ ở lề, dòng chữ hẹp lại cho vừa tầm mắt, và khoảng cách giữa các điều
 * đủ rộng để không dính vào nhau.
 */
/*
 * Bề rộng đọc được cho cả khối văn bản pháp lý. Đặt ở đây, một lần, thay vì bóp
 * từng đoạn văn: khi đó tiêu đề, đoạn văn, danh sách và bảng đều thẳng một mép.
 */
.legal-doc {
    max-width: 78rem;
}

.legal-doc section + section {
    margin-top: 22px;
    padding-top: 22px;
    border-top: 1px solid;
    border-top-color: var(--border);
}

.legal-heading {
    display: flex;
    align-items: baseline;
    gap: 10px;
    font-size: 1.02rem;
    font-weight: 600;
    margin: 0 0 8px;
}

.legal-number {
    flex: 0 0 auto;
    min-width: 26px;
    padding: 1px 7px;
    background: var(--accent-soft);
    color: var(--accent-text);
    border-radius: var(--radius-sm);
    font-size: .78rem;
    font-weight: 700;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.legal-doc p {
    /* Không bóp riêng đoạn văn trong một cái thẻ rộng: đo được ở 1920px thì
       thẻ 1056px mà đoạn văn chỉ 528px, nửa thẻ bỏ trống. Quy cả thẻ về bề rộng
       đọc được (xem .legal-doc bên dưới) thì mọi thứ cùng một mép. */
    font-size: .94rem;
    line-height: 1.7;
    color: var(--text-secondary);
    margin: 0 0 10px;
}

.legal-doc p:last-child { margin-bottom: 0; }

.legal-list {
    margin: 8px 0 0;
    padding-left: 20px;
}

.legal-list li {
    font-size: .88rem;
    line-height: 1.6;
    color: var(--text-secondary);
    margin-bottom: 6px;
}

.legal-list li:last-child { margin-bottom: 0; }

@media (max-width: 860px) {
    /* Số điều lùi lên trên tiêu đề: ở bề ngang này để cạnh nhau thì tiêu đề
       chỉ còn hai chữ một dòng. */
    .legal-heading { flex-direction: column; gap: 6px; }
    .legal-number { align-self: flex-start; }
    .legal-doc section + section { margin-top: 18px; padding-top: 18px; }
    .legal-list { padding-left: 18px; }
}


/* --- Máy tính bảng: giữ hai cột chừng nào còn đủ chỗ ------------------- */

/*
 * Hai lưới này trước đây tụt xuống một cột ngay từ 860px, nên ở 768px mỗi thẻ
 * rộng 718px chỉ để chứa một dòng chữ. Chúng chỉ cần xếp chồng khi bề ngang
 * thật sự không đủ cho hai cột — dưới 600px.
 */
@media (max-width: 600px) {
    .tool-tiles { grid-template-columns: minmax(0, 1fr); }
    .field-row { grid-template-columns: minmax(0, 1fr); }
    .why-grid, .how-grid { grid-template-columns: minmax(0, 1fr); }
}



/* --- Hero cao bằng khung màn ---------------------------------------------- */

/*
 * Phần mở đầu chiếm trọn chiều cao màn hình, nên nền chạy hết khung và mục kế
 * tiếp chỉ lộ ra khi người đọc cuộn xuống. Trước đây nền cắt ngang giữa chừng
 * và mục "Bắt đầu từ đây" nhô lên ngay, làm trang trông như bị hụt.
 *
 * Dùng `100svh` chứ không phải `100vh`: trên điện thoại, `vh` tính cả phần
 * thanh địa chỉ có thể thu vào, nên nội dung bị đẩy xuống dưới mép màn hình.
 * `svh` là chiều cao nhỏ nhất chắc chắn nhìn thấy được.
 */
.landing-hero {
    display: flex;
    align-items: center;
    min-height: 100svh;
    padding-top: 40px;
    padding-bottom: 72px;
}

/* Trình duyệt cũ không biết svh thì lùi về vh. Khai trước để svh ghi đè. */
@supports not (height: 100svh) {
    .landing-hero { min-height: 100vh; }
}

.landing-hero-inner { width: 100%; }

/*
 * Mũi tên cuộn ở đáy phần mở đầu. Đây là dấu hiệu duy nhất nói cho người đọc
 * biết bên dưới còn nội dung, khi phần mở đầu chiếm trọn màn hình.
 */
.hero-scroll {
    position: absolute;
    left: 50%;
    bottom: 22px;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 8px 14px;
    color: var(--text-muted);
    font-size: .74rem;
    text-decoration: none;
    border-radius: var(--radius-sm);
}

.hero-scroll:hover { color: var(--text); }

.hero-scroll svg { width: 16px; height: 16px; }

.hero-scroll-arrow { animation: hero-nhun 2s ease-in-out infinite; }

@keyframes hero-nhun {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(4px); }
}

/* --- Gõ chữ ---------------------------------------------------------------- */

/*
 * Con trỏ nhấp nháy ở cuối dòng đang gõ. Chỉ hiện trong lúc gõ; gõ xong thì
 * biến mất, vì một con trỏ nhấp nháy mãi trên trang tĩnh là thứ gây mất tập
 * trung chứ không phải trang trí.
 *
 * Phần mở đầu (`.hero-copy`) giữ cả câu trong DOM và chỉ giấu phần chưa tới
 * lượt, để chiều cao đứng yên ngay từ nhịp đầu. Con trỏ vì thế phải bám vào
 * thẻ mang phần ĐÃ GÕ; gắn ra thẻ bọc ngoài thì nó rơi xuống tận cuối đoạn
 * văn, cách chỗ đang gõ vài dòng.
 *
 * Và con trỏ đó nằm NGOÀI dòng chảy.
 *
 * Để nó chiếm chỗ thật thì chữ cuối cùng bị đẩy xuống dòng mới trong lúc đang
 * gõ, rồi gõ xong con trỏ biến mất và dòng đó nhảy lên lại. Đo được đúng một
 * dòng 25px ở đoạn mô tả trang chủ, và cả cột chữ bên dưới trượt theo — đúng
 * cái giật mà luật A18 nói.
 *
 * Đã đo bốn cách trên trang đang chạy: lề phải âm, bề rộng 0 kèm viền trái, lề
 * trái âm — cả ba vẫn thêm một dòng. Chỉ `position: absolute` giữ nguyên chiều
 * cao. Bỏ `top`/`left` để trình duyệt dùng vị trí tĩnh, tức đúng chỗ con trỏ
 * lẽ ra đứng nếu nó nằm trong dòng chảy.
 */
.hero-copy .is-typing .type-da-go::after {
    content: '';
    position: absolute;
    display: inline-block;
    width: 2px;
    height: 1em;
    margin-left: 3px;
    background: currentColor;
    vertical-align: text-bottom;
    animation: nhay-con-tro 1s step-end infinite;
}

@keyframes nhay-con-tro {
    0%, 49%   { opacity: 1; }
    50%, 100% { opacity: 0; }
}

/* Giữ chỗ cho tiêu đề trong lúc gõ, để trang không nhảy lên nhảy xuống. */
.hero-title { min-height: 1.15em; }

@media (max-width: 700px) {
    .landing-hero { min-height: auto; padding-top: 24px; padding-bottom: 40px; }
    .hero-scroll { display: none; }
}

/* Không muốn chuyển động thì không có gì nhấp nháy, không có gì nhún nhảy. */
@media (prefers-reduced-motion: reduce) {
    .hero-scroll-arrow { animation: none; }
    .hero-copy .is-typing .type-da-go::after { animation: none; opacity: 0; }
}


/* --- Khung trình diễn: chiều cao cố định ---------------------------------- */

/*
 * Chiều cao cố định là điều quan trọng nhất ở đây. Bản trước mỗi công cụ một
 * chiều cao khác nhau, nên khung phình ra thu vào mỗi vài giây và đẩy cả trang
 * nhảy theo — người dùng cuộn nhẹ một cái là màn hình đã đi chỗ khác.
 */
.demo-body {
    display: flex;
    flex-direction: column;
    /*
     * Không đặt chiều cao cứng nữa: khung kéo dài bằng cột chữ bên trái (xem
     * .landing-hero-inner). Nhưng vẫn phải có sàn, không thì lượt ít dòng làm
     * khung co lại và trang nhảy — đúng cái lỗi đã sửa ở vòng trước.
     */
    flex: 1 1 auto;
    min-height: 300px;
    padding: 14px;
    overflow: hidden;
}

/*
 * Dòng nhập và biểu mẫu khoá KHÔNG được co.
 *
 * `.demo-body` là hộp flex dọc, nên mặc định mọi con đều co được. Khi nội dung
 * chật, flex bóp dòng nhập xuống — đo được 8px ở 375px và vẫn còn 15px/19px ở
 * 1280px, tức chữ trong đó bị xén ngang ở MỌI bề ngang, kể cả màn rộng. Một
 * dòng chữ cao 8px trông như lỗi hiển thị chứ không như một thiết kế.
 *
 * Cấm co thì phần thiếu dồn về chỗ CÓ thể bỏ bớt (dãy dòng dữ liệu), và nếu
 * vẫn không vừa thì phép đo thấy ngay ở mức `.demo-body` thay vì âm thầm xén
 * một dòng chữ.
 */
.demo-prompt,
.demo-form { flex: none; }

.demo-qr {
    align-self: center;
    width: 132px;
    height: 132px;
    margin: 2px 0 8px;
    image-rendering: pixelated;   /* mã QR phải sắc cạnh, không được làm mượt */
    border-radius: 4px;
}

.demo-qr[hidden] { display: none; }

.demo-out {
    flex: 1 1 auto;
    margin: 0;
    overflow: hidden;
}

/*
 * Thuộc tính `hidden` của HTML là luật của trình duyệt, yếu hơn mọi luật do
 * người viết đặt ra. Bất kỳ chỗ nào đặt `display` cho một phần tử có thể bị ẩn
 * đều phải khai lại như thế này, không thì `hidden` thành vô nghĩa. Đã dính một
 * lần: biểu mẫu trong khung không bao giờ ẩn được.
 */
[hidden] { display: none !important; }

@media (max-width: 700px) {
    /* Chiều cao khung ở màn hẹp khai ĐÚNG MỘT CHỖ, ở cuối tệp. Trước đây nó
       nằm ở ba khối `@media (max-width: 700px)` khác nhau với ba con số khác
       nhau (232, 248, rồi `min-height: 260`), và giá trị thật là kết quả của
       thứ tự cascade chứ không phải của một quyết định. */
    .demo-qr { width: 104px; height: 104px; }
}


/* --- Nút chuyển lượt trong khung trình diễn ------------------------------- */

/*
 * Vòng chạy tự động vẫn giữ, nhưng người xem phải bấm quay lại được lượt vừa
 * trôi qua. Bấm một trong hai nút là dừng hẳn vòng tự chạy: lúc đó họ đang tự
 * lái, và một cái khung nhảy tiếp sau hai giây là phá đám.
 */
.demo-nav {
    display: inline-flex;
    gap: 2px;
}

.demo-nav button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    background: transparent;
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-family: inherit;
    font-size: .82rem;
    line-height: 1;
    cursor: pointer;
    transition: color var(--transition), border-color var(--transition);
}

.demo-nav button:hover {
    color: var(--text);
    border-color: var(--border-strong, var(--text-muted));
}

.demo-nav button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}



@media (max-width: 700px) {
    /* Ngón tay cần 44px; ở bàn phím và chuột thì 26px là đủ. */
    .demo-nav button { width: 44px; height: 44px; font-size: 1rem; }
    .demo-foot { flex-wrap: wrap; row-gap: 8px; }
}


/* --- Khung trình diễn mang hình dạng kết quả thật ------------------------- */

/*
 * Phần dưới đây chỉ chỉnh cỡ cho vừa khung. Bố cục và màu thì mượn nguyên các
 * lớp của trang công cụ (.res-head, .res-eyebrow, .res-title, .pill,
 * .stat-tile), nên demo không "giống" kết quả thật mà đúng là như vậy. Sửa
 * trang công cụ là demo đổi theo, không có bản sao nào để lệch.
 */

.hero-demo { display: flex; min-width: 0; }

.demo-window {
    display: flex;
    flex-direction: column;
    width: 100%;
}

.demo-panel {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 10px;
    min-height: 0;
    padding: 12px;
    background: var(--bg-subtle);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.demo-res-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    margin: 0;
    padding: 0 0 10px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.demo-res-main { min-width: 0; }

.demo-panel .res-eyebrow { font-size: .7rem; }

.demo-panel .res-title {
    display: block;
    margin: 2px 0 0;
    font-size: 1.32rem;
    line-height: 1.25;
    word-break: break-all;
}

.demo-panel .res-flags {
    display: flex;
    flex: 0 0 auto;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 4px;
}

.demo-panel .pill { font-size: .68rem; padding: 2px 7px; }

/* Ô số liệu: cùng khuôn với trang công cụ, chỉ nhỏ lại cho vừa khung. */
.demo-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: 8px;
    margin: 0;
}

.demo-stats:empty { display: none; }

.demo-panel .stat-tile {
    min-width: 0;
    padding: 8px 10px;
    background: var(--bg);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
}

.demo-panel .stat-label { font-size: .68rem; }
.demo-panel .stat-value { font-size: .95rem; word-break: break-all; }

/* Các dòng nhãn và giá trị còn lại, đóng khung như bảng của trang công cụ. */
.demo-out {
    flex: 1 1 auto;
    min-height: 0;
    margin: 0;
    overflow: hidden;
    background: var(--bg);
    border: 1px solid;
    border-color: var(--border);
    border-radius: var(--radius-sm);
}

.demo-out:empty { display: none; }

.demo-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 10px;
    border-bottom: 1px solid;
    border-bottom-color: var(--border);
}

.demo-row:last-child { border-bottom: 0; }

.demo-row dt {
    flex: 0 0 auto;
    color: var(--text-muted);
    font-size: .78rem;
}

.demo-row dd {
    min-width: 0;
    margin: 0;
    color: var(--text);
    font-size: .82rem;
    text-align: right;
    word-break: break-all;
}

.demo-qr {
    align-self: center;
    flex: 0 0 auto;
    width: 116px;
    height: 116px;
    image-rendering: pixelated;   /* mã QR phải sắc cạnh, không được làm mượt */
    border-radius: 4px;
}

/*
 * Dải 701–1280px: máy tính bảng và cửa sổ trình duyệt thu nhỏ.
 *
 * Ở đây khung không còn cột chữ bên trái để kéo cho bằng — `.landing-hero-inner`
 * xếp chồng một cột cho tới hết 1280px — mà luật cũ chỉ đặt `min-height`, nên
 * khung PHÌNH THEO TỪNG LƯỢT: đo được 295px ở lượt xử lý văn bản và 484px ở
 * lượt kiểm proxy, tức **cả trang giật gần 190px mỗi năm giây** khi khung tự
 * đổi công cụ. Dưới 700px có chiều cao chốt, trên 1280px có cột chữ giữ, nên
 * đúng dải giữa bị bỏ quên.
 *
 * Mốc trên phải là 1280 chứ không phải 1200: bố cục hai cột bắt đầu từ 1281px.
 * Đặt nhầm ở 1200 thì dải 1201–1280 vẫn giật, đo được 156px.
 *
 * Chốt cứng bằng lượt cao nhất đo được trong cả dải (484) làm tròn lên một
 * nhịp. Lượt thấp hơn thừa chỗ, nhưng thừa chỗ nhìn ra chủ ý, còn trang giật
 * thì không.
 */
@media (min-width: 701px) and (max-width: 1280px) {
    .demo-body { height: 488px; min-height: 488px; }
}

@media (max-width: 700px) {
    /*
     * Chiều cao khung ở màn hẹp — khai ĐÚNG MỘT CHỖ, và là con số đã đo.
     *
     * 512px là chiều cao để **không phải giấu gì cả**: điện thoại bày đúng
     * những khối mà máy tính bày. Đo từng lượt với mọi khối mở hết, lấy lượt
     * tốn chỗ nhất trong cả dải — "Kiểm tra cổng" ở 320px cần 480px — cộng đệm
     * 2×10px rồi thêm một nhịp. Thiếu nhịp ấy thì đúng lượt kiểm cổng ở 320px
     * hụt 5px: đã đo được khối 291px cho nội dung cần 296px.
     *
     * Bản trước chốt 312px rồi giấu bớt cho vừa: mỗi lượt chỉ còn một dòng dữ
     * liệu, lượt proxy và lượt mật khẩu mất hẳn phần lưới, lượt kiểm cổng mất
     * dãy ô số liệu. Không có gì bị cắt ngang nữa, nhưng chủ dự án nhìn ra ngay:
     * bản điện thoại **thiếu** so với bản máy tính. Khung cao thêm 200px đổi lấy
     * việc hai bản bày cùng một thứ — và cả khung vẫn cao 648px, tức vẫn thấp
     * hơn khung 684px của bản máy tính.
     *
     * `height` cùng `min-height` cùng một giá trị: để một mình `min-height` thì
     * khung phình theo lượt nhiều dòng nhất và trang nhảy mỗi lần đổi công cụ;
     * để một mình `height` thì một lượt bất ngờ dài ra sẽ bị cắt lặng lẽ. Hai
     * dòng bằng nhau thì khung đứng yên, và lượt nào vượt sẽ hiện ra ngay khi đo.
     */
    /* `justify-content` đi CÙNG luật này, không tách ra một luật `.demo-body`
       thứ hai: lượt ít nội dung nhất chỉ cần 255px trong một khung 492px, nên
       phải nói rõ chỗ trống nằm ở đâu — chia đều trên dưới thì nhìn ra chủ ý,
       dồn xuống đáy thì nhìn ra thiếu sót. (Tách làm hai luật thì phép kiểm
       "mỗi ngưỡng chỉ khai một lần" kêu ngay, và nó kêu đúng.) */
    .demo-body {
        height: 512px;
        min-height: 512px;
        padding: 10px;
        justify-content: center;
    }

    /* Phần đầu được phép cao hơn khi chữ xuống dòng — ở 320px dãy nhãn xuống
       ba hàng và cần 114px thay vì 74px. Chiều cao khung đã chốt ở
       `.demo-body`, nên phần đầu co giãn không làm trang nhảy: nó chỉ chia lại
       chỗ bên trong. */
    .demo-res-head { height: auto; min-height: 74px; }

    /*
     * Ghi chú ở chân khung LUÔN xuống dòng riêng, và luôn chừa hai dòng.
     *
     * Để nó khi thì nằm cạnh dãy chấm, khi thì tự rớt xuống, thì chân khung
     * cao 65px hay 92px tuỳ lượt — đo được ở 540px: bảy lượt cao 393px, riêng
     * lượt "Kiểm tra proxy" cao 420px vì ghi chú dài hơn một chữ. Cả trang
     * giật 27px chỉ vì một câu chú thích.
     */
    .demo-note {
        flex: 1 0 100%;
        display: -webkit-box;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 1;
        overflow: hidden;
        min-height: 1.45em;
    }

    .demo-panel { padding: 10px; gap: 8px; }
    .demo-panel .res-title { font-size: 1.1rem; }
    .demo-qr { width: 96px; height: 96px; }
    .demo-res-head { flex-direction: column; }
    .demo-panel .res-flags { justify-content: flex-start; }

    /*
     * KHÔNG còn luật giấu bớt nào ở đây.
     *
     * Bản trước có bốn luật: dãy dòng dữ liệu chỉ giữ dòng đầu, lượt proxy và
     * lượt mật khẩu bỏ hẳn khuôn lưới, lượt kiểm cổng bỏ dãy ô số liệu. Chúng
     * ra đời vì khung chỉ cao 312px. Nay khung đủ cao nên bỏ hết — điện thoại
     * bày đúng những khối máy tính bày, không hơn không kém.
     *
     * Chừa lại một dòng: dãy dòng dữ liệu phải co vừa nội dung của nó. Ô lưới
     * giữ nguyên chỗ thừa, nên để khối căng hết ô thì phần dưới là một dải nền
     * xám rỗng, nhìn hệt như một dòng nữa đang tải dở.
     */
    .demo-panel .demo-out { align-self: start; }
}

/*

/* --- Phần mở đầu hành xử như một trang riêng ------------------------------ */

/*
 * Cuộn qua dải phân cách là nhảy hẳn xuống mục công cụ; cuộn ngược lên là về
 * lại phần mở đầu. Việc này để trình duyệt lo bằng `scroll-snap` chứ không bắt
 * sự kiện cuộn bằng JavaScript: mượt hơn, và vẫn cuộn được bằng bàn phím, bằng
 * thanh cuộn, bằng chạm màn hình.
 *
 * `proximity` chứ không phải `mandatory`: `mandatory` khoá cứng người dùng vào
 * hai điểm và không cho dừng ở giữa, còn `proximity` chỉ kéo khi đã cuộn đủ gần
 * một điểm, nên phần trang bên dưới vẫn cuộn bình thường.
 *
 * Chỉ bật ở trang chủ (`html.trang-chu`).
 */
html.trang-chu {
    scroll-snap-type: y proximity;
}

/*
 * Điểm dính thứ nhất đặt ở `.page-wrapper` chứ không phải `.landing-hero`.
 *
 * Thanh trên cùng (đổi giao diện, đổi ngôn ngữ, tài khoản) nằm NGAY TRÊN phần
 * mở đầu. Dính vào `.landing-hero` nghĩa là cuộn cho mép trên của nó chạm mép
 * màn hình, và thanh kia bị đẩy lên khuất — mở trang ra là nó tự trôi mất.
 * `.page-wrapper` bắt đầu từ toạ độ 0 nên điểm dính rơi đúng đỉnh trang, giữ
 * nguyên thanh trên cùng.
 */
html.trang-chu .page-wrapper,
html.trang-chu #cong-cu {
    scroll-snap-align: start;
}

/*
 * `always` cho mục công cụ: cuộn một cái mạnh cũng không được nhảy vọt qua nó.
 * Không có dòng này thì lăn chuột nhanh sẽ bay thẳng xuống giữa trang.
 */
html.trang-chu #cong-cu {
    scroll-snap-stop: always;
    scroll-margin-top: 0;
}

/*
 * Người không muốn chuyển động thì bỏ hẳn: với họ, trang tự dời chỗ là khó chịu
 * chứ không phải hiệu ứng đẹp.
 */
@media (prefers-reduced-motion: reduce) {
    html.trang-chu { scroll-snap-type: none; }
}

/*
 * Trên điện thoại phần mở đầu cao hơn màn hình (nội dung cần chỗ), nên điểm
 * dính thứ hai sẽ rơi vào giữa lúc người dùng còn đang đọc dở. Tắt ở đây.
 */
@media (max-width: 860px) {
    html.trang-chu { scroll-snap-type: none; }
}


/* --- Ba thanh đo CPU và bộ nhớ máy chủ ----------------------------------- */

.mon-gauge {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
}

.mon-gauge-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.mon-gauge-value {
    font-size: 1.45rem;
    font-weight: 600;
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.mon-gauge-track {
    height: 8px;
    background: var(--bg-inset);
    border-radius: 999px;
    overflow: hidden;
}

.mon-gauge-fill {
    height: 100%;
    border-radius: 999px;
    transition: width .4s ease, background .3s ease;
}

/* Ba mức, cùng bộ màu với các thẻ sức khoẻ bên dưới để đọc không phải học lại. */
.mon-gauge-fill.is-ok        { background: var(--success); }
.mon-gauge-fill.is-degraded  { background: var(--warning); }
.mon-gauge-fill.is-down      { background: var(--danger); }

.mon-gauge-foot {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    font-size: .76rem;
    font-variant-numeric: tabular-nums;
}

/* Lời giải thích khi `open_basedir` chặn /proc. Trải hết bề ngang lưới ba ô
   đo, vì nó nói về cả ba chứ không riêng ô nào. */
.mon-blocked {
    grid-column: 1 / -1;
    margin: 0;
    padding: 9px 12px;
    font-size: .76rem;
    line-height: 1.5;
    /* Dùng `--warning-soft` và `--warning-text` vì đó là hai token CÓ THẬT,
       khai đủ cho cả nền sáng lẫn nền tối. `--warning-bg`/`--warning-border`
       nghe hợp lý nhưng không tồn tại: viết vào thì ô mất nền, mất viền, và
       chẳng có gì báo lỗi. */
    color: var(--warning-text);
    background: var(--warning-soft);
    border: 1px solid;
    border-color: color-mix(in srgb, var(--warning) 30%, transparent);
    border-radius: var(--radius);
}

@media (prefers-reduced-motion: reduce) {
    .mon-gauge-fill { transition: none; }
}


/* --- Dòng kết quả chưa lộ ------------------------------------------------- */

/*
 * `visibility` chứ không phải `display: none`: dòng chưa lộ vẫn chiếm chỗ, nên
 * bảng có đúng chiều cao cuối ngay từ nhịp đầu và khung không giật lên giật
 * xuống theo từng dòng hiện ra.
 */
.demo-row.is-cho {
    visibility: hidden;
}

.demo-row {
    transition: opacity .18s ease;
}

.demo-row.is-cho { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
    .demo-row { transition: none; }
}


/* --- Khung demo: lưới cứng, không gì đổi được chiều cao ------------------- */

/*
 * Đo cấu trúc từng lượt thì thấy bốn thứ đều khác nhau: số ô số liệu (0 đến 3),
 * có ảnh QR hay không, số nhãn trạng thái (1 đến 3), số dòng kết quả (1 đến 4).
 * Mỗi thứ đổi là khung cao thấp theo, và người xem thấy nó giật liên tục.
 *
 * Chia panel thành ba hàng, hai hàng đầu cao cố định, hàng cuối ăn hết chỗ còn
 * lại. Khi đó không nội dung nào thay đổi được chiều cao nữa.
 */
.demo-panel {
    display: grid;
    grid-template-rows: auto auto minmax(0, 1fr);
    gap: 10px;
    /*
     * Lấp đầy thân khung chứ không co theo nội dung. Thiếu dòng này thì lượt
     * một dòng kết quả cho panel cao 264px còn lượt hai dòng cho 277px, và
     * người xem thấy khung phình lên xẹp xuống mỗi lần đổi công cụ.
     */
    height: 100%;
    box-sizing: border-box;
}

/* Hàng 1: nhãn nhỏ, giá trị chính, các nhãn trạng thái. */
.demo-res-head {
    height: 62px;
    align-items: center;
    box-sizing: border-box;
}

.demo-panel .res-title {
    /* Một dòng, cắt bớt nếu quá dài. Để nó xuống dòng thì đầu khối cao thêm và
       cả khung tụt xuống. */
    display: block;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.demo-panel .res-flags {
    flex-wrap: nowrap;
    overflow: hidden;
}

/*
 * Hàng 2: cao cố định, chứa ảnh QR hoặc dãy ô số liệu hoặc không gì cả. Chính
 * hàng này giữ cho lượt "Kiểm tra cổng" (không có ô nào) cao bằng lượt "Đọc mã
 * QR" (có ảnh).
 */
.demo-mid {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 96px;
    min-height: 96px;
}

.demo-mid .demo-stats {
    flex: 1 1 auto;
    min-width: 0;
}

.demo-mid .demo-stats:empty { display: none; }

.demo-qr {
    flex: 0 0 auto;
    width: 92px;
    height: 92px;
    align-self: center;
    margin: 0;
}

/* Hàng 3: ăn hết chỗ còn lại, và cắt phần thừa thay vì đẩy khung dài ra. */
.demo-out {
    min-height: 0;
    overflow: hidden;
}

/* Dòng lệnh cũng phải một dòng: gõ dài ra mà xuống dòng là cả khung tụt xuống. */
.demo-prompt {
    display: flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    margin: 0 0 10px;
    overflow: hidden;
    white-space: nowrap;
}

.demo-prompt > span:last-child {
    overflow: hidden;
    text-overflow: ellipsis;
}

@media (max-width: 700px) {
    /*
     * Ba dòng này chữa chỗ dãy nhãn trạng thái thò hẳn ra NGOÀI khung: ở 375px
     * nhãn cuối nằm ở 373px trong khi khung hết ở 348px, còn ở 320px thì thò ra
     * tới 77px.
     *
     * Thủ phạm không phải nhãn quá dài, mà là XUỐNG CỘT. `.demo-res-head` thừa
     * `flex-wrap: wrap` của `.res-head`, dưới 700px thì đổi sang xếp dọc, và
     * chiều cao bị chốt 74px để khung không nhảy. Ba thứ đó gặp nhau thành một
     * hộp flex dọc CÓ XUỐNG DÒNG cao cố định: nội dung cần 77px mà chỉ có 74px,
     * nên flex làm đúng bổn phận của nó — mở một CỘT THỨ HAI bên cạnh và đẩy
     * dãy nhãn sang đó, ra ngoài bề ngang khung. Đây là lý do `flex-wrap: wrap`
     * trên `.res-flags` không cứu được: thứ xuống dòng là khối cha, không phải
     * dãy nhãn.
     *
     * `nowrap` cấm mở cột thứ hai; `gap: 6px` để hai hàng vừa đúng 74px (đo
     * được scrollHeight 77 xuống 73); `stretch` để mỗi hàng rộng bằng khung
     * thay vì rộng bằng nội dung.
     */
    .demo-res-head { height: 74px; align-items: stretch; flex-wrap: nowrap; gap: 6px; }
    .demo-panel .res-flags { max-width: 100%; }
    .demo-mid { height: 76px; min-height: 76px; }
    .demo-qr { width: 72px; height: 72px; }
}


/* Phần tiêu đề chưa gõ tới: vẫn chiếm chỗ để trang không cao dần khi gõ. */
.type-cho { visibility: hidden; }

/*
 * Chỉ trình đọc màn hình thấy.
 *
 * Cần vì `.type-cho` dùng `visibility: hidden`, mà thuộc tính đó gỡ chữ khỏi
 * cây trợ năng chứ không chỉ giấu bằng mắt. Thẻ đề mục còn nhận được
 * `aria-label`, nhưng thẻ `<p>` không có vai trò nào nên `aria-label` trên nó
 * bị bỏ qua — với những thẻ đó phải để sẵn một bản sao đầy đủ ở đây.
 *
 * Không dùng `display: none` hay `visibility: hidden`: cả hai đều giấu luôn
 * khỏi trình đọc, tức là đúng thứ đang cần tránh.
 */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


/* --- Bảng luật cấu hình xuất ra ------------------------------------------ */

/*
 * Mỗi dòng là một cách viết của cùng một khối. Tên phần mềm đứng bên trái cố
 * định bề rộng để mắt dò xuống theo cột, còn dòng lệnh chiếm hết phần còn lại
 * và tự cuộn ngang khi dài - xuống dòng giữa chừng thì dán ra sai lệnh.
 */
.rule-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 16px 0;
}

.rule-row {
    display: grid;
    grid-template-columns: 132px minmax(0, 1fr);
    gap: 10px;
    align-items: center;
}

.rule-name {
    font-size: 13px;
    color: var(--text-muted);
    text-align: right;
}

.rule-line {
    display: block;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-subtle);
    font-size: 13px;
    overflow-x: auto;
    white-space: nowrap;
}

@media (max-width: 700px) {
    /* Bề ngang điện thoại không đủ cho hai cột; xếp tên lên trên dòng lệnh. */
    .rule-row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
    .rule-name { text-align: left; }
}


/* Dòng đáng để mắt dừng lại trong một bảng dài: sáu cơ số hay gặp giữa 35 dòng. */
.data-table tbody tr.row-mark td { background: var(--accent-soft); font-weight: 600; }
.data-table tbody tr.row-mark:hover td { background: var(--accent-soft); }


/* --- Hàng lịch sử trong cột hẹp ------------------------------------------- */

/*
 * Cột lịch sử nằm bên phải trang 2FA và hẹp hơn hẳn cột công cụ. Một hàng ở đó
 * phải chứa sáu thứ, nên phần đồ cố định (ảnh đại diện, hai nút, nhãn hết giờ,
 * năm khoảng cách) ăn gần hết bề ngang và chữ chỉ còn vài chục pixel.
 *
 * Đo ở 1280px trước khi sửa: mã 2FA cao 64px vì bị cắt làm hai dòng, tên tài
 * khoản còn 35px, tiêu đề thẻ cũng xuống hai dòng.
 */
.result-item {
    gap: 10px;
    padding: 11px 13px;
}

.result-item .avatar {
    width: 32px;
    height: 32px;
    font-size: .85rem;
}

.result-item .item-title { font-size: .92rem; }
.result-item .item-sub { font-size: .76rem; }

.result-item .item-code {
    font-size: 1.05rem;
    letter-spacing: .03em;
    /* Mã sáu số KHÔNG được cắt làm hai dòng: đọc "115" rồi "658" ở hai hàng
       khác nhau là đúng kiểu gõ nhầm số. */
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Nhãn đếm ngược và hai nút thu lại, nhường bề ngang cho phần chữ. */
.result-item .badge { flex: 0 0 auto; padding: 2px 6px; font-size: .7rem; }
.result-item .btn-icon.btn-sm { width: 26px; height: 26px; padding: 3px; }

/* Tiêu đề thẻ lịch sử: một dòng, cắt bớt nếu chật, thay vì tự xuống hai dòng
   và đẩy cả thẻ cao lên. */
.card-header h2 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

@media (max-width: 480px) {
    /* Trên điện thoại hàng được phép xuống dòng nên chữ có đủ chỗ; chỉ giữ mã
       2FA không bị cắt. */
    .result-item .item-code { font-size: 1.05rem; }
}


/*
 * Ở màn hình rộng, cột lịch sử nằm bên phải và chỉ rộng chừng 285px. Sáu thứ
 * xếp trên một hàng thì tên tài khoản chỉ còn 49px - đọc ra "Tài ...".
 *
 * @media không giúp được: màn hình đang rộng 1280px, chật là chật ở CỘT chứ
 * không phải ở màn hình. @container nhìn đúng bề rộng của chính cột nên bắt
 * được tình huống này, và cũng tự đúng luôn khi cột đổi bề rộng ở khổ khác.
 */
.result-list { container-type: inline-size; }

@container (max-width: 340px) {
    /*
     * Hai dòng: ảnh đại diện cùng tên tài khoản ở trên, mã cùng nhãn hết giờ và
     * hai nút ở dưới. Thà cao thêm một dòng còn hơn cắt mất tên.
     *
     * Bề rộng của phần tên trừ đi đúng chỗ của ảnh đại diện (32px) và khoảng
     * cách (10px). Để nguyên 100% thì ảnh bị đẩy xuống nằm một mình một dòng và
     * cả hàng thành ba dòng, cao 142px cho một mục.
     */
    .result-item { flex-wrap: wrap; row-gap: 8px; }
    .result-item .item-main { flex: 1 1 calc(100% - 42px); }
    .result-item .item-code { margin-inline-end: auto; }
}

@container (max-width: 250px) {
    /*
     * Hẹp tới mức này thì ảnh đại diện chỉ là trang trí, còn tên tài khoản là
     * thông tin. Bỏ ảnh đi để tên có thêm 42px và không bị cắt cụt.
     */
    .result-item .avatar { display: none; }
    .result-item .item-main { flex: 1 1 100%; }
}


/* --- Khối kết quả: chặn chiều cao, dài quá thì cuộn trong khối ------------ */

/*
 * Bảng trong khối kết quả cuộn theo MẶC ĐỊNH, không phải theo lớp gắn tay.
 * Lớp .table-scroll vốn phải nhớ gắn vào từng chỗ, nên chỗ nhớ chỗ quên: đo
 * được bảng 963px ở trang đổi đơn vị và 879px ở trang thời gian, đều không có
 * thanh cuộn, trong khi bảng 129 dòng của prefix IPv6 lại có.
 */
.result-panel .table-wrap {
    /* min() để mức chặn co theo màn hình: trên máy xách tay cao 700px thì 460px
       đã chiếm hai phần ba chiều cao khung nhìn, còn lại không đủ chỗ cho phần
       kết quả bên dưới. */
    max-height: min(460px, 55vh);
    overflow: auto;
}

/*
 * Danh sách nhãn - giá trị và bảng luật cấu hình cũng chặn theo. Hôm nay cao
 * nhất mới 441px nên chưa thấy khác gì; đặt sẵn để thêm một dòng nữa vào công
 * cụ nào đó sau này không lặng lẽ kéo dài cả trang.
 */
.result-panel .kv,
.result-panel .rule-list {
    max-height: min(460px, 55vh);
    overflow: auto;
}

/*
 * Đầu bảng dính lại khi cuộn - cuộn tới dòng thứ sáu mươi mà mất tên cột thì
 * bảng thành một mớ số vô nghĩa.
 *
 * Phải tự tô nền: ô đầu bảng vốn trong suốt, các dòng sẽ trôi xuyên qua nó.
 * Và border-collapse làm mất kẻ dưới của ô đang dính, nên vẽ lại bằng bóng đổ
 * bên trong.
 */
.result-panel .table-wrap thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--surface);
    border-bottom: none;
    box-shadow: inset 0 -1px 0 var(--border);
}

/*
 * Khối "chép cả danh sách": đây là thứ để bấm chép chứ không phải để đọc từng
 * dòng, nên không đáng chiếm 1328px màn hình. Mười hai dòng là đủ thấy nó chứa
 * gì trước khi bấm chép.
 */
.result-panel .code-block {
    max-height: min(240px, 32vh);
    overflow: auto;
}

@media (max-width: 700px) {
    .result-panel .table-wrap,
    .result-panel .kv,
    .result-panel .rule-list { max-height: 320px; }
    .result-panel .code-block { max-height: 190px; }
}

/* ------------------------------------------------------------ kiểm tra proxy

   Công cụ này không theo khuôn "biểu mẫu trái, kết quả phải" của các công cụ
   mạng khác: dữ liệu vào là một danh sách dài, dữ liệu ra là một bảng rộng
   tám cột. Nên biểu mẫu nằm trên, bảng nằm dưới và chiếm hết bề ngang.
*/

.proxy-panel { display: block; }

.proxy-form-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 20px;
    align-items: start;
}

.proxy-side {
    display: flex;
    flex-direction: column;
}

.proxy-form textarea {
    font-family: var(--font-mono);
    font-size: .86rem;
    line-height: 1.5;
    resize: vertical;
}

.proxy-result { margin-top: 16px; }

.proxy-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
}

.proxy-trim { margin-bottom: 12px; }

/* Bảng có thể dài hàng trăm dòng, nên cuộn trong khung theo đúng luật A28 và
   đầu bảng dính lại để còn biết cột nào là cột nào. */
.proxy-table-wrap {
    max-height: min(560px, 62vh);
    overflow: auto;
}

.proxy-table th {
    position: sticky;
    top: 0;
    z-index: 1;
    /* Ô đầu bảng vốn trong suốt: không tô nền thì các dòng trôi xuyên qua nó. */
    background: var(--bg-subtle);
    box-shadow: inset 0 -1px 0 var(--border);
}



/* Dòng sống tô nền xanh nhạt để lướt mắt là thấy ngay, giống công cụ người
   dùng đang quen dùng. Màu lấy từ token chứ không viết cứng, nên bản tối vẫn
   đúng sắc độ. */
.proxy-table tr.proxy-live td {
    background: color-mix(in srgb, var(--success) 12%, transparent);
}



@media (max-width: 860px) {
    .proxy-form-grid {
        grid-template-columns: minmax(0, 1fr);
        gap: 14px;
    }
    .proxy-table-wrap { max-height: min(440px, 56vh); }
}

/* ---------------------------------------------------- nền trang chủ liền mạch

   Chủ dự án báo: thanh đăng nhập nằm trên nền trắng, nền màu bắt đầu ở dưới
   nó, và chỗ tiếp giáp thành **một đường kẻ ngang** trông rời rạc.

   Cách chữa: đưa lớp nền lên làm con đầu tiên của `.page-wrapper` thay vì nằm
   trong `<section>` phần mở đầu. Nhờ vậy nó phủ từ mép trên cùng của trang,
   đi qua phía sau thanh đăng nhập và thanh điều hướng; hai thanh đó chỉ cần
   bỏ nền riêng của mình đi là hết đường kẻ.

   Không dùng cách kéo `.landing-hero` lên bằng lề âm: lề âm phải khớp đúng
   chiều cao hai thanh, mà chiều cao đó đổi theo bề ngang màn hình (thanh đăng
   nhập chỉ hiện từ 1100px trở lên). Sai một lần là lệch cả trang.
*/

html.trang-chu .page-wrapper { position: relative; }

.landing-canvas {
    position: absolute;
    inset: 0 0 auto 0;
    /* Cao hơn phần mở đầu một chút để chỗ chuyển sang nội dung là một dải mờ
       dần, không phải một đường cắt. */
    height: min(1040px, 125vh);
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
}

/* Nội dung và hai thanh nổi lên trên lớp nền. */
html.trang-chu .page-wrapper > main {
    position: relative;
    z-index: 1;
}

/*
 * Thanh trên phải cao hơn phần nội dung, không bằng nó.
 *
 * Bản trước cho cả ba cùng `z-index: 1`, và `z-index` đó ghi đè luôn mức 50 của
 * `.site-header`. Cùng một mức thì thứ tự trong HTML quyết định, mà `main` nằm
 * sau — nên mọi thứ mọc ra từ thanh trên đều bị nội dung trang chủ vẽ đè:
 *
 *   · menu chọn ngôn ngữ bị thẻ ngay dưới cắt mất một nửa;
 *   · trên điện thoại, cả ngăn menu bung ra nằm dưới phần mở đầu — đo được
 *     `document.elementFromPoint()` giữa ngăn menu trả về `span.type-da-go`,
 *     tức là chạm vào menu thì trúng chữ tiêu đề. Không bấm được mục nào.
 *
 * Thanh trên là chỗ đặt `position: relative` để tạo ngữ cảnh xếp lớp cho ngăn
 * menu `position: fixed` bên trong, nên giữ nguyên `relative` và chỉ nâng mức.
 */
html.trang-chu .site-header,
html.trang-chu .user-bar {
    position: relative;
    z-index: 50;
}

/* Hai thanh bỏ nền và bỏ viền dưới: nền chung ở lớp dưới lo hết. */
html.trang-chu .site-header,
html.trang-chu .user-bar {
    background: transparent;
    border-bottom-color: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Phần mở đầu giờ trong suốt, và không còn đường kẻ dưới. */
html.trang-chu .landing-hero {
    background: transparent;
    border-bottom-color: transparent;
}

/*
   Nền: một dải chuyển màu rất nhạt phủ toàn khối, ba vệt sáng trôi lệch pha
   nhau, và một lớp lưới kỹ thuật mờ dần về đáy.

   Ba vệt chứ không phải hai, và mỗi vệt một chu kỳ lẻ (19s, 23s, 31s) để chúng
   không bao giờ về cùng một chỗ — hai vệt cùng chu kỳ thì mắt bắt được nhịp lặp
   chỉ sau một vòng, và lúc đó nó thành hình động chạy vòng chứ không còn là nền.
*/
.landing-canvas::before {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(
        180deg,
        color-mix(in srgb, var(--accent) 10%, var(--bg)) 0%,
        color-mix(in srgb, var(--accent) 5%, var(--bg)) 46%,
        var(--bg) 100%
    );
}

.landing-canvas .hero-aurora,
.landing-canvas .hero-glow {
    position: absolute;
    display: block;
    pointer-events: none;
    border-radius: 50%;
    filter: blur(70px);
    will-change: transform;
}

.landing-canvas .hero-aurora {
    inset: auto auto auto -6%;
    top: -16%;
    width: 52%;
    height: 62%;
    background: radial-gradient(circle at 50% 50%,
        color-mix(in srgb, var(--accent) 34%, transparent), transparent 68%);
    animation: landing-troi-1 19s ease-in-out infinite alternate;
}

.landing-canvas .hero-glow-2 {
    top: -24%;
    right: -10%;
    width: 56%;
    height: 68%;
    background: radial-gradient(circle at 50% 50%,
        color-mix(in srgb, var(--info) 30%, transparent), transparent 70%);
    animation: landing-troi-2 23s ease-in-out infinite alternate;
}

.landing-canvas .hero-glow-3 {
    top: 26%;
    left: 34%;
    width: 44%;
    height: 52%;
    background: radial-gradient(circle at 50% 50%,
        color-mix(in srgb, var(--accent) 20%, transparent), transparent 72%);
    animation: landing-troi-3 31s ease-in-out infinite alternate;
}

@keyframes landing-troi-1 {
    from { transform: translate3d(0, 0, 0) scale(1); }
    to   { transform: translate3d(14%, 8%, 0) scale(1.18); }
}
@keyframes landing-troi-2 {
    from { transform: translate3d(0, 0, 0) scale(1.06); }
    to   { transform: translate3d(-12%, 12%, 0) scale(.92); }
}
@keyframes landing-troi-3 {
    from { transform: translate3d(0, 0, 0) scale(.94); }
    to   { transform: translate3d(-16%, -10%, 0) scale(1.12); }
}

/* Lưới kỹ thuật: gợi cảm giác bản vẽ, mờ hẳn về đáy để không tranh chỗ với chữ. */
.landing-canvas .hero-grid-lines {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(to right, color-mix(in srgb, var(--border-strong) 42%, transparent) 1px, transparent 1px),
        linear-gradient(to bottom, color-mix(in srgb, var(--border-strong) 42%, transparent) 1px, transparent 1px);
    background-size: 46px 46px;
    -webkit-mask-image: radial-gradient(120% 92% at 50% 6%, rgba(0,0,0,.55), transparent 72%);
    mask-image: radial-gradient(120% 92% at 50% 6%, rgba(0,0,0,.55), transparent 72%);
    opacity: .55;
}

/* Dải mờ ở đáy: chỗ nền gặp nội dung phải tan ra chứ không cắt ngang. */
.landing-canvas::after {
    content: '';
    position: absolute;
    inset: auto 0 0 0;
    height: 220px;
    background: linear-gradient(180deg, transparent, var(--bg));
}

/* Không muốn chuyển động thì đứng yên hẳn, không giảm bớt. */
@media (prefers-reduced-motion: reduce) {
    .landing-canvas .hero-aurora,
    .landing-canvas .hero-glow { animation: none !important; }
}

/* Màn hình hẹp: lớp nền thấp lại cho vừa phần mở đầu đã xếp chồng, và bớt mờ
   để máy yếu không phải tô một vùng blur quá lớn. */
@media (max-width: 860px) {
    .landing-canvas { height: min(760px, 92vh); }
    .landing-canvas .hero-aurora,
    .landing-canvas .hero-glow { filter: blur(52px); }
    .landing-canvas::after { height: 160px; }
}

/* Dòng đang chờ tới lượt: xám, chữ nhạt. Người dùng thấy ngay danh sách mình
   vừa dán, và mỗi dòng đổi màu đúng lúc nó được kiểm xong. */
.proxy-table tr.proxy-cho td {
    background: var(--bg-subtle);
    color: var(--text-muted);
}

.proxy-table tr.proxy-die td {
    background: color-mix(in srgb, var(--danger) 7%, transparent);
    color: var(--text-secondary);
}

/* Nhãn trạng thái chờ: cùng họ với .pill-wait đã dùng ở công cụ mạng. */
.proxy-table .pill-wait {
    background: var(--bg-inset);
    color: var(--text-muted);
}

/* -------------------------------------------------- phần mở đầu vừa một màn

   Hai điều chủ dự án chỉ ra, cùng một gốc: chiều cao phần mở đầu đặt cứng
   `100vh`, trong khi thanh đăng nhập và thanh điều hướng nằm PHÍA TRÊN nó.
   Cộng lại là cao hơn màn hình, nên dòng "Cuộn xuống xem công cụ" bị đẩy xuống
   dưới nếp gấp và rơi ra vùng nền trắng.

   Cách chữa: không đặt chiều cao cho phần mở đầu nữa, mà cho nó nở ra chiếm
   đúng phần còn lại của khung màn sau khi hai thanh đã lấy chỗ. Không phải
   đoán chiều cao hai thanh — mà chiều cao đó lại đổi theo bề ngang, vì thanh
   đăng nhập chỉ hiện từ 1100px trở lên.
*/

html.trang-chu .page-wrapper {
    /* `svh` chứ không phải `vh`: trên điện thoại `vh` tính theo lúc thanh địa
       chỉ đã thu lại, nên trang luôn cao hơn màn hình một khoảng. */
    min-height: 100svh;
}

html.trang-chu .page-wrapper > main {
    display: flex;
    flex-direction: column;
}

/*
   Chiều cao còn lại sau khi thanh trên đã lấy chỗ.

   Dưới 1100px: hiện thanh điều hướng (56px), ẩn thanh đăng nhập.
   Từ 1100px:   ẩn thanh điều hướng, hiện thanh đăng nhập (48px).
   Đo trên trang đang chạy, không đoán.
*/
html.trang-chu { --landing-top: var(--header-height); }

@media (min-width: 900px) {
    html.trang-chu { --landing-top: 48px; }
}

html.trang-chu .landing-hero {
    /* Cao đúng phần còn lại của khung màn. Không dùng flex-grow: phần thân
       trang vốn đã dài hơn màn hình nên không có chỗ trống nào để chia thêm,
       và phần mở đầu sẽ giữ nguyên chiều cao nội dung của nó. */
    min-height: calc(100svh - var(--landing-top));
    display: flex;
    flex-direction: column;
    justify-content: center;
}

/*
 * Trên điện thoại thì KHÔNG chiếm trọn khung màn.
 *
 * Luật ở dải 700px bên trên đã hạ `min-height` về `auto`, nhưng luật này nằm
 * cuối tệp và còn nặng ký hơn (`html.trang-chu` + lớp) nên thắng luôn. Hậu quả
 * đo được ở 375×812: phần mở đầu cao 1094px, khung trình diễn bắt đầu ở y=740 —
 * người mở trang bằng điện thoại chỉ thấy mỗi chữ, không thấy công cụ nào chạy.
 *
 * Màn điện thoại xếp một cột nên hai khối đã nối đuôi nhau sẵn; ép thêm một
 * khung màn nữa chỉ tổ đẩy khung trình diễn xuống dưới mép.
 */
@media (max-width: 700px) {
    html.trang-chu .landing-hero {
        min-height: auto;
        padding-top: 12px;
        padding-bottom: 32px;
    }

    /*
     * Thắt lại các khoảng cách dọc để mép trên khung trình diễn lọt vào màn.
     *
     * Đo ở 375×812: cột chữ cao 628px, cộng thanh trên 56px và khe 32px thì
     * khung trình diễn bắt đầu ở y=736 — chỉ còn 76px ló ra dưới đáy màn, ai
     * không cuộn thì không biết là có. Cắt từng khoảng một (khe 32→16, các lề
     * dưới, ba lời hứa xếp thành hàng cuốn dòng) thay vì bỏ bớt nội dung.
     */
    .landing-hero-inner { gap: 16px; }
    .hero-eyebrow { margin-bottom: 10px; }
    .hero-title { margin-bottom: 10px; }
    .hero-lead { margin-bottom: 12px; }
    .hero-actions { margin-bottom: 14px; }

    /* Ba lời hứa nằm ngang và tự cuốn dòng: ba dòng riêng tốn 82px, cuốn lại
       còn hai dòng. Vẫn đọc được vì mỗi lời chỉ vài chữ. */
    .hero-points {
        flex-direction: row;
        flex-wrap: wrap;
        gap: 6px 14px;
    }
}

/* Dòng "cuộn xuống" bám đáy phần mở đầu, tức là bám đúng mép dưới khung màn. */
html.trang-chu .hero-scroll {
    margin-top: auto;
}

/*
   Cột chữ không bị bó lại khi cửa sổ hẹp bớt.

   `max-width: 17ch` hợp với màn rộng, nhưng ở khoảng 1200-1400px thì cột chữ
   chỉ còn hơn 400px mà tiêu đề vẫn ngắt ở 17 ký tự, để thừa một khoảng trống
   bên phải. Ở dải đó cho tiêu đề dùng hết bề ngang của cột.
*/
/*
   CHẶN DƯỚI phải có: luật này nằm cuối tệp nên nếu chỉ ghi max-width thì nó
   thắng luôn cả luật xếp chồng ở 1200px bên trên, và khung trình diễn không bao
   giờ xuống dưới nữa. Đã dính đúng như vậy: cửa sổ hẹp tới mức mất thanh bên mà
   hai cột vẫn cố chen nhau.
*/
@media (min-width: 1281px) and (max-width: 1440px) {
    .landing-hero-inner {
        /* Hai cột cân nhau: cột chữ không còn là cột hẹp hơn. */
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: 32px;
    }
    .hero-title { max-width: none; }
    .hero-lead { max-width: 52ch; }
}

/* ------------------------------------------------- bảng proxy: cột cố định

   Chủ dự án chỉ ra hai điều: bảng thiếu đường kẻ dọc nên mắt khó dò theo hàng,
   và một trường dài (tên tài khoản của nhà cung cấp proxy dài 37 ký tự) đẩy
   toàn bộ các cột sau lệch hẳn đi.

   Cách chữa: `table-layout: fixed` cộng bề rộng khai sẵn cho từng cột. Bảng
   tự động đo theo ô dài nhất, nên chỉ cần một dòng dữ liệu bất thường là cả
   bảng đổi hình; khai cứng thì bề rộng không phụ thuộc vào dữ liệu nữa. Ô nào
   dài quá thì cắt bằng dấu ba chấm, và giữ nguyên nội dung ở `title` để rê
   chuột vào vẫn đọc được đủ.
*/

.proxy-table {
    table-layout: fixed;
    /* Bảy cột khai cứng cộng lại 684px, cộng 300px cho cột kết quả chứa một
       địa chỉ IPv6. Hẹp hơn thì khung ngoài cho cuộn ngang chứ không bóp cột. */
    min-width: 984px;
}

/* Đường kẻ dọc giữa các cột. Chỉ kẻ giữa, không kẻ hai mép ngoài. */
.proxy-table th + th,
.proxy-table td + td {
    border-left: 1px solid;
    border-left-color: var(--border);
}

/*
   Bề rộng từng cột, lấy từ SỐ ĐO THẬT trên trang đang chạy chứ không ước lượng.
   Đo bằng cách dựng một thẻ ẩn mang đúng phông của ô rồi hỏi bề ngang:

     255.255.255.255  113px (phông mono 13,6px)   ·  655350   49px
     "TÀI KHOẢN"       66px (phông thường)        ·  "MẬT KHẨU"  63px
     "1234.5 ms"       68px (mono)                ·  "TRẠNG THÁI" 73px
     viên nhãn SOCKS5  60px                       ·  viên nhãn Sống 44px

   Cộng 20px đệm hai bên của ô, cộng vài px dư cho phông khác máy.

   **Không dùng đơn vị `ch` ở đây.** Với `table-layout: fixed`, trình duyệt lấy
   bề rộng cột từ HÀNG ĐẦU, tức là ô `th` — mà `th` dùng phông thường còn ô dữ
   liệu dùng phông mono rộng hơn. Khai 16ch tưởng đủ cho mười sáu chữ số, thực
   tế ra 122px trong khi địa chỉ IP cần 133px, và nó bị cắt ngay ở hàng đầu.

   Thứ tự: IP · Cổng · Tài khoản · Mật khẩu · Loại · Độ trễ · Kết quả · Trạng thái
*/

/* Vừa một địa chỉ IPv4 đầy đủ: 113 + 20 + dư. */
.proxy-table th:nth-child(1), .proxy-table td:nth-child(1) { width: 140px; }

/* Vừa sáu chữ số: 49 + 20 + dư. */
.proxy-table th:nth-child(2), .proxy-table td:nth-child(2) { width: 76px; }

/* Vừa đúng chữ "TÀI KHOẢN" và "MẬT KHẨU" ở đầu cột. */
.proxy-table th:nth-child(3), .proxy-table td:nth-child(3) { width: 92px; }
.proxy-table th:nth-child(4), .proxy-table td:nth-child(4) { width: 92px; }

/* Vừa viên nhãn SOCKS5. */
.proxy-table th:nth-child(5), .proxy-table td:nth-child(5) { width: 88px; }

/* Vừa "1234.5 ms". */
.proxy-table th:nth-child(6), .proxy-table td:nth-child(6) { width: 96px; }

/* Cột kết quả KHÔNG khai bề rộng: nó nhận hết phần còn lại, vì địa chỉ đi ra
   có thể là IPv6 dài tới 39 ký tự. */

/* Vừa chữ "TRẠNG THÁI" ở đầu cột. */
.proxy-table th:nth-child(8), .proxy-table td:nth-child(8) { width: 100px; }

/* Dài quá thì cắt, đừng đẩy cột khác đi. */
.proxy-table td,
.proxy-table th {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Đệm hai bên để chữ không dính sát vạch kẻ cột. */
    padding-inline: 10px;
}

/* Cột kết quả hay chứa địa chỉ IPv6 dài: cho chữ nhỏ hơn một nấc để vừa hơn. */
.proxy-table td:nth-child(7) { font-size: .78rem; }

/* ------------------------------------------- thanh bên ở màn hình vừa

   Chủ dự án yêu cầu: co cửa sổ nhỏ lại thì VẪN phải giữ thanh bên, đừng đổi
   sang thanh điều hướng ngang. Ngưỡng nay là 900px thay vì 1100px.

   Trong dải 900-1099px thanh bên thu hẹp lại: giữ nguyên 258px thì phần nội
   dung chỉ còn hơn 600px và mọi trang công cụ đều chật.
*/
@media (min-width: 900px) and (max-width: 1099px) {
    .app-shell { grid-template-columns: 212px minmax(0, 1fr); }

    .app-sidebar {
        padding: 14px 10px 12px;
        gap: 4px;
    }
    .app-sidebar .sidebar-link { padding-inline: 8px; }
    .app-sidebar .brand-name { font-size: .95rem; }

    /* Thanh đăng nhập bám sát mép phải hơn cho khỏi phí chỗ. */
    .user-bar { padding-inline: 18px; }
}

/*
   Cả bảng proxy căn trái, đầu cột và ô dữ liệu như nhau.

   Trước đây cột Cổng và Độ trễ mang lớp `.numeric` (căn phải) còn đầu cột của
   chúng căn trái, nên đọc xuống thấy chữ nhảy qua nhảy lại giữa hai mép. Số ở
   đây là dữ liệu tra cứu, không phải cột để cộng dồn hay so hơn kém, nên căn
   phải không đem lại gì.

   Vẫn giữ `tabular-nums` để các chữ số thẳng hàng theo cột dọc.
*/
.proxy-table th,
.proxy-table td {
    text-align: left;
    font-variant-numeric: tabular-nums;
}
