/* ============================================
   SHARED COMPONENTS — DevDash Generated
   AI coder: Mọi UI element PHẢI dùng class từ file này.
   Cần class mới → THÊM VÀO ĐÂY, không viết CSS riêng cho trang.
   ============================================ */

/* ---- Reset cơ bản ---- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: var(--font-family);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  color: var(--color-text);
  background: var(--color-bg);
}

/* ---- Layout ---- */
.container { max-width: var(--container-max); margin: 0 auto; padding: 0 var(--space-lg); }
.page { padding: var(--space-xl) 0; }
.page-header { margin-bottom: var(--space-xl); }
.page-title {
  font-size: var(--font-size-2xl);
  font-weight: var(--font-weight-bold);
  color: var(--color-text);
  margin-bottom: var(--space-sm);
}
.page-subtitle { font-size: var(--font-size-base); color: var(--color-text-secondary); }

.flex { display: flex; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.justify-center { justify-content: center; }
.gap-xs { gap: var(--space-xs); }
.gap-sm { gap: var(--space-sm); }
.gap-md { gap: var(--space-md); }
.gap-lg { gap: var(--space-lg); }

.grid { display: grid; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  line-height: var(--line-height-tight);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all var(--transition-fast);
  text-decoration: none;
}
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-sm { padding: var(--space-xs) var(--space-sm); font-size: var(--font-size-xs); }
.btn-lg { padding: var(--space-md) var(--space-lg); font-size: var(--font-size-base); }

.btn-primary { background: var(--color-primary); color: var(--color-text-inverse); }
.btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); }

.btn-secondary {
  background: var(--color-bg);
  color: var(--color-text);
  border-color: var(--color-border);
}
.btn-secondary:hover:not(:disabled) {
  background: var(--color-bg-secondary);
  border-color: var(--color-border-hover);
}

.btn-danger { background: var(--color-danger); color: var(--color-text-inverse); }
.btn-danger:hover:not(:disabled) { background: #b91c1c; }

.btn-ghost { background: transparent; color: var(--color-text-secondary); }
.btn-ghost:hover:not(:disabled) { background: var(--color-bg-secondary); color: var(--color-text); }

/* ---- Cards ---- */
.card {
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.card-header {
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--color-border);
  font-weight: var(--font-weight-semibold);
}
.card-body { padding: var(--space-lg); }
.card-footer {
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--color-border);
  background: var(--color-bg-secondary);
}

/* ---- Forms ---- */
.form-group { margin-bottom: var(--space-md); }
.form-label {
  display: block;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--color-text);
  margin-bottom: var(--space-xs);
}
.form-input {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-base);
  font-family: var(--font-family);
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  transition: border-color var(--transition-fast);
}
.form-input:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 3px var(--color-primary-light);
}
.form-input::placeholder { color: var(--color-text-tertiary); }
.form-input-error { border-color: var(--color-danger); }
.form-error { font-size: var(--font-size-xs); color: var(--color-danger); margin-top: var(--space-xs); }
.form-hint { font-size: var(--font-size-xs); color: var(--color-text-tertiary); margin-top: var(--space-xs); }

.form-select {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-base);
  font-family: var(--font-family);
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  cursor: pointer;
}

.form-textarea {
  width: 100%;
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-base);
  font-family: var(--font-family);
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  resize: vertical;
  min-height: 100px;
}

.form-checkbox-group { display: flex; align-items: center; gap: var(--space-sm); }
.form-checkbox { width: 16px; height: 16px; cursor: pointer; }

.form-row { display: flex; gap: var(--space-md); }
.form-row > .form-group { flex: 1; }

.form-actions { display: flex; gap: var(--space-sm); justify-content: flex-end; margin-top: var(--space-lg); }

/* ---- Tables ---- */
.table-wrapper { overflow-x: auto; border: 1px solid var(--color-border); border-radius: var(--radius-lg); }
.table { width: 100%; border-collapse: collapse; }
.table th {
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--color-text-secondary);
  background: var(--color-bg-secondary);
  border-bottom: 1px solid var(--color-border);
  text-align: left;
}
.table td {
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-size-sm);
  border-bottom: 1px solid var(--color-border);
}
.table tr:last-child td { border-bottom: none; }
.table tr:hover td { background: var(--color-bg-secondary); }
.table-empty { padding: var(--space-2xl); text-align: center; color: var(--color-text-tertiary); }

/* ---- Alerts ---- */
.alert {
  padding: var(--space-md);
  border-radius: var(--radius-md);
  font-size: var(--font-size-sm);
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
}
.alert-success { background: var(--color-success-light); color: #166534; border: 1px solid #bbf7d0; }
.alert-danger  { background: var(--color-danger-light);  color: #991b1b; border: 1px solid #fecaca; }
.alert-warning { background: var(--color-warning-light); color: #92400e; border: 1px solid #fde68a; }
.alert-info    { background: var(--color-info-light);    color: #155e75; border: 1px solid #a5f3fc; }

/* ---- Badges ---- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px var(--space-sm);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  border-radius: var(--radius-full);
}
.badge-primary   { background: var(--color-primary-light); color: var(--color-primary); }
.badge-success   { background: var(--color-success-light); color: var(--color-success); }
.badge-danger    { background: var(--color-danger-light);  color: var(--color-danger); }
.badge-warning   { background: var(--color-warning-light); color: var(--color-warning); }
.badge-secondary { background: var(--color-bg-tertiary);   color: var(--color-text-secondary); }

/* ---- Modal ---- */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.modal {
  background: var(--color-bg);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 500px;
  max-height: 90vh;
  overflow-y: auto;
}
.modal-sm { max-width: 400px; }
.modal-lg { max-width: 700px; }
.modal-header {
  padding: var(--space-lg);
  border-bottom: 1px solid var(--color-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.modal-title { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); }
.modal-close { background: none; border: none; font-size: var(--font-size-xl); color: var(--color-text-tertiary); cursor: pointer; }
.modal-close:hover { color: var(--color-text); }
.modal-body { padding: var(--space-lg); }
.modal-footer {
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--color-border);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
}

/* ---- Thanh phân trang (ui.js -> pager) ----
   🔴 Thay cho khối .pagination cũ, vốn là code CHẾT từ khung mẫu ban đầu: không
   một dòng JS nào dùng nó, và nó ăn bộ token cũ (--color-primary: #2563eb) —
   một màu xanh KHÁC hẳn màu thương hiệu, nên bật lên là lệch cả app.

   Hai nút + một nhãn, không vẽ dãy số trang: ở 390px một dãy 17 số là không bấm
   nổi bằng ngón cái, mà số trang cụ thể cũng không phải thứ người ta nhớ. */
.pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-12);
  padding-block: var(--sp-8);
}
.pager-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 40px = ngưỡng vùng chạm tối thiểu cho ngón tay cái */
  min-inline-size: 40px;
  min-block-size: 40px;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-ink);
  cursor: pointer;
}
.pager-btn:hover:not(:disabled) { background: var(--color-paper); }
.pager-btn:disabled { opacity: 0.4; cursor: default; }
.pager-btn-wide {
  inline-size: auto;
  padding-inline: var(--sp-16);
  font-family: inherit;
  font-size: var(--fs-13);
  font-weight: var(--font-weight-medium);
}
.pager-info {
  font-size: var(--fs-13);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted);
}

/* ---- Loading ---- */
.spinner {
  width: 20px; height: 20px;
  border: 2px solid var(--color-border);
  border-top-color: var(--color-primary);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-overlay {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-2xl);
  color: var(--color-text-secondary);
  gap: var(--space-sm);
}

/* ---- Utility ---- */
.text-sm { font-size: var(--font-size-sm); }
.text-xs { font-size: var(--font-size-xs); }
.text-lg { font-size: var(--font-size-lg); }
.text-secondary { color: var(--color-text-secondary); }
.text-success   { color: var(--color-success); }
.text-danger    { color: var(--color-danger); }
.text-center { text-align: center; }
.text-right  { text-align: right; }
.font-medium   { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }
.font-bold     { font-weight: var(--font-weight-bold); }

