/* migurei — control-room dashboard
   Hướng thẩm mỹ: phòng điều khiển kỹ thuật. Đây là bảng theo dõi một quy trình dài, nhiều
   giai đoạn, có cổng kiểm duyệt — nên nó phải đọc như một thiết bị đo, không như một trang
   tiếp thị. Dày dữ liệu, lưới bản vẽ mờ ở nền, số liệu bằng monospace canh cột, và đúng một
   thứ chuyển động: trạng thái đang chạy có nhịp đập. Mọi thứ khác đứng yên.
   Dark-first, nhưng light là một bảng thiết kế riêng chứ không phải bản đảo màu. */

/* ─────────────────────────── tokens ─────────────────────────── */
:root{
  /* light — giấy kỹ thuật: hơi ngả lạnh, không trắng tinh */
  --bg:#eef1f6;
  --bg-grid:rgba(15,32,58,.055);
  --panel:#ffffff;
  --panel-2:#f5f7fb;
  --panel-3:#eaeef5;
  --ink:#0d1520;
  --ink-2:#2b3646;
  --muted:#5d6a7d;
  --faint:#8593a6;
  --line:#d7dee9;
  --line-2:#c3cddc;

  --accent:#0b6b7a;         /* teal sâu — màu của thiết bị đo, không phải màu marketing */
  --accent-ink:#ffffff;
  --accent-soft:#d8eef1;
  --accent-glow:rgba(11,107,122,.22);

  --done:#0f7a3d;   --done-bg:#e2f4e8;   --done-line:#8fd0a8;
  --run:#0b6b7a;    --run-bg:#d8eef1;    --run-line:#7cc3ce;
  --wait:#a86a00;   --wait-bg:#fbf0d9;   --wait-line:#e0bd76;
  --fail:#b32020;   --fail-bg:#fbe6e6;   --fail-line:#e19a9a;
  --skip:#5d6a7d;   --skip-bg:#e9edf3;   --skip-line:#c3cddc;
  --idle:#93a0b2;   --idle-bg:#f0f3f8;   --idle-line:#dde4ee;

  --shadow-1:0 1px 2px rgba(13,21,32,.06);
  --shadow-2:0 2px 4px rgba(13,21,32,.06), 0 8px 20px -8px rgba(13,21,32,.14);
  --shadow-lift:0 4px 8px rgba(13,21,32,.08), 0 18px 40px -14px rgba(13,21,32,.22);

  --sans:'Archivo', ui-sans-serif, system-ui, sans-serif;
  --mono:'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  --page:2000px;
  --gut:32px;
  --r:10px;      /* bán kính: nhỏ và nhất quán — thiết bị đo không bo tròn nhiều */
  --r-sm:6px;
  --ease:cubic-bezier(.22,.61,.36,1);
}

@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){ color-scheme:dark;
    --bg:#080b11;
    --bg-grid:rgba(126,196,214,.055);
    --panel:#0e131c;
    --panel-2:#131a25;
    --panel-3:#19212e;
    --ink:#eaf2f7;
    --ink-2:#c3d0dc;
    --muted:#8b9bad;
    --faint:#63738a;
    --line:#1e2836;
    --line-2:#2b3849;

    --accent:#3ecfe0;
    --accent-ink:#04161a;
    --accent-soft:#0d2b33;
    --accent-glow:rgba(62,207,224,.30);

    --done:#3fcf7a;   --done-bg:#0c2418;   --done-line:#1e5c39;
    --run:#3ecfe0;    --run-bg:#0b2830;    --run-line:#1c5f6c;
    --wait:#f0b13c;   --wait-bg:#2a1f0a;   --wait-line:#6b501a;
    --fail:#f0605f;   --fail-bg:#2a1213;   --fail-line:#6e2827;
    --skip:#7a8798;   --skip-bg:#161d28;   --skip-line:#2b3849;
    --idle:#3c4859;   --idle-bg:#111823;   --idle-line:#1e2836;

    --shadow-1:0 1px 2px rgba(0,0,0,.5);
    --shadow-2:0 2px 6px rgba(0,0,0,.45), 0 12px 28px -12px rgba(0,0,0,.7);
    --shadow-lift:0 6px 14px rgba(0,0,0,.5), 0 24px 54px -18px rgba(0,0,0,.8);
  }
}
:root[data-theme="dark"]{ color-scheme:dark;
  --bg:#080b11;
  --bg-grid:rgba(126,196,214,.055);
  --panel:#0e131c;
  --panel-2:#131a25;
  --panel-3:#19212e;
  --ink:#eaf2f7;
  --ink-2:#c3d0dc;
  --muted:#8b9bad;
  --faint:#63738a;
  --line:#1e2836;
  --line-2:#2b3849;

  --accent:#3ecfe0;
  --accent-ink:#04161a;
  --accent-soft:#0d2b33;
  --accent-glow:rgba(62,207,224,.30);

  --done:#3fcf7a;   --done-bg:#0c2418;   --done-line:#1e5c39;
  --run:#3ecfe0;    --run-bg:#0b2830;    --run-line:#1c5f6c;
  --wait:#f0b13c;   --wait-bg:#2a1f0a;   --wait-line:#6b501a;
  --fail:#f0605f;   --fail-bg:#2a1213;   --fail-line:#6e2827;
  --skip:#7a8798;   --skip-bg:#161d28;   --skip-line:#2b3849;
  --idle:#3c4859;   --idle-bg:#111823;   --idle-line:#1e2836;

  --shadow-1:0 1px 2px rgba(0,0,0,.5);
  --shadow-2:0 2px 6px rgba(0,0,0,.45), 0 12px 28px -12px rgba(0,0,0,.7);
  --shadow-lift:0 6px 14px rgba(0,0,0,.5), 0 24px 54px -18px rgba(0,0,0,.8);
}

