/* =============================================================================
   CareLoop design system
   =============================================================================
   Before this file there were 18 different spacing values in use (2,3,4,5,6,7,
   8,9,10,11,12,13,14,15,16,18,20,44px). That is what "inconsistent margins and
   alignment" looks like in source: every component invented its own rhythm, so
   nothing lined up with anything else.

   Everything below is built from ONE 4px scale and a fixed set of tokens. The
   brand colour and overall look are carried over unchanged — this is a
   systematisation, not a redesign.

   Rule for any future change: use a token, or add a token. Never a raw pixel.
   ============================================================================= */

:root{
  /* --- spacing: a 4px scale. Nothing between these steps. ------------------ */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;

  /* --- radius and elevation ----------------------------------------------- */
  --r-sm:8px; --r-md:12px; --r-lg:16px; --r-pill:999px;

  /* Bề ngang một dòng ĐỌC được. Không phải bề ngang màn hình. */
  --w-read:92ch;
  --shadow-1:0 1px 2px rgba(16,18,22,.06);
  --shadow-2:0 4px 16px rgba(16,18,22,.10);

  /* --- type scale ---------------------------------------------------------- */
  /* Thứ bậc do CHỮ gánh, không do VIỀN gánh.
     Thang cũ có bước quá sát (11.5 · 13 · 14.5 · 16) nên không bậc nào chỉ huy
     được bậc nào, và cách duy nhất còn lại để phân biệt hai khối là vẽ một cái
     viền quanh mỗi khối — đó chính là cái lưới tám cái hộp. */
  --t-xs:12px; --t-sm:13px; --t-md:14.5px; --t-lg:17px; --t-xl:22px;
  --t-2xl:30px; --t-3xl:38px;

  /* --- colour: neutrals ---------------------------------------------------- */
  --bg:#f5f6f8; --panel:#fff; --panel-2:#fafbfc;
  --ink:#14171c; --ink-2:#3d434d; --muted:#6b7280; --line:#e4e7eb; --line-2:#eef0f3;

  /* --- colour: brand (unchanged from Phase 1) ------------------------------ */
  --brand:#8b3a62; --brand-ink:#6d2c4d; --brand-soft:#fbf1f6; --brand-line:#edd3e0;

  /* --- colour: meaning. Each is a (ink, surface, line) triple so contrast is
         fixed once. Semantic, never decorative: colour tells the operator what
         needs doing, so it is spent only on states that require action. ------ */
  --ok:#0f7b56;   --ok-bg:#ecfdf5;   --ok-line:#a7f3d0;
  --warn:#a35a08; --warn-bg:#fff8ec; --warn-line:#fcd9a0;
  --bad:#b3261e;  --bad-bg:#fef2f2;  --bad-line:#f6c9c6;
  --info:#1f5fa8; --info-bg:#eff6ff; --info-line:#c3ddf7;

  --focus:0 0 0 3px rgba(139,58,98,.22);
}

/* BẢN TỐI ÁP THEO HAI ĐƯỜNG, KHÔNG PHẢI MỘT.
   Trước đây chỉ có `prefers-color-scheme`, nên sản phẩm luôn đi theo hệ điều
   hành và người dùng KHÔNG CÓ CÁCH NÀO chọn. Máy của Albert để tối, nên anh
   thấy tối — và bản sáng thì tồn tại suốt mà CHƯA AI TỪNG NHÌN.

   `:root[data-theme="dark"]` cho người chọn thắng hệ điều hành; `[data-theme=
   "light"]` chặn media query lại. Cùng một bảng token, không đẻ màu mới. */
:root[data-theme="dark"]{
  --bg:#0e1014; --panel:#171a20; --panel-2:#1c2029;
  --ink:#e9ebee; --ink-2:#c3c8d0; --muted:#98a0ad; --line:#272c35; --line-2:#20242c;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --bg:#0e1014; --panel:#171a20; --panel-2:#1c2029;
    --ink:#e9ebee; --ink-2:#c3c8d0; --muted:#98a0ad; --line:#272c35; --line-2:#20242c;
    --brand:#e693ba; --brand-ink:#f0b6d0; --brand-soft:#2a1620; --brand-line:#3d2130;
    --ok:#5fd3a5;   --ok-bg:#0d2a20;   --ok-line:#1d4b3a;
    --warn:#f0b45f; --warn-bg:#2c2110; --warn-line:#54401b;
    --bad:#f08a84;  --bad-bg:#2d1614;  --bad-line:#5a2925;
    --info:#7db4ee; --info-bg:#111f30; --info-line:#254566;
    --shadow-1:0 1px 2px rgba(0,0,0,.4); --shadow-2:0 4px 16px rgba(0,0,0,.5);
  }
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink);
  font:var(--t-md)/1.55 -apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif}
:focus-visible{outline:none;box-shadow:var(--focus);border-radius:var(--r-sm)}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}

/* --- vỏ ứng dụng: LƯỚI sidebar + nội dung --------------------------------- */
#app{display:grid;grid-template-columns:236px minmax(0,1fr);min-height:100vh}

#side{background:var(--panel);border-right:1px solid var(--line);
  display:flex;flex-direction:column;gap:var(--s3);padding:var(--s4) var(--s3);
  position:sticky;top:0;height:100vh;overflow-y:auto}
#side .brand{font-weight:800;color:var(--brand);text-decoration:none;
  font-size:var(--t-lg);padding:0 var(--s2);line-height:1.2}
#side .brand small{display:block;font-weight:500;color:var(--muted);
  font-size:var(--t-xs);margin-top:2px}

nav{display:flex;flex-direction:column;gap:var(--s2);flex:1;min-height:0}
.nav-top{display:flex;flex-direction:column;gap:2px}
nav a{display:block;padding:var(--s2) var(--s3);border-radius:var(--r-sm);
  color:var(--ink-2);font:500 var(--t-md)/1.3 inherit;text-decoration:none;
  transition:background .12s,color .12s}
nav a:hover{background:var(--panel-2);color:var(--ink)}
nav a.on{background:var(--brand-soft);color:var(--brand-ink);font-weight:600}

.nav-group{display:flex;flex-direction:column;gap:2px}
.nav-head{display:flex;align-items:center;gap:var(--s2);width:100%;
  background:0;border:0;cursor:pointer;padding:var(--s2) var(--s3);
  color:var(--muted);font:600 var(--t-sm)/1.3 inherit;text-align:left;
  border-radius:var(--r-sm)}
.nav-head:hover{background:var(--panel-2);color:var(--ink-2)}
.nav-caret{margin-left:auto;transition:transform .15s}
.nav-group.open .nav-caret{transform:rotate(180deg)}
.nav-group .nav-items{display:none;flex-direction:column;gap:2px}
.nav-group.open .nav-items{display:flex}
/* CẤP CON PHẢI TRÔNG NHƯ CẤP CON.
   Trước đây `nav a` cho mọi mục cùng một padding, nên "Đơn hàng" nằm trong nhóm
   "Bán hàng" trông y hệt "Khách hàng" ở cấp một — thanh điều hướng có ba tầng và
   vẽ ra một tầng. Một cây phân cấp không nhìn thấy được thì không phải một cây. */
.nav-group .nav-items a{padding-left:var(--s5);font-size:var(--t-sm);
  position:relative}
.nav-group .nav-items a::before{content:"";position:absolute;left:var(--s3);
  top:0;bottom:0;width:2px;background:var(--line)}
.nav-group .nav-items a.on::before{background:var(--brand)}
/* VÀ NÓ PHẢI CUỘN, KHÔNG ĐÈ.
   `nav` có flex:1 nhưng không có overflow, nên mở hai nhóm cùng lúc là danh sách
   dài ra và tràn xuống ĐÈ LÊN khối tài khoản ở đáy. Đo được trên ảnh của Albert:
   "Doanh nghiệp" nằm chồng lên "Chị Quản Lý".
   Một cái vỏ không ai sở hữu — bài học T07, lặp lại nguyên vẹn. */
nav{overflow-y:auto;overscroll-behavior:contain}

#who{position:relative;border-top:1px solid var(--line);padding-top:var(--s3)}
.who-btn{display:flex;align-items:center;gap:var(--s2);width:100%;cursor:pointer;
  background:0;border:0;padding:var(--s2);border-radius:var(--r-sm);
  font:inherit;color:var(--ink);text-align:left}
.who-btn:hover{background:var(--panel-2)}
.who-av{flex:0 0 28px;height:28px;border-radius:var(--r-pill);
  background:var(--brand-soft);color:var(--brand-ink);
  display:grid;place-items:center;font-weight:700;font-size:var(--t-sm)}
.who-txt{min-width:0;overflow:hidden}
.who-txt b{display:block;font-size:var(--t-sm);font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.who-caret{color:var(--muted);font-size:var(--t-xs)}
/* `[hidden]` là một thuộc tính, và `display:flex` của một lớp THẮNG nó. Không có
   dòng dưới thì menu tài khoản mở vĩnh viễn — và nó mở như thế trong ảnh chụp
   1920px đầu tiên sau khi dựng xong. Một lỗi CSS im lặng: không cảnh báo, không
   lỗi, chỉ là một menu không ai bấm mà vẫn hiện. */
[hidden]{display:none!important}
.who-menu{position:absolute;bottom:calc(100% + var(--s1));left:0;right:0;
  background:var(--panel);border:1px solid var(--line);border-radius:var(--r-md);
  box-shadow:var(--shadow-2);padding:var(--s1);display:flex;flex-direction:column;z-index:30}
.who-menu a,.who-menu button{display:block;width:100%;text-align:left;
  padding:var(--s2) var(--s3);border-radius:var(--r-sm);border:0;background:0;
  color:var(--ink);font:inherit;text-decoration:none;cursor:pointer}
.who-menu a:hover,.who-menu button:hover{background:var(--panel-2)}

/* Khung LÀM VIỆC: dùng hết bề ngang. Đây là một công cụ, không phải một bài viết
   — chỗ nào cần giới hạn độ dài dòng chữ thì giới hạn bằng `ch` tại chỗ đó. */
main{padding:var(--s5);max-width:none;margin:0;min-width:0}

@media(max-width:900px){
  #app{grid-template-columns:minmax(0,1fr)}
  #side{position:sticky;top:0;height:auto;flex-direction:row;align-items:center;
    gap:var(--s3);overflow-x:auto;z-index:20}
  #side .brand small{display:none}
  nav{flex-direction:row;flex:1}
  .nav-top{flex-direction:row}
  .nav-group{display:none}
  #who{border-top:0;padding-top:0}
  main{padding:var(--s4)}
}

/* --- surfaces ------------------------------------------------------------- */
/* Thẻ KHÔNG có viền.
   Tám khối cùng viền, cùng bo góc, cùng nền thì không khối nào quan trọng hơn
   khối nào — mắt không biết nhìn đâu trước. Nền trắng trên nền xám đã đủ tách
   khối; viền chỉ thêm một đường kẻ nữa vào một màn hình đã đầy đường kẻ.
   (Stripe, Linear, Meta Business Suite đều bỏ viền thẻ từ lâu; SAP Fiori Horizon
   cũng đi cùng hướng.) */
.card{background:var(--panel);border:0;border-radius:var(--r-md);
  padding:var(--s4);margin-bottom:var(--s3);box-shadow:var(--shadow-1)}