.mt-sm { margin-top: var(--space-sm); }
.mt-md { margin-top: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.mb-sm { margin-bottom: var(--space-sm); }
.mb-md { margin-bottom: var(--space-md); }
.mb-lg { margin-bottom: var(--space-lg); }

.hidden  { display: none; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
.truncate  { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.break-word { word-break: break-word; }


/* ==========================================================================
   ==========================================================================
   APP COMPONENTS — QuanLyPhongTap
   Port 1:1 từ Mockups/index.html (Tailwind CDN + lucide) sang CSS thuần.

   QUY TẮC KHI DÙNG
   - Đặt class "app" lên <body>. Mọi màn hình sản phẩm nằm trong đó.
   - Icon: <svg class="icon"><use href="/icons.svg#home"></use></svg>
   - Giá trị ĐỘNG (bề rộng thanh tiến độ, chiều cao cột biểu đồ) truyền qua
     custom property bằng JS: el.style.setProperty("--progress", "45%").
     Đây là cách DUY NHẤT được phép, vì đó là DỮ LIỆU chứ không phải giá trị thiết kế.
   - Tra bảng "màn nào dùng class nào" ở docs/design-system.md.
   ==========================================================================
   ========================================================================== */


/* ==========================================================================
   A. NỀN TẢNG
   ========================================================================== */

/* Gốc ứng dụng — đặt lên <body class="app">. Ghi đè font/nền của body mặc định. */
.app {
  font-family: var(--font-sans);
  font-size: var(--fs-14);
  line-height: var(--lh-base);
  color: var(--color-ink);
  background: var(--color-paper);
  -webkit-font-smoothing: antialiased;
  min-block-size: 100dvh;
  display: flex;
  flex-direction: column;
}
.app input,
.app select,
.app textarea,
.app button { font-family: inherit; }
.app button { -webkit-tap-highlight-color: transparent; }

/* Vòng focus — bắt buộc thấy rõ trên MỌI thứ bấm được (đúng cặp giá trị của mockup) */
.app :focus-visible {
  outline: 2px solid var(--color-brand);
  outline-offset: 2px;
}
/* Trong danh sach bo goc + overflow:hidden, vong focus phai lui vao trong keo bi cat */
.panel-flush :focus-visible,
.member-list :focus-visible { outline-offset: -2px; }

/* Số liệu: chữ số đều bề rộng để cột số không nhảy khi đổi giá trị */
.tnum { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }

/* Icon sprite — cỡ qua --icon-size, màu ăn theo currentColor của phần tử cha */
.icon {
  inline-size: var(--icon-size, 20px);
  block-size: var(--icon-size, 20px);
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon-14 { --icon-size: var(--icon-14); }
.icon-16 { --icon-size: var(--icon-16); }
.icon-17 { --icon-size: var(--icon-17); }
.icon-18 { --icon-size: var(--icon-18); }
.icon-19 { --icon-size: var(--icon-19); }
.icon-20 { --icon-size: var(--icon-20); }
.icon-21 { --icon-size: var(--icon-21); }
.icon-24 { --icon-size: var(--icon-24); }
.icon-32 { --icon-size: var(--icon-32); }

/* Tông trạng thái dùng chung — đặt lên phần tử cha, con đọc var(--tone) */
.tone-ok    { --tone: var(--color-ok);    --tone-soft: var(--color-ok-soft); }
.tone-warn  { --tone: var(--color-warn);  --tone-soft: var(--color-warn-soft); }
.tone-bad   { --tone: var(--color-bad);   --tone-soft: var(--color-bad-soft); }
.tone-brand { --tone: var(--color-brand); --tone-soft: var(--color-brand-soft); }

/* Màu chữ */
.text-ink          { color: var(--color-ink); }
.text-muted        { color: var(--color-muted); }
.text-muted-strong { color: var(--color-muted-strong); }  /* khi chữ nằm trên nền paper */
.text-brand        { color: var(--color-brand); }
.text-ok           { color: var(--color-ok); }
.text-warn         { color: var(--color-warn); }
.text-bad          { color: var(--color-bad); }
.text-tone         { color: var(--tone); }

/* Cỡ chữ theo VAI TRÒ (không phải theo px) — cho chữ lẻ nằm ngoài component */
.text-eyebrow { font-size: var(--fs-11);   color: var(--color-muted); }
.text-caption { font-size: var(--fs-12);   color: var(--color-muted); }
.text-note    { font-size: var(--fs-12-5); color: var(--color-muted); line-height: var(--lh-relaxed); }
.text-body    { font-size: var(--fs-13-5); }
.text-body-lg { font-size: var(--fs-14); }
.text-strong  { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.text-h1      { font-size: var(--fs-19); font-weight: var(--font-weight-bold);     line-height: var(--lh-tight); }
.text-h2      { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.text-h3      { font-size: var(--fs-14); font-weight: var(--font-weight-semibold); }

/* Số tổng phụ — "3 khoản · 2.000.000đ" đứng bên phải một tiêu đề nhóm.
   Cả cụm là chữ caption màu phụ; riêng CON SỐ được nhấn bằng <strong> (đúng nghĩa
   HTML: đó mới là phần quan trọng), nên KHÔNG phải bọc thêm span để đổi màu/độ đậm.
   Đã có sẵn tabular-nums — không cần thêm .tnum. */
.text-subtotal {
  font-size: var(--fs-12);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.text-subtotal strong,
.text-subtotal b { color: var(--color-ink); font-weight: var(--font-weight-semibold); }

.lh-none      { line-height: var(--lh-none); }
.lh-tight     { line-height: var(--lh-tight); }
.lh-snug      { line-height: var(--lh-snug); }
.lh-relaxed   { line-height: var(--lh-relaxed); }
.strike       { text-decoration: line-through; }

/* Số liệu lớn — luôn bold + tabular + sát dòng */
.metric,
.metric-sm,
.metric-hero,
.metric-xl,
.metric-2xl {
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  line-height: var(--lh-none);
}
.metric-sm   { font-size: var(--fs-22); }  /* số đếm dòng "Cần xử lý hôm nay" */
.metric      { font-size: var(--fs-27); }  /* 3 thẻ trạng thái ở Tổng quan */
.metric-hero { font-size: var(--fs-28); }  /* doanh thu trên thẻ brand */
.metric-xl   { font-size: var(--fs-32); }  /* tổng doanh thu */
.metric-2xl  { font-size: var(--fs-36); }  /* lượt điểm danh hôm nay */


/* --- Utility bố cục, dùng kèm .flex / .items-center ở đầu file ------------ */

/* Ô co giãn chiếm hết chỗ trống còn lại của một hàng flex.
   min-inline-size:0 là BẮT BUỘC — thiếu nó, ô flex không co xuống dưới bề rộng
   nội dung, nên .truncate / text-overflow bên trong KHÔNG cắt được chữ dài.
   Dùng cho hàng flex bất kỳ. Riêng trong .panel-row đã có sẵn .panel-row-main. */
.flex-fill { flex: 1; min-inline-size: 0; }

/* Ô KHÔNG được co lại — giữ nguyên bề rộng nội dung khi hàng chật.
   Dùng cho ô đứng ở mép hàng flex mà nội dung phải đọc đủ: số tiền cuối .menu-item,
   nhãn ngày, cụm nút. Đừng trông chờ ô bên cạnh có flex-basis:0 rồi tưởng an toàn —
   đó là ăn may: chỉ cần ô kia đổi sang flex:none là ô này bị bóp chữ ngay.
   Ngược lại của .flex-fill; hai class này thường đi thành cặp trên cùng một hàng. */
.shrink-0 { flex-shrink: 0; }

/* Canh mép TRÊN cho hàng flex. Cần khi hai ô lệch chiều cao — ví dụ avatar 56px
   đứng cạnh khối tên có thể xuống 2 dòng: canh giữa sẽ làm avatar trôi xuống. */
.items-start { align-items: flex-start; }


/* ==========================================================================
   B. BỐ CỤC KHUNG
   ========================================================================== */

/* Cột chứa header + nội dung (nằm bên phải sidebar ở desktop) */
.app-body {
  flex: 1;
  min-inline-size: 0;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}

/* Khoá khung ứng dụng đúng một màn hình: header và thanh điều hướng đứng yên,
   chỉ .app-main cuộn.
   ---------------------------------------------------------------------------
   BẮT BUỘC phải là `block-size` (cao ĐÚNG BẰNG), không phải `min-block-size`
   (cao TỐI THIỂU). Với min-block-size, khung phình theo nội dung nên .app-main
   không bao giờ ngắn hơn nội dung của nó → `overflow-y: auto` không kích hoạt,
   mà `overscroll-behavior: contain` bên dưới thì VẪN chặn cuộn chuyển lên cửa
   sổ. Kết quả: lăn chuột trên vùng nội dung không cuộn được gì cả, dù cửa sổ
   thừa sức cuộn. Đây là lỗi đã gặp thật — xem docs/knowhow.md.

   Chỉ áp cho khung ứng dụng (index.html), KHÔNG áp cho login.html: màn đăng
   nhập cần trang tự cuộn khi bàn phím ảo đẩy nội dung cao hơn màn hình. */
.app-shell {
  block-size: 100dvh;
  overflow: hidden;
}

/* Vùng cuộn chính — trên điện thoại ẩn thanh cuộn như mockup */
.app-main {
  flex: 1;
  min-block-size: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.app-main::-webkit-scrollbar { inline-size: 0; block-size: 0; }

/* Header trên cùng — trắng, viền đáy, chừa tai thỏ */
.app-header {
  flex-shrink: 0;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-line);
  padding-top: env(safe-area-inset-top);
}
.app-header-inner {
  block-size: var(--app-header-h);
  padding-inline: var(--sp-16);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-12);
}
.app-header-inner-back { padding-inline: var(--sp-8); gap: var(--sp-4); justify-content: flex-start; }
.app-header-title   { font-size: var(--fs-17); font-weight: var(--font-weight-semibold); }
.app-header-name    { font-weight: var(--font-weight-semibold); line-height: var(--lh-tight); }
.app-header-eyebrow { font-size: var(--fs-11);   color: var(--color-muted); }
.app-header-sub     { font-size: var(--fs-11-5); color: var(--color-muted); }
.app-header-main    { min-inline-size: 0; }
.app-header-side    { text-align: right; flex-shrink: 0; }
.app-header-date    { font-size: var(--fs-12-5); font-weight: var(--font-weight-semibold); }

/* Thân một màn hình: lề 16px + nhịp dọc 12px, đúng nhịp mx-4/mt-3 của mockup */
.screen {
  padding: var(--sp-16);
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
}
.screen-tight { gap: var(--sp-10); }
.screen-intro {
  font-size: var(--fs-13-5);
  color: var(--color-muted-strong);
  line-height: var(--lh-relaxed);
}
/* Cho phần tử tràn hết bề ngang (danh sách học viên) khi nằm trong .screen */
.bleed { margin-inline: calc(var(--sp-16) * -1); }

/* Thanh dính đầu màn (ô tìm kiếm + chip lọc ở màn Học viên) */
.sticky-bar {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-sticky);
  background: var(--color-paper);
  margin-inline: calc(var(--sp-16) * -1);
  padding: var(--sp-12) var(--sp-16) var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-10);
}

/* Dòng đếm kết quả — nằm trên nền paper nên phải dùng muted-strong */
.list-count {
  font-size: var(--fs-12);
  color: var(--color-muted-strong);
  font-variant-numeric: tabular-nums;
}

/* Vỏ trang đăng nhập — trang DUY NHẤT không có header / tabbar / sidebar.
   Canh thẻ vào giữa cả dọc lẫn ngang trên nền paper.
   KHÔNG dùng .modal-overlay thay thế: lớp phủ đen nói với người dùng "có thứ gì
   phía sau đang bị chặn", trong khi đây là màn hình ĐẦU TIÊN — không có gì phía sau. */
.auth-shell {
  flex: 1;
  min-inline-size: 0;
  min-block-size: 100dvh;
  display: grid;
  /* Một cột duy nhất: rộng tối đa --auth-max-w, tự co lại trên máy hẹp */
  grid-template-columns: minmax(0, var(--auth-max-w));
  justify-content: center;   /* canh ngang */
  align-content: center;     /* canh dọc — nội dung cao hơn màn hình thì trang tự cuộn */
  gap: var(--sp-20);         /* nhịp giữa khối tên, form và câu ghi chú */
  background: var(--color-paper);
  /* Lề an toàn: lấy giá trị lớn hơn giữa lề thường và tai thỏ / thanh gạt của máy */
  padding-block: max(var(--sp-24), env(safe-area-inset-top))
                 max(var(--sp-24), env(safe-area-inset-bottom));
  padding-inline: max(var(--sp-16), env(safe-area-inset-left))
                  max(var(--sp-16), env(safe-area-inset-right));
}


/* ==========================================================================
   C. ĐIỀU HƯỚNG
   ========================================================================== */

/* --- Thanh tab dưới (mobile, < 900px) --- */
/* 🔴 NEO VÀO MÀN HÌNH, KHÔNG NEO VÀO KHUNG ỨNG DỤNG (sửa 21/09/2026).
   ---------------------------------------------------------------------------
   Trước đây thanh tab là phần tử flex CUỐI trong `.app-shell` (cao `100dvh`,
   `overflow: hidden`). Cách đó chỉ đúng khi `100dvh` bằng đúng vùng nhìn thấy.

   Trong ứng dụng đã CÀI trên Android, `100dvh` tính cả phần nằm dưới thanh điều
   hướng hệ thống, nên khung cao hơn màn hình thật — và thanh tab, vì đứng cuối,
   bị đẩy xuống ngoài vùng nhìn. `overflow: hidden` khiến KHÔNG có cách nào cuộn
   tới nó. Thao tác vài cái làm trình duyệt tính lại bố cục thì nó mới nhảy về.

   Đo được: khung cao hơn khung nhìn 120px → đỉnh thanh tab ở 863 trong khi màn
   hình chỉ tới 800, và không có thanh cuộn nào để với tới.

   `position: fixed` neo nó vào KHUNG NHÌN, nên khung ứng dụng đo sai bao nhiêu
   cũng không còn liên quan. Cổng học viên đã làm đúng cách này từ đầu (xem
   `.portal-tabbar`) và KHÔNG dính lỗi — đó là bằng chứng rõ nhất.

   ⚠️ Nằm ngoài luồng nghĩa là nó ĐÈ lên vùng cuộn. `.app-main` phải tự chừa chỗ
      — xem khối ngay dưới. Thiếu chỗ chừa là dòng cuối của mọi màn nằm vĩnh viễn
      dưới thanh tab. */
.tabbar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  flex-shrink: 0;
  display: flex;
  background: var(--color-surface);
  border-top: 1px solid var(--color-line);
  padding-bottom: env(safe-area-inset-bottom);
  z-index: var(--z-tabbar);
}

/* Chỗ chừa cho thanh tab đang nổi phía trên. Chỉ áp dưới 900px: từ 900px thanh
   tab tắt hẳn và điều hướng chuyển sang thanh bên, chừa chỗ nữa là thừa một dải
   trắng dưới đáy mọi màn hình. */
@media (max-width: 899.98px) {
  .app-main { padding-block-end: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
}
.tabbar-item {
  flex: 1;
  min-block-size: var(--tabbar-h);
  padding-block: var(--sp-8);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--color-muted);
  text-decoration: none;
}
.tabbar-item .icon { --icon-size: var(--icon-21); }
.tabbar-label { font-size: var(--fs-10-5); font-weight: var(--font-weight-medium); }
.tabbar-item[aria-current='page'] { color: var(--color-brand); }
.tabbar-item[aria-current='page'] .icon { stroke-width: 2.3; }
.tabbar-item[aria-current='page'] .tabbar-label { font-weight: var(--font-weight-semibold); }

/* --- Sidebar (chỉ desktop; được bật ở mục P) --- */
.sidebar {
  display: none;
  flex-direction: column;
  inline-size: var(--sidebar-width);
  flex-shrink: 0;
  background: var(--color-surface);
  border-right: 1px solid var(--color-line);
  block-size: 100dvh;
  position: sticky;
  inset-block-start: 0;
}
.sidebar-brand {
  padding: var(--sp-20) var(--sp-16) var(--sp-16);
  border-bottom: 1px solid var(--color-line);
}
.sidebar-brand-name { font-size: var(--fs-15); font-weight: var(--font-weight-bold); line-height: var(--lh-tight); }
.sidebar-brand-sub  { font-size: var(--fs-11-5); color: var(--color-muted); margin-top: var(--sp-2); }
/* Ô chọn chi nhánh. Chỉ xuất hiện khi phòng tập có từ hai chi nhánh trở lên,
   nên phần lớn phòng tập không bao giờ thấy nó. Chiếm trọn bề ngang khối
   thương hiệu để tên chi nhánh dài không bị cắt cụt. */
.sidebar-brand-pick { inline-size: 100%; margin-top: var(--sp-8); }
.sidebar-nav {
  flex: 1;
  min-block-size: 0;
  overflow-y: auto;
  padding: var(--sp-12) var(--sp-10);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.sidebar-section {
  font-size: var(--fs-11);
  font-weight: var(--font-weight-semibold);
  color: var(--color-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: var(--sp-12) var(--sp-12) var(--sp-4);
}
.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  min-block-size: var(--h-lg);
  padding-inline: var(--sp-12);
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-muted);
  font-size: var(--fs-14);
  font-weight: var(--font-weight-medium);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.sidebar-item .icon { --icon-size: var(--icon-19); }
.sidebar-item:hover { background: var(--color-paper); color: var(--color-ink); }
.sidebar-item[aria-current='page'] {
  background: var(--color-brand-soft);
  color: var(--color-brand);
  font-weight: var(--font-weight-semibold);
}
.sidebar-foot {
  border-top: 1px solid var(--color-line);
  padding: var(--sp-12);
  display: flex;
  align-items: center;
  gap: var(--sp-10);
}
.sidebar-foot-main { flex: 1; min-inline-size: 0; }
.sidebar-foot-name { font-size: var(--fs-13-5); font-weight: var(--font-weight-semibold); }
.sidebar-foot-role { font-size: var(--fs-11-5); color: var(--color-muted); }


/* ==========================================================================
   D. BỀ MẶT — "panel" là thẻ trắng bo 16px, xuất hiện ở mọi màn hình
   ========================================================================== */

.panel {
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-2xl);
}
.panel-pad   { padding: var(--sp-16); }
.panel-flush { overflow: hidden; }               /* khi bên trong là danh sách dòng */
.panel-title { font-size: var(--fs-14); font-weight: var(--font-weight-semibold); }
.panel-head  {
  padding: var(--sp-14) var(--sp-16) var(--sp-8);
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}
.panel-head .icon { --icon-size: var(--icon-17); }
.panel-head-title { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.panel-divider { border-top: 1px solid var(--color-line); margin-top: var(--sp-14); padding-top: var(--sp-14); }

/* Đoạn dẫn nhập nằm BÊN TRONG .panel-flush — panel này bỏ padding để danh sách
   chạy sát mép, nên chữ tự do bên trong không có lề. Class này trả lại lề đó.
   Ví dụ: "Bật khi phòng tập thực sự cần…" ở màn Cài đặt.
   Đặt ngay dưới .panel-head, hoặc làm phần tử đầu nếu panel không có tiêu đề. */
.panel-intro {
  padding: var(--sp-4) var(--sp-16) var(--sp-12);
  font-size: var(--fs-13-5);
  color: var(--color-muted);   /* nền ở đây là surface (trắng), không phải paper */
  line-height: var(--lh-relaxed);
}
.panel-intro:first-child { padding-top: var(--sp-14); }  /* khi phía trên không có .panel-head */

/* Dải chip lọc nằm BÊN TRONG .panel-flush — ví dụ lọc tài khoản theo nhóm quyền.
   Cùng lề với .panel-intro nhưng KHÔNG đặt màu hay cỡ chữ: .chip tự lo phần đó,
   và ghi đè ở đây sẽ làm chip trong panel trông khác chip ngoài panel. */
.panel-filters { padding: var(--sp-4) var(--sp-16) var(--sp-12); }

/* Đường dẫn mã QR ở màn Cài đặt. `overflow-wrap: anywhere` vì đây là một chuỗi
   KHÔNG có dấu cách — không bẻ được thì nó đẩy cả dòng tràn ngang ở 390px. */
.qr-link {
  flex: 1;
  min-inline-size: 150px;
  overflow-wrap: anywhere;
  font-size: var(--fs-12);
  color: var(--color-muted-strong);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.panel-filters:first-child { padding-top: var(--sp-14); }

/* Một dòng bên trong .panel-flush — dòng đầu không có viền trên */
.panel-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  inline-size: 100%;
  padding: var(--sp-12) var(--sp-16);
  border: 0;
  border-top: 1px solid var(--color-line);
  background: var(--color-surface);
  text-align: left;
  font-size: var(--fs-14);
  color: inherit;
}
.panel-row:first-child { border-top: 0; }
.panel-row-main { flex: 1; min-inline-size: 0; }

/* Nhóm nút thao tác ở cuối một .panel-row (danh sách Tài khoản: Đổi nhóm / Đổi
   mật khẩu / Chuyển chi nhánh). Cặp .panel-row-wrap + .row-actions phải đi cùng
   nhau: một cái cho dòng xuống hàng, cái kia chiếm trọn hàng dưới.

   Mobile-first: nút nằm HÀNG RIÊNG. Ở 390px hai nút đứng cạnh tên chỉ còn ~120px
   cho phần tên, đủ để bóp "Nguyễn Thị Mai Anh" thành ba dòng và dòng phụ bên dưới
   thành bốn dòng nữa. Từ 900px mới cho về một hàng như .panel-row thường. */
.panel-row-wrap { flex-wrap: wrap; }
.row-actions { display: flex; flex-wrap: wrap; gap: var(--sp-8); flex-basis: 100%; }
@media (min-width: 900px) {
  .panel-row-wrap { flex-wrap: nowrap; }
  .row-actions { flex-basis: auto; }
}

/* Khối gợi ý nền xanh nhạt — ví dụ "8 gói mẫu có sẵn để dùng thử" */
.note-brand {
  border-radius: var(--radius-xl);
  background: var(--color-brand-soft);
  color: var(--color-brand-dark);
  padding: var(--sp-12);
  font-size: var(--fs-12-5);
  line-height: var(--lh-relaxed);
}


/* ==========================================================================
   E. NÚT & ĐIỀU KHIỂN
   Xây trên .btn có sẵn (inline-flex, gap, cursor, transition).
   Bảng màu và bảng cỡ tách riêng — ghép tự do: class="btn btn-brand btn-xl btn-block"
   ========================================================================== */

/* --- Bảng màu --- */
.btn-brand {
  background: var(--color-brand);
  color: var(--color-on-brand);
  border-color: var(--color-brand);
}
.btn-brand:hover:not(:disabled)  { background: var(--color-brand-dark); border-color: var(--color-brand-dark); }
.btn-brand:active:not(:disabled) { background: var(--color-brand-dark); }

.btn-ink {
  background: var(--color-ink);
  color: var(--color-on-brand);
  border-color: var(--color-ink);
}

.btn-bad {
  background: var(--color-bad);
  color: var(--color-on-brand);
  border-color: var(--color-bad);
}

/* Nút nền trắng viền xám — nút hành động phụ, nút trong dòng */
.btn-line {
  background: var(--color-surface);
  color: var(--color-ink);
  border-color: var(--color-line);
}
.btn-line:hover:not(:disabled)  { background: var(--color-paper); }
.btn-line:active:not(:disabled) { background: var(--color-paper); }
.btn-line .icon { color: var(--color-brand); }

/* Nút nền trắng viền ĐỎ — thao tác PHÁ DỮ LIỆU đứng trong một biểu mẫu bình
   thường (Xoá học viên, Thu hồi tài khoản). Nền đỏ đặc (.btn-bad) ở những chỗ đó
   hút mắt hơn cả nút chính, mà nút chính mới là việc người ta vào đây để làm. */
.btn-line-bad {
  background: var(--color-surface);
  color: var(--color-bad);
  border-color: var(--color-bad);
}
.btn-line-bad:hover:not(:disabled)  { background: var(--color-bad-soft); }
.btn-line-bad:active:not(:disabled) { background: var(--color-bad-soft); }
.btn-line-bad .icon { color: var(--color-bad); }

/* Nút chữ xanh không nền — "Thêm" trong mục Ghi chú, "Xem thêm" */
.btn-plain {
  background: none;
  border-color: transparent;
  color: var(--color-brand);
  font-weight: var(--font-weight-semibold);
  padding: 0;
}

/* Nút đã bật (nền xanh lá nhạt) — "Đang bật" ở màn Khám phá tính năng */
.btn-ok-soft {
  background: var(--color-ok-soft);
  color: var(--color-ok);
  border-color: var(--color-ok-soft);
}

/* Nút viền nét đứt — "Thêm gói theo thời hạn" */
.btn-dashed {
  background: none;
  border: 1px dashed var(--color-dashed);
  color: var(--color-brand);
}
.btn-dashed:hover:not(:disabled) { background: var(--color-surface); }

/* --- Bảng cỡ (chiều cao lấy đúng từ mockup) --- */
.btn-xs { block-size: var(--h-xs); padding-inline: var(--sp-12); font-size: var(--fs-12-5); border-radius: var(--radius-lg); }
.btn-s  { block-size: var(--h-sm); padding-inline: var(--sp-14); font-size: var(--fs-12-5); border-radius: var(--radius-lg); font-weight: var(--font-weight-semibold); }
.btn-m  { block-size: var(--h-md); padding-inline: var(--sp-20); font-size: var(--fs-13-5); border-radius: var(--radius-xl); font-weight: var(--font-weight-semibold); }
.btn-l  { block-size: var(--h-lg); padding-inline: var(--sp-16); font-size: var(--fs-14);   border-radius: var(--radius-xl); font-weight: var(--font-weight-semibold); }
.btn-xl { block-size: var(--h-xl); padding-inline: var(--sp-20); font-size: var(--fs-15);   border-radius: var(--radius-xl); font-weight: var(--font-weight-semibold); }
.btn-block { inline-size: 100%; }
.btn-round { border-radius: var(--radius-full); padding-inline: var(--sp-12); }

/* Vùng bấm tối thiểu 44px cho nút thấp hơn, không làm xê dịch bố cục */
.btn-xs::after,
.btn-s::after,
.btn-m::after {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  block-size: var(--tap-min);
  transform: translateY(-50%);
}
.btn-xs, .btn-s, .btn-m { position: relative; }

/* Trạng thái vô hiệu hoá — mockup dùng nền xám đặc, KHÔNG dùng opacity */
.btn-line:disabled,
.btn-action:disabled {
  opacity: 1;
  background: var(--color-disabled);
  border-color: var(--color-line);
  color: var(--color-faint);
  cursor: not-allowed;
}
.btn-line:disabled .icon,
.btn-action:disabled .icon { color: var(--color-faint); }

/* Nút hành động lớn ở hồ sơ học viên (Đăng ký gói / Gia hạn / Bảo lưu / Thu tiền) */
.btn-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-8);
  block-size: var(--h-xl);
  inline-size: 100%;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--fs-13-5);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  transition: background var(--transition-fast);
}
.btn-action .icon { --icon-size: var(--icon-17); color: var(--color-brand); }
.btn-action:hover:not(:disabled)  { background: var(--color-paper); }
.btn-action:active:not(:disabled) { background: var(--color-paper); }

/* Nút chỉ có icon — luôn đủ 40px nhìn thấy, 44px vùng bấm */
.icon-btn {
  position: relative;
  display: inline-grid;
  place-items: center;
  inline-size: var(--h-md);
  block-size: var(--h-md);
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-ink);
  cursor: pointer;
  flex-shrink: 0;
}
.icon-btn::after {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  transform: translate(-50%, -50%);
}
.icon-btn:hover { background: var(--color-paper); }
.icon-btn-line {
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  color: var(--color-brand);
}
/* Nút icon nhỏ trong thẻ (bút chì sửa gói) — dùng muted để đạt 3:1, không dùng faint */
.icon-btn-sm {
  position: relative;
  display: inline-grid;
  place-items: center;
  inline-size: var(--h-xs);
  block-size: var(--h-xs);
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-muted);
  cursor: pointer;
  flex-shrink: 0;
}
.icon-btn-sm::after {
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  inline-size: var(--tap-min);
  block-size: var(--tap-min);
  transform: translate(-50%, -50%);
}
.icon-btn-sm:hover { background: var(--color-paper); color: var(--color-ink); }