/* ─────────────────────────── base ─────────────────────────── */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--sans); font-size:14px; line-height:1.55;
  font-feature-settings:'cv05' 1,'ss01' 1;
  -webkit-font-smoothing:antialiased;
  position:relative; min-height:100vh;
}
/* lưới bản vẽ — nền của một phòng kỹ thuật, không phải màu phẳng */
body::before{
  content:''; position:fixed; inset:0; pointer-events:none; z-index:0;
  background-image:
    linear-gradient(var(--bg-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
  background-size:48px 48px;
  mask-image:radial-gradient(ellipse 120% 80% at 50% 0%, #000 35%, transparent 100%);
}
body>*{position:relative; z-index:1}

::selection{background:var(--accent-glow); color:var(--ink)}
a{color:var(--accent); text-decoration:none}
a:hover{text-decoration:underline; text-underline-offset:2px}

/* thanh cuộn hợp tông, không phải mặc định hệ điều hành */
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--line-2); border-radius:99px; border:3px solid transparent; background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--muted); background-clip:content-box}

/* ─────────────────────────── thanh trên ───────────────────────────
   Một thanh ngang, không sidebar. Danh sách dự án từng nằm ở cột trái, nhưng ma trận trang chủ
   đã trả lời câu hỏi đó tốt hơn, và một cột dọc hết chỗ ngay khi danh mục vượt ~15 dự án. */
.topbar{
  position:sticky; top:0; z-index:40; display:flex; align-items:center; gap:14px;
  height:56px; padding:0 var(--gut);
  background:color-mix(in srgb, var(--panel) 82%, transparent);
  backdrop-filter:blur(14px) saturate(1.4); -webkit-backdrop-filter:blur(14px) saturate(1.4);
  border-bottom:1px solid var(--line);
}
.brand{
  display:flex; align-items:center; gap:9px; flex:none;
  font-weight:700; font-size:14px; letter-spacing:.16em; color:var(--ink);
}
.brand:hover{text-decoration:none}
.beacon{
  width:7px; height:7px; border-radius:50%; background:var(--accent); flex:none;
  box-shadow:0 0 0 3px var(--accent-glow); animation:beacon 3.4s var(--ease) infinite;
}
@keyframes beacon{
  0%,100%{box-shadow:0 0 0 3px var(--accent-glow); opacity:1}
  50%    {box-shadow:0 0 0 6px transparent;        opacity:.55}
}
.crumbs{display:flex; align-items:center; gap:9px; font-size:12.5px; color:var(--muted); min-width:0}
.crumbs a{color:var(--muted)}
.crumbs a:hover{color:var(--ink)}
.crumbs span{color:var(--faint)}
.spacer{flex:1}
.meta{color:var(--muted); font-size:11.5px; font-family:var(--mono); font-variant-numeric:tabular-nums}

/* ô chọn dự án — thay cho tên tĩnh trong breadcrumb; chiếm một dòng dù có bao nhiêu dự án */
.pick{
  appearance:none; font-family:var(--mono); font-size:12.5px; font-weight:700;
  color:var(--ink); background:var(--panel-2); border:1px solid var(--line);
  border-radius:var(--r-sm); padding:4px 26px 4px 10px; cursor:pointer; max-width:340px;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),
                   linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 14px) 52%, calc(100% - 9px) 52%;
  background-size:5px 5px, 5px 5px; background-repeat:no-repeat;
  transition:border-color .16s var(--ease), background-color .16s var(--ease);
}
.pick:hover{border-color:var(--line-2); background-color:var(--panel-3)}
.pick:focus{outline:0; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow)}

/* .seg đã được pathLabel() dùng cho từng mảnh đường dẫn từ trước — nhóm nút phải mang tên khác,
   nếu không mỗi mảnh "assessment/cobol/extract-P02.md" biến thành một cái hộp riêng có viền. */
.btnset{display:flex; gap:2px; padding:3px; background:var(--panel-2); border:1px solid var(--line); border-radius:var(--r-sm); flex:none}
.segbtn{
  appearance:none; border:0; background:transparent; color:var(--muted);
  font-family:var(--mono); font-size:10.5px; font-weight:500; letter-spacing:.03em;
  padding:5px 10px; border-radius:4px; cursor:pointer;
  transition:color .16s var(--ease), background .16s var(--ease);
}
.segbtn:hover{color:var(--ink); background:var(--panel-3)}
.segbtn[aria-pressed="true"]{background:var(--accent); color:var(--accent-ink)}
/* `日本語` là ba glyph rộng, nên nút ngôn ngữ không dùng chung chiều rộng cố định với nút theme.
   Chỉ chiều NGANG được đổi: padding dọc giữ đúng 5px của .segbtn, vì không có height cố định nào
   trong cascade — hạ nó xuống 0 làm hộp #langs thấp hơn #themes ngay cạnh nó chừng 9px. */
#langs .segbtn{min-width:30px; padding:5px 6px; font-size:11px}

main{max-width:var(--page); margin:0 auto; padding:28px var(--gut) 72px}

.hd{display:flex; align-items:baseline; gap:14px; flex-wrap:wrap; margin:0 0 18px}
.hd h1{font-size:21px; font-weight:600; letter-spacing:-.015em; margin:0}
.hd .spacer{min-width:20px}

/* ─────────────────────────── card ─────────────────────────── */
.card{
  background:var(--panel); border:1px solid var(--line); border-radius:var(--r);
  padding:18px 20px; margin:0 0 18px; box-shadow:var(--shadow-1);
}
.card>h2,.card>h3{margin:0 0 14px; font-size:12px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--muted)}
.grid{display:grid; gap:18px}
@media (min-width:1100px){ .grid.two{grid-template-columns:1fr 1fr} }