.card > h3:first-child{margin-top:0}
/* Tiêu đề khối THÔI IN HOA + THÔI bé hơn chữ thường.
   `HỒ SƠ` cỡ 12px in hoa nằm trên một khối cao 470px thì nó không phải tiêu đề —
   nó là một cái nhãn dán. Tiêu đề phải lớn hơn nội dung nó đặt tên. */
h3{margin:var(--s5) 0 var(--s3);font-size:var(--t-lg);text-transform:none;
  letter-spacing:0;color:var(--ink);font-weight:600}
.card > h3{margin-top:0}
.muted{color:var(--muted)}
.hint{font-size:var(--t-sm);color:var(--muted);margin-top:var(--s2);line-height:1.5}
.empty{text-align:center;padding:var(--s6) var(--s4);color:var(--muted)}

/* --- controls: one height, one radius, one rhythm ------------------------- */
/* --- nút: HÌNH DÁNG ở dưới, Ý NGHĨA ở đây -----------------------------------
   Một biến thể không có luật dùng thì nơi nào cũng dùng được — và khi cùng một
   hình dáng mang hai nghĩa ở hai màn hình, người dùng không học được gì và phải
   ĐỌC từng nút, mỗi lần. Sự đồng điệu là một cách truyền đạt chức năng mà không
   cần chữ; mỗi ngoại lệ xoá một phần bài học đó.

     (mặc định)  hành động PHỤ, lùi được          — bao nhiêu cũng được
     .primary    VIỆC CHÍNH của màn hình này      — đúng MỘT cái mỗi màn hình
     .danger     KHÔNG LÙI ĐƯỢC (xoá, gộp, gửi)   — luôn kèm một bước xác nhận
     .small      hành động ở cấp MỘT DÒNG         — không dùng ở cuối biểu mẫu
     .suggest    một GỢI Ý AI bấm được            — chỉ trong khối trợ lý
     .ghost      RỜI KHỎI chỗ đang đứng, không    — chỉ ở góc một hộp thoại;
                 đổi gì (đóng, bỏ qua)              nếu nút LÀM gì thì không ghost

   .danger không bao giờ đồng thời là .primary: một việc phá huỷ không được là
   việc dễ bấm nhất trên màn hình.

   Thứ tự trong một nhóm:  [Huỷ] [phụ] … [CHÍNH]  — việc chính luôn ở PHẢI CÙNG,
   vì đó là nơi mắt dừng cuối cùng khi đọc trái→phải. Nếu chỗ đó lúc thì "Lưu"
   lúc thì "Xoá" thì người dùng BUỘC phải đọc mỗi lần — đúng cái mà đồng bộ sinh
   ra để loại bỏ.  (UI-STANDARDS §2.2)
   -------------------------------------------------------------------------- */
.btn{display:inline-flex;align-items:center;gap:var(--s2);min-height:38px;
  padding:0 var(--s3);border-radius:var(--r-sm);border:1px solid var(--line);
  background:var(--panel);color:var(--ink);cursor:pointer;
  font:600 var(--t-sm)/1 inherit;text-decoration:none;white-space:nowrap;
  transition:border-color .12s,background .12s,color .12s}
.btn:hover{border-color:var(--brand);color:var(--brand)}
.btn:disabled{opacity:.55;cursor:default}
.btn.primary{background:var(--brand);border-color:var(--brand);color:#fff}
.btn.primary:hover{background:var(--brand-ink);border-color:var(--brand-ink);color:#fff}
.btn.small{min-height:30px;padding:0 var(--s2);font-size:var(--t-xs)}
.btn.danger{color:var(--bad);border-color:var(--bad-line)}
a.btn,a.btn:hover{text-decoration:none}
a.btn.primary{color:#fff}

input,select,textarea{width:100%;min-height:38px;padding:var(--s2) var(--s3);
  border-radius:var(--r-sm);border:1px solid var(--line);background:var(--panel);
  color:var(--ink);font:var(--t-sm)/1.45 inherit}
textarea{min-height:auto}
input:focus,select:focus,textarea:focus{border-color:var(--brand);outline:none;box-shadow:var(--focus)}
label{display:block;font-size:var(--t-sm);color:var(--muted);margin:var(--s3) 0 var(--s1)}
.bar{display:flex;gap:var(--s2);flex-wrap:wrap;align-items:center;margin-bottom:var(--s3)}
.center{max-width:440px;margin:10vh auto}
.err{color:var(--bad);font-size:var(--t-sm);margin-top:var(--s2)}
.err.ok{color:var(--ok)}

/* --- generated forms ------------------------------------------------------ */
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--s3)}
.grid2 label{display:flex;flex-direction:column;gap:var(--s1);margin:0}
.grid2 .cap{display:block;font-size:var(--t-sm);color:var(--ink-2);font-weight:600}
.grid2 input,.grid2 select,.grid2 textarea{width:100%}
.req{color:var(--bad);margin-left:var(--s1);font-weight:700}

/* --- tables --------------------------------------------------------------- */
table{width:100%;border-collapse:collapse;font-size:var(--t-sm)}
th,td{text-align:left;padding:var(--s2) var(--s3);border-bottom:1px solid var(--line-2)}
th{color:var(--muted);font-size:var(--t-xs);text-transform:none;letter-spacing:0;
  font-weight:700;background:var(--panel-2)}
tr:last-child td{border-bottom:0}
.num{text-align:right;font-variant-numeric:tabular-nums}
.money{text-align:right;font-weight:700;white-space:nowrap;font-variant-numeric:tabular-nums}

/* --- work rows: the most-looked-at component in the product --------------- */
.row{display:grid;grid-template-columns:52px 1fr auto;gap:var(--s3);align-items:start;
  background:var(--panel);border:1px solid var(--line);border-left:3px solid transparent;
  border-radius:var(--r-md);padding:var(--s3) var(--s4);margin-bottom:var(--s2);
  text-decoration:none;color:inherit;box-shadow:var(--shadow-1);
  transition:border-color .12s,box-shadow .12s}
.row:hover{border-color:var(--brand);border-left-color:var(--brand);box-shadow:var(--shadow-2)}
/* The left edge carries urgency, so a scanning eye sorts the list without reading. */
.row.is-overdue{border-left-color:var(--bad)}
.row.is-today{border-left-color:var(--warn)}
.pri{font-weight:800;font-size:var(--t-lg);text-align:center;border-radius:var(--r-sm);
  padding:var(--s2) 0;background:var(--brand-soft);color:var(--brand-ink);
  font-variant-numeric:tabular-nums}
.name{font-weight:700;font-size:var(--t-lg);line-height:1.3}
.sub{color:var(--muted);font-size:var(--t-sm);margin-top:2px}
/* WHY this person is on the list. The previous version showed it and the rewrite
   dropped it — it is the single most useful field on the row. */
.why{color:var(--ink-2);font-size:var(--t-sm);margin-top:var(--s1)}
.why b{color:var(--brand-ink);font-weight:600}
.tags{display:flex;gap:var(--s1);flex-wrap:wrap;margin-top:var(--s2)}
.tag{font-size:var(--t-xs);font-weight:700;padding:2px var(--s2);border-radius:var(--r-pill);
  background:var(--panel-2);border:1px solid var(--line);color:var(--muted);white-space:nowrap}
.tag.VIP,.tag.HIGH{background:var(--brand-soft);color:var(--brand-ink);border-color:var(--brand-line)}
.tag.good{background:var(--ok-bg);color:var(--ok);border-color:var(--ok-line)}
.tag.warn,.tag.pending{background:var(--warn-bg);color:var(--warn);border-color:var(--warn-line)}
.tag.bad{background:var(--bad-bg);color:var(--bad);border-color:var(--bad-line)}
.tag.ai{background:var(--brand-soft);color:var(--brand-ink);border-color:var(--brand-line)}

/* --- stats: numbers you can open ------------------------------------------ */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:var(--s2)}
.stat{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-md);
  padding:var(--s3);text-decoration:none;color:inherit;display:block;transition:all .12s}
a.stat:hover{border-color:var(--brand);background:var(--brand-soft);transform:translateY(-1px)}
.stat-n{font-size:var(--t-2xl);font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums}
.stat-n.bad{color:var(--bad)} .stat-n.warn{color:var(--warn)} .stat-n.good{color:var(--ok)}
/* T22 · hai loại số 0. `clear` = không có gì phải lo, làm nhạt để mắt lướt qua;
   `idle` = chưa bắt đầu, giữ nguyên độ đậm vì nó là một lời nhắc. Token đã có,
   không màu mới. */
.stat-n.stat-zero-clear{color:var(--muted);font-weight:600}
.stat-n.stat-zero-idle{color:var(--ink)}
.stat-l{font-size:var(--t-sm);color:var(--muted);margin-top:var(--s1)}
.stat-s{font-size:var(--t-xs);color:var(--muted)}

/* --- notices -------------------------------------------------------------- */
.notice{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap;
  background:var(--warn-bg);border:1px solid var(--warn-line);color:var(--warn);
  border-radius:var(--r-md);padding:var(--s3) var(--s4);margin-bottom:var(--s3);font-size:var(--t-sm)}
.notice.info{background:var(--info-bg);border-color:var(--info-line);color:var(--info)}
/* `.notice.good` và `.notice.bad` ĐƯỢC DÙNG từ trước mà chưa bao giờ được định
   nghĩa — màn hình `/loop` cũ viết class="notice good" và nhận về đúng cái nền
   vàng của `.notice`. Không lỗi, không cảnh báo, chỉ là một màu sai mà ai cũng
   tưởng là cố ý. Đúng bài học `css-classes.mjs` được viết ra để bắt; cổng ấy
   không bắt được vì nó hỏi "tên lớp có tồn tại ở đâu đó không", còn cái hỏng
   nằm ở TỔ HỢP hai lớp. */
.notice.good{background:var(--ok-bg);border-color:var(--ok-line);color:var(--ok)}
.notice.bad{background:var(--bad-bg);border-color:var(--bad-line);color:var(--bad)}

/* --- báo cáo: chênh lệch so với kỳ trước ----------------------------------
   Một dòng nhỏ dưới con số, KHÔNG phải một con số thứ hai cùng cỡ. Người đọc đi
   tìm giá trị của kỳ này; mức tăng giảm là ngữ cảnh của nó, và ngữ cảnh in to
   bằng nội dung thì không còn là ngữ cảnh. */
.stat-d{font-size:var(--t-xs);margin-top:var(--s1);color:var(--muted);
  font-variant-numeric:tabular-nums}
.stat-d.good{color:var(--ok)} .stat-d.bad{color:var(--bad)}

/* Thẻ chọn báo cáo con ở màn danh sách. Dựa trên `.stat` để nó là CÙNG một vật
   thể thị giác với các ô số bên trong báo cáo — người dùng học một lần. */
.rp-card{display:block;text-decoration:none;color:inherit;background:var(--panel-2);
  border:1px solid var(--line);border-radius:var(--r-md);padding:var(--s4);
  transition:all .12s}
.rp-card:hover{border-color:var(--brand);background:var(--brand-soft);transform:translateY(-1px)}
.rp-row{cursor:pointer}
.rp-name{font-weight:700;font-size:var(--t-md)}
.rp-q{font-size:var(--t-sm);color:var(--muted);margin-top:var(--s1)}