/* --- Chip lọc (Tất cả / Đang tập / ... , kỳ doanh thu, tab theo kỳ) --- */
.chip-row {
  display: flex;
  gap: var(--sp-8);
  overflow-x: auto;
  scrollbar-width: none;
  padding-block-end: var(--sp-2);
}
.chip-row::-webkit-scrollbar { block-size: 0; }
/* Khi chip-row nằm trong .screen mà muốn cuộn tràn mép: thêm .bleed + .chip-row-inset */
.chip-row-inset { padding-inline: var(--sp-16); }

.chip {
  position: relative;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  block-size: var(--h-xs);
  padding-inline: var(--sp-12);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--fs-12-5);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.chip::after {          /* nới vùng bấm lên 44px mà không đổi chiều cao thấy được */
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  block-size: var(--tap-min);
  transform: translateY(-50%);
}
.chip-wide { padding-inline: var(--sp-14); }
/* Danh sách chip XUỐNG DÒNG thay vì cuộn ngang. .chip-row cuộn một hàng — đúng
   cho một dải bộ lọc luôn nhìn thấy đủ, sai cho một tập người dùng tự thêm vào:
   giờ thứ mười một trôi ra ngoài mép và họ không biết mình đã gõ nó chưa. */
/* Tiêu đề ngày và nút "Cho nghỉ cả ngày" trên cùng một hàng. Nút xuống dòng khi
   tên ngày dài ("Thứ tư, 24/12/2026") gặp màn hẹp — không bao giờ đè lên nhau. */
.class-day-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8);
}

.chip-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
}
/* Một ô nhập và một nút đứng cạnh nhau trên cùng hàng. Ô nhập chiếm phần còn
   lại; nút giữ nguyên bề rộng nội dung. */
.field-row {
  display: flex;
  gap: var(--sp-8);
  align-items: center;
}
.field-row > .field { flex: 1 1 auto; min-inline-size: 0; }
.field-row > .btn { flex: 0 0 auto; }
.chip-active,
.chip[aria-pressed='true'] {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-on-brand);
}

/* --- Công tắc phân đoạn (Theo thời hạn / Theo buổi) --- */
.segmented {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-4);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
}
.segmented-btn {
  flex: 1;
  block-size: var(--h-sm);
  border: 0;
  border-radius: var(--radius-lg);
  background: none;
  color: var(--color-muted);
  font-size: var(--fs-13-5);
  font-weight: var(--font-weight-semibold);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.segmented-btn[aria-selected='true'],
.segmented-btn.is-active {
  background: var(--color-ink);
  color: var(--color-on-brand);
}

/* ── Màn Điểm danh: gom theo LỚP hoặc theo GIỜ ─────────────────────────────
   Dòng lớp là một NÚT (mở phiếu buổi học) nên phải tự đặt lại nền và viền của
   button, không mượn .panel-row trần được. */
.checkin-class {
  display: flex; align-items: center; gap: var(--sp-12);
  inline-size: 100%; border: 0; background: var(--color-surface);
  text-align: start; cursor: pointer;
  border-inline-start: 3px solid var(--tone, var(--color-line));
}
.checkin-class .icon { --icon-size: var(--icon-16); color: var(--color-muted); flex: none; }
/* min-inline-size: 0 là điều kiện CẦN để .truncate ăn trong flex: mặc định phần
   tử flex không co nhỏ hơn nội dung, nên tên lớp dài đẩy tràn ra thay vì cắt. */
.checkin-class .panel-row-main { min-inline-size: 0; }
.checkin-class .panel-row-main > span { display: block; }
.checkin-class-time { flex: none; font-weight: var(--font-weight-semibold); color: var(--color-ink); }
.checkin-class-count {
  flex: none; padding-inline: var(--sp-8);
  border-radius: var(--radius-full);
  background: var(--color-bg-tertiary); color: var(--color-ink);
  font-size: var(--fs-12-5); font-weight: var(--font-weight-semibold);
}
/* Bốn mức, ăn theo biến --tone của chính dòng đó. */
.checkin-class.tone-ok    { --tone: var(--color-ok); }
.checkin-class.tone-warn  { --tone: var(--color-warn); }
.checkin-class.tone-bad   { --tone: var(--color-bad); }
.checkin-class.tone-muted { --tone: var(--color-line); }

/* Nhóm gập: Tập tự do · Tất cả lượt đến */
.checkin-fold-head { cursor: pointer; list-style: none; }
.checkin-fold-head::-webkit-details-marker { display: none; }
.checkin-fold-head .icon { --icon-size: var(--icon-16); color: var(--color-muted); flex: none; }
.checkin-fold[open] .checkin-fold-head .icon { transform: rotate(90deg); }

/* Biểu đồ giờ — chỉ dùng cho phòng tập KHÔNG có lớp. Chiều dài cột đặt qua
   data-bar + applyDynamicSizes, đúng cách duy nhất được phép đặt kích thước
   theo dữ liệu (docs/design-system.md). */
.checkin-hour { display: flex; align-items: center; gap: var(--sp-10); padding-block: var(--sp-4); }
.checkin-hour-label { flex: none; inline-size: 2.5rem; color: var(--color-muted); font-size: var(--fs-12-5); }
/* RÃNH chiếm hết bề ngang; phần ĐẦY nằm trong nó qua ::after. Đặt --bar thẳng
   lên phần tử flex:1 thì hai luật chọi nhau và cột luôn dài hết cỡ. */
.checkin-hour-bar {
  position: relative; flex: 1; block-size: var(--sp-10);
  border-radius: var(--radius-full);
  background: var(--color-bg-tertiary);
  overflow: hidden;
}
.checkin-hour-bar::after {
  content: '';
  position: absolute; inset-block: 0; inset-inline-start: 0;
  inline-size: var(--bar, 0%);
  background: var(--color-brand);
}
.checkin-hour-n { flex: none; inline-size: 1.75rem; text-align: end; font-weight: var(--font-weight-semibold); }

/* Dải tab CUỘN NGANG — dùng khi số tab và độ dài nhãn không đoán trước được
   (màn Cài đặt: số nhóm đổi theo quyền và theo module đang bật).
   .segmented thường chia đều bằng flex:1, nên bốn nhãn tiếng Việt ở bề rộng
   điện thoại bị bóp đến mức cắt chữ. Ở đây nút GIÃN RA khi còn chỗ (flex-grow)
   nhưng KHÔNG co lại dưới bề ngang chữ (flex-shrink: 0) — hết chỗ thì cả dải
   cuộn ngang, y như .chip-row. */
.segmented-scroll {
  overflow-x: auto;
  scrollbar-width: none;
}
.segmented-scroll::-webkit-scrollbar { block-size: 0; }
.segmented-scroll .segmented-btn {
  flex: 1 0 auto;
  padding-inline: var(--sp-14);
  white-space: nowrap;
}

/* --- Nút nhóm chọn (Khuyến mãi 4 nút, Phương thức thanh toán 3 nút) --- */
.choice-grid   { display: grid; gap: var(--sp-8); }
.choice-grid-2 { grid-template-columns: repeat(2, 1fr); }
.choice-grid-3 { grid-template-columns: repeat(3, 1fr); }
.choice {
  block-size: var(--h-lg);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  color: var(--color-muted);
  font-size: var(--fs-13-5);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.choice[aria-pressed='true'],
.choice.is-active {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-on-brand);
}

/* --- Công tắc bật/tắt module — <button role="switch" aria-checked="..."> --- */
.switch {
  position: relative;
  flex-shrink: 0;
  inline-size: 46px;
  block-size: 27px;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-switch-off);
  cursor: pointer;
  transition: background var(--transition-base);
}
.switch::before {       /* vùng bấm 44px */
  content: '';
  position: absolute;
  inset-block-start: 50%;
  inset-inline: 0;
  block-size: var(--tap-min);
  transform: translateY(-50%);
}
.switch::after {        /* núm trượt */
  content: '';
  position: absolute;
  inset-block-start: var(--sp-3);
  inset-inline-start: var(--sp-3);
  inline-size: 21px;
  block-size: 21px;
  border-radius: var(--radius-full);
  background: var(--color-surface);
  box-shadow: var(--shadow-knob);
  transition: inset-inline-start var(--transition-base);
}
.switch[aria-checked='true']        { background: var(--color-brand); }
.switch[aria-checked='true']::after { inset-inline-start: 22px; }


/* ==========================================================================
   F. TRẠNG THÁI HỌC VIÊN
   ========================================================================== */

/* Nhãn trạng thái: chấm tròn + chữ. Luôn đi kèm 1 class tông .pill-* */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-8);
  border-radius: var(--radius-full);
  font-size: var(--fs-11);
  font-weight: var(--font-weight-semibold);
  white-space: nowrap;
  background: var(--tone-soft);
  color: var(--tone);
}
.pill-dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: var(--radius-full);
  background: var(--tone);
  flex-shrink: 0;
}
.pill-active   { --tone: var(--color-ok);    --tone-soft: var(--color-ok-soft); }    /* Đang tập */
.pill-expiring { --tone: var(--color-warn);  --tone-soft: var(--color-warn-soft); }  /* Sắp hết hạn */
.pill-expired  { --tone: var(--color-bad);   --tone-soft: var(--color-bad-soft); }   /* Đã hết hạn */
.pill-reserved { --tone: var(--color-brand); --tone-soft: var(--color-brand-soft); } /* Đang bảo lưu */

/* Trạng thái ĐIỂM DANH của một lượt giữ chỗ (phiếu buổi học).
   Dùng CHUNG khai báo với nhãn trạng thái học viên — cùng tông thì phải cùng một
   dòng, tách ra là hai bảng màu sẽ trôi lệch. Nhưng phải có TÊN RIÊNG: đọc
   `pill-active` ở chỗ nói "Đã điểm danh" thì người sửa sau sẽ tưởng hai thứ đó là
   một, rồi đổi màu "Đang tập" mà vô tình đổi luôn màu "Đã điểm danh". */
.pill-attended { --tone: var(--color-ok);    --tone-soft: var(--color-ok-soft); }    /* Đã điểm danh */
/* Dùng cho CẢ "Không đến" lẫn "Huỷ muộn": với học viên đó là cùng một hậu quả —
   không tập, và mất một buổi. Màu nói hậu quả, chữ nói nguyên nhân. Tô hai màu
   khác nhau cho cùng một kết cục là bắt người đọc đi tìm một khác biệt không có. */
.pill-noshow   { --tone: var(--color-bad);   --tone-soft: var(--color-bad-soft); }
/* Chờ — cố ý KHÔNG có màu cảnh báo: chưa điểm danh là chuyện bình thường của một
   buổi chưa diễn ra, không phải chuyện cần xử lý. --color-muted-strong đạt 4.98:1
   trên nền paper (AA), xem ghi chú token. */
.pill-pending  { --tone: var(--color-muted-strong); --tone-soft: var(--color-paper); }
/* Khách trial (ADR-070) — tông thương hiệu: nhận ra ngay "người này không phải
   học viên", để HLV chuẩn bị giới thiệu và lễ tân không đi tìm gói của họ. */
.pill-trial    { --tone: var(--color-brand); --tone-soft: var(--color-brand-soft); }

/* Thông tin cá nhân (tab Tài khoản của hồ sơ học viên, 30/09/2026): nhãn trái,
   giá trị phải; màn hẹp thì nhãn nằm trên giá trị. */
.info-list { margin-top: var(--sp-8); }
.info-row {
  display: grid;
  grid-template-columns: minmax(120px, 34%) 1fr;
  gap: var(--sp-8);
  padding-block: var(--sp-8);
  border-block-start: 1px solid var(--color-line);
  font-size: var(--fs-13-5);
}
.info-row:first-child { border-block-start: 0; }
.info-row dt { color: var(--color-muted-strong); }
.info-row dd { margin: 0; color: var(--color-ink); overflow-wrap: anywhere; }

/* ── Tab "Lịch sử tập luyện" trên hồ sơ học viên (30/09/2026) ───────────────
   Thẻ dùng lại .tt-card của Lịch lớp (màu viền trái đọc biến --tone); bốn lớp
   dưới đặt màu theo TRẠNG THÁI CỦA HỌC VIÊN, không theo sĩ số như Lịch lớp. */
