/* pdftool — hệ thống "square / hairline lattice".
   Bố cục ô vuông, viền tóc, góc sắc; sắc độ do đường kẻ gánh chứ không do đổ
   bóng. Chuyển từ prototype d4t0-pdftool-redesign.html (24/08/2026).

   Ba khác biệt so với prototype, đều có lý do đo được:

   1. --text-faint của prototype là #A6A399 = 2.42:1 trên nền. Nó không phải màu
      trang trí -- eyebrow, nhãn nhóm, hint dropzone, dòng phụ dưới tên file đều
      là chữ thật. Đẩy lên #918D84 để đạt >=4.5:1 trên CẢ ba nền (bg, surface,
      surface-muted). Kèm theo phải nới --surface-muted sáng hơn một nấc, vì nền
      upload là nền nhạt nhất mà chữ faint phải sống trên đó.
   2. --border-strong #C7C4B9 = 1.67:1, dùng làm viền <select> và nút tải.
      WCAG 1.4.11 đòi >=3:1 cho ranh giới nhận dạng một control. Đổi tên thành
      --control-border (đúng alias của branding/check-contrast.py, nên từ nay
      role này được ĐO chứ không còn "unresolved") và đặt #949084 = 3.05/3.19.
   3. Prototype chỉ có bảng sáng. Repo bắt buộc prefers-color-scheme: dark, nên
      bảng tối dưới đây là thiết kế thêm; mọi cặp đều đã đo, xem số ghi kèm.

   Đừng chỉnh mấy con số này bằng mắt. `python3 branding/check-contrast.py
   pdftool` đo cặp bắt buộc; các cặp ngoài ladder ghi tỉ lệ ngay tại token. */

:root {
  /* --- nền và bề mặt --- */
  --bg: #FAFAF8;
  --surface: #FFFFFF;
  /* Sáng hơn #F4F3EF của prototype: đây là nền của vùng upload, và chữ faint
     phải đạt 4.5:1 trên nó. */
  --surface-muted: #F7F6F3;

  /* --- đường kẻ --- */
  /* Viền tóc, trang trí — advisory trong ladder, được phép nhạt. */
  --border: #E1DFD7;
  /* Ranh giới của form control. WCAG 1.4.11 >=3:1 — bg 3.05, surface 3.19. */
  --control-border: #949084;

  /* --- chữ --- */
  --text: #1B1B18;          /* bg 16.52, surface 17.26 */
  --text-muted: #6E6C64;    /* bg 5.03, surface 5.26, surface-muted 4.74 */
  --text-faint: #918D84;    /* bg 4.66, surface 4.87, surface-muted 4.51 */

  /* Màu của mark D4T0 — token riêng, KHÔNG dùng --text (21/08/2026). Xem
     branding/tokens.css. */
  --logo-color: #293133;

  /* --- accent chính (tím): link, nút chạy, trạng thái chọn --- */
  --accent: #4A3AE3;        /* bg 6.69, surface 6.99 */
  --accent-hover: #3E2FCB;
  --accent-weak: #EDEAFC;   /* accent trên nền này 5.91 */
  --on-accent: #FFFFFF;     /* trên accent 6.99 */

  /* --- màu con số kết quả (cam đất) ---
     Nằm ngoài ladder nên không có alias; đo tay: surface 5.56, bg 5.32,
     trên --stat-weak 4.75. Prototype dùng #C1440E, chỉ đạt 4.38 trên nền cam
     nhạt của chính nó — đậm thêm một nấc là đủ. */
  --stat: #B8400D;
  --stat-weak: #FBEAE0;

  --error: #b91c1c;
  --error-bg: #fef2f2;

  /* --- chữ --- */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Góc sắc: viền tóc gánh việc phân tách, bo tròn sẽ làm mờ mắt lưới. */
  --radius: 2px;

  /* Ba bậc bo tròn + đổ bóng CHỈ còn phục vụ thanh nav và dropdown menu, là hai
     khối được yêu cầu giữ nguyên khi đổi giao diện (24/08/2026). Phần thân
     trang không dùng chúng nữa — nó dùng --radius ở trên. Giữ token lại vì
     check-contrast.py bắt được đúng chuyện này: bỏ token mà chưa sửa chỗ dùng
     thì cả dòng khai báo rụng, không báo lỗi ở đâu cả. */
  --radius-xs: 5px;
  --radius-sm: 8px;
  --radius-md: 13px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 8px 24px rgba(16, 24, 40, 0.06);

  /* Thang cách Fibonacci (xấp xỉ tỉ lệ vàng φ≈1.618) — giữ từ bản cũ. */
  --space-1: 8px;
  --space-2: 13px;
  --space-3: 21px;
  --space-4: 34px;
  --space-5: 55px;
  --space-6: 89px;

  --nav-h: 64px;
  --ease: cubic-bezier(.4, 0, .2, 1);
}