/* --- chips: outcome buttons, the most-pressed control --------------------- */
.chips{display:flex;gap:var(--s2);flex-wrap:wrap}
.chip{min-height:44px;padding:0 var(--s4);border-radius:var(--r-pill);border:1px solid var(--line);
  background:var(--panel);color:var(--ink);cursor:pointer;font:600 var(--t-md)/1 inherit;
  display:inline-flex;align-items:center;transition:all .12s}
.chip:hover{border-color:var(--brand);background:var(--brand-soft)}
.chip.on{background:var(--brand);color:#fff;border-color:var(--brand)}
.chip.small{min-height:30px;padding:0 var(--s3);font-size:var(--t-xs)}
/* "Khác (N)" — phần kết quả ít gặp, gập lại. Bộ kết quả là CẤU HÌNH nên nó dài
   ra theo từng cửa hàng; màn hình phải chịu được một danh sách dài mà không bắt
   người dùng đọc hết mới tìm được cái hay dùng. */
.chip-more{display:inline-block}
.chip-more>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center}
.chip-more>summary::-webkit-details-marker{display:none}

/* --- timeline ------------------------------------------------------------- */
.timeline{list-style:none;margin:0;padding:0}
.tl-item{display:grid;grid-template-columns:34px 1fr;gap:var(--s3);padding:var(--s3) 0;
  border-bottom:1px solid var(--line-2)}
.tl-item:last-child{border-bottom:0}
.tl-icon{width:32px;height:32px;border-radius:var(--r-pill);background:var(--panel-2);
  border:1px solid var(--line);display:flex;align-items:center;justify-content:center;font-size:var(--t-lg)}
.tl-item.tone-good .tl-icon{background:var(--ok-bg);border-color:var(--ok-line)}
.tl-item.tone-bad .tl-icon{background:var(--bad-bg);border-color:var(--bad-line)}
.tl-item.tone-ai .tl-icon{background:var(--brand-soft);border-color:var(--brand-line)}
.tl-item.future{opacity:.8}
.tl-item.future .tl-icon{border-style:dashed}
.tl-head{display:flex;gap:var(--s3);align-items:baseline;justify-content:space-between}
.tl-note{color:var(--muted);font-size:var(--t-sm);margin-top:2px;white-space:pre-wrap}
.tl-meta{display:flex;gap:var(--s2);align-items:center;margin-top:var(--s1);
  font-size:var(--t-xs);color:var(--muted)}

/* --- notes, AI, attention ------------------------------------------------- */
.note{padding:var(--s3);border:1px solid var(--line);border-radius:var(--r-md);
  margin-bottom:var(--s2);background:var(--panel-2)}
.note.pinned{border-color:var(--brand-line);background:var(--brand-soft)}
.ai-card{border-color:var(--brand-line)}
.ai-block + .ai-block{margin-top:var(--s4);padding-top:var(--s4);border-top:1px solid var(--line-2)}
.ai-action{font-weight:700;font-size:var(--t-lg);margin-top:var(--s2)}
.ai-text{margin:var(--s2) 0 0;line-height:1.6}
.ai-basis{margin-top:var(--s3);font-size:var(--t-sm)}
.ai-basis summary{cursor:pointer;color:var(--muted)}
.ai-basis td.src{font-size:var(--t-xs);font-family:ui-monospace,Consolas,monospace}
.attn{list-style:none;margin:0;padding:0}
/* Danh sách "khách cần chú ý" mắc ĐÚNG bệnh của danh sách khách hàng: tên bên
   trái, tiền bên phải, và ~1400px chết ở giữa — rồi nút hành động rơi xuống một
   hàng riêng, cách cái tên nó thuộc về gần một nghìn pixel.

   Không đổi thành bảng (mỗi dòng có nhiều hành động, bảng sẽ chật), mà GIỚI HẠN
   BỀ NGANG DÒNG: một dòng dài quá ~90 ký tự thì mắt phải quét cả màn hình để nối
   đầu này với đầu kia. Đây là chỗ dùng khung ĐỌC, không phải khung LÀM VIỆC
   (UI-STANDARDS §2.6) — và nó là ví dụ đầu tiên rằng "dùng hết bề ngang" không
   phải một luật áp cho mọi khối. */
.attn{max-width:var(--w-read)}

/* ===========================================================================
   KHUNG ĐỌC vs KHUNG LÀM VIỆC — và chỗ trống phải ĐI ĐÂU
   ===========================================================================
   Albert đo được: "Việc hôm nay" bỏ phí 800px trên 1684px — 48% bề ngang.

   Lý lẽ của cái mũ `110ch` ở trên KHÔNG SAI: một dòng dài hơn ~90 ký tự thì mắt
   phải quét cả màn hình để nối cái tên ở đầu này với con số ở đầu kia.

   Nhưng nó chỉ trả lời một nửa câu hỏi. Nó nói "dòng không được dài quá" và im
   lặng về "vậy chỗ còn lại làm gì" — nên chỗ còn lại thành một khoảng trống bằng
   nửa màn hình.

     Một luật giới hạn mà không nói phần bị giới hạn đi đâu thì sinh ra rác,
     không sinh ra trật tự.

   Chữa ở tầng LƯỚI, một lần, cho mọi danh sách: giữ nguyên độ dài dòng, và khi
   màn hình đủ rộng cho HAI dòng cạnh nhau thì xếp hai cột. 1920px chứa vừa hai
   cột 620px + khoảng cách; 1440px thì không, nên nó vẫn một cột.
   =========================================================================== */
@media (min-width:1500px){
  .cols-read{display:grid;grid-template-columns:repeat(auto-fill,minmax(620px,1fr));
    gap:var(--s3);max-width:none;align-items:start}
  /* Trong lưới hai cột, đường kẻ dưới cùng của mỗi cột thành một nét cụt lơ
     lửng — bỏ nó đi và để khoảng cách gánh việc phân tách. */
  .cols-read > *{border-bottom:0}
}
.attn-row{display:flex;gap:var(--s3);justify-content:space-between;align-items:center;
  flex-wrap:wrap;
  padding:var(--s3) 0;border-bottom:1px solid var(--line-2);flex-wrap:wrap}
.attn-row:last-child{border-bottom:0}
.attn-head{display:flex;gap:var(--s2);align-items:center;flex-wrap:wrap}
.attn-head .name{font-weight:700;text-decoration:none;color:inherit;font-size:var(--t-md)}
.attn-head .name:hover{color:var(--brand)}
.attn-right{text-align:right;margin-left:auto;display:flex;gap:var(--s3);
  align-items:center;flex-wrap:wrap;justify-content:flex-end}

/* --- explanation panel (cockpit "vì sao?") -------------------------------- */
.why-box{margin-top:var(--s2);padding:var(--s3);border:1px solid var(--brand-line);
  border-radius:var(--r-md);background:var(--brand-soft);text-align:left;font-size:var(--t-sm)}
.why-box p{margin:0 0 var(--s2)}
.why-box td.src{font-size:var(--t-xs);font-family:ui-monospace,Consolas,monospace}

/* --- scenario runner ------------------------------------------------------ */
.script{background:var(--brand-soft);border:1px solid var(--brand-line);
  border-left:3px solid var(--brand);border-radius:var(--r-md);padding:var(--s4);
  font-size:var(--t-lg);line-height:1.65}
.opt{display:block;width:100%;text-align:left;margin-bottom:var(--s2);padding:var(--s4);
  border-radius:var(--r-md);border:1px solid var(--line);background:var(--panel);
  color:var(--ink);cursor:pointer;font:var(--t-lg)/1.4 inherit}
.opt:hover{border-color:var(--brand);background:var(--brand-soft)}

/* --- login ---------------------------------------------------------------- */
.pitch{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-md);
  padding:var(--s3) var(--s4);margin-bottom:var(--s4);font-size:var(--t-sm);line-height:1.6}
.pitch b{display:block;margin-bottom:var(--s2);color:var(--ink)}
.pitch ul{margin:0;padding-left:var(--s5);color:var(--muted)}
.pitch li{margin-bottom:2px}

/* --- feedback ------------------------------------------------------------- */
.spinner{display:inline-block;width:13px;height:13px;border:2px solid var(--line);
  border-top-color:var(--brand);border-radius:50%;animation:spin .7s linear infinite;
  vertical-align:-2px;margin-right:var(--s1)}
@keyframes spin{to{transform:rotate(360deg)}}
[aria-busy="true"]{opacity:.6}
#toast{position:fixed;left:50%;bottom:var(--s5);transform:translate(-50%,var(--s2));z-index:60;
  background:var(--ink);color:var(--bg);padding:var(--s3) var(--s5);border-radius:var(--r-pill);
  font-size:var(--t-sm);font-weight:600;opacity:0;pointer-events:none;
  transition:opacity .18s,transform .18s;max-width:90vw;text-align:center;box-shadow:var(--shadow-2)}
#toast.on{opacity:1;transform:translate(-50%,0)}