.tt-card.hist-attended { --tone: var(--color-ok);   background: var(--color-ok-soft); }
.tt-card.hist-noshow   { --tone: var(--color-bad);  background: var(--color-bad-soft); }
.tt-card.hist-booked   { --tone: var(--color-brand); background: var(--color-brand-soft); }
.tt-card.hist-excused  { --tone: var(--color-muted-strong); background: var(--color-paper); opacity: 0.75; }
.tt-card-state {
  font-size: var(--fs-12);
  font-weight: var(--font-weight-medium);
  color: var(--tone, var(--color-muted-strong));
}
/* Danh sách theo ngày — CHỈ màn hẹp; từ 900px lưới tuần thay chỗ (xem .week-grid). */
.hist-list { margin-top: var(--sp-12); }
.hist-day + .hist-day { margin-top: var(--sp-12); }
.hist-day-label {
  font-size: var(--fs-12);
  font-weight: var(--font-weight-medium);
  color: var(--color-muted-strong);
  margin-bottom: var(--sp-4);
}
.hist-time { min-inline-size: 44px; font-weight: var(--font-weight-medium); }
@media (min-width: 900px) {
  .hist-list { display: none; }
}

/* Dòng lịch hẹn của khách trial (màn Khách trial) — tông thương hiệu để nổi hơn
   dòng SĐT/Sales bên trên: đây là thông tin Sales cần nhìn đầu tiên. */
.trial-next {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  margin-top: var(--sp-4);
  color: var(--color-brand);
  font-size: var(--fs-13-5);
  font-weight: var(--font-weight-medium);
}
.trial-next .icon { flex-shrink: 0; }

/* Chấm tròn 8px đứng riêng (nhãn 3 thẻ trạng thái ở Tổng quan) */
.dot {
  inline-size: 8px;
  block-size: 8px;
  border-radius: var(--radius-full);
  background: var(--tone);
  flex-shrink: 0;
}

/* Băng trạng thái trong thẻ gói hiện tại: "Còn 30 ngày" / "Đã hết hạn 5 ngày" */
.status-banner {
  border-radius: var(--radius-xl);
  padding: var(--sp-10) var(--sp-12);
  font-size: var(--fs-13-5);
  font-weight: var(--font-weight-semibold);
  text-align: center;
  background: var(--color-paper);
  color: var(--color-ink);
}
.status-banner.tone-warn,
.status-banner.tone-bad,
.status-banner.tone-brand,
.status-banner.tone-ok { background: var(--tone-soft); color: var(--tone); }

/* Khối cảnh báo công nợ ở hồ sơ học viên */
.debt-alert {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  padding: var(--sp-14);
  border: 1px solid var(--color-debt-border);
  border-radius: var(--radius-2xl);
  background: var(--color-bad-soft);
}
.debt-alert .icon { --icon-size: var(--icon-20); color: var(--color-bad); }
.debt-alert-main  { flex: 1; min-inline-size: 0; }
.debt-alert-title { font-size: var(--fs-13); font-weight: var(--font-weight-semibold); color: var(--color-bad); }
.debt-alert-sub   { font-size: var(--fs-12); color: var(--color-debt-text); }


/* ==========================================================================
   G. MÀN TỔNG QUAN
   ========================================================================== */

/* 3 thẻ số liệu: Đang tập / Sắp hết hạn / Đã hết hạn */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-10);
}
.stat-card {
  display: block;
  padding: var(--sp-12);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-2xl);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.stat-card:hover, .stat-card:active { background: var(--color-paper); }
.stat-card-label {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  font-size: var(--fs-11);
  color: var(--color-muted);
  line-height: var(--lh-tight);
}
.stat-card-value { margin-top: var(--sp-8); }

/* Dòng "Cần xử lý hôm nay" — vạch màu + số lớn + mô tả + mũi tên */
.task-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  inline-size: 100%;
  padding: var(--sp-12) var(--sp-16);
  border: 0;
  border-top: 1px solid var(--color-line);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.task-row:hover, .task-row:active { background: var(--color-paper); }
.task-bar {
  inline-size: 4px;
  block-size: 32px;
  border-radius: var(--radius-full);
  background: var(--tone);
  flex-shrink: 0;
}
.task-count {
  inline-size: 32px;
  flex-shrink: 0;
  color: var(--tone);
  font-size: var(--fs-22);
  font-weight: var(--font-weight-bold);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-none);
}
.task-text { flex: 1; min-inline-size: 0; font-size: var(--fs-13-5); }
.task-row .icon-chevron { --icon-size: var(--icon-18); color: var(--color-faint); }
/* Trạng thái rỗng của khối: "Không có việc gấp." */
.task-empty {
  padding: var(--sp-24) var(--sp-16);
  border-top: 1px solid var(--color-line);
  text-align: center;
  font-size: var(--fs-13-5);
  color: var(--color-muted);
}

/* Thẻ doanh thu nền xanh (chỉ chủ phòng tập) */
.hero-card {
  display: block;
  inline-size: 100%;
  padding: var(--sp-16);
  border: 0;
  border-radius: var(--radius-2xl);
  background: var(--color-brand);
  color: var(--color-on-brand);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.hero-card:hover, .hero-card:active { background: var(--color-brand-dark); }
.hero-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-8);
}
.hero-card-label { font-size: var(--fs-12); color: var(--color-on-brand-muted); }
.hero-card-top .icon { --icon-size: var(--icon-18); color: var(--color-on-brand-faint); }
.hero-card-value { margin-top: var(--sp-4); }
.hero-card-note {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-8);
  font-size: var(--fs-12);
  color: var(--color-on-brand-muted);
}
.hero-card-note .icon { --icon-size: var(--icon-14); }

/* Ô hành động nhanh 2 cột (Thêm học viên / Thu tiền còn nợ) */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--sp-10);
}
.tile {
  display: block;
  padding: var(--sp-14);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-2xl);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.tile:hover, .tile:active { background: var(--color-paper); }
.tile .icon { --icon-size: var(--icon-20); color: var(--color-brand); }
.tile-label { margin-top: var(--sp-8); font-size: var(--fs-13-5); font-weight: var(--font-weight-semibold); }

/* Danh sách "Ba việc nên làm hôm nay" (ngày đầu tiên) */
.steps { list-style: none; display: flex; flex-direction: column; gap: var(--sp-10); }
.step  { display: flex; gap: var(--sp-12); font-size: var(--fs-13-5); }
.step-num {
  display: grid;
  place-items: center;
  inline-size: 20px;
  block-size: 20px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  background: var(--color-brand-soft);
  color: var(--color-brand);
  font-size: var(--fs-11);
  font-weight: var(--font-weight-bold);
}
.step-link {
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--color-line);
}


/* ==========================================================================
   H. DANH SÁCH HỌC VIÊN
   Một bộ markup duy nhất: mobile = dòng, desktop (>= 900px) = bảng 5 cột.
   ========================================================================== */

.avatar {
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  flex-shrink: 0;
  border-radius: var(--radius-full);
  font-size: var(--fs-13);
  font-weight: var(--font-weight-bold);
  background: var(--color-brand-soft);
  color: var(--color-brand);
}
.avatar-brand { background: var(--color-brand-soft); color: var(--color-brand); }
.avatar-rose  { background: var(--color-avatar-rose-bg); color: var(--color-avatar-rose-text); }
.avatar-lg    { inline-size: 56px; block-size: 56px; font-size: var(--fs-18); }

.member-list  { border-top: 1px solid var(--color-line); }
.member-head  { display: none; }   /* hàng tiêu đề bảng — chỉ desktop */
.member-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  inline-size: 100%;
  padding: var(--sp-12) var(--sp-16);
  border: 0;
  border-bottom: 1px solid var(--color-line);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.member-row:hover, .member-row:active { background: var(--color-paper); }
.member-who {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  flex: 1;
  min-inline-size: 0;
}
.member-main { min-inline-size: 0; }
.member-name {
  display: block;
  font-size: var(--fs-14-5);
  font-weight: var(--font-weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.member-sub {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--fs-12-5);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.member-debt { color: var(--color-bad); font-weight: var(--font-weight-semibold); }
.member-cell { display: none; }    /* SĐT / Gói / Hết hạn — chỉ desktop */
.member-side { flex-shrink: 0; text-align: right; }
.member-meta {
  display: block;
  margin-top: var(--sp-4);
  font-size: var(--fs-11-5);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}


/* ==========================================================================
   H2. DANH SÁCH THÁNG (màn "Theo tháng")
   Cùng nguyên tắc với .member-list: MỘT bộ markup, CSS đổi hình.
   Dưới 900px = dòng gộp (tên tháng + chuỗi đếm ở dòng phụ).
   Từ 900px    = bảng có hàng tiêu đề, 5 thông tin thành 5 cột.

   Cột "Doanh thu" CÓ THỂ VẮNG MẶT: lễ tân không được xem tiền, máy chủ trả
   revenue = null và frontend bỏ hẳn ô .month-money ở CẢ hàng tiêu đề LẪN mọi dòng.
   Bảng phải tự thu về 4 cột — xem chỗ xử lý ở mục P.
   ========================================================================== */

.month-list { border-top: 1px solid var(--color-line); }
.month-head { display: none; }   /* hàng tiêu đề bảng — chỉ desktop */
.month-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  inline-size: 100%;
  min-block-size: var(--tap-min);
  padding: var(--sp-14) var(--sp-16);
  border: 0;
  border-bottom: 1px solid var(--color-line);
  background: var(--color-surface);
  text-align: left;
  color: inherit;
  cursor: pointer;
  transition: background var(--transition-fast);
}
.month-row:hover, .month-row:active { background: var(--color-paper); }

/* Ô trái: icon lịch + tên tháng + chuỗi đếm */
.month-who {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  flex: 1;
  min-inline-size: 0;
}
.month-main { min-inline-size: 0; }
.month-name {
  display: block;
  font-size: var(--fs-14-5);
  font-weight: var(--font-weight-semibold);
}
/* Chuỗi đếm gộp — CHO XUỐNG DÒNG, tối đa 2 dòng. Cắt một dòng ở đây là nuốt mất
   "2 hết hạn", đúng lỗi mà .menu-sub-wrap sinh ra để chữa (xem mục O). */
.month-sub {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: break-word;
  margin-top: var(--sp-2);
  font-size: var(--fs-12-5);
  color: var(--color-muted);
  line-height: var(--lh-snug);
}
.month-cell { display: none; }   /* Đang tập / Mới / Hết hạn — chỉ desktop */

/* Cột tiền. Dùng CÙNG class ở hàng tiêu đề và ở từng dòng — nhờ vậy khi lễ tân
   xem, chỉ cần bỏ đúng một tên class là cả cột biến mất, không sót nhãn nào.
   KHÔNG canh phải bằng :last-child: thiếu cột tiền thì "Hết hạn" sẽ thành ô cuối
   và bị canh phải oan. */
.month-money {
  flex-shrink: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.month-row .month-money {
  font-size: var(--fs-14);
  font-weight: var(--font-weight-semibold);
}


/* ==========================================================================
   H3. BẢNG BÓC TÁCH BÁO CÁO LỚP (tab "Báo cáo" của màn Lịch lớp)
   Cùng công thức với .month-list: MỘT bộ markup, CSS đổi hình ở 900px.
   Dưới 900px = thẻ xếp dọc — tên + chuỗi số chiếm trọn dòng đầu, hai ô tỷ lệ
                nằm cạnh nhau ở dòng dưới.
   Từ 900px    = bảng 3 cột có hàng tiêu đề: Lớp | Lấp đầy | Đi học (mục P4).

   Vì sao CHỈ hai tỷ lệ lên cột, còn buổi/chỗ/đến/vắng/huỷ gom vào .report-sub:
   hai tỷ lệ là hai câu hỏi kinh doanh ("lớp nào ế", "lớp nào khách đặt rồi bỏ"),
   phần còn lại là dấu vết để kiểm chứng con số. .report-sub hiện ở CẢ HAI bề
   ngang — khác .month-sub (bị ẩn ở desktop vì ba số của nó đã thành ba cột) —
   nên không con số nào biến mất khi đổi kích thước cửa sổ.

   🔴 Ô tỷ lệ có HAI hình: biết tỷ lệ thì dưới số là thanh tiến độ, chưa có mẫu
   số (API trả null) thì KHÔNG vẽ thanh, thay bằng .report-rate-note nói ra lý
   do. Thanh rỗng đọc ra y hệt 0% — đúng thứ máy chủ cố ý tránh khi trả null.
   ========================================================================== */

/* Một nửa của khối tổng (hai tỷ lệ lớn nằm trong .grid-2).
   Một nửa có thanh tiến độ, nửa kia KHÔNG (tỷ lệ null) — chênh nhau đúng chiều
   cao thanh cộng lề của nó. Để chữ tự rơi thì hai câu chú thích lệch 14px và
   khối trông như bị xô. Đẩy chú thích xuống đáy giữ hai nửa thẳng hàng mà không
   phải vẽ một thanh rỗng — thanh rỗng chính là hiện 0% bằng hình. */
.rate-tile { display: flex; flex-direction: column; }
.rate-tile .text-note { margin-top: auto; padding-top: var(--sp-6); }

.report-head { display: none; }   /* hàng tiêu đề bảng — chỉ desktop */
.report-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-10) var(--sp-12);
  padding: var(--sp-14) var(--sp-16);
  border-top: 1px solid var(--color-line);
}
/* Ô tên chiếm trọn dòng đầu, đẩy hai ô tỷ lệ xuống dòng dưới */
.report-who { flex: 1 1 100%; min-inline-size: 0; }
.report-name {
  font-size: var(--fs-14-5);
  font-weight: var(--font-weight-semibold);
  /* Tên lớp do người dùng đặt và có thể là một từ rất dài. Cắt bằng dấu ba chấm
     ở đây là giấu mất thứ duy nhất phân biệt dòng này với dòng kia — cho xuống
     dòng thì thà thẻ cao thêm một dòng còn hơn. */
  overflow-wrap: anywhere;
}
.report-sub {
  margin-top: var(--sp-2);
  font-size: var(--fs-12-5);
  color: var(--color-muted);
  line-height: var(--lh-snug);
}
.report-rate { flex: 1 1 0; min-inline-size: 0; }
.report-rate-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-6);
}
.report-rate-label { font-size: var(--fs-11-5); color: var(--color-muted); }
.report-rate-value { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
/* Đứng đúng chỗ thanh tiến độ (.progress cũng có margin-top --sp-6) để hai ô
   cạnh nhau — một ô có thanh, một ô không — vẫn cao bằng nhau.

   🔴 --color-muted, KHÔNG phải --color-faint. Câu này là thứ DUY NHẤT giải thích
   vì sao chỗ đó là dấu gạch chứ không phải một con số — đọc không ra thì cái dấu
   gạch thành bí ẩn. --color-faint được đánh dấu "chỉ cho hình trang trí" trong
   design-tokens.css và không đạt 4.5:1. Cùng bài học với .perm-item-locked. */
.report-rate-note {
  margin-top: var(--sp-6);
  font-size: var(--fs-11-5);
  color: var(--color-muted);
}


/* ==========================================================================
   I. GÓI TẬP
   ========================================================================== */

.pkg-card { padding: var(--sp-16); }
.pkg-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-12);
}
.pkg-name { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.pkg-meta { margin-top: var(--sp-2); font-size: var(--fs-12-5); color: var(--color-muted); }
.pkg-foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-12);
  margin-top: var(--sp-12);
  padding-top: var(--sp-12);
  border-top: 1px solid var(--color-line);
}
.pkg-price { font-size: var(--fs-18); font-weight: var(--font-weight-bold); font-variant-numeric: tabular-nums; }
.pkg-count { font-size: var(--fs-12); color: var(--color-muted); font-variant-numeric: tabular-nums; }


/* ==========================================================================
   J. DOANH THU
   ========================================================================== */

/* Bảng cộng trừ: <dl class="dl"> */
.dl {
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
  font-size: var(--fs-13-5);
}
.dl-row   { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-12); }
.dl-term  { color: var(--color-muted); }
.dl-value { font-weight: var(--font-weight-semibold); font-variant-numeric: tabular-nums; }
.dl-row-total {
  padding-top: var(--sp-8);
  border-top: 1px solid var(--color-line);
}
.dl-row-total .dl-term  { color: var(--color-ink); font-weight: var(--font-weight-semibold); }
.dl-row-total .dl-value { font-weight: var(--font-weight-bold); }
.dl-value-lg  { font-size: var(--fs-17); font-weight: var(--font-weight-bold); }
.dl-value-bad { color: var(--color-bad); }
.dl-value-ok  { color: var(--color-ok); }