/* Bảng tối — prototype không có, thiết kế thêm. Số đo ghi kèm từng token. */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #171614;
    --surface: #1E1D1A;
    --surface-muted: #262420;

    --border: #35332E;
    --control-border: #6E6A61;   /* bg 3.36, surface 3.13 */

    --text: #F0EEE9;             /* bg 15.60, surface 14.54 */
    --text-muted: #A8A49B;       /* bg 7.28, surface 6.78, surface-muted 6.23 */
    --text-faint: #918D84;       /* bg 5.40, surface 5.04, surface-muted 4.62 */
    --logo-color: #F0EEE9;

    --accent: #A192FF;           /* bg 6.95, surface 6.48 */
    --accent-hover: #B4A8FF;
    --accent-weak: #262038;      /* accent trên nền này 6.00 */
    /* Nền trang, không phải trắng: trắng chỉ 3.23:1 trên accent đã sáng lên. */
    --on-accent: #14120F;        /* trên accent 7.19 */

    --stat: #F0906A;             /* surface 7.15 */
    --stat-weak: #33211A;        /* stat trên nền này 6.48 */

    --error: #fca5a5;
    --error-bg: #2a1517;

    --shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 8px 24px rgba(0, 0, 0, 0.35);
  }
}

* { box-sizing: border-box; }

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
[role="tab"]:focus-visible,
.dropzone:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 15px;
  -webkit-font-smoothing: antialiased;
  line-height: 1.6;
}

.page {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 20px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Topnav (single-line, no compact-on-scroll because pdftool is a
   single-screen tool — no reason to swap heights). */
.topnav {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--nav-h);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topnav {
    background: var(--bg);
  }
}

.topnav-brand {
  display: block;
  line-height: 0;
}

/* Mask, not <img> — see branding/CLAUDE.md why.
   36×36 is the repo-wide size (branding/chrome.css, 22/08/2026). */
.topnav-logo {
  display: block;
  width: 36px;
  height: 36px;
  background-color: var(--logo-color);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-image: url("/logo.svg");
  /* No rotate() here: logo.svg is generated pre-rotated (branding/build-icons.py,
     LOGO_ROTATE = 45). Rotating again lands the mark at 90°. */
  mask-image: url("/logo.svg");
}

.menu {
  position: relative;
}

/* 44×44 for WCAG 2.5.5's touch target — branding/chrome.css sanctions it over
   its own 36×36 reference precisely because the button has no border and no
   fill, so the extra 8px is hit area nobody can see.

   No background (22/08/2026, repo-wide): a filled square drew a box around an
   icon that reads fine on its own. The hover tint stays — it appears under the
   cursor, which is where an affordance belongs. */
.menu-btn {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
  transition: background 0.15s ease;
}

.menu-btn:hover {
  background: var(--accent-weak);
}

/* Canonical 2-bar→X morph: center with margin (not transform
   translate), so open state is a pure rotate ±45° — symmetric.
   See CLAUDE.md "Known gotchas" #2. */
.menu-btn span {
  position: absolute;
  left: 50%;
  top: 50%;
  display: block;
  width: 16px;
  height: 1.5px;
  margin: -0.75px 0 0 -8px;
  background: var(--text);
  border-radius: 1px;
  transition: transform 0.25s ease;
}