/* --- mobile: the queue is worked on a phone ------------------------------- */
@media(max-width:720px){
  main{padding:var(--s3)}
  header{padding:var(--s2) var(--s3)}
  .row{grid-template-columns:44px 1fr;padding:var(--s3)}
  .money{grid-column:2;text-align:left;margin-top:var(--s1)}
  /* A config table has as many columns as the entity has fields, so on a phone
     it scrolls inside its card; the PAGE never scrolls sideways. */
  .card table{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
  .card{overflow-wrap:anywhere}
  #who span.email{display:none}
  #who .short{display:inline}
  table{font-size:var(--t-xs)}
  th,td{padding:var(--s2)}
  .attn-right{text-align:left;margin-left:0;min-width:0}
}
@media(max-width:560px){.grid2{grid-template-columns:1fr}}

/* --- Hộp thoại ------------------------------------------------------------
   Three columns that scroll independently. The page itself must not scroll:
   a staff member reading a thread should never lose the message box off the
   bottom of the screen, which is what a single scrolling page does the moment
   a conversation gets long. */
/* CHIỀU CAO ĐO TỪ CHÍNH MÌNH, không trừ một hằng số đoán.
   `calc(100vh - 150px)` để lại 150px chết ở đáy trên màn 900px — đo được: ba cột
   cao 750, khung nhìn 900, và cái khoảng trắng ấy là thứ Albert nhìn thấy.

   Con số 150 là chiều cao của phần trên CỘNG với lề, ước lượng một lần rồi không
   ai đo lại. `100dvh` trừ đúng hai lề của `main` thì không cần đoán gì.

     Một hằng số trừ đi "phần bên trên" là một hằng số sai ngay lần đầu ai đó đổi
     phần bên trên. */
.inbox-3col{display:grid;grid-template-columns:280px minmax(0,1fr) 300px;
  gap:var(--s3);height:calc(100dvh - var(--s5) * 2);min-height:420px}
.inbox-3col > *{overflow-y:auto;min-width:0}
/* CỘT CHAT KHÔNG PHẢI CÁI CUỘN — khung tin bên trong nó mới là.
   Luật `> *` ở trên cho MỌI cột tự cuộn, kể cả cột giữa. Hệ quả đo được: khung
   tin cao 1862px cho 26 tin, tức nó nở ra hết cỡ và cả cột trôi theo — nên ô
   soạn bị đẩy xuống dưới cả nghìn pixel (chỗ "thừa một mảng ở dưới" Albert
   thấy), và lệnh cuộn-về-tin-mới-nhất đặt trên khung tin thì rơi vào hư không
   vì khung tin không hề cuộn.

     Đặt lệnh cuộn lên một phần tử không cuộn thì không có lỗi nào cả — chỉ có
     một dòng code không làm gì. */
.inbox-3col > .inbox-thread{overflow:hidden}
.inbox-3col > .inbox-thread > .msg-list{min-height:0}
/* Danh sách hội thoại là DÒNG DÀY ĐẶC, không phải một chồng thẻ có viền.
   Mỗi thẻ cao ~166px cho hai dòng chữ, nên sáu hội thoại đã đầy cột và người
   trực chat phải cuộn để thấy cái thứ bảy. Messenger, Gmail, Intercom đều dùng
   dòng: tên · trích đoạn · nhãn · thời gian, ngăn nhau bằng MỘT đường kẻ mảnh.

     Một danh sách để CHỌN NHANH thì mật độ quan trọng hơn sự thoáng. Chị Trang
     mở màn hình này để tìm người vừa nhắn, không để ngắm từng thẻ. */
.inbox-list{display:flex;flex-direction:column;gap:0;
  background:var(--panel);border-radius:var(--r-md);
  /* `overflow:hidden` ở đây từng là để bo góc ăn vào dòng đầu và dòng cuối. Nó
     GHI ĐÈ `overflow-y:auto` của lưới ba cột — cùng thuộc tính, luật sau thắng —
     nên danh sách CẮT CỤT thay vì cuộn.

     Đo được: nội dung cao hơn khung 734px. Với 14 hội thoại, người trực chat
     không tới được quá nửa số người đang chờ trả lời, và không có gì báo.

       Một `overflow:hidden` viết để bo góc sẽ lặng lẽ nuốt mất khả năng cuộn của
       chính khối ấy — và cái mất đi không hiện ra ở chỗ nó được viết.

     `hidden` ngang, `auto` dọc: góc vẫn bo, và danh sách vẫn cuộn. */
  overflow-x:hidden;overflow-y:auto}
.inbox-list .row{display:block;background:0;border:0;border-left:3px solid transparent;
  border-bottom:1px solid var(--line-2);border-radius:0;
  padding:var(--s3);margin:0;box-shadow:none}
.inbox-list .row:last-child{border-bottom:0}
.inbox-list .row:hover{background:var(--panel-2)}
.inbox-list .name{font-size:var(--t-sm)}
/* Trích đoạn gọn MỘT dòng: hai dòng làm cao gấp rưỡi mà không nói thêm gì —
   người ta nhận ra hội thoại bằng TÊN, trích đoạn chỉ để phân biệt. */
.inbox-list .sub{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;
  overflow:hidden;font-size:var(--t-xs);margin-top:2px}
.inbox-list .tags{margin-top:var(--s1);gap:var(--s1)}
.inbox-list .tag{font-size:var(--t-xs)}
/* The list rows are two columns here, not the queue's three: there is no
   priority number to carry, and an empty first column reads as a bug. */
.inbox-row{grid-template-columns:1fr}
.inbox-list .row.is-active{border-left-color:var(--brand);background:var(--brand-soft)}
.inbox-thread{display:flex;flex-direction:column;gap:var(--s2);
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--panel);padding:var(--s3);overflow:hidden}
.msg-list{flex:1;overflow-y:auto;display:flex;flex-direction:column;
  gap:var(--s2);padding-right:var(--s2)}
.msg{max-width:78%;padding:var(--s2) var(--s3);border-radius:var(--r-lg);
  border:1px solid var(--line)}
.msg.in{align-self:flex-start;background:var(--panel-2)}
.msg.out{align-self:flex-end;background:var(--brand-soft,var(--panel-2));
  border-color:var(--brand)}
.msg-body{white-space:pre-wrap;word-break:break-word}
.msg-meta{margin-top:var(--s1);font-size:var(--t-xs);color:var(--muted)}
.composer{border-top:1px solid var(--line-2);padding-top:var(--s2)}
.composer textarea{width:100%;resize:vertical}
.inbox-side{display:flex;flex-direction:column;gap:var(--s2);
  /* Cột phải phải có RANH GIỚI của chính nó, không chỉ có viền của mấy cái thẻ
     bên trong. Đo được: 2/3 cột của hộp thoại có viền hoặc nền — cột này là cột
     duy nhất trong suốt, nên mắt không biết nó bắt đầu từ đâu. */
  border-left:1px solid var(--line);padding-left:var(--s3);overflow-y:auto}
.card.soft{padding:var(--s3);background:var(--panel-2)}
/* A suggestion is a block of prose on a button, so it must be allowed to wrap
   and to be taller than a control. */
.btn.suggest{height:auto;min-height:44px;padding:var(--s2) var(--s3);line-height:1.45}

@media(max-width:1100px){
  /* Below this the side panel would squeeze the thread to unreadable. It goes
     under the conversation rather than away: "what do I say now" is the reason
     the screen exists. */
  .inbox-3col{grid-template-columns:240px minmax(0,1fr);height:auto}
  .inbox-side{grid-column:1 / -1}
}
/* ---------------------------------------------------------------------------
   ≤720px — MỘT màn hình, HAI trạng thái. Không xếp chồng.
   ---------------------------------------------------------------------------
   Đo được trước khi sửa: ô soạn tin cách đỉnh 1850px (2.2 màn) với 14 hội thoại,
   vì ba cột xếp chồng theo đúng thứ tự DOM — danh sách, rồi luồng tin, rồi gợi
   ý. Chị Trang cuộn quãng đó HAI CHIỀU, 120 lần một ngày.

     Xếp chồng không phải một thiết kế cho điện thoại. Xếp chồng là việc KHÔNG
     thiết kế cho điện thoại, viết bằng CSS. `1fr` trả lời câu "làm sao cho nó
     không vỡ"; không ai hỏi "làm sao cho nó dùng được".

   Và 1850 là SÀN chứ không phải ca xấu nhất — nó lớn lên theo số hội thoại. Nên
   bản sửa không làm quãng đó NGẮN LẠI, nó làm quãng đó KHÔNG TỒN TẠI: danh sách
   và luồng tin không cùng có mặt.

   `data-view` đến từ ROUTE (`?view=list`), không từ `innerWidth`. Ở ≥1100px
   thuộc tính đó không có tác dụng gì. Một trạng thái tính từ chiều rộng lúc
   dựng là một trạng thái sai ngay khi người dùng xoay máy. */
/* Ở màn rộng danh sách luôn ở đó, nên nút quay lại là một nút không làm gì mà
   vẫn chiếm chỗ. Nó VẮNG MẶT, không phải mờ đi. Luật này phải đứng TRƯỚC media
   query bên dưới: cùng độ đặc hiệu thì cái viết sau thắng, và một luật đúng đặt
   sai chỗ là một luật không chạy. */
.ib-back{display:none;font-size:var(--t-xl);line-height:1;color:var(--muted);
  text-decoration:none;padding:0 var(--s2) 0 0}
.ib-back:hover{color:var(--ink)}

@media(max-width:720px){
  .inbox-3col{grid-template-columns:1fr;height:calc(100vh - 110px)}
  .inbox-3col[data-view="thread"] .inbox-list{display:none}
  .inbox-3col[data-view="list"] .inbox-thread,
  .inbox-3col[data-view="list"] .inbox-side{display:none}
  /* Chỉ luồng tin cuộn. Ô soạn dính đáy khung, nên bàn phím mở lên thì ngón cái
     vẫn tới được nó mà không phải cuộn tìm. */
  .inbox-thread{display:flex;flex-direction:column;overflow:hidden}
  .inbox-thread .msg-list{flex:1;overflow-y:auto;min-height:0}
  .inbox-thread .composer{position:sticky;bottom:0;background:var(--panel)}
  .msg{max-width:92%}
  /* Lối về danh sách: MỘT chạm, và nó nằm TRONG thanh tiêu đề đã có sẵn.
     Thêm một hàng riêng cho nó là trả lại đúng cái chỗ vừa tiết kiệm được. */
  .ib-back{display:inline-block}
}
/* Câu gợi ý đầu tiên, ngay TRÊN ô soạn. Ở màn rộng cả panel gợi ý nằm cạnh nên
   dải này thừa; ở màn hẹp panel nằm dưới cùng — tức là sau chỗ gõ — nên không
   ai kéo xuống xem. */
.ib-hint{display:none;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);
  background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r-sm);
  margin-bottom:var(--s2);font-size:var(--t-sm)}