/* ─────────────────────────── KPI ─────────────────────────── */
.kpis{display:grid; grid-template-columns:repeat(auto-fit,minmax(158px,1fr)); gap:12px; margin:0 0 20px}
.kpi{
  background:var(--panel); border:1px solid var(--line); border-radius:var(--r);
  padding:15px 17px; position:relative; overflow:hidden; box-shadow:var(--shadow-1);
}
/* gờ sáng bên trái — dấu hiệu đây là một chỉ số đo, không phải một ô văn bản */
.kpi::before{content:''; position:absolute; left:0; top:0; bottom:0; width:2px; background:var(--line-2)}
.kpi.on::before{background:var(--accent); box-shadow:0 0 12px var(--accent-glow)}
/* KPI thật là `<i>nhãn</i><b>giá trị</b>` — nhãn ĐỨNG TRƯỚC, nên phải đảo thứ tự hiển thị:
   con số là thứ mắt phải chạm đầu tiên, nhãn chỉ để xác nhận nó là con số gì. */
.kpi{display:flex; flex-direction:column-reverse; align-items:flex-start; gap:3px}
.kpi>b,.kpi .count,.kpi strong{
  font-family:var(--mono); font-size:27px; font-weight:500;
  letter-spacing:-.02em; font-variant-numeric:tabular-nums; color:var(--ink); line-height:1.15;
}
.kpi>i,.kpi .meta,.kpi small{
  font-style:normal; font-size:10.5px; letter-spacing:.07em; text-transform:uppercase;
  color:var(--muted); font-family:var(--sans); font-weight:500;
}

/* ─────────────────────────── project cards ─────────────────────────── */
.pgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(330px,1fr)); gap:16px}
.pcard{
  background:var(--panel); border:1px solid var(--line); border-radius:var(--r);
  padding:0; overflow:hidden; box-shadow:var(--shadow-1);
  transition:transform .2s var(--ease), box-shadow .2s var(--ease), border-color .2s var(--ease);
}
.pcard:hover{transform:translateY(-2px); box-shadow:var(--shadow-lift); border-color:var(--line-2)}
/* card là một <a>: nếu không ghim màu, MỌI giá trị bên trong biến thành màu link
   và mắt mất hết thứ tự ưu tiên — cái đáng nhấn lẫn với cái chỉ là dữ liệu. */
.pcard,.pcard:hover{color:var(--ink); text-decoration:none}
.pcard>.meta{padding:11px 17px 0; font-family:var(--mono); font-size:10.5px; color:var(--faint)}
.pcard>.meta:empty{display:none}          /* tên trùng id → chuỗi rỗng, đừng để nó chiếm một dòng */
.pcard>svg{display:block; padding:12px 17px 2px; max-width:100%; height:auto}
.pcard-kv span{color:var(--ink)}
.pcard-h b{font-family:var(--mono); font-weight:700; font-size:13.5px; letter-spacing:.02em}
.pcard-h .spacer,.pcard-h{justify-content:space-between}
.pcard-h .tag{margin-left:auto}
.pcard-h{
  display:flex; align-items:center; gap:10px; padding:14px 17px;
  border-bottom:1px solid var(--line); background:var(--panel-2);
}
.pid{font-family:var(--mono); font-weight:700; font-size:13.5px; letter-spacing:.02em; color:var(--ink)}
.pcard-kv{padding:13px 17px; display:grid; gap:7px}
/* nhãn là <i>, giá trị là text-node ngay sau nó — nên hàng phải là flex và <i> phải
   có bề rộng cố định, nếu không hai thứ dính liền thành một chuỗi không đọc được. */
.pcard-kv>span,.kv>span,.kvrow{display:flex; gap:12px; align-items:baseline; font-size:12.5px}
.pcard-kv i,.kv i,.kvrow>i{
  font-style:normal; color:var(--muted); flex:none; min-width:104px;
  font-size:11px; letter-spacing:.03em;
}
.kvrow>:first-child:not(i){color:var(--muted); min-width:104px; flex:none}
.kv{display:grid; gap:7px}

/* ─────────────────────────── trạng thái ─────────────────────────── */
.tag,.chip{
  display:inline-flex; align-items:center; gap:6px; white-space:nowrap;
  padding:2.5px 9px; border-radius:99px;
  font-family:var(--mono); font-size:10.5px; font-weight:500; letter-spacing:.05em;
  background:var(--idle-bg); color:var(--idle); border:1px solid var(--idle-line);
}
.chips{display:flex; flex-wrap:wrap; gap:6px}
.tag.done,.chip.done,.done{background:var(--done-bg); color:var(--done); border-color:var(--done-line)}
.tag.running,.chip.running,.running{background:var(--run-bg); color:var(--run); border-color:var(--run-line)}
.tag.waiting-approval,.chip.waiting-approval,.waiting-approval{background:var(--wait-bg); color:var(--wait); border-color:var(--wait-line)}
.tag.failed,.tag.blocked,.chip.failed,.chip.blocked,.failed,.blocked{background:var(--fail-bg); color:var(--fail); border-color:var(--fail-line)}
.tag.skipped,.chip.skipped,.skipped{background:var(--skip-bg); color:var(--skip); border-color:var(--skip-line)}
.tag.not-started,.chip.not-started,.not-started{background:var(--idle-bg); color:var(--idle); border-color:var(--idle-line)}

.dot{width:8px; height:8px; border-radius:50%; background:var(--idle); flex:none; display:inline-block}
.dot.done{background:var(--done)}
.dot.running{background:var(--run); box-shadow:0 0 0 0 var(--accent-glow); animation:pulse 2s var(--ease) infinite}
.dot.waiting-approval{background:var(--wait)}
.dot.failed,.dot.blocked{background:var(--fail)}
.dot.skipped{background:var(--skip)}
/* Trong ô bảng, chấm đứng cạnh chữ chứ không nằm trong flex parent: inline-block được nhắc lại
   ở đây một cách tường minh, vì lần trước quy tắc nền bị đổi và cột trạng thái lặng lẽ trắng. */