.menu-btn span:nth-child(1) {
  transform: translateY(-4px);
}

.menu-btn span:nth-child(2) {
  transform: translateY(3px);
}

.menu-btn[aria-expanded="true"] span:nth-child(1) {
  transform: rotate(45deg);
}

.menu-btn[aria-expanded="true"] span:nth-child(2) {
  transform: rotate(-45deg);
}

.menu-panel {
  position: absolute;
  top: 52px;
  right: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  padding: 6px;
  display: flex;
  flex-direction: column;
  min-width: 200px;
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  z-index: 10;
}

.menu-panel[hidden] {
  display: none;
}

.menu-panel a {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  border-radius: var(--radius-xs);
  color: var(--text);
  text-decoration: none;
  font-size: 14px;
  font-weight: 600;
}

.menu-panel a:hover {
  background: var(--accent-weak);
  color: var(--accent);
}

.flag-icon {
  width: 18px;
  height: 12px;
  border-radius: 2px;
  flex-shrink: 0;
}

.menu-panel-divider {
  height: 1px;
  background: var(--border);
  margin: 6px 4px;
}

/* Desktop: no hamburger, links laid out in a row (branding/chrome.css, 720px).
   CSS only — the button and panel keep their ids, so menu.js is untouched and
   still drives the dropdown below the breakpoint.

   The eleven .menu-panel-tab links are hidden up here on purpose. They are the
   tool list, not site navigation, and this page already renders the same eleven
   as a visible .tab-strip — a second copy strung across the bar would both
   overflow it and duplicate what is on screen a few pixels lower. On a phone
   they stay in the dropdown, where the strip has to scroll. That leaves the
   language switch as the one genuine nav item, so its divider goes too. */
@media (min-width: 720px) {
  .menu-btn {
    display: none;
  }

  /* Both selectors are required: [hidden] is a UA default and a bare class rule
     does not outrank it — the symptom is an empty-looking nav, not an error. */
  .menu-panel,
  .menu-panel[hidden] {
    display: flex;
  }

  .menu-panel {
    position: static;
    flex-direction: row;
    align-items: center;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    min-width: 0;
    max-height: none;
    overflow: visible;
  }

  .menu-panel a {
    padding: 8px 10px;
  }

  .menu-panel .menu-panel-tab,
  .menu-panel .menu-panel-divider {
    display: none;
  }
}


/* ============================================================
   HERO
   ============================================================ */
.page-header {
  margin: var(--space-4) 0 var(--space-3);
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 10px;
}

.page-header h1 {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.25;
  margin: 0 0 8px;
}

.page-header .subtitle {
  color: var(--text-muted);
  font-size: 15px;
  margin: 0 0 14px;
  max-width: 52ch;
}

.privacy-note {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: 13px;
  color: var(--text-faint);
  margin: 0;
}
.privacy-note svg {
  flex-shrink: 0;
  margin-top: 3px;
}

/* ============================================================
   MẮT LƯỚI CÔNG CỤ — ô vuông, viền tóc dùng chung cạnh
   ============================================================ */
.tool-groups {
  margin: var(--space-3) 0;
}

.tool-section + .tool-section {
  margin-top: 18px;
}

.tool-section-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin: 0 0 8px;
}

.tool-grid {
  display: grid;
  /* 120px chứ không phải 108px của prototype: ở khổ 720px, 108px cho ra 6 cột
     rộng 113px, vừa hụt so với nhãn tiếng Anh dài nhất ("Compress PDF") nên cả
     hàng bị đội cao lên vì một ô xuống dòng. 120px cho 5 cột rộng 136px, đủ
     cho cả hai ngôn ngữ. */
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  /* bù lại margin âm -1px ở .tab bên dưới, nếu không hàng/cột đầu bị cắt */
  padding: 1px 0 0 1px;
}

/* Giữ tên .tab để không phải đụng menu.js / app.js — chỉ đổi hình thức từ
   viên thuốc bo tròn sang ô vuông trong mắt lưới. */