/* Biểu đồ cột 6 tháng — CSS thuần.
   JS truyền chiều cao mỗi cột: bar.style.setProperty('--bar', pct + '%') */
.chart {
  display: flex;
  align-items: flex-end;
  gap: var(--sp-8);
  block-size: 128px;
  margin-top: var(--sp-16);
}
.chart-col {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-6);
  block-size: 100%;
}
.chart-value {
  font-size: var(--fs-10-5);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--color-muted);
}
.chart-bar-track {
  flex: 1;
  inline-size: 100%;
  min-block-size: 0;
  display: flex;
  align-items: flex-end;
}
.chart-bar {
  inline-size: 100%;
  block-size: var(--bar, 0%);
  min-block-size: 2px;
  border-start-start-radius: var(--radius-md);
  border-start-end-radius: var(--radius-md);
  background: var(--color-chart-bar);
}
.chart-label {
  font-size: var(--fs-10);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
/* Cột tháng hiện tại — nổi bật bằng màu chủ đạo */
.chart-col-current .chart-bar   { background: var(--color-brand); }
.chart-col-current .chart-value { color: var(--color-brand); }
.chart-note { margin-top: var(--sp-12); font-size: var(--fs-11-5); color: var(--color-muted); }


/* ==========================================================================
   K. FORM (kiểu app — bo 12px, cao 44/48px)
   Khác .form-input mặc định ở trên: dùng .field cho mọi màn hình sản phẩm.
   ========================================================================== */

.field-group { display: flex; flex-direction: column; gap: var(--sp-14); }
.field-label {
  display: block;
  margin-bottom: var(--sp-6);
  font-size: var(--fs-13);
  font-weight: var(--font-weight-medium);
}
.field-label-sm {
  display: block;
  margin-bottom: var(--sp-4);
  font-size: var(--fs-11-5);
  color: var(--color-muted);
}
.field-required { color: var(--color-bad); }

.field {
  inline-size: 100%;
  block-size: var(--h-xl);
  padding-inline: var(--sp-14);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  color: var(--color-ink);
  font-size: var(--fs-15);
}
.field::placeholder { color: var(--color-muted); }  /* a11y: placeholder phải đạt 4.5:1 */
.field-sm   { block-size: var(--h-lg); padding-inline: var(--sp-12); font-size: var(--fs-14); }
.field-soft { background: var(--color-paper); }     /* ô nhập trong Cài đặt / lọc theo kỳ */
.field-lg   { font-size: var(--fs-18); font-weight: var(--font-weight-semibold); }
.field-textarea {
  inline-size: 100%;
  block-size: auto;
  padding: var(--sp-14);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  font-size: var(--fs-15);
  resize: vertical;
}
.field-note { margin-top: var(--sp-6); font-size: var(--fs-12-5); color: var(--color-muted); line-height: var(--lh-relaxed); }
.field-note-warn { color: var(--color-warn); font-weight: var(--font-weight-medium); }
.field-error {
  margin-top: var(--sp-6);
  font-size: var(--fs-12-5);
  color: var(--color-bad);
}
.field-invalid { border-color: var(--color-bad); }

/* Ô tìm kiếm có icon + nút xoá */
.search { position: relative; }
.search-icon {
  position: absolute;
  inset-inline-start: var(--sp-14);
  inset-block-start: 50%;
  transform: translateY(-50%);
  --icon-size: var(--icon-18);
  color: var(--color-muted);
  pointer-events: none;
}
.search-input {
  inline-size: 100%;
  block-size: var(--h-lg);
  padding-inline: var(--sp-44) var(--sp-40);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  font-size: var(--fs-14);
}
.search-input::placeholder { color: var(--color-muted); }
.search-input-lg { block-size: var(--h-xl); padding-inline-end: var(--sp-12); }
.search-clear {
  position: absolute;
  inset-inline-end: var(--sp-8);
  inset-block-start: 50%;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  inline-size: var(--h-xs);
  block-size: var(--h-xs);
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--color-muted);
  cursor: pointer;
}

/* Khối gập "Thêm thông tin (không bắt buộc)" — <details class="disclosure"> */
.disclosure {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
}
.disclosure-summary {
  block-size: var(--h-xl);
  padding-inline: var(--sp-14);
  display: flex;
  align-items: center;
  font-size: var(--fs-14);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  list-style: none;
}
.disclosure-summary::-webkit-details-marker { display: none; }
.disclosure-body {
  padding: var(--sp-14);
  border-top: 1px solid var(--color-line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
}

/* --- Bảy khối tích quyền trong phiếu Nhóm quyền -------------------------- */
/* Dựng TRÊN .disclosure ở trên — cùng là <details>, cùng cái hộp viền bo. Chỉ
   thêm những gì khối quyền cần mà khối "Thêm thông tin" không có: một con số
   đếm bên phải tiêu đề, một mũi tên, và dòng ô tích. */

/* Ghép cùng .disclosure-summary. Tách ra vì .disclosure-summary không có gap,
   mà ở đây tiêu đề / số đếm / mũi tên là ba thứ phải rời nhau. */
.perm-summary { gap: var(--sp-8); }

/* "3/8" — mẫu số là số ô TÍCH ĐƯỢC của khối, không tính quyền chỉ-chủ. */
.perm-count { font-size: var(--fs-12-5); color: var(--color-muted); }

.perm-chev {
  --icon-size: var(--icon-16);
  color: var(--color-muted);
  transition: transform var(--transition-base);
}
details[open] > .perm-summary > .perm-chev { transform: rotate(90deg); }

/* Cả dòng là <label>, nên bấm vào tên hay mô tả đều tích — vùng bấm rộng bằng
   cả dòng thay vì đúng 18px của ô vuông. */
.perm-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-10);
  cursor: pointer;
}
/* `display: flex` ở trên đè thuộc tính [hidden] của trình duyệt — ô "Ghi nhớ đăng
   nhập" (login.html) cần ẩn được khi người gõ là học viên. */
.perm-item[hidden] { display: none; }
/* flex column để .feature-title và .feature-desc (vốn là <span>) xếp chồng —
   flex item tự thành khối, nên HTML vẫn hợp lệ bên trong <label>. */
.perm-item-text {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-inline-size: 0;
}
/* Ô tích thật của trình duyệt: bàn phím và trình đọc màn hình chạy sẵn.
   --icon-18 là token vuông đúng cỡ này; không có token riêng cho ô tích. */
.perm-box {
  flex-shrink: 0;
  inline-size: var(--icon-18);
  block-size: var(--icon-18);
  margin-block-start: var(--sp-2);
  accent-color: var(--color-brand);
  cursor: pointer;
}
.perm-box:disabled { cursor: not-allowed; opacity: 0.5; }

/* Quyền chỉ nhóm chủ giữ được: mờ đi để thấy ngay là không chạm vào được.
   Chữ hạ xuống --color-muted (4.87:1) chứ KHÔNG xuống --color-faint — mô tả
   vẫn phải đọc được thì người dùng mới hiểu mình đang không được cấp cái gì. */
.perm-item-locked { cursor: not-allowed; }
.perm-item-locked .feature-title { color: var(--color-muted); }

/* Thanh tiến độ số buổi đã dùng.
   JS: fill.style.setProperty('--progress', pct + '%') */
/* 🔴 CẢ HAI phải khai display. Hai class này KHÔNG được ngầm dựa vào việc người
   gọi dùng thẻ <div>: thẻ buổi học ở thời khoá biểu là một <button>, mà nội dung
   hợp lệ của <button> chỉ là phrasing content — không đặt <div> vào được, phải
   dùng <span>. Span mặc định là `inline`, mà phần tử inline BỎ QUA inline-size và
   block-size, nên thanh vẽ ra 0×0: rãnh xám hiện bình thường còn ruột màu biến
   mất. Không có lỗi nào, chỉ là một thanh rỗng trông như dữ liệu bằng 0. */
.progress {
  display: block;
  block-size: 8px;
  margin-top: var(--sp-6);
  border-radius: var(--radius-full);
  background: var(--color-paper);
  overflow: hidden;
}
.progress-fill {
  display: block;
  block-size: 100%;
  inline-size: var(--progress, 0%);
  border-radius: var(--radius-full);
  background: var(--color-brand);
  transition: inline-size var(--transition-base);
}
.progress-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-12);
  font-size: var(--fs-13);
}


/* ==========================================================================
   L. SHEET TRƯỢT TỪ ĐÁY
   Mobile: trượt lên từ đáy, chừa 32px ở trên.
   Desktop (>= 900px): tự chuyển thành hộp thoại giữa màn hình (xem mục P).
   ========================================================================== */

.sheet-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-sheet);
  background: var(--color-overlay);
  animation: app-fade var(--dur-fade) ease-out;
}
.sheet {
  position: absolute;
  inset-inline: 0;
  inset-block: var(--sp-32) 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--color-paper);
  border-start-start-radius: var(--radius-3xl);
  border-start-end-radius: var(--radius-3xl);
  animation: app-sheet-up var(--dur-sheet) var(--ease-sheet);
}
/* VẼ LẠI phiếu đang mở (refreshSheet) — tắt hiệu ứng mở. paintSheet thay cả
   innerHTML nên khung phiếu là phần tử MỚI, và hiệu ứng mở chạy lại sau mỗi lần
   bấm điểm danh: người dùng thấy phiếu "đóng rồi mở", tưởng trang bị tải lại.
   Hai class để thắng cả `.sheet` trong mục P (hộp thoại desktop). */
.sheet-overlay.sheet-still,
.sheet-still .sheet { animation: none; }

/* CÂY BẠN TẬP (migration 061) — dưới người chính, thụt vào, có đường nối.
   Dùng chung cho phiếu buổi học (nhân sự) và thẻ lớp của mình (cổng học viên).
   Đường dọc là viền trái của <ul>; mỗi dòng có một nhánh ngang (::before). */
.guest-tree {
  list-style: none;
  margin: var(--sp-6) 0 0;
  padding: 0;
  margin-inline-start: var(--sp-6);
  border-inline-start: 1px solid var(--color-line);
}
.guest-tree-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-block-size: 32px;
  padding-inline-start: var(--sp-16);
}
.guest-tree-row::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 50%;
  inline-size: var(--sp-12);
  border-block-start: 1px solid var(--color-line);
}
.guest-tree-name {
  flex: 1;
  min-inline-size: 0;
  font-size: var(--fs-13-5);
  color: var(--color-ink);
  overflow-wrap: anywhere;
}
.guest-tree-name-empty { color: var(--color-muted); }
.guest-tree-form {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-inline-size: 0;
}
.guest-tree-form .field { flex: 1; min-inline-size: 0; }
.sheet-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  block-size: var(--app-header-h);
  padding-inline: var(--sp-8);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-line);
}
.sheet-title { font-size: var(--fs-16); font-weight: var(--font-weight-semibold); }
.sheet-body  { flex: 1; min-block-size: 0; overflow-y: auto; }
.sheet-pad   { padding: var(--sp-16); display: flex; flex-direction: column; gap: var(--sp-16); }
.sheet-lead  {
  padding: var(--sp-12) var(--sp-16) 0;
  font-size: var(--fs-13-5);
  color: var(--color-muted);
}
.sheet-lead-name { color: var(--color-ink); font-weight: var(--font-weight-semibold); }
/* Câu báo lỗi ĐỨNG YÊN trong phiếu — xem showSheetError() ở public/js/ui.js.
   Nằm ngay trên nút chính vì đó là chỗ mắt đang nhìn khi vừa bấm nút. */
.sheet-error {
  margin-bottom: var(--sp-12);
  padding: var(--sp-10) var(--sp-12);
  border-radius: var(--radius-md);
  background: var(--color-bad-soft);
  color: var(--color-bad);
  font-size: var(--fs-13);
  font-weight: var(--font-weight-medium);
}

.sheet-foot {
  flex-shrink: 0;
  padding: var(--sp-12);
  padding-bottom: max(var(--sp-12), env(safe-area-inset-bottom));
  background: var(--color-surface);
  border-top: 1px solid var(--color-line);
}

@keyframes app-sheet-up {
  from { transform: translateY(14px); opacity: 0.4; }
  to   { transform: translateY(0);    opacity: 1; }
}
@keyframes app-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes app-pop {
  from { transform: scale(0.97); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
.fade-in { animation: app-fade var(--dur-fade) ease-out; }


/* ==========================================================================
   M. NHẬT KÝ TIMELINE (lịch sử đăng ký / bảo lưu / thanh toán / ghi chú)
   Ngày ở lề trái, vạch dọc, chấm tròn — đọc như một cuốn sổ.
   ========================================================================== */

.ledger-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-14);
  margin-top: var(--sp-12);
}

/* Tiêu đề một NHÓM trong nhật ký — "Tháng 09/2026" bên trái, số tổng phụ bên phải.
   Dùng khi thân panel là nhiều .ledger-list nối nhau (lịch sử thanh toán gom theo tháng).

   Tự lo khoảng cách: nhóm ĐẦU chỉ cách tiêu đề panel một nhịp; từ nhóm THỨ HAI trở đi
   mới có vạch ngăn. JS KHÔNG phải đếm index nữa, và không được mượn .panel-divider —
   class đó dành cho vạch ngăn giữa các PHẦN của một panel, không phải giữa các nhóm
   cùng loại trong một danh sách.

   ĐIỀU KIỆN: các .ledger-group-head phải là ANH EM RUỘT của nhau (markup phẳng:
   head, list, head, list...). Bọc mỗi nhóm trong một <div> riêng là selector ~ hết ăn. */
.ledger-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-8);
  margin-top: var(--sp-12);
  font-size: var(--fs-14);
  font-weight: var(--font-weight-semibold);
}
.ledger-group-head ~ .ledger-group-head {
  margin-top: var(--sp-14);
  padding-top: var(--sp-14);
  border-top: 1px solid var(--color-line);
}
/* Danh sách nằm sát tiêu đề của chính nó — khoảng hở trong nhóm phải NHỎ HƠN
   khoảng hở giữa hai nhóm, nếu không mắt không gom được đâu với đâu. */
.ledger-group-head + .ledger-list { margin-top: var(--sp-8); }

.ledger {
  position: relative;
  padding-inline-start: var(--ledger-indent);
}
.ledger::before {
  content: '';
  position: absolute;
  inset-inline-start: var(--ledger-rail);
  inset-block: var(--sp-4);
  inline-size: 1px;
  background: var(--color-timeline);
}
.ledger-date {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: var(--ledger-date-w);
  text-align: right;
  font-size: var(--fs-11);
  line-height: var(--lh-tight);
  color: var(--color-muted);
  font-variant-numeric: tabular-nums;
}
.ledger-dot {
  position: absolute;
  inset-inline-start: var(--ledger-dot-x);
  inset-block-start: 5px;
  inline-size: var(--ledger-dot-size);
  block-size: var(--ledger-dot-size);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 2px solid var(--color-faint);
}
.ledger-main { font-size: var(--fs-13-5); font-weight: var(--font-weight-medium); line-height: var(--lh-snug); }
.ledger-sub  { margin-top: var(--sp-2); font-size: var(--fs-12-5); color: var(--color-muted); line-height: var(--lh-snug); }
.ledger-empty { margin-top: var(--sp-8); font-size: var(--fs-13); color: var(--color-muted); line-height: var(--lh-relaxed); }


/* ==========================================================================
   N. THÔNG BÁO, TRẠNG THÁI RỖNG, SKELETON, LỖI
   ========================================================================== */

/* ── Popup LƯỢT ĐẾN thời gian thực (checkin-feed.js) ──────────────────────
   Góc dưới màn hình, tự bật tự tắt. Lễ tân đứng cạnh máy ngó qua là thấy.

   🔴 KHÔNG dùng chung vùng với .toast-region. Toast là một dòng chữ sống 2,6
      giây; thẻ này là một khối bốn dòng sống 8 giây. Chung vùng thì hai thứ đẩy
      nhau lên xuống và cái nào cũng đọc dở.

   Nằm DƯỚI toast một bậc về thứ tự chồng (--z-tabbar + 1, không phải --z-toast):
   thông báo kết quả thao tác vẫn phải đè lên được, vì đó là phản hồi cho việc
   người dùng vừa chủ động làm. */