td .dot{display:inline-block; vertical-align:middle; margin:0 7px 0 0}
/* nhịp đập: thứ chuyển động DUY NHẤT lặp lại trên trang, và nó chỉ nói một điều —
   ở đây đang có việc chạy thật. Nếu mọi thứ đều nhấp nháy thì không gì có nghĩa. */
@keyframes pulse{
  0%  {box-shadow:0 0 0 0 var(--accent-glow)}
  70% {box-shadow:0 0 0 7px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

/* ─────────────────────────── pipeline / phase ─────────────────────────── */
.pipe{display:flex; flex-wrap:wrap; gap:5px; margin:2px 0 0}
.cell{
  flex:1 1 0; min-width:26px; height:26px; border-radius:4px;
  background:var(--idle-bg); border:1px solid var(--idle-line);
  display:flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:9.5px; color:var(--muted);
  transition:transform .16s var(--ease), box-shadow .16s var(--ease);
}
.cell:hover{transform:translateY(-2px); box-shadow:var(--shadow-2); z-index:2}
.cell.done{background:var(--done-bg); border-color:var(--done-line); color:var(--done)}
.cell.running{background:var(--run-bg); border-color:var(--run); color:var(--run); box-shadow:0 0 0 1px var(--run), 0 0 14px -2px var(--accent-glow)}
.cell.waiting-approval{background:var(--wait-bg); border-color:var(--wait-line); color:var(--wait)}
.cell.failed,.cell.blocked{background:var(--fail-bg); border-color:var(--fail-line); color:var(--fail)}
.cell.skipped{background:var(--skip-bg); border-color:var(--skip-line); color:var(--skip)}

.ribbon{display:flex; align-items:center; gap:9px; flex-wrap:wrap; margin:0 0 14px}
.bar{height:5px; border-radius:99px; background:var(--panel-3); overflow:hidden; position:relative}
.bar>i{display:block; height:100%; background:var(--accent); border-radius:99px; transition:width .5s var(--ease)}

/* ─────────────────────────── stage ─────────────────────────── */
.stage{
  display:flex; align-items:flex-start; gap:12px; padding:9px 0;
  border-bottom:1px solid var(--line);
}
.stage:last-child{border-bottom:0}
.stage .n{
  width:26px; flex:none; color:var(--faint); font-family:var(--mono); font-size:11px;
  font-variant-numeric:tabular-nums; padding-top:1px;
}
.stage .nm{flex:1; min-width:0}
.stage .nm small{display:block; color:var(--muted); font-family:var(--mono); font-size:10.5px; margin-top:2px}
.gatebox,.gate{
  display:inline-flex; align-items:center; gap:5px; padding:2px 8px; border-radius:var(--r-sm);
  font-family:var(--mono); font-size:10.5px; font-weight:500;
  background:var(--panel-2); border:1px solid var(--line); color:var(--muted);
}

/* ─────────────────────────── bảng ─────────────────────────── */
table{width:100%; border-collapse:collapse; font-size:12.5px}
th,td{padding:8px 11px; text-align:left; border-bottom:1px solid var(--line); vertical-align:top}
th{
  font-size:10px; font-weight:600; letter-spacing:.09em; text-transform:uppercase;
  color:var(--muted); background:var(--panel-2);
  position:sticky; top:58px; z-index:5; background-clip:padding-box;
  border-bottom:1px solid var(--line-2);
}
/* 58px là chiều cao topbar, và nó CHỈ đúng khi thứ đang cuộn là tài liệu. `sticky` neo vào tổ
   tiên cuộn gần nhất, và `.mxwrap` đặt overflow-x:auto — theo spec, một trục không-visible làm
   trục còn lại tính ra `auto`, nên wrapper là một scroll box ở CẢ HAI chiều và chính nó là tổ
   tiên ấy. Scrollport của nó chỉ cao bằng bảng, nên 58px đẩy `thead` xuống DƯỚI hàng đầu tiên
   và z-index:5 phủ nó lên hàng thứ hai: header nằm giữa bảng, không lỗi console nào. Ma trận
   toàn cảnh thoát được vì `.mxt th` đặt position:static; bảng quản trị tài khoản là `<table>`
   trần trong cùng wrapper nên không thoát. Trong một scrollport thì 0 là offset đúng — không có
   topbar nào bên trong wrapper để phải né. */
.mxwrap th{top:0}
tbody tr{transition:background .12s var(--ease)}
tbody tr:hover{background:var(--panel-2)}
td.num,.nowrap{white-space:nowrap}
td .mono,.count{font-family:var(--mono); font-variant-numeric:tabular-nums}

/* ─────────────────────────── tài liệu ─────────────────────────── */
.docgroup{margin:0 0 18px}
.kindhead,.doc-h{
  display:flex; align-items:center; gap:9px; margin:0 0 8px;
  font-size:11px; font-weight:600; letter-spacing:.09em; text-transform:uppercase; color:var(--muted);
}
.kindnote,.note,.cmt-note{color:var(--muted); font-size:12px; line-height:1.6}
/* Ghi chú ledger có thể dài hơn nghìn ký tự. Trong bảng nó thu về đúng một dòng — thiết bị đo
   không để một hàng cao hơn màn hình — và click mở đầy đủ (app.js noteCell / toggleNote). */
.note{
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden;
  overflow-wrap:anywhere; cursor:pointer;
}
.note:hover{color:var(--ink)}
.note.open{-webkit-line-clamp:unset}
/* Cột note giữ một phần cố định của hàng (không phải "phần còn lại"), nên mở một ghi chú ra
   thì chỉ hàng đó cao lên — các cột trạng thái, artifact bên cạnh không nhảy. Trạng thái và
   badge không bao giờ xuống dòng; đường dẫn chỉ gãy tại dấu "/" (app.js pathLabel). */
.notecol{width:38%; min-width:26ch}
.pathcol{min-width:16ch}
.pathcol .file{white-space:normal}
.seg{white-space:nowrap}
.kindblock{display:grid; gap:6px}
/* `.doc` là một hàng đứng riêng; `.file` là một LINK trong ô bảng. Gộp style hai thứ này
   biến mọi tên file trong bảng thành một cái hộp dài ngắn tuỳ tên — cột không còn thẳng. */
.doc{
  display:flex; align-items:center; gap:10px; padding:8px 12px;
  background:var(--panel-2); border:1px solid var(--line); border-radius:var(--r-sm);
  cursor:pointer; transition:border-color .16s var(--ease), background .16s var(--ease), transform .16s var(--ease);
}
.doc:hover{border-color:var(--accent); background:var(--panel); transform:translateX(2px)}
.file{
  font-family:var(--mono); font-size:12px; color:var(--accent);
  cursor:pointer; border-bottom:1px solid transparent; transition:border-color .16s var(--ease);
}
.file:hover{text-decoration:none; border-bottom-color:var(--accent)}
/* bảng tài liệu: cột đầu co giãn, các cột meta bám phải và không xuống dòng */
.doclist{margin:0 0 4px}
.doclist td{padding:5px 11px 5px 0; border-bottom:1px solid var(--line)}
.doclist td:first-child{width:46%}
.doclist tr:last-child td{border-bottom:0}
.doc-name{font-family:var(--mono); font-size:12px; color:var(--ink); flex:1; min-width:0; overflow-wrap:anywhere}
.orphan{opacity:.62}
.empty{color:var(--faint); font-style:italic; font-size:12.5px; padding:14px 0}

.sev{display:inline-flex; align-items:center; gap:5px; font-family:var(--mono); font-size:11px; font-weight:500}
.legend{display:flex; flex-wrap:wrap; gap:14px; font-size:11px; color:var(--muted); padding:10px 0 0}
.legend>*{display:inline-flex; align-items:center; gap:6px}
.toc{display:grid; gap:5px; font-size:12.5px}
.cmt{
  background:var(--panel-2); border-left:2px solid var(--accent); border-radius:0 var(--r-sm) var(--r-sm) 0;
  padding:11px 14px; margin:10px 0; font-size:12.5px; color:var(--ink-2);
}
.log{
  max-height:280px; overflow:auto; font-family:var(--mono); font-size:11px; line-height:1.75;
  background:var(--panel-2); border:1px solid var(--line); border-radius:var(--r-sm); padding:11px 13px;
}
code,.mono{font-family:var(--mono); font-size:12px}
:not(pre)>code{background:var(--panel-3); padding:1.5px 5px; border-radius:4px; font-size:11.5px; color:var(--ink-2)}

/* ─────────────────────────── markdown ─────────────────────────── */
/* 78ch là bề rộng đọc văn xuôi. Nhưng tài liệu ở đây là bảng, read-trace và khối lệnh —
   giới hạn chúng ở 78ch bỏ trống nửa hộp thoại mà không ai đọc dễ hơn. Văn bản giữ 78ch,
   bảng và code được phép tràn hết. */
.md{font-size:14px; line-height:1.72; color:var(--ink-2)}
.md>p,.md>ul,.md>ol,.md>blockquote,.md>h1,.md>h2,.md>h3,.md>h4{max-width:82ch}
.md h1,.md h2,.md h3,.md h4{color:var(--ink); font-weight:600; letter-spacing:-.01em; line-height:1.3; margin:1.9em 0 .6em}
.md h1{font-size:23px} .md h2{font-size:19px} .md h3{font-size:16px} .md h4{font-size:14px}
.md h1:first-child,.md h2:first-child{margin-top:0}
.md h2{padding-bottom:.34em; border-bottom:1px solid var(--line)}
.md p{margin:0 0 1.05em}
.md ul,.md ol{margin:0 0 1.05em; padding-left:1.35em}
.md li{margin:.28em 0}
.md table{margin:1.2em 0; font-size:12.5px; width:100%}
.md table th{position:static}       /* trong hộp thoại, header bảng không được dính theo trang */
.md td,.md th{padding:7px 12px}
.md blockquote{margin:1.2em 0; padding:2px 0 2px 16px; border-left:2px solid var(--accent); color:var(--muted)}
.md pre{
  background:var(--panel-2); border:1px solid var(--line); border-radius:var(--r-sm);
  padding:13px 15px; overflow:auto; margin:1.2em 0;
}
.md pre code{font-family:var(--mono); font-size:11.5px; line-height:1.65; background:none; padding:0}
.md code{font-family:var(--mono); font-size:.88em}
.md hr{border:0; border-top:1px solid var(--line); margin:2em 0}
.md img{max-width:100%}

/* ─────────────────────────── dialog ─────────────────────────── */
dialog{
  border:1px solid var(--line-2); border-radius:var(--r); padding:0;
  background:var(--panel); color:var(--ink);
  width:min(1080px,94vw); max-height:88vh; box-shadow:var(--shadow-lift);
}
dialog::backdrop{background:rgba(4,8,14,.62); backdrop-filter:blur(3px)}
dialog>header{
  position:static; height:auto; padding:13px 17px; gap:10px;
  background:var(--panel-2); backdrop-filter:none;
}
#vwrap{padding:20px 24px 26px; overflow:auto; max-height:calc(88vh - 56px)}
button{
  appearance:none; font-family:var(--sans); font-size:12px; font-weight:500;
  padding:5px 12px; border-radius:var(--r-sm); cursor:pointer;
  background:var(--panel); color:var(--ink-2); border:1px solid var(--line);
  transition:border-color .16s var(--ease), color .16s var(--ease), background .16s var(--ease);
}
button:hover{border-color:var(--line-2); color:var(--ink); background:var(--panel-3)}
button.tog[aria-pressed="true"]{background:var(--accent); color:var(--accent-ink); border-color:var(--accent)}

/* ─────────────────────────── diagram ─────────────────────────── */
.dgwrap{
  overflow:auto; background:var(--panel-2); border:1px solid var(--line);
  border-radius:var(--r-sm); padding:16px;
}
.dg{min-width:100%}
.dg-node{cursor:pointer; transition:opacity .16s var(--ease)}
.dg-node:hover{opacity:.8}
.dg-step{font-family:var(--mono); font-size:10px}

/* ───────────────── mức nghiêm trọng & phán quyết cổng ─────────────────
   Ba con số blocker/major/minor luôn đứng cạnh nhau, nên chúng phải đọc được
   thành một hình: đỏ đậm → hổ phách → xám. Số 0 chìm đi, số khác 0 nổi lên —
   mắt tìm cái đang cháy, không phải đọc cả ba. */
.sev{gap:3px}
.sev b{
  display:inline-flex; align-items:center; justify-content:center;
  min-width:21px; height:19px; padding:0 5px; border-radius:4px;
  font-family:var(--mono); font-size:10.5px; font-weight:700;
  font-variant-numeric:tabular-nums; border:1px solid transparent;
}
.s-b{background:var(--fail-bg); color:var(--fail); border-color:var(--fail-line)}
.s-m{background:var(--wait-bg); color:var(--wait); border-color:var(--wait-line)}
.s-n{background:var(--skip-bg); color:var(--skip); border-color:var(--skip-line)}
/* một hạng mục bằng 0 không đáng chiếm sự chú ý ngang một hạng mục đang cháy */
.sev b:where([data-zero]),.s-b:empty,.s-m:empty,.s-n:empty{opacity:.4}

/* phán quyết cổng — cùng bảng màu với trạng thái, nhưng đặc hơn: đây là kết luận, không phải tiến độ */
.tag.fail,.chip.fail,.gatebox.fail,.gate.fail{background:var(--fail-bg); color:var(--fail); border-color:var(--fail)}
.tag.wait,.chip.wait,.gatebox.wait,.gate.wait{background:var(--wait-bg); color:var(--wait); border-color:var(--wait-line)}
.tag.idle,.chip.idle,.gatebox.idle,.gate.idle{background:var(--idle-bg); color:var(--idle); border-color:var(--idle-line)}
.gatebox.done,.gate.done{background:var(--done-bg); color:var(--done); border-color:var(--done-line)}

/* ───────────────── trang chi tiết một bước ─────────────────
   `.step` là card của đúng một bước đang được mở. Nó không cạnh tranh với các card
   khác trên trang vì nó là thứ duy nhất trên trang — nên nó được rộng hơn và có
   một gờ accent để mắt biết mình đang đứng ở đâu trong lộ trình. */
.card.step{padding:22px 24px; border-left:2px solid var(--accent)}
.card.step>.page-title{margin-bottom:14px}

/* ─────────────────────────── ma trận toàn cảnh ───────────────────────────
   Mỗi hàng một dự án, mỗi cột một bước. Ở dạng này "danh mục đang ở đâu" là một
   hình nhìn thấy được, không phải kết luận rút ra sau khi đọc lần lượt từng card. */
.mxwrap{overflow-x:auto; margin:0 -4px}
/* width:auto — với width:100% cột tên dự án nuốt hết chỗ thừa và đẩy dải ô sang tận mép
   phải, nên hai thứ cần đọc cạnh nhau (tên và tiến trình của nó) lại nằm hai đầu màn hình. */
.mxt{width:auto; min-width:min(100%,760px); border-collapse:separate; border-spacing:3px 4px}
.mxt th,.mxt td{border:0; padding:0}
.mx-h{
  width:30px; font-family:var(--mono); font-size:10px; font-weight:500; color:var(--faint);
  text-align:center; padding-bottom:5px !important; position:static; background:none;
  letter-spacing:0; text-transform:none;
}
.mx-p{
  text-align:left; padding-right:18px !important; width:1px; white-space:nowrap;
  position:static; background:none; border:0; text-transform:none; letter-spacing:0;
}
.mx-p a{display:flex; flex-direction:column; line-height:1.34}
.mx-p a:hover{text-decoration:none}
.mx-p b{font-size:12.5px; color:var(--ink)}
.mx-p .meta{font-size:10.5px}
.mx-c{width:30px}
.mx{
  display:flex; align-items:center; justify-content:center; height:28px; border-radius:5px;
  background:var(--idle-bg); border:1px solid var(--idle-line);
  transition:transform .14s var(--ease), box-shadow .14s var(--ease);
}
.mx:hover{transform:translateY(-2px); box-shadow:var(--shadow-2); text-decoration:none; z-index:2}
.mx.done{background:var(--done-bg); border-color:var(--done-line)}
.mx.running{background:var(--run-bg); border-color:var(--run); box-shadow:0 0 12px -3px var(--accent-glow)}
.mx.waiting-approval{background:var(--wait-bg); border-color:var(--wait-line)}
.mx.failed,.mx.blocked{background:var(--fail-bg); border-color:var(--fail-line)}
.mx.skipped{background:var(--skip-bg); border-color:var(--skip-line)}
.mx-n{
  text-align:right; padding-left:16px !important; white-space:nowrap; width:1px;
  font-size:11.5px; color:var(--muted); position:static; background:none;
  text-transform:none; letter-spacing:0; font-weight:400;
}
thead .mx-n{font-size:10px; color:var(--faint)}

/* hàng đợi hành động — thứ duy nhất trên trang chủ đòi một con người làm gì đó */
.card.urgent{border-color:var(--wait-line); box-shadow:0 0 0 1px var(--wait-line), var(--shadow-2)}
.card.urgent>h2{color:var(--wait)}
.ok{color:var(--done); font-size:13px; display:flex; align-items:center; gap:8px}
.ok::before{content:'✓'; font-weight:700}
.card>h2 .count{
  display:inline-flex; align-items:center; justify-content:center; min-width:20px; height:19px;
  padding:0 6px; margin-left:7px; border-radius:99px; background:var(--wait-bg); color:var(--wait);
  border:1px solid var(--wait-line); font-family:var(--mono); font-size:10.5px; font-weight:700;
}

/* ─────────────────────────── rail dọc + chi tiết ───────────────────────────
   Strip ngang cũ bị cắt ở bước thứ bảy, nên nửa sau lộ trình — đúng nửa chứa các
   cổng kiểm toán — không tồn tại với người chỉ nhìn màn hình. Rail dọc luôn hiện đủ. */
.split{display:grid; grid-template-columns:274px 1fr; gap:20px; align-items:start}
@media (max-width:1080px){ .split{grid-template-columns:1fr} .rail{position:static !important} }
.rail{
  position:sticky; top:80px; display:flex; flex-direction:column; gap:2px;
  background:var(--panel); border:1px solid var(--line); border-radius:var(--r); padding:6px;
  box-shadow:var(--shadow-1); max-height:calc(100vh - 96px); overflow:auto;
}
.rl{
  display:flex; align-items:center; gap:9px; padding:8px 10px; border-radius:var(--r-sm);
  color:var(--ink-2); border-left:2px solid transparent;
  transition:background .14s var(--ease), border-color .14s var(--ease);
}
.rl:hover{background:var(--panel-2); text-decoration:none}
.rl.on{background:var(--accent-soft); border-left-color:var(--accent); color:var(--ink)}
.rl.on .rl-t{color:var(--accent); font-weight:600}
.rl-n{width:20px; flex:none; font-size:10.5px; color:var(--faint); text-align:right}
.rl-b{display:flex; flex-direction:column; min-width:0; flex:1; line-height:1.3}
.rl-t{font-size:12.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.rl-s{font-size:10px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.rl-g{
  flex:none; font-family:var(--mono); font-size:10px; font-weight:700; padding:1px 6px;
  border-radius:99px; background:var(--wait-bg); color:var(--wait); border:1px solid var(--wait-line);
}
.rl-g.bad{background:var(--fail-bg); color:var(--fail); border-color:var(--fail-line)}
.detail{min-width:0}

/* ─────────────────────────── tài liệu: thu gọn + tìm ─────────────────────────── */
.dochd{display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:22px 0 12px}
.dochd h3{margin:0; font-size:12px; font-weight:600; letter-spacing:.1em; text-transform:uppercase; color:var(--muted)}
.docq{
  flex:1; min-width:190px; max-width:340px; appearance:none;
  padding:6px 11px; border-radius:var(--r-sm); border:1px solid var(--line);
  background:var(--panel-2); color:var(--ink); font-family:var(--mono); font-size:11.5px;
  transition:border-color .16s var(--ease), background .16s var(--ease);
}
.docq:focus{outline:0; border-color:var(--accent); background:var(--panel); box-shadow:0 0 0 3px var(--accent-glow)}
.docq::placeholder{color:var(--faint)}
.kindblock{margin:0 0 4px; border:1px solid var(--line); border-radius:var(--r-sm); overflow:hidden}
.kindblock.open{border-color:var(--line-2)}
.kindhead{
  display:flex; align-items:center; gap:9px; width:100%; text-align:left;
  padding:8px 12px; border:0; border-radius:0; background:var(--panel-2);
  font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); cursor:pointer;
}
.kindhead:hover{background:var(--panel-3); color:var(--ink)}
.kindhead code{background:none; padding:0; font-size:11px; color:inherit; letter-spacing:.06em}
.kindhead .count{
  font-family:var(--mono); font-size:10.5px; color:var(--faint);
  background:var(--panel-3); padding:1px 7px; border-radius:99px; letter-spacing:0;
}
.caret{color:var(--faint); font-size:10px; width:9px; flex:none}
.kindbody{padding:11px 12px 6px; border-top:1px solid var(--line)}
.docgroup{margin:0 0 16px}
.docgroup>h4{
  margin:0 0 8px; font-size:11px; font-weight:600; letter-spacing:.09em;
  text-transform:uppercase; color:var(--faint);
}
.docgroup>h4 .count{font-family:var(--mono); color:var(--muted)}

/* Ghi chú ledger có thể dài hơn nghìn ký tự; không cắt thì một dòng đẩy hết bảng
   ra khỏi màn hình. Cắt bốn dòng, bấm để mở — kiểm tra bởi tests/run-tests.sh §11. */
.note{-webkit-line-clamp:4; -webkit-box-orient:vertical; display:-webkit-box; overflow:hidden}
.note.open{-webkit-line-clamp:unset; display:block}
/* .dot là inline-block ở mọi ô bảng: trong <td> nó bị inline hoá và biến mất — §11 canh dòng này */
 td .dot{display:inline-block; vertical-align:middle; margin-right:6px}

/* ─────────────────────────── hộp xem tài liệu ───────────────────────────
   Tiêu đề tách hai tầng: tên file là thứ người ta tìm, thư mục là thứ người ta cần khi
   đi hỏi ai đó "file nào". Gộp thành một chuỗi `a/b/c/d.md` thì cả hai đều khó đọc. */
.vhead{
  position:sticky; top:0; z-index:2; display:flex; align-items:center; gap:12px;
  padding:12px 14px 12px 18px; background:var(--panel-2); border-bottom:1px solid var(--line);
}
.vname{display:flex; flex-direction:column; min-width:0; flex:1; line-height:1.3}
.vname strong{font-size:13px; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
/* không dùng direction:rtl để cắt đầu đường dẫn — nó đẩy dấu chấm của ".migurei" ra sau
   và biến một đường dẫn có thật thành một đường dẫn không tồn tại. */
.vname .meta{font-size:10.5px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.ibtn{
  flex:none; width:30px; height:28px; display:flex; align-items:center; justify-content:center;
  padding:0; font-size:13px; line-height:1; color:var(--muted);
  background:var(--panel); border:1px solid var(--line); border-radius:var(--r-sm); cursor:pointer;
  transition:color .16s var(--ease), border-color .16s var(--ease), background .16s var(--ease);
}
.ibtn:hover{color:var(--ink); border-color:var(--line-2); background:var(--panel-3)}
.vfoot{
  position:sticky; bottom:0; padding:8px 18px; background:var(--panel-2);
  border-top:1px solid var(--line); font-size:10.5px;
}
#vwrap{padding:22px 26px 26px; overflow:auto; max-height:calc(88vh - 104px)}

/* --- trang login ------------------------------------------------------------------
   Không dùng biến màu nào chưa có: trang này phải render đúng kể cả khi ai đó đổi bảng màu,
   và nó là trang duy nhất một người thấy trước khi biết dashboard trông thế nào.
   Thay biến: --card → --panel, --fg → --ink, --fail-fg → --fail (ba biến brief dùng mà
   :root chưa có; --line, --muted, --bg, --accent, --fail-bg, --fail-line đã có sẵn). */
.loginwrap{min-height:100vh;display:grid;place-items:center;padding:24px}
.loginbox{width:100%;max-width:360px;display:flex;flex-direction:column;gap:10px;
  padding:28px;border:1px solid var(--line);border-radius:12px;background:var(--panel)}
.loginbrand{display:flex;align-items:center;gap:8px;font-weight:700;letter-spacing:.08em;
  font-size:15px;margin-bottom:2px}
.loginhint{margin:0 0 8px;font-size:13px;color:var(--muted)}
.loginbox label{font-size:12px;color:var(--muted);margin-top:4px}
.loginbox input{padding:9px 11px;font:inherit;font-size:14px;border:1px solid var(--line);
  border-radius:8px;background:var(--bg);color:var(--ink)}
.loginbox button{margin-top:14px;padding:10px;font:inherit;font-weight:600;cursor:pointer;
  border:0;border-radius:8px;background:var(--accent);color:#fff}
.loginerr{padding:9px 11px;border-radius:8px;font-size:13px;
  background:var(--fail-bg);color:var(--fail);border:1px solid var(--fail-line)}
.loginfoot{margin:12px 0 0;font-size:12px;color:var(--muted);white-space:pre-line}
.loginlangs{margin-top:14px; text-align:center; font-size:11.5px; color:var(--muted)}
.loginlangs a{color:var(--muted)}

/* --- topbar account block ---------------------------------------------------------
   Built by app.js renderTopbar() from viewer.role, not hidden by CSS — see the comment
   at web/app.html's #views container. */
.who{cursor:default;font-weight:600}
#account .segbtn{text-decoration:none}

/* --- admin page (renderAdmin, hblab-only) ---------------------------------------
   `.acts` là một div BÊN TRONG ô, không phải chính ô. Đặt display:flex lên `<td>` thì ô thôi
   là table-cell, và một table-cell giãn tới chiều cao của row còn một flex box chỉ cao bằng
   nội dung — nên border-bottom của nó vẽ cao hơn border của các ô kia 3-4px và đường kẻ giữa
   hai row bị bậc ở cột cuối. Cột vẫn thẳng hàng hoàn hảo về left và width, nên lỗi này sống
   sót qua một lần kiểm: nó nằm trọn ở trục dọc. Cùng họ với `td .dot` ở trên. */
.acts{display:flex;gap:4px;flex-wrap:wrap}
.acts .segbtn{padding:3px 7px;font-size:11px}
.segbtn.danger{color:var(--fail)}
.addrow{display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin-top:8px}
.addrow input,.addrow select,.au-form input,.au-form select{padding:7px 9px;font:inherit;
  font-size:13px;border:1px solid var(--line);border-radius:7px;background:var(--bg);
  color:var(--ink)}

/* --- admin detail (renderAdminUser, hblab-only) ---------------------------------
   Nhãn bên trái, control bên phải, một cột nhãn cố định — không dùng flex tự co, vì bề rộng
   nhãn khi ấy đổi theo độ dài chuỗi và ba dòng của cùng một form không thẳng hàng nhau. */
.au-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 14px}
.au-head>h2{margin:0!important;font-family:var(--mono);font-size:15px!important;
  letter-spacing:0!important;text-transform:none!important;color:var(--ink)!important}
.au-head .segbtn{text-decoration:none}
.au-facts{display:flex;gap:26px;flex-wrap:wrap;margin:0 0 22px;font-size:12.5px}
.au-facts>div{display:flex;flex-direction:column;gap:2px}
.au-facts .meta{font-size:10px;letter-spacing:.09em;text-transform:uppercase}
.au-form{display:grid;gap:12px;max-width:760px}
.au-row{display:grid;grid-template-columns:130px minmax(0,1fr);gap:12px;align-items:center}
.au-row>span:first-child{font-size:11px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted)}
.au-projrow{align-items:start}
.au-projrow>span:first-child{padding-top:7px}
.au-row input,.au-row select{width:100%}
.au-form>.meta{margin:-4px 0 0;font-size:12px;line-height:1.6}
/* Một cột trên mọi bề rộng: mỗi dòng là một quyết định cấp quyền, và hai dòng cạnh nhau làm
   người đọc quét ngang trong khi câu hỏi ("tài khoản này xem được những gì") là một danh sách. */
.pboxes{display:grid;gap:2px}
.pbox{display:flex;align-items:center;gap:9px;padding:6px 8px;border-radius:var(--r-sm);
  cursor:pointer;font-size:12.5px}
.pbox:hover{background:var(--panel-2)}
.pbox input{width:auto;margin:0;flex:none;accent-color:var(--accent);cursor:pointer}
.pbox input:disabled{cursor:default}
.pbox:has(input:disabled){opacity:.5;cursor:default}
.pbox .meta{font-size:11.5px}
.au-acts{display:flex;gap:8px;flex-wrap:wrap;align-items:center;
  padding-top:6px;border-top:1px solid var(--line)}
.segbtn.primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent);
  font-weight:600}
.segbtn.primary:hover{background:var(--accent);color:var(--accent-ink)}