.tab {
  appearance: none;
  border: 1px solid var(--border);
  /* gộp cạnh chung của hai ô cạnh nhau thành MỘT đường tóc */
  margin: -1px 0 0 -1px;
  background: var(--surface);
  color: var(--text-muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 10px 12px;
  text-align: left;
  cursor: pointer;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.tab:hover {
  background: var(--surface-muted);
  color: var(--text);
}
.tab[aria-selected="true"] {
  background: var(--text);
  color: var(--bg);
  border-color: var(--text);
  /* nổi lên trên các ô bên cạnh để bốn cạnh đậm không bị cạnh nhạt đè */
  position: relative;
  z-index: 1;
}

/* ============================================================
   CARD — phẳng, vuông, chia hàng bằng viền tóc (kiểu sổ cái)
   ============================================================ */
.panels {
  margin-bottom: var(--space-4);
}

.tabpanel {
  background: var(--surface);
  border: 1px solid var(--border);
}

/* Bẫy đã biết #1 (xem CLAUDE.md): đặt display trên một class có thể mang
   thuộc tính hidden sẽ đè mất [hidden]{display:none} mặc định của UA. */
.tabpanel[hidden] {
  display: none;
}

.tabpanel h2 {
  font-size: 17px;
  font-weight: 600;
  margin: 0 0 4px;
  letter-spacing: -0.01em;
}

.panel-help {
  font-size: 13.5px;
  color: var(--text-muted);
  margin: 0;
  max-width: 56ch;
}

/* Đầu card + các hàng. Mỗi hàng cách nhau đúng một đường tóc. */
.card-header {
  padding: 22px 24px;
}

.card-row {
  padding: 22px 24px;
  border-top: 1px solid var(--border);
}

.card-row--upload {
  background: var(--surface-muted);
}

/* ============================================================
   DROPZONE + CHIP FILE
   ============================================================ */
.dropzone {
  display: block;
  width: 100%;
  padding: 26px 20px;
  text-align: center;
  cursor: pointer;
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  transition: background .15s var(--ease);
}
.dropzone:hover,
.dropzone.is-dragover {
  background: var(--accent-weak);
}
.dropzone svg {
  color: var(--text-faint);
  margin-bottom: 8px;
}
.dropzone-text,
.dropzone-hint {
  display: block;
}
.dropzone-text {
  font-size: 14px;
  font-weight: 500;
  margin: 0 0 2px;
}
.dropzone-hint {
  font-size: 12.5px;
  color: var(--text-faint);
  margin: 0;
}
.dropzone[hidden] {
  display: none;
}

.file-info {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 10px 12px;
}
/* Bẫy #1 lần nữa: .file-info có display nên phải trả lại [hidden]. */
.file-info[hidden] {
  display: none;
}

/* Đã chọn file thì chip thay chỗ dropzone. Làm bằng CSS chứ không bằng JS: các
   wireX() bật/tắt .file-info ở bốn chỗ khác nhau, thêm một lệnh ẩn dropzone vào
   từng chỗ là bốn dịp để quên một chỗ. Panel nhiều file không có .file-info nên
   dropzone của chúng ở nguyên -- đúng ý, vì còn phải thêm được file nữa. */
.card-row--upload:has(.file-info:not([hidden])) .dropzone {
  display: none;
}

.file-chip-icon {
  width: 30px;
  height: 30px;
  background: var(--accent-weak);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.file-chip-meta {
  flex: 1;
  min-width: 0;
}
.file-chip-name,
.file-chip-size {
  display: block;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-chip-name {
  font-size: 13.5px;
  font-weight: 500;
}
.file-chip-size {
  font-size: 12px;
  color: var(--text-faint);
}
.file-chip-remove {
  border: none;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  padding: 4px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
}
.file-chip-remove:hover {
  color: var(--text);
  background: var(--border);
}

/* ============================================================
   FIELDSET / NHÃN
   ============================================================ */
.opts {
  border: none;
  margin: 0 0 18px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
/* Bẫy #1: .opts có display:flex nên [hidden] phải được trả lại tường minh. */
.opts[hidden] {
  display: none;
}
.opts:last-of-type {
  margin-bottom: 18px;
}

.opts legend,
.field-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0;
  margin: 0 0 2px;
}

.opts label {
  font-size: 13.5px;
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.opts input[type="checkbox"],
.opts input[type="radio"] {
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.opts input[type="range"] {
  accent-color: var(--accent);
  flex: 1;
  min-width: 120px;
}

.opts input[type="number"],
.opts select,
select {
  appearance: none;
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 7px 10px;
  font-size: 13px;
  font-family: inherit;
  color: var(--text);
  cursor: pointer;
  max-width: 100%;
}

.opts select,
select {
  padding-right: 30px;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" viewBox="0 0 10 6"><path d="M1 1l4 4 4-4" stroke="%236E6C64" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>');
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.opts input[type="number"] {
  cursor: text;
  width: 8ch;
}

/* ============================================================
   SEGMENTED CONTROL — hai nửa vuông, ngăn bằng một đường tóc.

   Dựng thẳng trên chính các <input type="radio"> cũ, không phải một widget
   mới: getMode() trong app.js vẫn đọc input:checked như trước, nên cả cờ
   userPickedMode (bản vá 24/08) lẫn mọi logic chọn chế độ chạy nguyên vẹn.
   Radio bị ẩn về mặt thị giác nhưng vẫn nhận focus và phím mũi tên.
   ============================================================ */
.opts-mode {
  gap: 6px;
}

.segmented {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border: 1px solid var(--border);
}

.segment {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--surface);
  padding: 11px 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  text-align: center;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.segment + .segment {
  border-left: 1px solid var(--border);
}
.segment svg {
  flex-shrink: 0;
}

/* Radio thật: giấu khỏi mắt, giữ cho bàn phím và trình đọc màn hình. */
.segment input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.segment:has(input:checked) {
  background: var(--text);
  color: var(--bg);
}
.segment:has(input:focus-visible) {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.method-hint {
  font-size: 12.5px;
  color: var(--text-faint);
  margin: 6px 0 0;
}
.method-hint[hidden] {
  display: none;
}

/* ============================================================
   DANH SÁCH FILE
   ============================================================ */
.file-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  border: 1px solid var(--border);
}
.file-list[hidden] {
  display: none;
}
.file-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  font-size: 13.5px;
}
.file-list li + li {
  border-top: 1px solid var(--border);
}
.file-list li .file-name {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.file-list li .file-size {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-faint);
  flex-shrink: 0;
}
.file-list li button {
  border: none;
  background: none;
  color: var(--text-faint);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 4px 6px;
  flex-shrink: 0;
}
.file-list li button:hover {
  color: var(--text);
  background: var(--border);
}
.file-list.sortable li {
  cursor: grab;
}
.file-list.sortable li.dragging {
  opacity: .5;
}

.mini-btn,
.file-list-clear {
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-muted);
  font-family: inherit;
  font-size: 12.5px;
  padding: 6px 10px;
  cursor: pointer;
  transition: background .12s var(--ease);
}
.mini-btn:hover,
.file-list-clear:hover {
  background: var(--surface-muted);
  color: var(--text);
}
.file-list-clear {
  margin-bottom: 12px;
}
.file-list-clear[hidden] {
  display: none;
}

/* ============================================================
   CHỌN TRANG (tách / xoay / xoá)
   ============================================================ */
.page-selector {
  margin-bottom: 18px;
}
.page-selector[hidden] {
  display: none;
}
.page-selector-actions {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.page-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(58px, 1fr));
  padding: 1px 0 0 1px;
  max-height: 260px;
  overflow-y: auto;
}
.page-grid label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  border: 1px solid var(--border);
  margin: -1px 0 0 -1px;
  padding: 9px 4px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-muted);
  cursor: pointer;
  transition: background .12s var(--ease);
}
.page-grid label:hover {
  background: var(--surface-muted);
  color: var(--text);
}
.page-grid input[type="checkbox"] {
  accent-color: var(--accent);
  margin: 0;
}
.page-grid label.checked {
  background: var(--accent-weak);
  color: var(--accent);
  border-color: var(--accent);
  position: relative;
  z-index: 1;
}

/* ============================================================
   SẮP XẾP TRANG
   ============================================================ */
.page-order {
  margin-bottom: 18px;
}
.page-order[hidden] {
  display: none;
}

.order-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border);
  max-height: 320px;
  overflow-y: auto;
}
.order-list li {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px 6px 10px;
}
.order-list li + li {
  border-top: 1px solid var(--border);
}
.order-list li.dragging {
  opacity: .45;
}