.ib-hint-text{flex:1;min-width:0;color:var(--ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@media(max-width:1100px){ .ib-hint{display:flex} }

/* A review card is a decision, so it gets room to hold the context the decision
   needs rather than being squeezed into a table row. */
.review-card{margin-bottom:var(--s3)}
.review-card textarea{width:100%;resize:vertical}
.review-card .cap{display:block;margin:var(--s2) 0 var(--s1)}

.promo-card{margin-bottom:var(--s3)}
.promo-card .why{margin-top:var(--s2);white-space:pre-wrap}

/* The inbox row carries two states at once: nobody has read it, and nobody has
   answered it. They are different facts and a colleague reading a thread
   clears only the first. */
.inbox-row.is-unread{border-left-color:var(--bad)}
.inbox-row.is-waiting{border-left-color:var(--warn)}
.inbox-row.is-unread .name{font-weight:700}
.inbox-filters{padding:var(--s3);flex-wrap:wrap;margin:0;
  border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--panel);z-index:1}

/* CỘT PHẢI: ba khối, mỗi khối có ranh giới và một cái nhãn.
   Đo được: 2/3 cột của hộp thoại có viền hoặc nền riêng — cột phải là cột duy
   nhất không có, nên ba nhóm nội dung khác hẳn nhau trôi vào nhau thành một dải
   chữ. Một khối không có ranh giới thì không phải một khối; nó là phần cuối của
   khối phía trên. */
.ib-block{padding:var(--s3);margin:0}
.ib-block-h{font-size:var(--t-sm);font-weight:700;color:var(--ink);
  padding-bottom:var(--s2);margin-bottom:var(--s2);border-bottom:1px solid var(--line)}
.ib-sug{background:var(--panel-2);border-radius:var(--r-sm);padding:var(--s2) var(--s3);
  margin-top:var(--s2);font-size:var(--t-sm);line-height:1.5}
/* The decision sits inside the message it is about, so it must read as part of
   the bubble rather than as a panel that happens to be nearby. */
.msg-decide{margin-top:var(--s2);padding-top:var(--s2);
  border-top:1px dashed var(--line)}
.msg-decide textarea{width:100%;resize:vertical;margin-bottom:var(--s2)}
.msg.out{max-width:88%}

/* A checkbox is not a text field. The blanket `input{width:100%;min-height}`
   above stretched every tick box into a square the size of an input, which put
   its label on the next line and made a one-word choice look like a form. */
input[type=checkbox],input[type=radio]{
  width:auto;min-height:0;padding:0;margin:0;flex:none;
  /* Still comfortably clickable on a phone — 13px is the browser default and
     is genuinely hard to hit. */
  inline-size:18px;block-size:18px;accent-color:var(--brand)}
label:has(> input[type=checkbox]),label:has(> input[type=radio]){
  display:inline-flex;align-items:center;gap:var(--s2);min-height:38px;
  cursor:pointer}

/* --- capability picker: how a business composes a role --------------------- */
/* Grouped in columns rather than one long list. Twenty-four checkboxes in a
   single column is a scroll, and a scroll is where people stop reading. */
.caplist{display:grid;gap:var(--s3);
  grid-template-columns:repeat(auto-fit,minmax(240px,1fr))}
.capgroup{border:1px solid var(--line);border-radius:var(--r-md);
  padding:var(--s3) var(--s4);margin:0}
.capgroup legend{padding:0 var(--s2);font:600 var(--t-sm)/1 inherit;color:var(--muted)}
/* `align-items:center` + không cho XUỐNG DÒNG nghĩa là một nhãn dài hơn cột sẽ
   TRÀN RA NGOÀI ô và đè lên ô bên cạnh. Đó là lỗi "chữ đè lên nhau" cuối cùng
   còn đỏ trong audit:layout, và nó chỉ lộ ra ở những nhãn quyền dài như
   "Soạn tin nhắn cho khách" hay "Sửa thông tin khách hàng".
   `min-width:0` là mảnh còn thiếu: một item flex mặc định KHÔNG co nhỏ hơn nội
   dung của nó, nên chữ đẩy ô rộng ra thay vì tự xuống dòng. */
.capbox{display:flex;align-items:flex-start;gap:var(--s2);min-height:32px;
  font-size:var(--t-sm);cursor:pointer;min-width:0;line-height:1.4}
.capbox > span,.capbox > label{min-width:0;overflow-wrap:anywhere}
.capbox input{flex:0 0 auto;margin-top:2px}
/* Disabled rather than hidden: hiding a capability someone knows exists reads as
   a bug, while a dimmed one reads as "not yours to give" — which is the truth. */
.capbox.is-off{opacity:.45;cursor:not-allowed}
.capbox .tag{margin-left:auto}

/* --- the activity trail ---------------------------------------------------- */
/* The vendor is not a colleague. A row that presents them as one is the single
   most misleading thing this screen could do, so it carries its own colour. */
.aud-row.is-vendor td{background:var(--warn-bg)}
.aud-row.is-vendor td:first-child{box-shadow:inset 3px 0 0 var(--warn)}
/* Notable ≠ alarming: permission changes and exports deserve weight, not alarm. */
.aud-row.is-notable .aud-what{font-weight:600}
.aud-what{line-height:1.35}

/* --- day one: three steps, not ten ---------------------------------------- */
/* Numbered because the order matters — importing before the company profile
   exists means the first messages go out unsigned. */
.firstrun{list-style:decimal;margin:var(--s3) 0 0;padding-left:var(--s5);
  display:grid;gap:var(--s4)}
.firstrun li{padding-left:var(--s2)}
.firstrun li .name{font-weight:600}
.firstrun li .sub{color:var(--muted);font-size:var(--t-sm);margin:2px 0 var(--s2)}
.firstrun li.is-done{opacity:.6}
/* A suspended business reads as suspended without anyone reading the column. */
tr.is-off td{opacity:.55}

/* --- T06 · hồ sơ khách hàng: xem, sửa, cách liên hệ, gộp ------------------- */
/* Dựng theo standard-saas/T06-ho-so-khach-hang/uiux-chi/mockup.html — token
   giống hệt, nên lệch ở đây là lệch so với bản vẽ đã duyệt. */
.field .cap{display:block;font-size:var(--t-xs);color:var(--muted);
  text-transform:none;letter-spacing:0}
.field .val{font-size:var(--t-md)}
/* Lịch sử, không phải thuộc tính: hiện xám chứ KHÔNG giấu — giấu thì người ta
   tưởng sản phẩm thiếu, xám thì người ta hiểu vì sao không sửa được. */
.field.locked .val{color:var(--muted)}
/* THÔI IN HOA.
   `BA LẦN MUA GẦN NHẤT` · `THÔNG TIN RIÊNG CỦA CỬA HÀNG` — chữ in hoa cộng giãn
   chữ là ngôn ngữ phần mềm doanh nghiệp khoảng 2015, và nó đọc CHẬM HƠN chữ
   thường vì mất hết đường viền trên dưới của con chữ. Nhãn mục là thứ để LƯỚT
   qua, không phải để hô. */
.sect{font-size:var(--t-sm);font-weight:600;color:var(--muted);
  margin-bottom:var(--s2);letter-spacing:0;text-transform:none}
.idrow{display:flex;gap:var(--s3);align-items:center;padding:var(--s2) 0;
  border-bottom:1px solid var(--line-2)}
.idrow:last-of-type{border-bottom:0}
.idrow .what{flex:1}
/* Đủ để NHẬN RA một người, không chỉ tên — chị Trang quyết bằng "3 đơn, mua 15
   ngày trước", không quyết bằng một cái tên. */
.who{background:var(--panel-2);border:1px solid var(--line);
  border-radius:var(--r-md);padding:var(--s3)}
.who.pick{border-color:var(--brand);box-shadow:0 0 0 2px var(--brand-soft)}
.who .name{font-size:var(--t-md);font-weight:700}

/* =============================================================================
   T06 vòng 2 · Bốn linh kiện tiện ích + bố cục trang chi tiết
   =============================================================================
   Bốn cái đầu do anh Duy đòi khi anh từ chối dựng bằng `style=`. Chúng nhỏ đến
   mức trông như thừa — và đó chính là lý do 129 thuộc tính `style=` tồn tại:
   không ai thêm một lớp cho `margin-left:auto`, nên ai cũng viết nó tại chỗ,
   129 lần, ở 129 nơi không ai duyệt.

     Nếu phải viết `style=`, tức là quy chuẩn còn thiếu một linh kiện.
     Đi thêm linh kiện, đừng đi vòng.
   ========================================================================== */

/* 1 · nhãn trung tính. .tag.good/.warn/.bad đã có; thiếu đúng cái này, nên hạng
      khách hàng từng bị tô bằng --brand — một màu THƯƠNG HIỆU dùng làm TRẠNG
      THÁI, khiến 26 dòng trông như 26 cảnh báo. (UI-STANDARDS §2.4) */
.tag.info{background:var(--info-bg);color:var(--info);border-color:var(--info-line)}

/* 2 · đẩy phần còn lại sang phải. Thay cho `style="margin-left:auto"`. */
.push{margin-left:auto}

/* 3 · chỗ trống GIỮ SẴN cho một dòng lỗi. Không có nó, lỗi hiện ra đẩy cả bố cục
      xuống ~20px — và người dùng đang nhắm bấm nút bên dưới. (§2.5) */
.hint.reserve{min-height:1.3em;margin-top:var(--s2)}

/* 4 · khoảng cách dọc theo thang. Thay cho `style="margin-top:10px"` (10px không
      nằm trên thang 4px, và nó xuất hiện 16 lần). */
.mt-2{margin-top:var(--s2)} .mt-3{margin-top:var(--s3)} .mt-4{margin-top:var(--s4)}
.mb-0{margin-bottom:0} .m-0{margin:0}

/* --- bố cục trang chi tiết -------------------------------------------------
   Chế độ khung LÀM VIỆC: dùng hết bề ngang (UI-STANDARDS §2.6). Trang này là
   phép thử cho vòng vỏ ứng dụng — nếu một trang rộng hết cỡ đọc tốt, ta có bằng
   chứng để bỏ `max-width:1240px` toàn app.
   ĐO ĐƯỢC: trước đây 7 khối xếp dọc trong cột 1208px, bên cạnh trống 680px. */
/* MỘT vấn đề, đừng giải hai lần.
   ------------------------------------------------------------------------
   Rail sinh ra để chữa trang dài 2.1 màn. Rồi ba cột chữa CHÍNH vấn đề đó bằng
   cách đưa mọi khối về một màn hình. Khi mọi thứ đã nhìn thấy được cùng lúc thì
   KHÔNG CÒN CHỖ NÀO ĐỂ "NHẢY TỚI" — bấm "Lịch sử" thì nó vốn đã ở đầu cột ba.
   Rail thành một thanh trang trí ăn 244px bề ngang, và cái nó gọi là điều hướng
   suy cho cùng vẫn chỉ là cuộn.

     Một thanh điều hướng cho một trang không cần cuộn là một thanh điều hướng
     KHÔNG DẪN ĐI ĐÂU. Giữ nó lại chỉ vì đã dựng ra nó là trả tiền hai lần cho
     một vấn đề đã giải xong.

   Nên: KHÔNG rail ở màn rộng. Nó chỉ xuất hiện dưới 1200px — chỗ bố cục thật sự
   thành một cột dài, tức là chỗ "nhảy tới" có nghĩa thật. */
.detail-shell{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s5);align-items:start}
.detail-shell > .rail{display:none}

/* rail — chỗ NHẢY TỚI, không phải chỗ giấu đi. Chị Trang chống tab vì +1 cú bấm
   × 120 lần/ngày cho việc chính của chị. */
.rail{position:sticky;top:var(--s5);display:flex;flex-direction:column;gap:var(--s1)}
.rail a{display:block;padding:var(--s2) var(--s3);border-radius:var(--r-sm);
  color:var(--muted);font:600 var(--t-sm)/1.3 inherit;text-decoration:none;
  border:1px solid transparent;transition:background .12s,color .12s}
.rail a:hover{background:var(--brand-soft);color:var(--brand)}
.rail a.on{background:var(--brand-soft);color:var(--brand-ink);border-color:var(--brand-line)}
.rail hr{border:0;border-top:1px solid var(--line);margin:var(--s3) 0 var(--s2);width:100%}

/* MULTI-COLUMN, KHONG PHAI GRID CHIA TAY.
   Grid ba o doi nguoi viet phai TU quyet muc nao vao o nao, va quyet dinh ay chi
   dung o mot chieu rong. Multi-column tu can chieu cao o MOI chieu rong — do la
   viec cua trinh duyet, khong phai viec cua nguoi. */
.detail-cols{column-gap:var(--s4)}
.detail-sect{break-inside:avoid;margin-bottom:var(--s4)}
.detail-sect > *{margin-bottom:0}
/* Hai cột: cột trái là việc của chị Trang, cột phải là việc của anh Bảo. Hai
   người dùng nhu cầu ngược nhau, và bề ngang đủ cho cả hai CÙNG LÚC — đó chính
   là 680px đang bỏ trống. */
/* Ba cột ở màn rộng — vì ĐO ra là có 1873px bề ngang và cột dài nhất vẫn 1385px.
   Hai cột chia theo chủ đề lệch tải 1000 ⟷ 1385; ba cột chia theo chiều cao đo
   được cho 910 / 457 / 1018. Chia cột là một phép cân tải, không phải một cách
   nhóm theo chủ đề. */
/* KHONG con luat ba cot.
   Trang chi tiet giờ co BON muc (Ho so · Kich ban · Nen lam gi · Lich su), va bon
   khoi khong chia deu duoc vao ba cot: do duoc 606/378/986 — cot ngan nhat bang
   38% cot dai nhat, tuc nua duoi lai thanh mot cot. Hai cot thi 999/986 = 99%.

     So cot phai do NOI DUNG quyet dinh, khong do chieu rong man hinh. Them mot
     cot vi "man hinh rong" la lam dung cai loi vua chua o cho khac. */
/* Hai cột: b gộp xuống dưới a, c đứng riêng — vì a+b (1367) cân hơn b+c (1475). */
@media(min-width:1200px){ .detail-cols{columns:2} }