.feed-region {
  position: fixed;
  inset-block-end: calc(var(--tabbar-h) + var(--sp-16) + env(safe-area-inset-bottom));
  inset-inline-start: var(--sp-12);
  inline-size: min(340px, calc(100vw - var(--sp-24)));
  z-index: calc(var(--z-tabbar) + 1);
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
  pointer-events: none;
}
/* Từ 900px không còn thanh tab dưới, và sidebar chiếm lề trái. */
@media (min-width: 900px) {
  .feed-region {
    inset-block-end: var(--sp-20);
    inset-inline-start: auto;
    inset-inline-end: var(--sp-20);
  }
}

/* Chỉ THẺ nhận chuột, vùng chứa thì không — nửa màn hình trống phía trên thẻ
   không được nuốt cú bấm vào nội dung bên dưới. */
.feed-item { pointer-events: auto; }
.feed-card {
  inline-size: 100%;
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  padding: var(--sp-12);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
  text-align: start;
  cursor: pointer;
  animation: feed-in var(--dur-sheet) var(--ease-sheet);
}
.feed-card:hover { background: var(--color-paper); }
.feed-main  { flex: 1; min-inline-size: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.feed-name  { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.feed-sub   { font-size: var(--fs-12-5); color: var(--color-muted-strong); }
.feed-where { font-size: var(--fs-12-5); color: var(--color-brand-dark); }
.feed-warn  { font-size: var(--fs-12-5); color: var(--color-warn); font-weight: var(--font-weight-medium); }
.feed-time  { font-size: var(--fs-12-5); color: var(--color-muted); align-self: flex-start; }

/* Trượt vào từ phía nó nằm, không phải từ trên xuống: nó là thứ CHEN VÀO bên
   cạnh việc đang làm, không phải thứ rơi xuống che mất. */
@keyframes feed-in {
  from { transform: translateY(8px); opacity: 0; }
  to   { transform: translateY(0);   opacity: 1; }
}

/* Vùng chứa toast — nằm trên thanh tab dưới */
.toast-region {
  position: fixed;
  inset-inline: var(--sp-12);
  inset-block-end: calc(var(--tabbar-h) + var(--sp-16) + env(safe-area-inset-bottom));
  z-index: var(--z-toast);
  pointer-events: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-8);
}
/* Màn đăng nhập không có thanh tab dưới, nên không phải chừa 56px cho nó.
   Toast ở đây gần như luôn là báo sai mật khẩu — phải nằm sát đáy, đúng chỗ mắt tìm. */
.auth-shell ~ .toast-region { inset-block-end: max(var(--sp-16), env(safe-area-inset-bottom)); }
.toast {
  padding: var(--sp-12) var(--sp-16);
  border-radius: var(--radius-xl);
  background: var(--color-ink);
  color: var(--color-on-brand);
  font-size: var(--fs-13);
  font-weight: var(--font-weight-medium);
  box-shadow: var(--shadow-lg);
  animation: app-fade var(--dur-fade) ease-out;
}
.toast-error { background: var(--color-bad); }

/* Trạng thái rỗng dùng chung (không tìm thấy, chưa có dữ liệu, mất mạng) */
.empty {
  padding: var(--sp-56) var(--sp-32);
  text-align: center;
}
.empty-icon {
  display: grid;
  place-items: center;
  inline-size: 48px;
  block-size: 48px;
  margin: 0 auto var(--sp-12);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-2xl);
  background: var(--color-surface);
  color: var(--color-faint);
}
.empty-icon .icon      { --icon-size: var(--icon-24); }
.empty-icon-brand      { background: var(--color-brand-soft); border-color: transparent; color: var(--color-brand); }
.empty-icon-bad        { background: var(--color-bad-soft);   border-color: transparent; color: var(--color-bad); }
.empty-icon-lg         { inline-size: 64px; block-size: 64px; margin-bottom: var(--sp-16); }
.empty-icon-lg .icon   { --icon-size: var(--icon-32); }
.empty-title { font-size: var(--fs-15); font-weight: var(--font-weight-semibold); }
.empty-title-lg { font-size: var(--fs-19); font-weight: var(--font-weight-bold); }
.empty-text {
  margin-top: var(--sp-6);
  font-size: var(--fs-13);
  color: var(--color-muted-strong);
  line-height: var(--lh-relaxed);
}
.empty-text-wide { max-inline-size: 280px; margin-inline: auto; }
.empty-action { margin-top: var(--sp-16); }

/* Skeleton — hiệu ứng .sk giữ nguyên tên như mockup */
.sk {
  border-radius: var(--radius-2xl);
  background: linear-gradient(
    90deg,
    var(--color-sk-base) 25%,
    var(--color-sk-shine) 37%,
    var(--color-sk-base) 63%
  );
  background-size: 400% 100%;
  animation: app-shimmer var(--dur-sk) ease infinite;
}
@keyframes app-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}
.sk-stat     { block-size: 86px; }
.sk-block    { block-size: 168px; }
.sk-block-sm { block-size: 112px; }
.sk-line     { block-size: 14px; border-radius: var(--radius-sm); }


/* ==========================================================================
   O. DANH SÁCH MỤC & TÍNH NĂNG (màn Khác, Cài đặt, Khám phá, Điểm danh)
   ========================================================================== */

/* Dòng menu điều hướng trong .panel.panel-flush */
.menu-item {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  inline-size: 100%;
  padding: var(--sp-14) var(--sp-16);
  border: 0;
  border-top: 1px solid var(--color-line);
  background: var(--color-surface);
  text-align: left;
  cursor: pointer;
  color: inherit;
  text-decoration: none;
  transition: background var(--transition-fast);
}
.menu-item:first-child { border-top: 0; }
.menu-item:hover, .menu-item:active { background: var(--color-paper); }

/* --- Dòng menu ĐANG ĐƯỢC CHỌN -------------------------------------------------
   Cho .menu-item vừa là lối đi vừa là BỘ LỌC — khối "Theo loại hình" ở màn chi
   tiết tháng: bấm "Pilates · 5 người" thì danh sách dưới lọc còn đúng 5 người đó.

   CHỈ áp khi có aria-pressed="true" trên chính <button class="menu-item">.
   Các màn dùng .menu-item làm lối đi thuần (Khác, Doanh thu, Cài đặt) không đặt
   thuộc tính này nên KHÔNG đổi một pixel nào.

   Ba dấu hiệu chồng lên nhau, vì một dấu hiệu là không đủ khi mắt quét qua 5 dòng:
   vạch chỉ thị bên trái + nền xanh nhạt + tiêu đề màu brand. Riêng màu chữ thì
   KHÔNG BAO GIỜ đủ — người không phân biệt được màu sẽ không thấy gì (WCAG 1.4.1). */
.menu-item[aria-pressed='true'] {
  background: var(--color-brand-soft);
  box-shadow: inset 3px 0 0 0 var(--color-brand);   /* vạch chỉ thị bên trái */
}
.menu-item[aria-pressed='true']:hover,
.menu-item[aria-pressed='true']:active { background: var(--color-brand-soft-hover); }
.menu-item[aria-pressed='true'] .menu-title {
  color: var(--color-brand);
  font-weight: var(--font-weight-semibold);
}
/* Chữ phụ phải đậm màu hơn khi nằm trên nền xanh nhạt: --color-muted chỉ còn
   4.16:1 ở đó (trượt AA), --color-muted-strong được 4.81:1. Đã tính, không ước lượng. */
.menu-item[aria-pressed='true'] .menu-sub,
.menu-item[aria-pressed='true'] .menu-sub-wrap { color: var(--color-muted-strong); }
/* Icon cuối dòng ăn theo màu nhấn — dù frontend để mũi tên hay đổi sang dấu tick. */
.menu-item[aria-pressed='true'] .icon-chevron { color: var(--color-brand); }
.menu-icon { --icon-size: var(--icon-19); color: var(--color-brand); }
.menu-main  { flex: 1; min-inline-size: 0; }
.menu-title { display: block; font-size: var(--fs-14-5); font-weight: var(--font-weight-medium); }
.menu-sub   {
  display: block;
  font-size: var(--fs-12);
  color: var(--color-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Dòng phụ CHO XUỐNG DÒNG — tối đa 2 dòng rồi mới cắt.
   Dùng THAY .menu-sub khi câu phụ là chuỗi thông tin ghép, mất phần đuôi là mất
   nghĩa: "12 đang tập · 5 mới · 2 hết hạn" bị .menu-sub nuốt mất "2 hết hạn" trên
   điện thoại, và người dùng không có cách nào biết là mình đang thiếu thông tin.
   Câu phụ chỉ mô tả chung ("Xem và sửa các gói đang bán") thì cứ .menu-sub — cắt
   một dòng giữ cho danh sách đều nhịp.

   -webkit-line-clamp cần đúng bộ ba display/-webkit-box-orient/overflow mới chạy;
   line-clamp là tên chuẩn mới, đặt kèm để sau này không phải quay lại sửa.
   Dùng riêng một mình, KHÔNG ghép với .menu-sub. */
.menu-sub-wrap {
  font-size: var(--fs-12);
  color: var(--color-muted);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: break-word;
}

.icon-chevron { --icon-size: var(--icon-18); color: var(--color-faint); flex-shrink: 0; }

/* Dòng tính năng có công tắc (Cài đặt → Có thể bật thêm) */
.feature-row {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-12);
  padding: var(--sp-14) var(--sp-16);
  border-top: 1px solid var(--color-line);
}
.feature-icon { --icon-size: var(--icon-19); color: var(--color-brand); margin-top: var(--sp-2); }
.feature-main  { flex: 1; min-inline-size: 0; }
.feature-title { font-size: var(--fs-14); font-weight: var(--font-weight-medium); }
.feature-desc  { margin-top: var(--sp-2); font-size: var(--fs-12-5); color: var(--color-muted); line-height: var(--lh-snug); }

/* Thẻ tính năng ở màn Khám phá */
.feature-card  { padding: var(--sp-16); }
.feature-badge {
  display: grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  flex-shrink: 0;
  border-radius: var(--radius-xl);
  background: var(--color-brand-soft);
  color: var(--color-brand);
}
.feature-badge .icon { --icon-size: var(--icon-18); }

/* Danh sách "Đang sử dụng" có dấu tích */
.check-list { list-style: none; display: flex; flex-direction: column; gap: var(--sp-8); margin-top: var(--sp-10); }
.check-item { display: flex; align-items: center; gap: var(--sp-10); font-size: var(--fs-13-5); }
.check-item .icon { --icon-size: var(--icon-16); color: var(--color-ok); }

/* Dòng gợi ý khi gõ tìm học viên để điểm danh */
.suggest-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  padding: var(--sp-12);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
}
.suggest-main  { flex: 1; min-inline-size: 0; }
.suggest-title { font-size: var(--fs-14); font-weight: var(--font-weight-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-sub   { font-size: var(--fs-12); color: var(--color-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-list  { display: flex; flex-direction: column; gap: var(--sp-8); margin-top: var(--sp-8); }

/* Dòng đã check-in */
.checkin-row .icon { --icon-size: var(--icon-18); color: var(--color-ok); }
/* Nút GỠ một lượt đã ghi. Phải thoát khỏi màu xanh "đã xong" của dòng: cùng màu
   với dấu tích đầu dòng thì cái x đọc như một dấu xác nhận thứ hai, chứ không
   phải một nút phá đi. */
.checkin-row .icon-btn .icon { color: var(--color-muted); }
.checkin-row .icon-btn:hover .icon { color: var(--color-bad); }
.checkin-time { font-size: var(--fs-12-5); color: var(--color-muted); font-variant-numeric: tabular-nums; }


/* ==========================================================================
   O2. LỊCH LỚP — MÀN NGÀY (bố cục GỐC, chạy ở mọi bề rộng dưới 900px)

   🔴 ĐỌC TRƯỚC KHI SỬA. Bố cục gốc của màn Lịch lớp là MÀN NGÀY: một dải ngày
   cuộn ngang để chọn, dưới là danh sách buổi học của ngày đó xếp DỌC. Lưới tuần
   7 cột nằm ở mục P3 và chỉ mọc ra từ 900px.

   Đừng đảo chiều. Lưới 7 cột ở bề ngang 400px thì mỗi ô còn chưa tới 50px —
   không đủ chỗ cho tên lớp, giờ học lẫn sĩ số, mà đây lại là màn lễ tân mở
   nhiều nhất trên điện thoại.
   ========================================================================== */

/* Thanh chuyển tuần: ‹ · nhãn tuần · Hôm nay · › */
.week-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}
.week-nav-label {
  min-inline-size: 0;
  text-align: center;
  font-size: var(--fs-14);
  font-weight: var(--font-weight-semibold);
}

/* Dải ngày — dựng TRÊN .chip-row (đã lo cuộn ngang + giấu thanh cuộn + tràn mép).
   Ở đây chỉ thêm phần .chip không làm được: hai dòng chữ xếp chồng và một ô đếm. */
.day-strip { gap: var(--sp-6); }

.day-chip {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  /* Rộng 56px, cao ~62px — vượt vùng bấm tối thiểu 44px theo CẢ HAI chiều.
     Đây là nút được chạm nhiều nhất của màn và thường bằng ngón cái một tay,
     nên nó cố ý to hơn .chip (32px) chứ không tái dùng .chip. */
  inline-size: var(--sp-56);
  padding-block: var(--sp-8);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  color: var(--color-muted);
  cursor: pointer;
  transition: background var(--transition-fast), color var(--transition-fast);
}
.day-chip-dow { font-size: var(--fs-11); font-weight: var(--font-weight-medium); }
.day-chip-num {
  font-size: var(--fs-17);
  font-weight: var(--font-weight-bold);
  line-height: var(--lh-none);
  font-variant-numeric: tabular-nums;
}
/* Số lớp trong ngày. LUÔN chiếm chỗ kể cả khi rỗng — nếu không, cả hàng chip cao
   thấp so le mỗi lần đổi tuần và dải ngày trông như bị hỏng. */
.day-chip-count {
  min-block-size: var(--sp-14);
  font-size: var(--fs-10-5);
  font-weight: var(--font-weight-semibold);
  color: var(--color-brand);
}
/* Hôm nay — viền màu chủ đạo. Ngày ĐANG CHỌN mới đổi nền, hai tín hiệu khác nhau
   để người dùng thấy được cả hai cùng lúc. */
.day-chip-today { border-color: var(--color-brand); }
/* Bảy ngày của tuần sau, nhạt hơn một bậc để thấy rõ chỗ tuần mới bắt đầu */
.day-chip-next { background: var(--color-paper); }
.day-chip[aria-pressed='true'] {
  background: var(--color-ink);
  border-color: var(--color-ink);
  color: var(--color-on-brand);
}
.day-chip[aria-pressed='true'] .day-chip-count { color: var(--color-on-brand-muted); }

/* Khối "ngày đang chọn": tiêu đề ngày + danh sách buổi.
   Phải tự giữ nhịp dọc: nó là MỘT con của .screen nên hai thứ bên trong không
   ăn được gap của .screen nữa. */
.class-day {
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
}

/* Một dòng buổi học. Dựng trên .panel-row (đã có viền, padding, flex, và đã sẵn
   sàng cho thẻ <button>) — chỉ thêm cột giờ và hai dòng chữ ở giữa. */
.slot-row { cursor: pointer; transition: background var(--transition-fast); }
.slot-row:hover, .slot-row:active { background: var(--color-paper); }
.slot-time {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  inline-size: var(--sp-44);
}
.slot-time-start { font-size: var(--fs-14); font-weight: var(--font-weight-semibold); }
.slot-time-end   { font-size: var(--fs-11-5); color: var(--color-muted); }
.slot-name { display: block; font-size: var(--fs-14-5); font-weight: var(--font-weight-medium); }
.slot-meta {
  display: block;
  margin-top: var(--sp-2);
  font-size: var(--fs-12);
  color: var(--color-muted);
}

/* Buổi ĐÃ HUỶ — phải khác hẳn buổi bình thường, không chỉ nhạt đi một chút.
   Nền xám + gạch ngang tên + nhãn "Đã huỷ" ở cuối dòng: ba tín hiệu, nhìn lướt
   qua cũng không lẫn được với buổi vẫn diễn ra. */
.slot-row-off { background: var(--color-paper); }
.slot-row-off .slot-name { color: var(--color-muted-strong); text-decoration: line-through; }
.slot-row-off .slot-time-start { color: var(--color-muted-strong); font-weight: var(--font-weight-medium); }

/* Nhãn sĩ số "3/8".
   Màu chỉ là tín hiệu PHỤ: lớp đầy có thêm chữ "đầy", các mức còn lại có câu
   .sr-only nói rõ còn mấy chỗ (WCAG 1.4.1 — không dùng màu làm tín hiệu duy nhất). */
.seat-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-4);
  padding: var(--sp-3) var(--sp-8);
  border-radius: var(--radius-full);
  background: var(--tone-soft);
  color: var(--tone);
  font-size: var(--fs-12-5);
  font-weight: var(--font-weight-semibold);
}
.seat-badge-ok   { --tone: var(--color-ok);   --tone-soft: var(--color-ok-soft); }
.seat-badge-warn { --tone: var(--color-warn); --tone-soft: var(--color-warn-soft); }
.seat-badge-full { --tone: var(--color-bad);  --tone-soft: var(--color-bad-soft); }
.seat-badge-tag  { font-size: var(--fs-11); font-weight: var(--font-weight-medium); }

/* Dòng người giữ chỗ ĐÃ HUỶ trong phiếu buổi học — cùng ngôn ngữ với .slot-row-off */
/* Dòng một người giữ chỗ trong phiếu buổi học (SHEETS.bookingRowShell).

   Hai nút "Điểm danh" + "Huỷ chỗ" chiếm ~170px. Ở bề ngang 400px, nếu để chúng
   bóp cột chữ thì tên còn "Bùi Minh Kh...", dòng gói còn "PT 10 buổi ..." — đúng
   hai thứ lễ tân cần để biết mình đang bấm cho ai. Nên nút nằm ở TẦNG DƯỚI cùng
   với gói, và sàn 150px của .booking-info đẩy nút xuống hàng khi chật. */
.booking-row { align-items: flex-start; }
/* Hai tầng: [tên ........ nhãn] rồi [gói / cây bạn tập ...... nút]. */
.booking-body {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
/* 🔴 Nhãn trạng thái CỐ ĐỊNH góc phải ngang dòng tên (PM chốt 29/09/2026) — không
   bao giờ xuống dòng; tên dài thì tên tự gãy trong phần của nó. */
.booking-head {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-8);
}
.booking-name {
  flex: 1;
  min-inline-size: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-6);
}
.booking-head > .pill { flex-shrink: 0; white-space: nowrap; }
.booking-content {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--sp-8);
}
/* Sàn 150px: hết chỗ thì NÚT rơi xuống hàng dưới, không bóp gói/SĐT còn "PT 10…". */
.booking-info { flex: 1; min-inline-size: 150px; }
.booking-actions {
  display: flex;
  gap: var(--sp-8);
  margin-inline-start: auto;
}