/* Số trang gốc, cỡ mono cố định để cột không nhảy khi qua 2 rồi 3 chữ số.
   Vị trí mới thì không in ra: nó CHÍNH LÀ thứ tự của dòng trong danh sách,
   in thêm một cột số nữa chỉ tạo ra hai con số phải đối chiếu với nhau. */
.order-num {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  min-width: 3ch;
}
.order-spacer {
  flex: 1;
}

.order-move {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: background .12s var(--ease), color .12s var(--ease);
}
.order-move svg {
  width: 15px;
  height: 15px;
}
.order-move:hover:not(:disabled) {
  background: var(--surface-muted);
  color: var(--text);
}
.order-move:disabled {
  opacity: .3;
  cursor: default;
}

/* ============================================================
   NÚT CHẠY
   ============================================================ */
.run-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: none;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 13px 20px;
  cursor: pointer;
  transition: background .15s var(--ease);
}
.run-btn:hover:not(:disabled) {
  background: var(--accent-hover);
}
.run-btn:disabled {
  opacity: .45;
  cursor: not-allowed;
}
.run-btn.processing {
  opacity: .8;
  cursor: progress;
}

.spinner {
  width: 15px;
  height: 15px;
  border: 2px solid color-mix(in srgb, var(--on-accent) 35%, transparent);
  border-top-color: var(--on-accent);
  border-radius: 50%;
  animation: spin .7s linear infinite;
  flex-shrink: 0;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ============================================================
   KẾT QUẢ
   ============================================================ */
.result {
  border-top: 1px solid var(--border);
  padding: 22px 24px;
  margin: 22px -24px -22px;
}
/* Bẫy #1. */
.result[hidden] {
  display: none;
}

/* Thanh co ngót: rãnh là kích thước gốc, phần tô là kích thước sau khi xử lý.
   Đọc được ngay tỉ lệ mà không cần dịch con số phần trăm trong đầu. */
.shrink-bar-labels {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
}
.shrink-bar-labels .before {
  color: var(--text-faint);
}
.shrink-bar-labels .after {
  color: var(--text);
  font-weight: 600;
}

.shrink-bar-track {
  position: relative;
  height: 8px;
  background: var(--surface-muted);
  border: 1px solid var(--border);
  overflow: hidden;
  margin-bottom: 10px;
}
.shrink-bar-fill {
  position: absolute;
  inset: 0;
  width: 100%;
  background: var(--accent);
  transition: width .6s var(--ease) .1s;
}

.saved-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--stat);
  background: var(--stat-weak);
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  padding: 3px 8px;
  margin-bottom: 14px;
}
.saved-badge[hidden] {
  display: none;
}