/* KHÔNG có luật nào ẩn một mục đi — và đây là chỗ thiết kế tự sửa mình lần thứ hai.
   Bản đầu cho rail "chuyển mục thật" ở ≤1280px, vì ở đó hết bề ngang để tiêu.
   Chạy bộ kiểm trình duyệt thì các mục bị ẩn làm bốn khẳng định đỏ — và lý do
   chúng đỏ mới là điều đáng nghe:

     Người dùng màn hẹp CHÍNH LÀ chị Trang. Chị cầm điện thoại một tay, giữa lúc
     tính tiền, 120 lần một ngày. Bắt chị bấm rail để thấy nội dung là đặt cái
     giá đó lên đúng người ít chịu được nhất — trong khi anh Bảo, người có màn
     rộng, không phải trả gì.

   Và vì "Việc lúc này" là khối ĐẦU TIÊN, chị không phải cuộn cũng không phải bấm.
   Màn hẹp cuộn — đó là việc điện thoại vốn làm, và cuộn rẻ hơn bấm đúng chỗ.
   Rail là chỗ nhảy tới, ở MỌI chiều rộng. (ADR-007) */
/* Dưới 1200px bố cục thành MỘT cột dài — ở đây nhảy tới một mục là một việc thật.
   Dải ngang dính trên đầu, không phải cột dọc: bề ngang mới là thứ khan hiếm. */
@media(max-width:1199px){
  .detail-shell > .rail{display:flex;flex-direction:row;overflow-x:auto;
    position:sticky;top:0;z-index:5;background:var(--bg);
    padding:var(--s2) 0;margin:0;gap:var(--s1);scrollbar-width:none}
  .detail-shell > .rail::-webkit-scrollbar{display:none}
  .rail a{white-space:nowrap}
  .rail hr{display:none}
}

/* "Việc lúc này" — BIẾN THỂ của .card, không phải lớp panel mới (§2.1).
   Thay summaryCard(205px) + profileSection(392px) của vòng 1 bằng đúng bốn thứ
   chị Trang cần để trả lời một tin nhắn, với 0 cú bấm. */
.card.now{border-color:var(--brand-line)}
.now-name{display:flex;gap:var(--s3);align-items:center;flex-wrap:wrap}
.now-name h2{margin:0;font-size:var(--t-2xl);font-weight:700;line-height:1.2}
.now-line{margin:var(--s1) 0 0;color:var(--ink-2);font-size:var(--t-lg)}
.now-line b{font-variant-numeric:tabular-nums}
.buys{width:100%;border-collapse:collapse;margin-top:var(--s2)}
.buys td{padding:var(--s2) 0;border-bottom:1px solid var(--line-2);font-size:var(--t-sm)}
.buys tr:last-child td{border-bottom:0}
/* tiền căn PHẢI, chữ số đều bề ngang — để mắt so được theo cột (§2.3) */
.buys .amt{text-align:right;font-variant-numeric:tabular-nums;font-weight:600;white-space:nowrap}
/* `width:1%` ép cột ngày hẹp hết mức — và không có đệm phải thì nó DÍNH vào tên
   hàng: ảnh chụp 1920px đọc ra "15/8/2026Đơn hàng". Không một con số nào trong
   `audit:layout` bắt được điều này; chỉ mắt người trên ảnh thật bắt được. */
.buys .when{color:var(--muted);white-space:nowrap;width:1%;padding-right:var(--s3)}
@media(max-width:480px){.buys .when{display:none}}

.kv{width:100%;border-collapse:collapse}
.kv th{text-align:left;font-weight:500;color:var(--muted);font-size:var(--t-sm);
  padding:var(--s2) var(--s3) var(--s2) 0;white-space:nowrap;width:1%}
.kv td{padding:var(--s2) 0;font-size:var(--t-sm)}
/* Dùng trong `.bar`: ô nhập chiếm phần còn lại. Thay `style="flex:1;min-width:200px"`. */
.grow{flex:1;min-width:180px}
/* Con số là ƯỚC TÍNH. Vòng 1 tô bằng #92400e cứng — chế độ tối thành chữ nâu
   trên nền đen. Màu ý nghĩa phải là token, và --warn nghĩa là "cần chú ý". */
.est{color:var(--warn)}

/* --- bảng: hợp đồng §2.3 của UI-STANDARDS, viết thành lớp ------------------
   Anh Duy chặn bản vẽ T07 vì nó dùng `.ba-tbl` — một lớp của dự án KHÁC, đếm
   trong file này ra 0. Nếu dựng theo, ta được một cái bảng KHÔNG CÓ KIỂU GÌ giữa
   một sản phẩm đã token hoá 100%.

   Và nó lặp đúng bài học T06: `.push` được DÙNG ở profile-card.js mà chưa bao giờ
   được ĐỊNH NGHĨA, nên nút "Sửa" suốt sáu vòng không hề được đẩy sang phải.

     Một lớp không tồn tại thì IM LẶNG không làm gì — không lỗi, không cảnh báo,
     chỉ là một bố cục sai mà ai cũng tưởng là cố ý.

   Hợp đồng §2.3 tồn tại từ vòng trước dưới dạng VĂN BẢN. Đây là lần nó thành CSS. */
/* KHỐI `.tbl` CŨ ĐÃ BỎ ở U04 — nó là hệ bảng THỨ HAI cho cùng một thứ, và mọi
   quy tắc của nó đã có trong `.ba-tbl` bên dưới. Giữ cả hai là giữ hai đường để
   trôi khỏi nhau ở lần sửa sau.  (ui-canon §4) */

/* --- biểu mẫu: hợp đồng §2.5 ----------------------------------------------- */
.fld label{display:block;font-size:var(--t-sm);font-weight:600;margin-bottom:var(--s1)}
.fld input[type=text],.fld input[type=number],.fld input[type=date],.fld select,
.fld textarea{width:100%;min-height:38px;border:1px solid var(--line);
  border-radius:var(--r-sm);padding:var(--s2) var(--s3);background:var(--panel);
  color:var(--ink);font:inherit}
.fld input:disabled,.fld select:disabled{background:var(--panel-2);color:var(--muted)}
.fld-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:var(--s3)}
.opts{display:flex;gap:var(--s1);flex-wrap:wrap}
/* Ô xám KHÔNG giải thích thì người ta tưởng sản phẩm hỏng. Khoá thì phải nói vì
   sao khoá VÀ lối đi thay thế.
   TÊN PHẢI CÓ PHẠM VI. Bản đầu đặt tên `.locked`, và nó khớp luôn `.field.locked`
   đã có trong khối "Hồ sơ" từ T06 — nên T07 âm thầm vẽ một khung viền quanh ô
   "Nguồn · biết từ", đẩy nó lệch khỏi lưới hai cột. Không lỗi, không cảnh báo,
   chỉ là một khối trông hỏng ở một màn hình không ai đang sửa.
   Lần thứ BA cùng một họ: `.push` dùng-mà-không-định-nghĩa, `.ba-tbl` mượn của dự
   án khác, `.locked` trùng tên. Một cái tên chung là một chỗ nối không ai sở hữu. */
.lock-note{display:flex;gap:var(--s2);align-items:flex-start;background:var(--panel-2);
  border:1px solid var(--line);border-radius:var(--r-sm);padding:var(--s3)}
/* Nhãn–giá trị dạng LƯỚI, không phải bảng.
   Thử `column-count` trước và nó làm khối CAO HƠN (425 -> 501px): cột không chia
   đều được khi mỗi hàng là một hộp flex. Lưới thì hành vi đoán trước được, và đó
   là thứ đáng giá hơn sự khéo léo. */
.kv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
  gap:var(--s3) var(--s5)}
.kv-grid dt{font-size:var(--t-xs);color:var(--muted);margin:0 0 var(--s1)}
.kv-grid dd{margin:0;font-size:var(--t-sm);overflow-wrap:anywhere}

/* Lối về, cỡ một dòng chữ nhỏ ngay trên tên khách — thay cho một hàng 38px ngang
   toàn màn hình chỉ để chứa một liên kết. */
.crumb{margin:0 0 var(--s1);font-size:var(--t-xs)}
.crumb a{color:var(--muted);text-decoration:none}
.crumb a:hover{color:var(--brand)}

/* --- thanh cuộn ------------------------------------------------------------
   Lỗi số 5 anh Albert chỉ ra: "thanh scroll thì lại lộ liễu và xấu". Mặc định
   của Windows là một thanh xám đậm, và nó xuất hiện ở MỌI khung cuộn lồng nhau.

   Viết ở đây vì `audit:layout` vừa XANH VÌ LÝ DO SAI: một dòng `scrollbar-width:
   none` đặt cho riêng dải điều hướng đủ làm bài kiểm tra "có kiểu riêng" đạt,
   trong khi thanh cuộn của cả ứng dụng vẫn thô nguyên. Luật số 5 của skill: một
   khẳng định đạt vì lý do sai nguy hơn một khẳng định đỏ. */
*{scrollbar-width:thin;scrollbar-color:var(--line) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line);border-radius:var(--r-pill);
  border:3px solid transparent;background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:var(--muted);background-clip:content-box}

/* --- object header: một đối tượng có MỘT danh tính, không phải một cái thẻ ---
   Mẫu Object Page của SAP Fiori: dynamic header mang tiêu đề, key facts và HÀNH
   ĐỘNG CHÍNH, rồi nội dung là các section bên dưới. Trước đó tôi băm đối tượng
   thành tám cái thẻ ngang hàng nhau — nên không có gì nói "đây là ai", chỉ có
   tám cái hộp nói "đây là dữ liệu". */
.obj{display:flex;gap:var(--s4) var(--s5);align-items:flex-start;flex-wrap:wrap;
  padding-bottom:var(--s4);border-bottom:1px solid var(--line);margin-bottom:var(--s5)}
.obj-id{min-width:0;flex:1 1 320px}
.obj h1{margin:0;font-size:var(--t-3xl);font-weight:700;line-height:1.15;
  letter-spacing:-.02em}
.obj .crumb{margin:0 0 var(--s1)}
.obj-facts{display:flex;gap:var(--s2) var(--s5);flex-wrap:wrap;margin:var(--s3) 0 0}
.obj-fact{min-width:0}
.obj-fact dt{font-size:var(--t-xs);color:var(--muted);margin:0}
.obj-fact dd{margin:var(--s1) 0 0;font-size:var(--t-lg);font-weight:600;
  font-variant-numeric:tabular-nums}
.obj-act{display:flex;gap:var(--s2);align-items:center;margin-left:auto}

/* Bảng có hàng bấm được. Con trỏ và nền hover nói rằng cả hàng là một mục tiêu —
   nếu không người dùng chỉ nhắm vào chữ xanh và trượt. */
.tbl.rows tbody tr{cursor:pointer}
.tbl.rows tbody tr:hover{background:var(--panel-2)}
.tbl.rows tbody tr:focus-visible{outline:none;box-shadow:var(--focus)}
.tbl.rows td a{color:inherit;text-decoration:none;font-weight:600}
.tbl.rows tbody tr:hover td a{color:var(--brand)}

/* Cột co theo NỘI DUNG, không dàn đều.
   Một bảng 5 cột với `width:100%` chia đều bề ngang, nên cột "Giá trị" bị đẩy
   cách cột "Tên khách" 1200px — mắt phải đi hết chiều ngang màn hình để nối một
   cái tên với một con số. Cột đầu ăn phần còn lại; các cột còn lại vừa nội dung. */
.tbl.rows th:not(:first-child),.tbl.rows td:not(:first-child){width:1%;white-space:nowrap}
.tbl.rows td:first-child{max-width:0}