/* Dòng phụ (gói / số điện thoại) cho xuống 2 dòng rồi mới cắt. */
.booking-meta {
  margin-top: var(--sp-2);
  font-size: var(--fs-12);
  color: var(--color-muted);
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.booking-row-off { background: var(--color-paper); }
.booking-row-off .text-body-lg { color: var(--color-muted-strong); text-decoration: line-through; }

/* Bảy nút chọn thứ trong phiếu Lớp — nối dài họ .choice-grid-2 / .choice-grid-3 */
/* Bảy cột thì khoảng hở phải hẹp lại: với gap 8px của .choice-grid, ở màn 360px
   mỗi nút chỉ còn 38px; với gap 4px nó lên 43px — gần đúng vùng bấm 44px, mà vẫn
   giữ được cả bảy thứ trên MỘT hàng (xuống hai hàng thì mất nhịp của tờ lịch). */
.choice-grid-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); gap: var(--sp-4); }
/* .choice có padding ngang tính cho chữ dài ("Chuyển khoản"); ở đây chữ chỉ hai
   ký tự mà có tới 7 cột, giữ nguyên padding là lưới tràn ngang ở màn 360px. */
.choice-compact { padding-inline: 0; font-size: var(--fs-12-5); }

/* Lưới tuần KHÔNG tồn tại trên điện thoại. Nó được bật ở mục P3, từ 900px. */
.week-grid { display: none; }



/* ==========================================================================
   P. DESKTOP >= 900px
   Mockup chỉ có bản điện thoại. Bốn thay đổi lớn ở đây là QUYẾT ĐỊNH THIẾT KẾ,
   xem docs/design-system.md mục "Quyết định cho desktop" để biết lý do:
     1. Thanh tab dưới  -> sidebar trái cố định 6 mục
     2. Danh sách dòng  -> bảng 5 cột: .month-list ngay tại 900px (mục H2),
        còn .member-list phải đợi tới 1024px mới đủ chỗ (mục P2, có lý do đo được)
     3. Sheet trượt đáy -> hộp thoại giữa màn hình
     4. Nội dung        -> giới hạn bề rộng --content-max, canh giữa
   ========================================================================== */

@media (min-width: 900px) {

  /* --- Khung --- */
  .app { flex-direction: row; }
  .sidebar { display: flex; }
  .tabbar  { display: none; }

  /* Trên máy tính PHẢI thấy thanh cuộn. Ẩn nó là đúng trên điện thoại (mockup vẽ
     vậy, và ở đó ngón tay tự biết vuốt), nhưng trên máy tính thanh cuộn là dấu
     hiệu DUY NHẤT báo cho người dùng biết còn nội dung phía dưới. */
  .app-main { scrollbar-width: thin; scrollbar-color: var(--color-faint) transparent; }
  .app-main::-webkit-scrollbar { inline-size: 10px; block-size: 10px; }
  .app-main::-webkit-scrollbar-track { background: transparent; }
  .app-main::-webkit-scrollbar-thumb {
    background: var(--color-line);
    border-radius: var(--radius-full);
    border: 2px solid transparent;
    background-clip: content-box;
  }
  .app-main:hover::-webkit-scrollbar-thumb { background: var(--color-faint); background-clip: content-box; }

  .app-header-inner {
    max-inline-size: var(--content-max);
    margin-inline: auto;
    inline-size: 100%;
    padding-inline: var(--sp-24);
    block-size: 64px;
  }
  /* Desktop có sidebar nên nút quay lại chỉ còn ở màn chi tiết */
  .app-header-inner-back { padding-inline: var(--sp-16); }

  .screen {
    max-inline-size: var(--content-max);
    margin-inline: auto;
    inline-size: 100%;
    padding: var(--sp-24);
    gap: var(--sp-16);
  }
  .bleed { margin-inline: calc(var(--sp-24) * -1); }
  .sticky-bar { margin-inline: calc(var(--sp-24) * -1); padding: var(--sp-16) var(--sp-24) var(--sp-12); }

  /* Bố cục 2 cột tuỳ chọn cho màn Tổng quan — cột phải là khối phụ */
  .split {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    gap: var(--sp-16);
    align-items: start;
  }
  .split > * { display: flex; flex-direction: column; gap: var(--sp-16); }

  /* Ô hành động nhanh không kéo giãn hết chiều ngang */
  .tile-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

  /* --- Danh sách học viên: từ 900px mới CHỈ đóng khung thành thẻ ---
     Việc hoá bảng 5 cột nằm ở mục P2 (>= 1024px), KHÔNG phải ở đây.
     Ở 900px danh sách vẫn là dòng gộp — giống điện thoại, chỉ khác là nằm trong thẻ. */
  .member-list {
    border: 1px solid var(--color-line);
    border-radius: var(--radius-2xl);
    overflow: hidden;
    background: var(--color-surface);
  }
  .member-row:last-child { border-bottom: 0; }

  /* --- Danh sách tháng -> bảng (cùng công thức với danh sách học viên) --- */
  .month-list {
    border: 1px solid var(--color-line);
    border-radius: var(--radius-2xl);
    overflow: hidden;
    background: var(--color-surface);
  }
  .month-head,
  .month-row {
    display: grid;
    grid-template-columns:
      minmax(var(--month-col-name), 2fr)    /* Tháng */
      minmax(var(--month-col-count), 1fr)   /* Đang tập */
      minmax(var(--month-col-count), 1fr)   /* Mới */
      minmax(var(--month-col-count), 1fr)   /* Hết hạn */
      var(--month-col-money);               /* Doanh thu — xem ghi chú bên dưới */
    align-items: center;
    gap: var(--sp-16);
  }
  .month-head {
    padding: var(--sp-10) var(--sp-16);
    background: var(--color-paper);
    border-bottom: 1px solid var(--color-line);
    font-size: var(--fs-12);
    font-weight: var(--font-weight-semibold);
    color: var(--color-muted-strong);
  }
  .month-row { padding: var(--sp-10) var(--sp-16); }
  .month-row:last-child { border-bottom: 0; }
  .month-who  { flex: 0 1 auto; }
  .month-sub  { display: none; }   /* ba số đã tách thành ba cột riêng */
  /* Mũi tên là lời mời "bấm vào đây" của điện thoại. Ở bảng thì cả hàng sáng lên
     khi rê chuột, nên mũi tên lặp 12 lần chỉ còn là rác — và nó sẽ chiếm một cột
     thứ 6 không khai báo, làm hàng tiêu đề lệch khỏi các dòng. */
  .month-row .icon-chevron { display: none; }
  .month-cell {
    display: block;
    font-size: var(--fs-13-5);
    color: var(--color-muted-strong);
    font-variant-numeric: tabular-nums;
  }

  /* Không có quyền xem tiền -> markup không có ô .month-money nào -> bỏ luôn cột
     thứ 5, khỏi để lại khoảng trống bên phải.
     Đây là chỗ DUY NHẤT biết cột tiền vắng mặt hay không, nên phải nhờ :has().
     Trình duyệt quá cũ không hiểu :has() thì rơi về lưới 5 cột: bảng vẫn thẳng
     hàng, chỉ thừa một cột rỗng bên phải — xấu một chút, không vỡ. Đổi lại, các
     cột LUÔN khớp giữa hàng tiêu đề và dòng, thứ mà track `auto` không làm được. */
  .month-list:not(:has(.month-money)) .month-head,
  .month-list:not(:has(.month-money)) .month-row {
    grid-template-columns:
      minmax(var(--month-col-name), 2fr)
      minmax(var(--month-col-count), 1fr)
      minmax(var(--month-col-count), 1fr)
      minmax(var(--month-col-count), 1fr);
  }

  /* --- P4. Bóc tách báo cáo lớp -> bảng 3 cột (mục H3) ---
     Hàng tiêu đề và mỗi dòng là hai lưới RIÊNG BIỆT, nên hai track tỷ lệ phải
     là số đo cố định — cùng lý do đã ghi ở --month-col-*: track `auto` co theo
     nội dung của riêng từng lưới và cột sẽ lệch dần. */
  .report-head,
  .report-row {
    display: grid;
    grid-template-columns:
      minmax(var(--report-col-name), 2fr)   /* Lớp / Giáo viên */
      var(--report-col-rate)                /* Lấp đầy */
      var(--report-col-rate);               /* Đi học */
    align-items: center;
    gap: var(--sp-16);
    padding: var(--sp-10) var(--sp-16);
  }
  .report-head {
    background: var(--color-paper);
    border-bottom: 1px solid var(--color-line);
    font-size: var(--fs-12);
    font-weight: var(--font-weight-semibold);
    color: var(--color-muted-strong);
  }
  /* Hàng tiêu đề đã nói "Lấp đầy" / "Đi học". Nhắc lại ở từng dòng vừa thừa,
     vừa không đứng vừa một dòng trong cột 104px cùng với con số.

     🔴 Giấu bằng MẮT, không phải `display: none`. Hàng tiêu đề ở đây chỉ là ba
     thẻ <span> trong một lưới, KHÔNG phải <table> có <th> — trình đọc màn hình
     không nối được tiêu đề với ô. `display: none` bứt nhãn khỏi cây trợ năng,
     và người dùng màn hình đọc chỉ còn nghe "99% … 92%" mà không biết số nào là
     lấp đầy. Đây đúng là công thức của .sr-only (mục B) — giữ khớp với nó. */
  .report-rate-label {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }
  .report-rate-head { justify-content: flex-start; }

  /* --- Sheet -> hộp thoại giữa màn hình (hình thức của .modal) --- */
  .sheet-overlay {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-24);
    background: var(--color-overlay-strong);
  }
  .sheet {
    position: relative;
    inset: auto;
    inline-size: 100%;
    max-inline-size: var(--sheet-max-w);
    max-block-size: min(88vh, 760px);
    border-radius: var(--radius-3xl);
    box-shadow: var(--shadow-lg);
    animation: app-pop var(--dur-fade) var(--ease-sheet);
  }
  .sheet-foot { padding-bottom: var(--sp-12); }

  /* --- Toast xuống góc phải, không còn thanh tab dưới để tránh --- */
  .toast-region {
    inset-inline: auto var(--sp-24);
    inset-block-end: var(--sp-24);
    max-inline-size: 360px;
  }

  /* Số liệu lớn hơn một bậc cho dễ đọc ở khoảng cách ngồi bàn */
  .stat-card { padding: var(--sp-16); }
}


/* ==========================================================================
   P2. DANH SÁCH HỌC VIÊN -> BẢNG, TỪ 1024px
   Bảng này bật MUỘN HƠN mọi thứ khác trong mục P. Đây là quyết định thiết kế,
   không phải quên đồng bộ:

   Ở đúng điểm gãy 900px, một danh sách .bleed chỉ được 640px thật (900 - 260
   sidebar). Năm cột đủ đọc cần 654px -> tràn ngang trên chính màn hình được
   dùng nhiều nhất của phần mềm. Đo thật, không phải suy đoán.
   Từ 1024px thì có 764px, thừa 110px cho các cột fr thở.

   Trong khoảng 900-1023px: có sidebar, nhưng danh sách giữ nguyên dòng gộp —
   đúng bố cục đã thiết kế cho điện thoại, đọc tốt ở mọi bề rộng, không bao giờ tràn.

   Áp cho CẢ BỐN màn dùng chung .member-list: Học viên · Học viên theo kỳ ·
   Thu tiền · chi tiết tháng. Sửa ở đây là cả bốn màn đổi theo.
   ========================================================================== */