.result-stats {
  font-size: 13px;
  color: var(--text-muted);
  margin: 0 0 12px;
}
.result-stats [data-stat] {
  font-family: var(--font-mono);
  color: var(--text);
  font-weight: 600;
}

.result-note {
  font-size: 12.5px;
  color: var(--text-faint);
  margin: 0 0 14px;
  line-height: 1.55;
}
.result-note[hidden] {
  display: none;
}

/* Hàng tải về: biểu tượng đặc, tên file kiểu mono, nút tải vuông bên phải. */
.download-btn {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: inherit;
  border: 1px solid var(--border);
  padding: 10px 12px;
  transition: background .15s var(--ease);
}
.download-btn:hover {
  background: var(--surface-muted);
}
.download-btn svg {
  width: 34px;
  height: 34px;
  padding: 9px;
  background: var(--accent);
  color: var(--on-accent);
  flex-shrink: 0;
}
.download-btn .btn-label {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.download-btn .btn-name {
  font-family: var(--font-mono);
  font-size: 12.5px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.download-btn .btn-meta {
  font-size: 12px;
  color: var(--text-faint);
}

.result-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--border);
}
.result-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  font-size: 13.5px;
}
.result-list li + li {
  border-top: 1px solid var(--border);
}
.result-list li .file-name {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.result-list li .file-size {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-faint);
  flex-shrink: 0;
}
.result-list li a {
  color: var(--accent);
  font-size: 12.5px;
  font-weight: 600;
  text-decoration: none;
  flex-shrink: 0;
}
.result-list li a:hover {
  text-decoration: underline;
}