/* --- danh sách VIỆC: dòng, không phải thẻ ----------------------------------
   Cùng bệnh với danh sách khách và danh sách hội thoại, và lần thứ ba trong một
   ngày: mỗi việc là một thẻ có viền cao ~135px, với ~1400px chết giữa lý do và
   số tiền — nên chỉ 8 trong 12 việc nhìn thấy được, và chị Trang phải cuộn để
   biết hôm nay còn gì.

     Ba màn hình khác nhau, một hình dạng lỗi. Đó không phải ba lần sơ suất —
     đó là một quy chuẩn thiếu: `UI-STANDARDS` nói khi nào dùng BẢNG, nhưng
     chưa bao giờ nói một DANH SÁCH thì trông ra sao.
*/
/* Giới hạn bề ngang DÒNG, đúng luật đã áp cho "khách cần chú ý": đây là danh
   sách để QUÉT và HÀNH ĐỘNG, không phải bảng để so cột. Không giới hạn thì số
   tiền nằm cách cái tên nó thuộc về ~1300px, và mắt phải đi hết chiều ngang màn
   hình để nối hai đầu của MỘT dòng.

     "Dùng hết bề ngang" là luật cho chỗ SO SÁNH, không phải cho chỗ ĐỌC. */
#queue{background:var(--panel);border-radius:var(--r-md);overflow:hidden;max-width:var(--w-read)}
@media (min-width:1500px){
  /* Hai cột thì mỗi cột tự có nền, nên cái nền chung ở đây thành một tấm to
     bao ngoài hai tấm nhỏ — bỏ đi. */
  /* `max-width` phải gỡ Ở ĐÂY, không trông chờ vào `.cols-read`: một ID có độ
     ưu tiên cao hơn một lớp, nên `.cols-read{max-width:none}` thua `#queue`. Bản
     sửa đầu tiên quên điều đó và con số bỏ phí TĂNG từ 48% lên 56%. */
  #queue{background:0;overflow:visible;max-width:none}
  #queue .row{background:var(--panel);border-radius:var(--r-md)}
}
#queue .row{background:0;border:0;border-left:3px solid transparent;
  border-bottom:1px solid var(--line-2);border-radius:0;
  padding:var(--s3) var(--s4);margin:0;box-shadow:none;align-items:center}
#queue .row:last-child{border-bottom:0}
#queue .row:hover{background:var(--panel-2)}
#queue .why{margin-top:2px;font-size:var(--t-sm)}
#queue .tags{margin-top:var(--s1)}

/* --- ba con số của bản xem trước nhập liệu ---------------------------------
   Ba con số này KHÔNG ngang hàng nhau, và bản cũ vẽ chúng thành ba dòng bảng
   giống hệt nhau — nên "sẽ ghi đè 680 khách" trông ngang với "đọc được 1200
   dòng". Cân nặng thị giác phải đi theo RỦI RO.

   Giữ trên MỘT hàng ở mọi chiều rộng (anh Duy): ba con số cạnh nhau là một SO
   SÁNH — 120 với 680 — còn xếp dọc thì thành ba sự thật rời rạc. */
.imp-nums{display:flex;gap:var(--s5);flex-wrap:nowrap;margin:var(--s3) 0}
.imp-num{min-width:0}
.imp-num b{display:block;font-size:var(--t-2xl);line-height:1.1;
  font-variant-numeric:tabular-nums}
.imp-num span{font-size:var(--t-sm);color:var(--muted)}
/* --warn, KHÔNG --bad: chưa hỏng gì, chỉ là chỗ phải xem kỹ. Dùng --bad ở đây
   thì lần sau khi có thứ thật sự hỏng, màu đỏ đã mất hết trọng lượng. */
.imp-num.is-warn b{color:var(--warn)}
.imp-num.is-warn span{color:var(--warn);font-weight:600}
@media(max-width:520px){
  .imp-nums{gap:var(--s4)}
  .imp-num b{font-size:var(--t-xl)}
}

/* Cảnh báo dạng chữ, không phải một hộp. `--warn` nghĩa là CẦN CHÚ Ý, chưa hỏng —
   dùng `--bad` ở đây thì lần sau có thứ thật sự hỏng, màu đỏ đã mất trọng lượng. */
.warn-text{color:var(--warn);font-weight:600}

/* T24 · MỘT quyết định thay cho mười bốn.
   `style="margin-top:10px"` xuất hiện 14 chỗ trong JS: 10px không nằm trên thang
   4px, và mỗi lần viết lại là một quyết định bố cục không ai duyệt. Token nói
   "12px tồn tại"; nó không nói "khoảng cách sau một khối thì dùng cái nào" —
   nên đặt tên cho cái nào, một lần.  (Luật số 4) */
.mt-block{margin-top:var(--s3)}
/* Cảnh báo nhẹ trong luồng nhập dữ liệu. Trước đây là `color:#92400e` viết cứng,
   tức KHÔNG CÓ BẢN TỐI — chữ nâu trên nền tối là chữ không đọc được. */
.hint.warn-soft{color:var(--warn)}

/* Hai lớp thay cho hai chuỗi `style=` lặp lại. `.bar` ĐÃ có gap theo token, nên
   mọi khai báo gap kèm theo chỉ là một quyết định thứ hai về cùng một khoảng
   cách — và quyết định thứ hai bao giờ cũng trôi khỏi quyết định thứ nhất. */
.bar.wrap{flex-wrap:wrap}
/* `.push` đã khai ở trên — bản thứ hai bỏ đi. Hai khai báo giống hệt nhau
   là hai chỗ để trôi khỏi nhau ở lần sửa sau. */


/* Danh sách thụt vào trong một khối cảnh báo. Bốn chỗ trong JS tự viết
   `margin:6px 0 0 18px` và `margin:8px 0 0 18px` — hai con số cho cùng một ý,
   và cả hai đều ngoài thang 4px. */
.list-in{margin:var(--s2) 0 0 var(--s5);padding:0}

/* ---------------------------------------------------------------------------
   HỘP THOẠI — linh kiện duy nhất để đặt một biểu mẫu
   ---------------------------------------------------------------------------
   Ba màn hình (chat · khuyến mãi · đơn hàng) trước đây nhét biểu mẫu vào chỗ
   cái nút tình cờ đứng, vì trong sản phẩm này chưa từng có hộp thoại nào. */
.ba-modal{border:0;border-radius:var(--r-lg);padding:0;background:var(--panel);
  color:var(--ink);width:min(92vw,560px);max-height:86dvh;
  display:flex;flex-direction:column;box-shadow:0 12px 40px rgba(0,0,0,.28)}
.ba-modal.sm{width:min(92vw,400px)}
.ba-modal.lg{width:min(94vw,860px)}
.ba-modal::backdrop{background:rgba(0,0,0,.42)}
.mo-head{display:flex;align-items:center;gap:var(--s3);
  padding:var(--s4) var(--s5);border-bottom:1px solid var(--line)}
.mo-head h3{margin:0;flex:1;font-size:var(--t-lg)}
.mo-x{font-size:var(--t-lg);line-height:1;padding:0 var(--s3)}
/* Chỉ THÂN cuộn. Đầu và chân đứng yên, nên nút chính không bao giờ trôi khỏi
   tầm mắt — đúng cái đã xảy ra khi biểu mẫu nằm trong cột rail đang cuộn. */
.mo-body{padding:var(--s5);overflow-y:auto;flex:1}
.mo-foot{display:flex;justify-content:flex-end;gap:var(--s3);
  padding:var(--s4) var(--s5);border-top:1px solid var(--line)}
.ba-modal .err:empty{display:none}
.ba-modal .err{padding:0 var(--s5);color:var(--bad)}
/* Biểu mẫu rộng: hai cột ở màn đủ chỗ, một cột ở màn hẹp. Mười một trường xếp
   dọc trong một dải hẹp là thứ Albert nhìn thấy và hỏi "ra CÁI GÌ ĐÂY?". */
.mo-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--s3) var(--s4)}
.mo-grid .wide{grid-column:1/-1}
@media(max-width:640px){.mo-grid{grid-template-columns:1fr}}

/* U01 · SẢN PHẨM — ảnh thật, không phải một ô dán đường dẫn. */
.p-thumb{width:44px}
.p-thumb img{width:36px;height:36px;object-fit:cover;border-radius:var(--r-sm);
  display:block;border:1px solid var(--line)}
/* Ô trống có HÌNH DẠNG. Một ô rỗng hoàn toàn làm hàng bị lệch và trông như lỗi
   tải ảnh; một khung xám nói rõ "mặt hàng này chưa có ảnh". */
.p-noimg{display:block;width:36px;height:36px;border-radius:var(--r-sm);
  background:var(--bg);border:1px dashed var(--line)}
.p-noimg.lg{width:160px;height:160px}
.p-row{cursor:pointer}
.p-row:hover{background:var(--bg)}
.p-img-edit{display:flex;gap:var(--s5);align-items:flex-start;flex-wrap:wrap}
.p-img-view img{width:160px;height:160px;object-fit:cover;
  border-radius:var(--r-md);border:1px solid var(--line);display:block}
/* Thư viện ảnh. Mỗi tấm mang theo NÚT của chính nó, xếp dọc dưới ảnh — một hàng
   nút chung ở dưới thì người dùng phải nhớ mình đang chọn tấm nào. */
.p-gal{display:inline-flex;flex-direction:column;gap:var(--s2);align-items:center;
  padding:var(--s2);border:1px solid var(--line);border-radius:var(--r-sm)}
.p-gal img{width:72px;height:72px;object-fit:cover;border-radius:var(--r-sm);display:block}
/* Hai nút của một tấm. Chúng đã mang `btn small`; hai lớp này chỉ là móc để nối
   sự kiện và để hàng nút không giãn ra bằng bề ngang ảnh. */
.p-gal-cover,.p-gal-del{align-self:stretch}
/* Bán dưới giá nhập phải NHÌN THẤY NGAY. `--bad` ở đây đúng nghĩa "đã hỏng". */
.bad-text{color:var(--bad)}

/* U02 · BIỂU MẪU ĐƠN HÀNG — một bản, ba chỗ dùng. */
.of-line{align-items:center;gap:var(--s2)}
.of-line .of-sku{flex:1;min-width:140px}
.of-line .of-qty{width:72px}
.of-line .of-price{width:120px}
.of-line .of-sum{min-width:96px;text-align:right}
/* Mô tả món hàng xuống dòng riêng, không chen vào hàng đang nhập số. */
.of-line .of-facts{flex-basis:100%}
.of-details summary{cursor:pointer;color:var(--muted);font-size:var(--t-sm)}

/* ---------------------------------------------------------------------------
   U04 · MỘT hệ bảng, không phải ba
   ---------------------------------------------------------------------------
   Đếm được trước vòng này: `table` trần (mọi nơi) · `.tbl` (5 chỗ) · `.ba-tbl`
   (11 chỗ, do chính U01-U03 dựng ra). Ba lần quyết cho cùng một thứ.

   Và `.ba-tbl-wrap` — cái khung tôi viết để bảng rộng cuộn bên trong — CHƯA BAO
   GIỜ ĐƯỢC ĐỊNH NGHĨA. Nó im lặng không làm gì suốt ba vòng, và cả sáu cổng đều
   xanh, vì không cổng nào hỏi "lớp này có tồn tại không".

     Chính file này đã ghi bài học ấy hai lần — `.push` dùng-mà-không-định-nghĩa,
     `.locked` trùng tên. Một luật viết trong chú thích không phải một luật.
     Nên lần này nó thành `test/guards/css-classes.mjs`.  (Luật số 7)

   `.ba-tbl` là lớp CANON của sản phẩm này (application-canon). `.tbl` giữ lại
   như một BÍ DANH, không phải một bản sao — một bản sao thứ hai sẽ trôi khỏi bản
   thứ nhất ở lần sửa sau. */