@media (min-width: 1024px) {
  .member-head,
  .member-row {
    display: grid;
    grid-template-columns:
      minmax(var(--member-col-name), 2.2fr)     /* Học viên */
      var(--member-col-phone)                   /* SĐT — cố định */
      minmax(var(--member-col-package), 1.6fr)  /* Gói hiện tại */
      var(--member-col-date)                    /* Hết hạn / Nợ từ — cố định */
      minmax(var(--member-col-status), 1.2fr);  /* Trạng thái */
    align-items: center;
    gap: var(--sp-16);
  }
  .member-head {
    padding: var(--sp-10) var(--sp-16);
    background: var(--color-paper);
    border-bottom: 1px solid var(--color-line);
    font-size: var(--fs-12);
    font-weight: var(--font-weight-semibold);
    color: var(--color-muted-strong);
  }
  .member-row { padding: var(--sp-10) var(--sp-16); }
  .member-who  { flex: 0 1 auto; }
  .member-sub  { display: none; }        /* đã có cột "Gói hiện tại" riêng */
  .member-cell {
    display: block;
    font-size: var(--fs-13-5);
    color: var(--color-muted-strong);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .member-side { text-align: left; }
  .member-meta { margin-top: var(--sp-2); }
}


/* ==========================================================================
   P3. LỊCH LỚP: MÀN NGÀY -> LƯỚI TUẦN, TỪ 900px

   Đây là PHẦN THÊM, không phải bản chính. Bố cục gốc (mục O2) là màn ngày và nó
   chạy đúng ở mọi bề rộng; khối này chỉ đổi hình khi màn hình đủ rộng để bảy cột
   cùng đọc được.

   Ngân sách bề rộng — đã ĐO THẬT trên trình duyệt, không phải suy đoán:
     đúng 900px -> lưới rộng 582px, mỗi cột 76px, và lòng chứa TÊN LỚP chỉ còn 52px
                  (76 trừ viền 2, padding cột 4x2, padding thẻ 6x2, vạch màu 4).
                  52px ở cỡ chữ 12.5 là khoảng 8 ký tự một dòng.
     từ 1040px -> nội dung chạm --content-max, cột rộng gấp đôi, tên lớp thoải mái.

   Đó là lý do tên lớp ở đây KHÔNG dùng .truncate một dòng (ở 52px thì "Reformer
   cơ bản" chỉ còn "Refor..."), và là lý do mọi padding ở khối này nhỏ hơn chuẩn
   của .panel: từng 2px lề đều lấy mất chỗ của thứ duy nhất đáng đọc ở đây.
   ========================================================================== */

@media (min-width: 900px) {
  /* Bộ chọn ngày và danh sách một ngày nhường chỗ — lưới tuần đã nói đủ cả tuần.
     Giữ cả hai lại là hiện CÙNG MỘT dữ liệu hai lần trên một màn hình. */
  .day-strip,
  .class-day { display: none; }

  /* Lưới tuần là TẤM BẢNG chứa thời khoá biểu; bố cục thật nằm ở .timetable.
     Nền TRẮNG, không để trong suốt: nền app là paper (#EFF1F4), nên ô trống trong
     suốt cũng chính là màu nền — chúng tàng hình và người đọc không dóng được cột
     nào với cột nào. Có tấm bảng trắng thì ô trống mới tô paper lên được. */
  .week-grid {
    display: block;
    padding: var(--sp-12);
    border: 1px solid var(--color-line);
    border-radius: var(--radius-2xl);
    background: var(--color-surface);
  }

  /* ── THỜI KHOÁ BIỂU TUẦN ─────────────────────────────────────────────────
     Một lưới PHẲNG: hàng đầu là bảy ngày, mỗi hàng sau là một GIỜ BẮT ĐẦU có
     thật trong tuần. Cột đầu là trục giờ.

     🔴 Không lồng bảy cột riêng nữa. Cột riêng thì thẻ chỉ xếp chồng theo thứ
        tự, nên buổi 06:30 của Thứ Hai nằm ngang hàng với buổi 08:30 của Thứ Bảy
        và hàng ngang của lưới không mang nghĩa gì. Lưới phẳng ép mọi buổi cùng
        giờ về đúng một hàng — đó là toàn bộ lý do đọc được một tờ lịch.

     Cột giờ 52px: đủ cho "06:30" ở --fs-12 kể cả khi trình duyệt phóng chữ, và
     lấy của bảy cột ngày ~7px mỗi cột — chấp nhận được vì thẻ giờ đã cao hơn
     bản cũ nên tên lớp có chỗ xuống dòng. */
  .timetable {
    display: grid;
    grid-template-columns: 52px repeat(7, minmax(0, 1fr));
    gap: var(--sp-4);
    align-items: stretch;
  }

  .tt-corner { grid-column: 1; }

  /* Đầu cột cũng là nút chọn ngày — bấm vào rồi thu màn hình lại thì danh sách
     ngày ở mục O2 mở đúng ngày đó, không phải chọn lại. */
  .tt-day {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: var(--sp-6);
    padding-block: var(--sp-6);
    border: 0;
    border-radius: var(--radius-lg);
    background: none;
    color: var(--color-muted);
    cursor: pointer;
  }
  .tt-day:hover { background: var(--color-paper); }
  .tt-day-dow  { font-size: var(--fs-11-5); font-weight: var(--font-weight-medium); }
  .tt-day-date { font-size: var(--fs-14); font-weight: var(--font-weight-bold); }

  /* Trục giờ. Căn TRÊN chứ không giữa: ô có hai lớp thì con số phải đứng cạnh
     lớp đầu tiên, để mắt dóng ngang được. */
  .tt-time {
    display: flex;
    justify-content: flex-end;
    padding-block-start: var(--sp-8);
    padding-inline-end: var(--sp-4);
    font-size: var(--fs-12);
    font-weight: var(--font-weight-semibold);
    color: var(--color-muted);
  }

  .tt-cell {
    display: flex;
    flex-direction: column;
    gap: var(--sp-4);
    min-inline-size: 0;
    padding: var(--sp-4);
    border-radius: var(--radius-lg);
  }
  /* Ô TRỐNG là một Ô THẬT, không phải khoảng hở. Tô nền chứ không vẽ viền nét đứt:
     viền nhạt trên nền nhạt thì gần như không thấy, mà đây là thứ giữ cho mắt dóng
     được cột ở những hàng chỉ có một hai lớp. */
  .tt-cell:empty { background: var(--color-paper); }

  /* HÔM NAY là tín hiệu mạnh: nền nhạt suốt cả cột, đầu cột in đậm màu chủ đạo.
     NGÀY ĐANG CHỌN chỉ đánh dấu ở ĐẦU CỘT, không phải từng ô.

     Bản đầu viền xanh mọi ô của cột đang chọn, kể cả ô TRỐNG — nên cột chủ nhật
     có năm cái khung xanh rỗng, mỗi cái kéo mắt về một chỗ không có gì để xem.
     Trên máy tính "ngày đang chọn" gần như không mang nghĩa (danh sách ngày đã bị
     ẩn); nó chỉ còn để bàn giao sang màn điện thoại khi thu cửa sổ lại. */
  /* Liệt kê CẢ :empty: `.tt-cell:empty` cũng có hai class nên bằng điểm với
     `.tt-today`, và nếu không nói rõ thì cột hôm nay bị đứt quãng đúng ở những ô
     trống — nửa cột xanh nửa cột xám. */
  .tt-today,
  .tt-cell.tt-today:empty { background: var(--color-brand-soft); }
  .tt-day.tt-today {
    color: var(--color-brand);
    font-weight: var(--font-weight-semibold);
  }
  .tt-day.tt-current { box-shadow: inset 0 0 0 1px var(--color-brand); }

  .tt-card {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    inline-size: 100%;
    /* Padding 4px ở khoảng CHẬT (900–1179px): lòng ô chỉ ~68px, mỗi 2px lề đều
       lấy mất chỗ của tên lớp — thứ duy nhất ở đây bắt buộc phải đọc được.
       Từ 1180px nới lên 6px. */
    padding: var(--sp-4);
    /* Viền mảnh quanh thẻ, KHÔNG chỉ dựa vào bóng đổ: nền tấm bảng nay là trắng,
       mà thẻ cũng trắng — bóng đổ một mình không đủ tách hai lớp trắng. */
    border: 1px solid var(--color-line);
    border-inline-start: var(--sp-3) solid var(--tone, var(--color-brand));
    border-radius: var(--radius-md);
    background: var(--color-surface);
    box-shadow: var(--shadow-sm);
    color: var(--color-ink);
    text-align: left;
    cursor: pointer;
    transition: background var(--transition-fast);
  }
  .tt-card:hover { background: var(--color-paper); }

  .tt-card-name {
    /* 11.5px ở khoảng chật để "Strength" nằm trọn một dòng (12.5px thì bị bẻ
       thành "Strengt/h"); từ 1180px trở lại 12.5px. */
    font-size: var(--fs-11-5);
    font-weight: var(--font-weight-semibold);
    line-height: var(--lh-snug);
    /* Từ dài hơn lòng cột thì BẺ XUỐNG DÒNG chứ đừng để bị xén ngang thân chữ:
       -webkit-box + overflow:hidden không thêm dấu ba chấm cho một TỪ bị tràn,
       nó cắt thẳng — người đọc không biết là chữ còn nữa hay hết rồi. */
    overflow-wrap: anywhere;
    display: -webkit-box;
    /* Ba dòng rồi mới cắt. Con số này cho trường hợp CHẬT NHẤT (đúng 900px, lòng
       ô ~64px): cắt hai dòng ở đó thì "Reformer Cơ bản" chỉ còn "Reformer Cơ…".
       Màn rộng hơn thì tên về một hai dòng và không ai thấy con số 3 này. */
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* MÁY TẬP (dòng 2) và GIÁO VIÊN (dòng 3) hiện ở MỌI độ rộng — PM chốt 28/09/2026:
     thẻ phải nói đủ "học gì · máy gì · ai dạy". Bản trước giấu giáo viên dưới
     1180px vì họ tên dài co còn "Phạm …"; nay thẻ in TÊN GỌI (hai chữ cuối, xem
     teacherShortName) nên vừa ô hẹp. Đổi lại lưới cao thêm hai dòng mỗi thẻ. */
  /* Khoảng CHẬT (900–1179px, lòng ô ~56px): cho XUỐNG DÒNG thay vì cắt "…".
     "WUND…" hay "Long N…" là thông tin mất hẳn; thêm một dòng thì còn đọc được. */
  .tt-card-equip,
  .tt-card-meta {
    display: block;
    font-size: var(--fs-10-5);
    line-height: var(--lh-snug);
    overflow-wrap: anywhere;
  }
  .tt-card-equip { color: var(--color-ink); }
  .tt-card-meta { color: var(--color-muted-strong); }

  /* GIỜ KẾT THÚC CHỈ HIỆN TỪ 1180px. Ở 900px "đến 07:25" không đứng chung hàng
     với "6/8" nổi nên phải xuống dòng. Hàng của lưới đã nói giờ bắt đầu, và màn
     điện thoại vẫn có đủ ở danh sách ngày. */
  .tt-card-end { display: none; }

  /* Thanh sĩ số — dùng lại .progress, chỉ hạ chiều cao và lề trên của nó. */
  .tt-card-bar {
    block-size: 4px;
    margin-top: var(--sp-4);
  }
  .tt-card-bar .progress-fill { background: var(--tone, var(--color-brand)); }

  /* Hàng chân thẻ: sĩ số bên trái, giờ kết thúc dạt phải. Cho xuống dòng được —
     ở đúng 900px lòng ô chỉ còn ~74px và hai cụm không đứng chung một hàng nổi. */
  .tt-card-foot {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: var(--sp-4);
    font-size: var(--fs-11);
  }
  .tt-card-seats {
    font-weight: var(--font-weight-semibold);
    color: var(--tone, var(--color-muted-strong));
  }
  /* Hàng của thời khoá biểu đã nói giờ BẮT ĐẦU, nên thẻ chỉ cần nói giờ kết thúc.
     Viết hẳn chữ "đến 07:25". Bản đầu viết "–07:25" với ý để nó đọc tiếp từ nhãn
     hàng (06:30 … –07:25) — người đọc ĐẦU TIÊN không hiểu dấu gạch đó là gì. Một
     quy ước phải giải thích mới hiểu thì nó không phải quy ước; ba ký tự chữ rẻ
     hơn nhiều so với một dấu câu thông minh. */
  .tt-card-end {
    margin-inline-start: auto;
    color: var(--color-muted);
  }
  /* Buổi đã huỷ — cùng ngôn ngữ với .slot-row-off ở màn ngày */
  .tt-card-off {
    border-inline-start-color: var(--color-bad);
    background: var(--color-bad-soft);
    box-shadow: none;
  }
  .tt-card-off .tt-card-name { color: var(--color-muted-strong); text-decoration: line-through; }
  .tt-card-cancel {
    font-size: var(--fs-11);
    font-weight: var(--font-weight-semibold);
    color: var(--color-bad);
  }

  .tt-summary {
    margin-top: var(--sp-12);
    padding-block-start: var(--sp-12);
    border-top: 1px solid var(--color-line);
    text-align: center;
    font-size: var(--fs-12-5);
    color: var(--color-muted-strong);
  }
  .tt-blank {
    padding-block: var(--sp-32);
    text-align: center;
    font-size: var(--fs-13-5);
    color: var(--color-muted);
  }
}

@media (min-width: 1180px) {
  /* Đủ chỗ cho tên giáo viên, giờ kết thúc và lề rộng — xem lý do ở mục P.

     1180px chứ không phải 1100px, và con số này ĐO RA chứ không chọn cho tròn:
       lòng ô = (W - 260 sidebar - 48 lề màn - 24 lề bảng - 2 viền - 52 trục giờ
                 - 28 khe) / 7  - 8 lề ô - 12 lề thẻ - 3 vạch màu - 2 viền thẻ
     "6/8" + khe + "đến 07:25" cần ~76px. Ở 1100px lòng ô chỉ 72px nên hàng chân
     xuống dòng và cả lưới phình từ 593px lên 742px — đúng thứ vừa đi sửa. */
  .tt-card { padding: var(--sp-6); }
  .tt-card-end { display: inline; }
  .tt-card-name { font-size: var(--fs-12-5); }
  /* Đủ rộng thì về một dòng, tên quá dài mới cắt "…". */
  .tt-card-equip,
  .tt-card-meta {
    font-size: var(--fs-11);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* ==========================================================================
   Q. TÔN TRỌNG prefers-reduced-motion
   Tắt mọi chuyển động trang trí, giữ nguyên thay đổi trạng thái tức thì.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .sheet,
  .fade-in,
  .toast,
  .feed-card,
  .sheet-overlay { animation: none; }
  .sk {
    animation: none;
    background: var(--color-sk-base);
  }
  .switch,
  .switch::after,
  .progress-fill,
  .sidebar-item,
  .chip,
  .choice,
  .perm-chev,
  .segmented-btn { transition: none; }
}


/* ── Ô nhập có TIỀN TỐ CỐ ĐỊNH ──────────────────────────────────────────────
   Dùng cho tên đăng nhập nhân viên: phần "alignx-" do máy chủ ghép, không sửa
   được, nên nó là NHÃN chứ không phải ô nhập. Gộp vào một khối trông liền mạch
   để người dùng đọc ra tên đầy đủ trong một nhịp mắt.
   Không dùng .field-group vì nó xếp DỌC; đây cần hai thứ nằm CẠNH nhau. */
.input-prefix {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  overflow: hidden;
}
.input-prefix-tag {
  display: flex;
  align-items: center;
  padding: 0 var(--space-sm);
  background: var(--color-bg);
  color: var(--color-muted);
  font-size: var(--text-sm);
  border-right: 1px solid var(--color-border);
  white-space: nowrap;
}
/* Bỏ viền và bo góc riêng của .field — khối cha đã mang cả hai. */
.input-prefix-field {
  border: 0;
  border-radius: 0;
  flex: 1;
  min-width: 0;
}
.input-prefix:focus-within { border-color: var(--color-primary); }

/* ==========================================================================
   NÚT CÀI ĐẶT ỨNG DỤNG — dùng ở login.html và portal.html
   ==========================================================================
   Nút bấm dùng lại `.btn .btn-line .btn-block`, KHÔNG có kiểu nút mới.
   Ở đây chỉ có bảng chỉ đường cho iOS — Apple không cho trang web tự gọi
   "Thêm vào MH chính", nên iOS chỉ hướng dẫn được (xem public/js/install-app.js).
   ========================================================================== */

.install-steps {
  margin-top: var(--sp-10);
  padding-inline-start: var(--sp-20);
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
  list-style: decimal;
  font-size: var(--fs-12-5);
  color: var(--color-muted-strong);
  line-height: var(--lh-relaxed);
}

/* ==========================================================================
   THƯƠNG HIỆU PHÒNG TẬP — phiếu Sửa phòng tập ở trang quản trị (ADR-049)
   ==========================================================================
   Ba class, không hơn. Ô nhập dùng lại `.field`, nút dùng lại `.btn .btn-line`,
   dòng công tắc dùng lại `.panel-row`, cảnh báo dùng lại `.field-note .text-warn`.
   ========================================================================== */

/* Ô chọn màu gốc của trình duyệt, nhét vừa vào .input-prefix cạnh ô mã hex.
   Giữ cả HAI đường nhập là cố ý: người có sẵn mã thương hiệu thì dán, người
   không có thì bấm chọn. */
.brand-swatch {
  inline-size: var(--h-md);
  block-size: var(--h-md);
  flex-shrink: 0;
  padding: var(--sp-4);
  border: 0;
  background: none;
  cursor: pointer;
}

.brand-logo-row {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
}

/* Nền ô carô nhạt để thấy được logo có nền trong suốt hay không — thứ quyết định
   icon trên iOS có thành ô đen tịt không (xem ADR-048). */
.brand-logo-preview {
  inline-size: 56px;
  block-size: 56px;
  flex-shrink: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  object-fit: contain;
  background: var(--color-surface);
}

.brand-logo-empty {
  display: grid;
  place-items: center;
  font-size: var(--fs-10-5);
  color: var(--color-muted);
  background: var(--color-paper);
}