.error-msg {
  color: var(--error);
  background: var(--error-bg);
  border: 1px solid currentColor;
  font-size: 13px;
  padding: 10px 12px;
  margin: 14px 0 0;
}
.error-msg[hidden] {
  display: none;
}

/* ============================================================
   FOOTER — căn giữa, cùng max-width/padding với .page (CLAUDE.md)
   ============================================================ */
.footer {
  margin-top: auto;
  padding: var(--space-2) 20px;
  background: var(--bg);
  text-align: center;
}

.footer-credit {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}

/* ============================================================
   MÀN HÌNH HẸP
   ============================================================ */
/* Điện thoại: lề hẹp, KHÔNG tràn mép.

   Trên iPhone 14 Pro (393pt) lề cũ ăn mất 40pt của .page cộng 36pt padding
   card, còn lại 317pt cho nội dung -- 19% chiều ngang dành cho khoảng trắng ở
   rìa, trên đúng thiết bị ít chỗ nhất. Ở đây lề .page còn 10pt và padding
   card còn 12pt, nội dung được 349pt: hơn 32pt mà card vẫn còn đủ bốn cạnh.

   Một cái lề duy nhất trên .page là điều kiện để mọi thứ thẳng hàng: mắt lưới
   công cụ, viền trái/phải của card và footer đều lấy mép từ đó, nên không có
   khối nào thụt ra thụt vào so với khối khác. Đừng thêm padding riêng cho
   từng khối để "chỉnh cho đẹp" -- đó đúng là cách làm chúng lệch nhau. */
@media (max-width: 480px) {
  .page {
    padding: 0 10px;
  }

  .page-header {
    margin: var(--space-3) 0 var(--space-2);
  }
  .page-header h1 { font-size: 22px; }

  .card-header,
  .card-row { padding: 16px 12px; }

  /* Số âm phải khớp padding ngang mới của .card-row, nếu không hàng kết quả
     lệch khỏi các hàng còn lại đúng bằng phần chênh. */
  .result { padding: 16px 12px; margin: 16px -12px -16px; }

  /* Ba cột đặt cứng, không auto-fill: ở 393pt thì minmax(92px) cho ra 4 cột
     rộng 93pt, hụt so với nhãn dài nhất nên một ô xuống dòng và đội cao cả
     hàng, lại còn thừa một ô lẻ ở hàng cuối. Ba cột cho ra 124pt vừa đủ, và
     9 công cụ PDF chia đúng 3 hàng đầy, 3 công cụ ảnh đúng 1 hàng đầy. */
  .tool-grid { grid-template-columns: repeat(3, 1fr); }
  .tab { padding: 10px; }

  .footer { padding: var(--space-2) 10px; }
}

/* Cảm ứng: vùng chạm >=44pt (Apple HIG), input >=16px để iOS Safari không tự
   phóng khi focus, và bỏ hiệu ứng hover vì trên cảm ứng nó trông như lỗi. */
@media (hover: none) and (pointer: coarse) {
  .tab,
  .run-btn,
  .download-btn,
  .mini-btn,
  .file-list-clear,
  .segment,
  .order-move,
  .file-chip-remove {
    min-height: 44px;
  }
  .order-move {
    min-width: 44px;
  }
  .opts input[type="number"],
  .opts select,
  select {
    font-size: 16px;
  }
  .tab:not([aria-selected="true"]):hover,
  .download-btn:hover,
  .dropzone:hover {
    background: inherit;
  }
}