.ba-tbl-wrap{overflow-x:auto;max-width:100%}
.ba-tbl{width:100%;border-collapse:collapse;font-size:var(--t-sm)}
.ba-tbl th:first-child,.ba-tbl td:first-child{padding-left:var(--s3)}
.ba-tbl .num,.ba-tbl th.num{
  text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
/* Hàng bấm được — dùng ở khách hàng, sản phẩm, đơn hàng, khuyến mãi. Trước U04
   mỗi màn tự viết `style="cursor:pointer"` hoặc một lớp riêng. */
.ba-tbl.rows tbody tr{cursor:pointer}
.ba-tbl.rows tbody tr:hover{background:var(--panel-2)}
.ba-tbl.rows tbody tr:focus-visible{outline:none;box-shadow:var(--focus)}
.ba-tbl.rows td a{color:inherit;text-decoration:none;font-weight:600}
.ba-tbl.rows tbody tr:hover td a{color:var(--brand)}

/* ---------------------------------------------------------------------------
   U04 · SÁU LỚP ĐƯỢC DÙNG MÀ CHƯA BAO GIỜ ĐƯỢC ĐỊNH NGHĨA
   ---------------------------------------------------------------------------
   `test/guards/css-classes.mjs` tìm ra chúng ở lần chạy đầu tiên. Cái nặng nhất
   là `.btn.ghost` — chính hộp thoại tôi dựng ở U01 dùng nó cho nút đóng, và nút
   ấy suốt ba vòng trông y hệt một nút phụ.

     Một lớp không tồn tại thì im lặng không làm gì. Đó là lý do câu hỏi này
     phải là một LỆNH CHẠY, không phải một câu trong chú thích. */
.btn.ghost{border-color:transparent;background:transparent;color:var(--muted)}
.btn.ghost:hover{background:var(--panel-2);color:var(--ink)}
/* Địa chỉ IP, mã máy, khoá — chữ đều bề ngang thì mắt dò được theo cột. */
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:var(--t-xs)}
/* Câu mở đầu của một màn hình rỗng: to hơn chữ thường, nhạt hơn tiêu đề. */
.lead{font-size:var(--t-lg);color:var(--ink-2)}
/* `.input` được dùng ở 9 chỗ để nói "ô này là ô nhập liệu" — nhưng input/select
   đã có kiểu sẵn từ quy tắc phần tử, nên lớp này chỉ cần KHÔNG phá gì và tồn
   tại, để bộ kiểm không phải miễn trừ một cái tên thật. */
.input{font:inherit}
.mt-0{margin-top:0}

/* Trong khung cuộn, bảng lấy chiều rộng theo NỘI DUNG, không ép về 100%.
   Đo ở 390px trước khi có dòng này: mỗi hàng cao 118px vì mọi ô đều xuống dòng,
   và 25 đơn thành 4,2 màn cuộn. Ép bảng vừa màn hẹp không làm bảng nhỏ đi — nó
   làm bảng CAO LÊN, và cái giá rơi vào đúng người dùng màn hẹp. */
.ba-tbl-wrap > .ba-tbl{min-width:max-content}

/* ---------------------------------------------------------------------------
   U05 · VỎ ỨNG DỤNG — thu được, kéo được, nhớ được
   ---------------------------------------------------------------------------
   Trước vòng này bề rộng là hằng số 236px. Trên màn 1280px đó là 18,4% bề ngang,
   mọi lúc — kể cả suốt một ca trực chat không ai bấm vào menu lần nào.

   Một khoảng chỗ không đổi được thì nó tính phí ở MỌI phút, kể cả những phút
   không ai dùng tới nó. */
:root{
  --nav-w:236px;
  /* Bề rộng tay nắm là một QUYẾT ĐỊNH có tên, không phải một con số rải trong
     hai chỗ. Nó cũng là thứ `right` suy ra, nên hai giá trị không thể trôi khỏi
     nhau — đúng cái mà `-3px` viết tay đã làm. */
  --grip:6px;
}
/* CHỈ Ở MÀN RỘNG. Khối này nằm cuối tệp nên nó THẮNG media query 900px viết ở
   trên — và lần đầu tôi viết nó không có điều kiện, kết quả là ở 390px thanh
   điều hướng vẫn ăn 236px, chừa 154px cho nội dung, và TÁM màn hình tràn ngang.
   Bộ kiểm bắt được cả tám.

     Một quy tắc CSS viết sau sẽ lặng lẽ huỷ một quyết định viết trước, và cái
     bị huỷ không nằm ở chỗ ta đang sửa. Ở màn hẹp thanh điều hướng đã có cách
     xử riêng từ lâu — thu/kéo là chuyện của màn rộng, nơi CÓ chỗ để nhường. */
@media(min-width:901px){
  #app{grid-template-columns:var(--nav-w) minmax(0,1fr)}
  #side{position:sticky}
}

/* Tay nắm là một phần tử RIÊNG rộng 6px, không phải cái viền 1px của #side.
   Một mục tiêu chuột 1px là một mục tiêu người ta trượt, và trượt vài lần thì
   họ kết luận là kéo không được. */
.nav-grip{position:absolute;top:0;right:calc(var(--grip) / -2);width:var(--grip);height:100%;
  cursor:col-resize;z-index:5}
.nav-grip:hover,.nav-grip:focus-visible{background:var(--brand);opacity:.5}
/* Trong lúc kéo, KHÔNG chọn chữ và KHÔNG đổi con trỏ giữa chừng — cả hai làm
   thao tác kéo có cảm giác trượt tay. */
body.nav-dragging{user-select:none;cursor:col-resize}
body.nav-dragging *{cursor:col-resize!important}

.nav-toggle{position:absolute;top:var(--s3);right:var(--s2);
  width:22px;height:22px;padding:0;line-height:1;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--panel);color:var(--muted);
  cursor:pointer;font-size:var(--t-sm)}
.nav-toggle:hover{color:var(--brand);border-color:var(--brand)}

/* THU LẠI nghĩa là NHƯỜNG CHỖ, không phải giấu cánh cửa: nút mở vẫn ở đó, và
   nội dung dãn ra lấp đúng chỗ vừa trả lại. */
@media(min-width:901px){
  body.nav-collapsed #app{grid-template-columns:0 minmax(0,1fr)}
  body.nav-collapsed #side{padding:0;overflow:visible;border-right:0}
  body.nav-collapsed #side > *:not(.nav-toggle){display:none}
  body.nav-collapsed .nav-toggle{right:calc(var(--s3) * -1 - 22px);background:var(--panel);
    border-color:var(--line);z-index:6}
}
/* Ở màn hẹp KHÔNG có chỗ để nhường, nên nút thu và tay nắm chỉ là hai đích ngắm
   không làm gì. Ẩn chúng đi thay vì để chúng nằm đó im lặng. */
@media(max-width:900px){ .nav-toggle,.nav-grip{display:none} }

/* U06 · MỘT hình dạng cho một câu gợi ý, dùng ở cả hai khối.
   Trước đây cùng một cặp nút xếp ngang ở khối này và xếp dọc ở khối kia, một
   cái có `.primary` một cái không, và tên móc JS cũng khác — ba khác biệt cho
   cùng một việc.  Người dùng không đọc tên lớp CSS; họ đọc HÌNH DẠNG. */
.ib-sug-row{display:flex;gap:var(--s2);align-items:flex-start;margin-top:var(--s2)}
.ib-sug-row .ib-sug{flex:1;min-width:0}
/* Nút giữ nguyên bề ngang khi câu gợi ý dài ngắn khác nhau — nếu không, hai
   hàng cạnh nhau có hai cái nút hai cỡ, và đó lại là "mỗi chỗ một kiểu". */
.ib-sug-row .btn{flex:none}

/* U09 · MÀU Ý NGHĨA DƯỚI DẠNG LỚP, không phải `style="color:…"` trong JS.
   Ba chỗ viết `<b style="color:var(--ok)">` — vi phạm cùng lúc hai luật: màu
   trong thuộc tính style (§3), và chữ đậm mang nghĩa "chú ý" thay vì "giá trị"
   (§4). Một dòng mã vi phạm hai luật thường là dấu hiệu cả hai luật cùng thiếu
   một cái tên để gọi. */
.ok-text{color:var(--ok)}

/* U10 · TONE CỦA TOAST — `data-tone` được GHI từ đầu và chưa bao giờ được ĐỌC.
   `toast(msg, 'ok')` và `toast(msg, 'bad')` cho ra hai cái toast giống hệt nhau,
   nên một lỗi trông y như một lời khen. Lần thứ mười một của cùng hình dạng:
   một thuộc tính có người ghi, không ai đọc. */
#toast[data-tone="bad"]{background:var(--bad);color:#fff}
#toast[data-tone="warn"]{background:var(--warn);color:#fff}


/* ---------------------------------------------------------------------------
   U11 · BỐN LỚP THAY CHO 48 CHỖ `style=` VIẾT TAY
   ---------------------------------------------------------------------------
   Đếm được: 81 thuộc tính `style=` trong JS, 38 khai báo khác nhau — nhưng
   chín cái đầu chiếm 48 chỗ, và tất cả đều là MỘT quyết định lặp lại.

     margin-top:var(--s3)   11 chỗ  ->  .mt-block  (đã có)
     margin-top:12px         9 chỗ  ->  .mt-block  (12px CHÍNH LÀ --s3)
     margin-left:auto        6 chỗ  ->  .push      (đã có)
     color:var(--bad)        5 chỗ  ->  .bad-text  (đã có)
     width:auto              5 chỗ  ->  .w-auto    (mới)
     margin-top:0            4 chỗ  ->  .mt-0      (đã có)
     margin-top:var(--s4)    3 chỗ  ->  .mt-lg     (mới)
     flex:1                  3 chỗ  ->  .flex-1    (mới)

   Chín trong số đó viết `12px` thay vì `var(--s3)` — cùng một khoảng cách, hai
   cách viết, và cách thứ hai không đổi theo khi ai đó chỉnh thang.

     Token nói "12px tồn tại". Nó không nói "khoảng cách sau một khối thì dùng
     cái nào" — và chừng nào chưa nói, mỗi người sẽ tự quyết lại.  (§4) */
.w-auto{width:auto}
.mt-lg{margin-top:var(--s4)}
.flex-1{flex:1;min-width:0}

/* Bề ngang ô nhập — MỘT THANG có tên, thay cho tám con số viết tay.
   Đếm được ở U11: max-width 120/170/180/220/320px và width 80px, mỗi cái viết
   một lần ở một màn hình. Không con số nào sai; cái sai là mỗi màn hình tự
   quyết lại, nên hai ô cùng vai ở hai chỗ rộng khác nhau. */
.w-xs{max-width:120px}
.w-sm{max-width:180px}
.w-md{max-width:220px}
.w-lg{max-width:320px}
.w-full{width:100%}
.t-xs{font-size:var(--t-xs)}
.mt-sm{margin-top:var(--s2)}
.mb-sm{margin-bottom:var(--s2)}
.mb-block{margin-bottom:var(--s3)}
