/* =====================================================================
   HỆ THIẾT KẾ — PWA quản lý phòng gym
   Hướng thị giác: "bảng điểm & sổ tay", lấy màu từ bảng hiệu của phòng —
   khiên đen, chữ vàng kim. Một tấm bảng đen duy nhất mang sức nặng thị
   giác (số liệu hôm nay, dưới dải vàng dập nổi); phần còn lại là sổ tay
   sáng ngả giấy, kẻ mảnh, chữ số cột thẳng.

   Điều hướng theo lối ứng dụng điện thoại, không theo lối web:
   - mỗi màn hình là một vùng cuộn riêng, thanh trên và thanh dưới đứng yên
   - màn gốc: tiêu đề lớn nằm trong nội dung, thu nhỏ về thanh trên khi cuộn
   - màn con: thanh trên gọn, nút quay lại bên trái, tiêu đề canh giữa
   - biểu mẫu mở dạng tấm trượt từ đáy (bottom sheet), không phải trang mới

   Một file, không build step. Mọi số đo nằm trên lưới 4px.
   ===================================================================== */
@import url("fonts.css");

/* ---- 1. Token: màu --------------------------------------------------
   Lấy theo nhận diện của phòng: khiên đen, chữ vàng kim. Xám lệch ấm về
   phía vàng, không dùng xám trung tính.

   MỘT bảng màu duy nhất cho cả sáng lẫn tối: `light-dark(sáng, tối)` tự
   chọn theo `color-scheme` đang có hiệu lực. Trước đây bảng tối được chép
   ba lần (media query, data-theme, màn đăng nhập) và đã có lần lệch nhau
   thật — bản tối bật bằng tay còn giữ màu xanh của phiên bản cũ. Chép tay
   thì sớm muộn cũng lệch, nên đừng dựng lại kiểu đó.

   Muốn một vùng bất kỳ đổi chế độ: đặt `color-scheme` cho vùng đó, KHÔNG
   định nghĩa lại token màu. Xem `.screen--signin` làm mẫu. */
:root{
  color-scheme:light dark;

  /* thang vàng kim, sáng dần rồi tối lại — đúng lối chữ dập nổi kim loại
     trên bảng hiệu. Chỉ dùng cho dải viền và con số chủ đạo.
     Không đổi theo chế độ: đây là màu thương hiệu, không phải màu nền. */
  --gold-1:#fff3c4; --gold-2:#f0cf6b; --gold-3:#d4a72c;
  --gold-4:#a67c0c; --gold-5:#6b4e05;

  --c-plane:  light-dark(#f4f2ec,#0a0906);
  --c-surface:light-dark(#fffdf8,#14120c);
  --c-sunken: light-dark(#eae5d9,#1d1a12);
  --c-line:   light-dark(#ddd6c6,#2b271c);
  --c-ink:    light-dark(#15120a,#ffffff);
  --c-ink-2:  light-dark(#5c5545,#b4aa93);
  --c-ink-3:  light-dark(#736b59,#7c7462);

  /* Nhấn phải gánh hai vai khác nhau: chữ trên nền sáng cần đủ tương phản
     nên dùng vàng sẫm, còn nút bấm mới được xài vàng kim thật với chữ đen.
     Một token không kham nổi cả hai, nên tách ra. */
  --c-accent:          light-dark(#8a6608,#f0cf6b);
  --c-accent-ink:      light-dark(#ffffff,#1a1405);
  --c-accent-soft:     light-dark(#f6ecd0,#2a2210);
  --c-accent-fill:     light-dark(var(--gold-3),var(--gold-2));
  --c-accent-fill-ink: #1a1405;

  /* bảng điểm: nền tối cố định ở cả hai chế độ — đây là chủ ý */
  --c-board:#0a0906;
  --c-board-2:#1e1a10;
  --c-board-ink:#ffffff;
  --c-board-ink-2:#a99e84;
  --c-board-line:#2e2819;

  /* trạng thái: 4 vai trò cố định, tách khỏi màu nhấn. Cảnh báo phải ngả
     cam — để vàng như trước thì lẫn thẳng vào màu thương hiệu.
     Màu vạch (`--c-good` …) dùng cho vạch 3px nên giữ nguyên ở cả hai chế
     độ; chỉ nền và chữ mới đổi. */
  --c-good:#1a7f37; --c-good-bg:light-dark(#e2f3e7,#0f2718); --c-good-ink:light-dark(#146c2e,#56d364);
  --c-warn:#c2650a; --c-warn-bg:light-dark(#fbe9d5,#2b1c08); --c-warn-ink:light-dark(#9a4f00,#e8a13c);
  --c-crit:#b42318; --c-crit-bg:light-dark(#fbe5e2,#2c1412); --c-crit-ink:light-dark(#9f1d13,#f98c82);
  --c-idle:#736b59; --c-idle-bg:light-dark(#eae5d9,#1d1a12); --c-idle-ink:light-dark(#5c5545,#b4aa93);

  --scrim:light-dark(rgba(10,9,6,.55),rgba(0,0,0,.65));
}
/* Nút chuyển chế độ bằng tay. Chỉ ép `color-scheme`, không đụng token —
   nên không có gì để lệch. Chưa có màn nào bật hai thuộc tính này; để sẵn
   vì đặc tả yêu cầu cả hai đường, và vì thêm nút chuyển sau này chỉ tốn
   đúng một dòng JS đặt thuộc tính. */
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{color-scheme:dark}

/* ---- 2. Token: nhịp, bo góc, chữ ------------------------------------ */
:root{
  --s-1:4px;  --s-2:8px;  --s-3:12px; --s-4:16px;
  --s-5:20px; --s-6:24px; --s-8:32px; --s-10:40px;
  --s-14:56px; --s-16:64px;

  --r-sm:10px; --r-md:14px; --r-lg:20px; --r-xl:26px; --r-full:999px;

  --t-micro:11px; --t-xs:12px; --t-sm:13px; --t-md:15px;
  --t-base:16px; --t-lg:17px; --t-xl:20px; --t-2xl:26px;
  --t-title:31px; --t-hero:44px;

  --tap:44px;
  --gutter:var(--s-5);
  --w-app:480px;
  --icon:20px; --icon-lg:24px;
  --navbar-h:52px;
  --tabbar-h:60px;
  --z-nav:30; --z-bar:40; --z-sheet:50;

  --font:"Be Vietnam Pro",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
}

/* ---- 2b. Token: nhịp chuyển động -------------------------------------
   Màu, khoảng cách, cỡ chữ đều có thang riêng, nhưng thời lượng thì trước
   đây nằm rải rác dưới dạng số trần — .12s, .15s, .18s, .22s, .26s, .28s,
   .3s, .34s, .36s, .42s, .5s. Mười một con số cho chừng ấy việc, và mỗi
   lần thêm một hiệu ứng lại đẻ thêm một con số nữa. Nay còn năm bậc.

   Ba đường cong, mỗi đường một vai. Dùng chung một đường cho cả vào lẫn
   ra là lỗi hay gặp nhất: vật đi ra mà cũng "hãm lại ở cuối" thì trông
   như bị dính. */
:root{
  --d-press:80ms;   /* lún xuống dưới ngón tay — chậm hơn là thấy trễ */
  --d-1:140ms;      /* đổi tại chỗ: nền, chữ, viền, vành lấy nét */
  --d-2:220ms;      /* hiện/ẩn vật nhỏ: thông báo, viên nền tab, lớp mờ */
  --d-3:320ms;      /* màn hình, tấm trượt, dòng danh sách nổi lên */
  --d-4:500ms;      /* khoảnh khắc dàn dựng lúc mở app */

  /* Hai giá trị không thuộc thang trên vì chúng không phải nhịp chuyển
     cảnh — chúng là hành vi. Đổi chúng là đổi cái ứng dụng nói ra. */
  --d-spin:700ms;   /* nhịp của mọi dấu hiệu "đang chạy": vòng quay, vạch chờ */
  --d-wait:400ms;   /* chờ ngần này mới thừa nhận là chậm, dưới mức đó im */
  --d-off:.01ms;    /* "tắt" cho reduced-motion — không dùng 0 vì sự kiện
                       animationend vẫn phải bắn, có chỗ đang chờ nó */

  --e-out:cubic-bezier(.32,.72,0,1);    /* vào rồi dừng — nhịp ứng dụng gốc */
  --e-in:cubic-bezier(.4,0,1,1);        /* đi ra: nhanh dần rồi biến mất */
  --e-back:cubic-bezier(.2,1.25,.5,1);  /* nhả tay: vượt nhẹ rồi lắng xuống */

  --ease:var(--e-out);   /* tên cũ, giữ lại để chỗ đang dùng khỏi phải sửa */
}

/* ---- 3. Nền tảng và cảm giác chạm ------------------------------------ */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;height:100dvh;overflow:hidden;      /* cuộn nằm ở từng màn hình */
  background:var(--c-plane);color:var(--c-ink);
  font:400 var(--t-base)/1.5 var(--font);
  font-synthesis-weight:none;-webkit-font-smoothing:antialiased;
  -webkit-tap-highlight-color:transparent;      /* bỏ ô xám khi chạm */
  -webkit-touch-callout:none;
  user-select:none;                             /* chrome không bôi đen được */
  touch-action:manipulation;                    /* bỏ trễ 300ms nhấn đúp */
}
/* nội dung đọc được vẫn cho chọn */
.selectable,input,textarea{user-select:text;-webkit-user-select:text}
img,svg{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3{margin:0;font-weight:800;letter-spacing:-.025em;text-wrap:balance}
:focus-visible{outline:2px solid var(--c-accent);outline-offset:2px;border-radius:var(--r-sm)}
/* Vành lấy nét vẽ RA NGOÀI 2px, nên phần tử nằm trong hộp có `overflow:hidden`
   thì hai cạnh bên bị cắt sạch — đo được: dòng danh sách cách mép `.list`
   đúng 1px, mà vành cần 2px. Bàn phím Tab tới nơi mà không thấy đang ở đâu.
   Với mấy chỗ đó thì vẽ vành vào PHÍA TRONG. */
.list .row:focus-visible,.chips .chip:focus-visible{outline-offset:-3px}
.num{font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1}
.ic{width:var(--icon);height:var(--icon);flex:none;color:inherit}
.ic-lg{width:var(--icon-lg);height:var(--icon-lg)}
.eyebrow{font-size:var(--t-micro);font-weight:600;letter-spacing:.1em;
  text-transform:uppercase;color:var(--c-ink-3)}

/* ---- 4. Khung ứng dụng và ngăn xếp màn hình -------------------------- */
.app{position:relative;height:100dvh;max-width:var(--w-app);margin-inline:auto;
  background:var(--c-plane);overflow:hidden}

/* mỗi màn hình là một vùng cuộn độc lập, như một view trong ngăn xếp */
.screen{position:absolute;inset:0;display:flex;flex-direction:column;
  overflow:hidden;background:var(--c-plane)}
.screen__scroll{flex:1;overflow-y:auto;overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;scrollbar-width:none}
.screen__scroll::-webkit-scrollbar{display:none}

/* ---- 5. Thanh điều hướng trên ---------------------------------------- */
.navbar{position:relative;z-index:var(--z-nav);flex:none;
  display:flex;align-items:center;gap:var(--s-1);
  height:calc(var(--navbar-h) + env(safe-area-inset-top));
  padding:env(safe-area-inset-top) var(--s-2) 0;
  background:color-mix(in srgb,var(--c-plane) 82%,transparent);
  backdrop-filter:saturate(180%) blur(20px);
  -webkit-backdrop-filter:saturate(180%) blur(20px)}
/* tiêu đề canh giữa, không dính theo số lượng nút hai bên */
.navbar__title{position:absolute;inset-inline:56px;text-align:center;
  font-size:var(--t-lg);font-weight:700;letter-spacing:-.01em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;pointer-events:none}
.navbar__side{display:flex;align-items:center;min-width:var(--tap)}
.navbar__side--end{margin-inline-start:auto;justify-content:flex-end}
/* đường kẻ chỉ hiện khi nội dung đã cuộn qua */
.navbar::after{content:"";position:absolute;inset:auto 0 0;height:1px;
  background:var(--c-line);opacity:0}
.navbar--bordered::after{opacity:1}

.iconbtn{position:relative;display:grid;place-items:center;
  width:var(--tap);height:var(--tap);
  flex:none;border:0;background:none;color:var(--c-ink);
  border-radius:var(--r-full);cursor:pointer;
  transition:background var(--d-1) var(--e-out),transform var(--d-2) var(--e-back)}
.iconbtn:active{background:var(--c-sunken);transform:scale(.9);
  transition:transform var(--d-press) var(--e-out)}
.iconbtn--accent{color:var(--c-accent)}

/* tiêu đề lớn của màn gốc, nằm trong vùng cuộn */
.largetitle{padding:var(--s-1) var(--gutter) var(--s-4);
  font-size:var(--t-title);font-weight:800;letter-spacing:-.035em}
.largetitle small{display:block;margin-top:2px;
  font-size:var(--t-sm);font-weight:500;letter-spacing:0;color:var(--c-ink-2)}

/* khi cuộn, tiêu đề nhỏ trên thanh mới hiện ra và kẻ chân mới đậm lên */
@supports (animation-timeline:scroll()){
  .screen--root .navbar__title{opacity:0;
    animation:nav-reveal linear both;
    animation-timeline:scroll(nearest);animation-range:20px 60px}
  .screen--root .navbar::after{
    animation:nav-reveal linear both;
    animation-timeline:scroll(nearest);animation-range:20px 60px}
  @keyframes nav-reveal{to{opacity:1}}
}
@media (prefers-reduced-motion:reduce){
  .screen--root .navbar__title{opacity:1;animation:none}
}

.page{padding:0 var(--gutter) var(--s-8)}
/* Chừa chỗ cho thanh nút dán đáy. Phải hơn CHIỀU CAO THẬT của thanh đó
   (nút 44px + đệm trên dưới) chứ không phải chỉ bằng vùng chạm: đo trên máy
   390px thấy dòng cuối danh sách vẫn bị che 31px khi cuộn hết cỡ. */
.page.has-cta{padding-bottom:calc(var(--tap) * 2 + var(--s-8))}
/* Thanh tab dưới nằm ĐÈ lên vùng cuộn (absolute), nên màn nào có nó cũng phải
   tự chừa chỗ — cuộn hết cỡ mà không chừa thì 29px cuối vĩnh viễn nằm sau
   thanh tab. Màn có cả thanh nút dán đáy thì phải cộng cả hai. */
.screen--tabbed .page{padding-bottom:calc(var(--tabbar-h) + var(--s-8))}
.screen--tabbed .page.has-cta{padding-bottom:calc(var(--tabbar-h) + var(--tap) * 2 + var(--s-8))}
.section-label{margin:var(--s-6) 0 var(--s-2)}
.page > .section-label:first-child{margin-top:var(--s-1)}

/* ---- 6. Bảng điểm — điểm nhấn thị giác duy nhất ---------------------- */
.board{position:relative;overflow:hidden;
  background:radial-gradient(120% 140% at 100% 0%,var(--c-board-2),var(--c-board));
  color:var(--c-board-ink);border-radius:var(--r-lg);
  padding:var(--s-6) var(--s-5) var(--s-5);margin-bottom:var(--s-3)}
/* dải vàng dập nổi — chỉ trang trí, không mã hóa dữ liệu */
.board::before{content:"";position:absolute;inset:0 0 auto;height:3px;
  background:linear-gradient(to right,
    var(--gold-5),var(--gold-3) 18%,var(--gold-1) 34%,
    var(--gold-3) 52%,var(--gold-4) 72%,var(--gold-2))}
.board .eyebrow{color:var(--c-board-ink-2)}
.board__hero{display:flex;align-items:flex-end;gap:var(--s-3);margin-top:var(--s-2)}
/* con số hôm nay là chỗ duy nhất được phép "kim loại" — chữ tô bằng thang
   vàng. Trình duyệt không cắt được nền thì rơi về màu vàng đặc, vẫn đọc tốt. */
.board__num{font-size:var(--t-hero);font-weight:800;line-height:1;letter-spacing:-.04em;
  color:var(--gold-2)}
@supports (-webkit-background-clip:text){
  .board__num{background:linear-gradient(160deg,var(--gold-1),var(--gold-3) 48%,var(--gold-2));
    -webkit-background-clip:text;background-clip:text;color:transparent}
}
.board__unit{font-size:var(--t-md);color:var(--c-board-ink-2);padding-bottom:5px}
.board__foot{display:flex;justify-content:space-between;align-items:flex-end;
  gap:var(--s-4);margin-top:var(--s-5);
  padding-top:var(--s-4);border-top:1px solid var(--c-board-line)}
.board__stat{display:flex;flex-direction:column;gap:2px}
.board__stat b{font-size:var(--t-xl);font-weight:800;letter-spacing:-.02em}
.board__stat span{font-size:var(--t-xs);color:var(--c-board-ink-2)}
.spark{display:flex;align-items:flex-end;gap:5px;height:34px}
.spark i{display:block;width:6px;border-radius:2px 2px 0 0;
  background:var(--c-board-ink-2);opacity:.45}
.spark i[data-today]{background:var(--gold-2);opacity:1}

/* ---- 6b. Màn đăng nhập ----------------------------------------------
   Cố ý tối ở cả hai chế độ, cùng lý do với bảng điểm: nhận diện của phòng
   là mặt tiền đen ăn đèn vàng ban đêm; cho nó sáng lên thì không còn là nó
   nữa.

   ĐÂY LÀ MẪU ĐỂ TÁI THEME MỘT VÙNG: ép `color-scheme` rồi thôi. Mọi token
   `light-dark()` bên trong tự nhảy sang nhánh tối, kể cả những token thêm
   về sau. Đừng chép lại danh sách màu — bản cũ chép và đã lệch mất một
   token so với bảng tối thật. */
.screen--signin{
  color-scheme:dark;
  background:var(--c-plane);color:var(--c-ink)}
/* cạnh sáng của tấm biển */
.screen--signin::before{content:"";position:absolute;inset:0 0 auto;height:3px;
  background:linear-gradient(to right,
    var(--gold-5),var(--gold-3) 18%,var(--gold-1) 34%,
    var(--gold-3) 52%,var(--gold-4) 72%,var(--gold-2))}
/* đèn hắt ra từ sau tấm biển, canh đúng vào logo. Đủ để nền không phẳng lì
   mà không thành vầng hào quang. */
.screen--signin::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(58% 26% at 50% 26%,rgba(240,207,107,.16),transparent 70%)}
.screen--signin .screen__scroll{position:relative}  /* nằm trên vệt đèn */

.signin{display:flex;flex-direction:column;justify-content:center;min-height:100%}
/* Logo là ảnh vuông nền đen. Đặt thẳng lên nền tối thì mắt vẫn thấy mép ô
   vuông, vì đen của ảnh không bao giờ trùng đúng đen của màn. Trộn kiểu
   "screen" cho đen của ảnh thành trong suốt, chỉ còn khiên vàng nổi lên. */
.signin__mark{margin:var(--s-10) 0 var(--s-8)}
.signin__mark img{width:calc(var(--s-16) * 2);height:calc(var(--s-16) * 2);
  margin-inline:auto;mix-blend-mode:screen}
.signin__note{margin:var(--s-8) 0 var(--s-10);text-align:center;
  font-size:var(--t-sm);line-height:1.7;color:var(--c-ink-3)}

/* ---- 7. Ô số liệu ---------------------------------------------------- */
.tiles{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-3);margin-bottom:var(--s-3)}
.tile{display:flex;flex-direction:column;gap:var(--s-1);
  background:var(--c-surface);border:1px solid var(--c-line);
  border-radius:var(--r-md);padding:var(--s-4)}
.tile--wide{grid-column:1/-1}
.tile__label{font-size:var(--t-sm);color:var(--c-ink-2)}
.tile__value{font-size:var(--t-2xl);font-weight:800;line-height:1.1;letter-spacing:-.03em}
.tile__value .unit{font-size:var(--t-md);font-weight:600;color:var(--c-ink-2);
  margin-inline-start:3px}
.tile__hint{font-size:var(--t-xs);color:var(--c-ink-3)}
.pos{color:var(--c-good-ink)} .neg{color:var(--c-crit-ink)}

/* ---- 8. Danh sách ---------------------------------------------------- */
.list{background:var(--c-surface);border:1px solid var(--c-line);
  border-radius:var(--r-md);overflow:hidden;margin-bottom:var(--s-3)}
.row{position:relative;display:flex;align-items:center;gap:var(--s-3);
  min-height:var(--tap);padding:var(--s-3) var(--s-4);color:inherit;
  overflow:hidden;transition:background var(--d-1) var(--e-out)}
/* Dòng danh sách bấm được bằng <button> (màn điểm danh POST thẳng, không phải
   điều hướng nên không dùng <a>). Gỡ mặc định của button để trông y hệt .row
   dạng liên kết: nút mặc định co lại vừa nội dung và căn giữa chữ. */
button.row{width:100%;text-align:left;background:none;border:0;font:inherit;
  cursor:pointer;-webkit-appearance:none;appearance:none}
.row + .row{border-top:1px solid var(--c-line)}
.row:active{background:var(--c-sunken)}
.row[data-level]::before{content:"";position:absolute;inset:0 auto 0 0;width:3px}
.row[data-level="good"]::before{background:var(--c-good)}
.row[data-level="warn"]::before{background:var(--c-warn)}
.row[data-level="crit"]::before{background:var(--c-crit)}
.row[data-level="idle"]::before{background:var(--c-idle)}
.row__body{flex:1;min-width:0}
.row__title{display:block;font-weight:600;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row__meta{display:block;font-size:var(--t-sm);color:var(--c-ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row__end{flex:none;display:flex;align-items:center;gap:var(--s-2)}
.row__chevron{color:var(--c-ink-3)}

.avatar{width:var(--s-10);height:var(--s-10);flex:none;border-radius:var(--r-full);
  overflow:hidden;display:grid;place-items:center;background:var(--c-sunken);
  color:var(--c-ink-2);font-size:var(--t-sm);font-weight:700;letter-spacing:.02em}
.avatar--good{background:var(--c-good-bg);color:var(--c-good-ink)}
.avatar--warn{background:var(--c-warn-bg);color:var(--c-warn-ink)}
.avatar--crit{background:var(--c-crit-bg);color:var(--c-crit-ink)}
.avatar--count{font-size:var(--t-base);font-weight:800}
.avatar--lg{width:var(--s-16);height:var(--s-16);font-size:var(--t-xl)}
.avatar img{width:100%;height:100%;border-radius:inherit;object-fit:cover;display:block}

/* ---- 9. Thẻ và cặp nhãn – giá trị ------------------------------------ */
.card{background:var(--c-surface);border:1px solid var(--c-line);
  border-radius:var(--r-md);padding:var(--s-4) var(--s-4) var(--s-3);
  margin-bottom:var(--s-3)}
.pairs{display:flex;flex-direction:column}
.pair{display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);
  padding:var(--s-3) 0;border-bottom:1px solid var(--c-line);font-size:var(--t-md)}
.pair:first-child{padding-top:0}
.pair:last-child{padding-bottom:0;border-bottom:0}
.pair__k{color:var(--c-ink-2)}
.pair__v{font-weight:600;text-align:right}

.idcard{display:flex;align-items:center;gap:var(--s-3);
  padding-bottom:var(--s-3);margin-bottom:var(--s-3);border-bottom:1px solid var(--c-line)}
.idcard__name{font-size:var(--t-xl);font-weight:800;letter-spacing:-.02em}
.idcard__sub{display:block;margin-top:2px;font-size:var(--t-sm);font-weight:400;color:var(--c-ink-2)}

/* ---- 10. Trạng thái: màu + icon + chữ, không bao giờ chỉ có màu ------ */
.tag{display:inline-flex;align-items:center;gap:var(--s-1);
  padding:var(--s-1) var(--s-2) var(--s-1) 6px;border-radius:var(--r-full);
  font-size:var(--t-xs);font-weight:700;white-space:nowrap}
.tag .ic{width:14px;height:14px}
.tag--good{background:var(--c-good-bg);color:var(--c-good-ink)}
.tag--warn{background:var(--c-warn-bg);color:var(--c-warn-ink)}
.tag--crit{background:var(--c-crit-bg);color:var(--c-crit-ink)}
.tag--idle{background:var(--c-idle-bg);color:var(--c-idle-ink)}

.result{display:flex;flex-direction:column;align-items:center;gap:var(--s-2);
  padding:var(--s-8) var(--s-5);border-radius:var(--r-lg);
  text-align:center;margin-bottom:var(--s-4)}
.result .ic{width:52px;height:52px}
.result__title{font-size:var(--t-2xl)}
.result__sub{margin:0;color:var(--c-ink-2);font-size:var(--t-md)}
.result--good{background:var(--c-good-bg)} .result--good .ic{color:var(--c-good-ink)}
.result--warn{background:var(--c-warn-bg)} .result--warn .ic{color:var(--c-warn-ink)}
.result--crit{background:var(--c-crit-bg)} .result--crit .ic{color:var(--c-crit-ink)}
.result--idle{background:var(--c-idle-bg)} .result--idle .ic{color:var(--c-idle-ink)}

.callout{display:flex;gap:var(--s-2);align-items:flex-start;
  padding:var(--s-3) var(--s-4);border-radius:var(--r-md);
  background:var(--c-warn-bg);font-size:var(--t-md);margin-bottom:var(--s-4)}
.callout .ic{color:var(--c-warn-ink);margin-top:2px}

/* ---- 11. Nút --------------------------------------------------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s-2);
  width:100%;min-height:var(--tap);padding-inline:var(--s-5);
  border:1px solid transparent;border-radius:var(--r-sm);
  background:var(--c-accent-fill);color:var(--c-accent-fill-ink);
  font:700 var(--t-base)/1 var(--font);cursor:pointer;
  /* Nhấn và nhả KHÔNG đối xứng. Lún xuống phải tức thì, còn bật lên thì
     vượt nhẹ rồi lắng — đó là cảm giác của một cái nút thật. Dùng chung
     một nhịp cho cả hai chiều là thứ khiến nút trên web thấy "mềm oặt". */
  position:relative;overflow:hidden;
  transition:transform var(--d-2) var(--e-back),opacity var(--d-1) var(--e-out)}
.btn:active{transform:scale(.97);transition:transform var(--d-press) var(--e-out)}
.btn--ghost{background:var(--c-surface);border-color:var(--c-line);color:var(--c-ink)}
.btn--quiet{background:transparent;color:var(--c-ink-2)}
.btn + .btn{margin-top:var(--s-2)}
.btn[disabled],.btn[aria-disabled="true"]{opacity:.45;pointer-events:none}

.cta-bar{position:absolute;inset-inline:0;z-index:var(--z-bar);
  bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding:var(--s-3) var(--gutter);
  background:linear-gradient(to top,var(--c-plane) 55%,transparent)}
.screen:not(.screen--tabbed) .cta-bar{bottom:0;
  padding-bottom:calc(var(--s-3) + env(safe-area-inset-bottom))}

/* ---- 12. Tấm trượt từ đáy (biểu mẫu, lựa chọn) ----------------------- */
.sheet{position:absolute;inset:0;z-index:var(--z-sheet);
  display:flex;flex-direction:column;justify-content:flex-end;
  background:var(--scrim);backdrop-filter:blur(2px)}
.sheet__panel{display:flex;flex-direction:column;max-height:92dvh;
  background:var(--c-plane);border-radius:var(--r-xl) var(--r-xl) 0 0;
  padding-bottom:env(safe-area-inset-bottom);
  animation:sheet-up var(--d-3) var(--e-out) both}
@keyframes sheet-up{from{transform:translateY(100%)}}
.sheet__grip{width:36px;height:4px;border-radius:2px;background:var(--c-ink-3);
  opacity:.4;margin:var(--s-2) auto var(--s-1)}
.sheet__head{display:flex;align-items:center;gap:var(--s-2);
  padding:var(--s-2) var(--s-2) var(--s-2) var(--gutter)}
.sheet__head h2{flex:1;font-size:var(--t-xl)}
.sheet__body{overflow-y:auto;overscroll-behavior:contain;
  padding:var(--s-2) var(--gutter) var(--s-5)}

/* ---- 13. Form -------------------------------------------------------- */
.field{margin-bottom:var(--s-4)}
.field > label{display:block;margin-bottom:6px;
  font-size:var(--t-sm);font-weight:700;color:var(--c-ink-2)}
.input,.select,.textarea{
  width:100%;min-height:var(--tap);padding:var(--s-3);
  font:400 var(--t-base)/1.4 var(--font);color:var(--c-ink);
  background:var(--c-surface);border:1px solid var(--c-line);border-radius:var(--r-sm);
  transition:border-color var(--d-1) var(--e-out),box-shadow var(--d-1) var(--e-out)}
/* Vành lấy nét trước đây bật ra tức khắc. Cho nó lớn dần trong 140ms thì mắt
   theo kịp ô nào vừa nhận con trỏ — quan trọng khi bàn phím ảo vừa đẩy màn lên. */
.input:focus,.select:focus,.textarea:focus{outline:0;border-color:var(--c-accent);
  box-shadow:0 0 0 3px var(--c-accent-soft)}
.textarea{min-height:88px;resize:vertical}
/* Ô chọn ảnh: giữ nút gốc của trình duyệt — nó tự dịch theo ngôn ngữ máy và tự
   hiện tên tệp vừa chọn — chỉ mặc lại cho khớp hệ thiết kế. Thay bằng nhãn tự
   vẽ thì mất cả hai thứ đó và phải viết thêm JS để bù. */
.input[type=file]{display:flex;align-items:center;padding:var(--s-2)}
.input[type=file]::file-selector-button{
  margin-inline-end:var(--s-3);padding:0 14px;min-height:32px;
  border:1px solid var(--c-line);border-radius:var(--r-sm);
  background:var(--c-plane);color:var(--c-ink);
  font:600 var(--t-sm)/1 var(--font);cursor:pointer}
.field__hint{margin:6px 0 0;font-size:var(--t-sm);color:var(--c-ink-3)}
.field__hint b{color:var(--c-ink);font-weight:700}
/* Lỗi hiện ra thì phải thấy nó ĐẾN, không phải bỗng dưng có ở đó. Trượt
   xuống 4px trong 220ms là đủ để mắt bắt được mà không thành hoạt cảnh. */
.field__error{display:flex;align-items:center;gap:6px;margin:6px 0 0;
  font-size:var(--t-sm);font-weight:600;color:var(--c-crit-ink);
  animation:loi-hien var(--d-2) var(--e-out) both}
@keyframes loi-hien{from{opacity:0;transform:translateY(-4px)}}
.field__error .ic{width:16px;height:16px}
.field:has(.field__error) .input{border-color:var(--c-crit)}

.search{position:relative;margin-bottom:var(--s-3)}
.search > .ic{position:absolute;inset-inline-start:var(--s-3);top:50%;
  translate:0 -50%;color:var(--c-ink-3);pointer-events:none}
.search .input{padding-inline-start:calc(var(--s-3) * 2 + var(--icon))}

/* ---- 14. Chip lọc ---------------------------------------------------- */
.chips{display:flex;gap:var(--s-2);overflow-x:auto;
  margin-bottom:var(--s-4);padding-bottom:var(--s-1);scrollbar-width:none}
.chips::-webkit-scrollbar{display:none}
.chip{flex:none;display:inline-flex;align-items:center;gap:6px;min-height:36px;
  padding-inline:var(--s-3);border:1px solid var(--c-line);border-radius:var(--r-full);
  background:var(--c-surface);color:var(--c-ink-2);
  font-size:var(--t-sm);font-weight:600;white-space:nowrap;
  /* Chip đổi sang trạng thái bật là đổi cả nền lẫn chữ lẫn viền. Nhảy phát
     một thì mắt không biết cái nào vừa đổi; 140ms là đủ để bám theo. */
  transition:background var(--d-1) var(--e-out),color var(--d-1) var(--e-out),
             border-color var(--d-1) var(--e-out),transform var(--d-2) var(--e-back)}
.chip b{font-weight:700;color:var(--c-ink-3);transition:color var(--d-1) var(--e-out)}
.chip[aria-current="true"]{background:var(--c-ink);color:var(--c-plane);border-color:var(--c-ink)}
.chip[aria-current="true"] b{color:var(--c-plane);opacity:.6}

/* ---- 15. Thanh tab dưới ---------------------------------------------
   3–5 đích đến, luôn có nhãn, luôn có đúng một mục đang bật.
   Mục đang bật có viên nền sau icon, không chỉ đổi màu. */
.tabbar{position:absolute;inset-inline:0;bottom:0;z-index:var(--z-bar);
  display:flex;
  background:color-mix(in srgb,var(--c-surface) 88%,transparent);
  backdrop-filter:saturate(180%) blur(20px);
  -webkit-backdrop-filter:saturate(180%) blur(20px);
  border-top:1px solid var(--c-line);
  padding-bottom:env(safe-area-inset-bottom)}
.tabbar a{flex:1;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:2px;height:var(--tabbar-h);
  color:var(--c-ink-3);font-size:var(--t-micro);font-weight:600}
.tabbar .tabbar__icon{display:grid;place-items:center;
  width:56px;height:26px;border-radius:var(--r-full);
  transition:background var(--d-1) var(--e-out),color var(--d-1) var(--e-out)}
.tabbar a[aria-current="page"]{color:var(--c-accent)}
.tabbar a[aria-current="page"] .tabbar__icon{background:var(--c-accent-soft)}

/* ---- 16. Tiện ích ---------------------------------------------------- */
.muted{color:var(--c-ink-2)}
.center{text-align:center}
.void{opacity:.5}
.void .num{text-decoration:line-through}
.qr-frame{background:#fff;border-radius:var(--r-lg);padding:var(--s-5);
  aspect-ratio:1;display:grid;place-items:center;margin-bottom:var(--s-3)}
.qr-frame > svg{width:100%;height:100%;display:block}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap}

/* =====================================================================
   17. CHUYỂN ĐỘNG
   Nguyên tắc: chỉ transform và opacity; mỗi hiệu ứng phải là phản hồi
   cho một thao tác có thật. Một khoảnh khắc dàn dựng duy nhất: bảng điểm
   lúc mở app. Không có hiệu ứng nào chỉ để cho đẹp.
   ===================================================================== */

/* --- ngăn xếp màn hình: đẩy vào từ phải, gỡ ra về phải --------------- */
.screen{display:none}
.screen.is-active{display:flex}
@media (prefers-reduced-motion:no-preference){
  ::view-transition-group(root){animation-duration:var(--d-3)}
  ::view-transition-old(root),::view-transition-new(root){
    animation-timing-function:var(--ease);mix-blend-mode:normal}

  [data-nav="push"]::view-transition-new(root){animation-name:slide-in-right}
  [data-nav="push"]::view-transition-old(root){animation-name:slide-out-left}
  [data-nav="pop"]::view-transition-new(root){animation-name:slide-in-left}
  [data-nav="pop"]::view-transition-old(root){animation-name:slide-out-right}
  [data-nav="tab"]::view-transition-new(root){animation-name:tab-in;animation-duration:var(--d-2)}
  [data-nav="tab"]::view-transition-old(root){animation-name:tab-out;animation-duration:var(--d-1)}
}
@keyframes slide-in-right{from{transform:translateX(100%)}}
@keyframes slide-out-left{to{transform:translateX(-28%);opacity:.55}}
@keyframes slide-in-left{from{transform:translateX(-28%);opacity:.55}}
@keyframes slide-out-right{to{transform:translateX(100%)}}
@keyframes tab-in{from{opacity:0;transform:scale(.985)}}
@keyframes tab-out{to{opacity:0}}

/* --- vào màn: dòng danh sách nổi lên lần lượt ------------------------ */
.row--in{animation:row-in var(--d-3) var(--e-out) both;animation-delay:calc(var(--i,0) * 28ms)}
@keyframes row-in{from{opacity:0;transform:translateY(10px)}}

/* --- bảng điểm: cột dựng lên như vừa ghi xong một set --------------- */
.spark--in{animation:bar-up var(--d-4) var(--e-out) both;
  animation-delay:calc(120ms + var(--i,0) * 45ms);transform-origin:bottom}
@keyframes bar-up{from{transform:scaleY(0)}}
.board{animation:board-in var(--d-4) var(--e-out) both}
@keyframes board-in{from{opacity:0;transform:translateY(8px) scale(.99)}}

/* --- kết quả check-in: khoảnh khắc quan trọng nhất của sản phẩm ----- */
.result .ic{animation:stamp var(--d-3) var(--e-back) both;animation-delay:60ms}
@keyframes stamp{from{opacity:0;transform:scale(.72)}
  60%{opacity:1;transform:scale(1.06)}to{transform:scale(1)}}
.result__title{animation:row-in var(--d-3) var(--e-out) both;animation-delay:.16s}
.result__sub{animation:row-in var(--d-3) var(--e-out) both;animation-delay:.22s}

/* --- tấm trượt ------------------------------------------------------- */
.sheet{display:none}
.sheet.is-open{display:flex;animation:scrim-in var(--d-2) var(--e-out) both}
@keyframes scrim-in{from{opacity:0}}
.sheet__grip{cursor:grab;touch-action:none}
.sheet__head{touch-action:none}

/* --- kéo xuống để làm mới -------------------------------------------- */
.ptr{position:absolute;inset-inline:0;top:calc(var(--navbar-h) + 4px);z-index:1;
  margin-inline:auto;width:26px;height:26px;opacity:0;pointer-events:none;
  border:2.5px solid var(--c-line);border-top-color:var(--c-accent);
  border-radius:var(--r-full)}
.ptr--spin{animation:ptr-spin var(--d-spin) linear infinite;opacity:1 !important}
@keyframes ptr-spin{to{rotate:360deg}}

/* --- thông báo ngắn --------------------------------------------------- */
.toast{position:fixed;inset-inline:0;z-index:99;
  bottom:calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 16px);
  width:max-content;max-width:calc(var(--w-app) - 40px);margin-inline:auto;
  padding:12px 18px;border-radius:var(--r-full);
  background:var(--c-ink);color:var(--c-plane);
  font:600 var(--t-md)/1.3 var(--font);text-align:center;
  opacity:0;translate:0 16px;pointer-events:none;
  transition:opacity var(--d-2) var(--e-out),translate var(--d-3) var(--e-out)}
.toast.is-on{opacity:1;translate:0 0}

/* --- phản hồi chạm ----------------------------------------------------
   Lún xuống tức thì (--d-press), bật lên chậm hơn và vượt nhẹ (--e-back).
   Vật càng nhỏ lún càng sâu: ngón tay che gần hết chip nên 6% mới thấy,
   còn cả một dòng danh sách thì 0,5% là vừa. */
.row,.chip,.tabbar a{-webkit-user-drag:none}
.chip,.tabbar a,.row{transition:transform var(--d-2) var(--e-back),
                                background var(--d-1) var(--e-out)}
.row:active{transform:scale(.995)}
.chip:active,.tabbar a:active{transform:scale(.94)}
.row:active,.chip:active,.tabbar a:active{transition:transform var(--d-press) var(--e-out)}
.tabbar a[aria-current="page"] .tabbar__icon{animation:pill-in var(--d-2) var(--e-back) both}
@keyframes pill-in{from{transform:scale(.6);opacity:0}}

/* --- điều khiển đang chờ máy chủ --------------------------------------
   Đây là khoảng trống lớn nhất của bản trước: chạm xong thì KHÔNG CÓ GÌ
   xảy ra cho tới lúc máy chủ trả lời. Ở quầy, mạng 4G, khoảng lặng đó dài
   200–600ms — đủ để người ta tưởng máy chưa nhận và chạm thêm lần nữa.

   htmx tự gắn class `htmx-request` lên đúng phần tử vừa bắn request (đã
   đọc trong bản 4.0.0-beta6: `requestClass`, có đếm tham chiếu nên nhiều
   request chồng nhau vẫn đúng). Không phải viết JS gì thêm.

   Hai việc, không hơn:
   1. KHÓA lại — chạm lần hai không bắn thêm request. Đây là việc chức
      năng, không phải trang trí: nút "Bán gói" bấm hai lần là hai phiếu thu.
   2. GIỮ NGUYÊN TƯ THẾ LÚN — cái nút ở lại dưới ngón tay cho tới khi có
      câu trả lời. Không đổi chữ trên nút thành "Đang lưu…": đổi chữ thì
      nút nhảy chiều rộng, và người dùng mất luôn cái tên của việc mình
      vừa bấm.

   KHÔNG dùng cho `.input`: ô tìm bắn request theo từng phím gõ, khóa nó
   lại là chặn đứng bàn phím.

   BIỂU MẪU LÀ TRƯỜNG HỢP RIÊNG, và là trường hợp quan trọng nhất. Khi
   `hx-post` nằm trên thẻ `<form>` thì htmx gắn class lên CHÍNH CÁI FORM,
   không phải lên cái nút vừa bấm — nên chỉ bắt `.btn.htmx-request` là hụt
   sạch. Tám form trong ứng dụng đều là thao tác ghi (thêm hội viên, gia
   hạn, hủy phiếu, ghi liên hệ…); gửi hai lần là hai bản ghi. Đây đúng là
   chỗ cần khóa nhất mà lại là chỗ dễ sót nhất. */
.btn.htmx-request,button.row.htmx-request,.iconbtn.htmx-request,.chip.htmx-request,
form.htmx-request{
  pointer-events:none}
.btn.htmx-request,form.htmx-request .btn[type=submit]{transform:scale(.97)}
.iconbtn.htmx-request{transform:scale(.9);background:var(--c-sunken)}
.chip.htmx-request{transform:scale(.94)}
button.row.htmx-request{background:var(--c-sunken)}

/* Vạch chạy chỉ hiện khi máy chủ chậm quá 400ms. Mạng tốt thì không thấy
   gì cả — không có cú nháy "đang tải" vô nghĩa, mà chậm thật thì có tín
   hiệu. Dùng currentColor nên tự tương phản: nút vàng ra vạch đen, nút
   nền sáng ra vạch mực. Vàng kim để dành cho bảng điểm và màn đăng nhập —
   đó là chỗ của thương hiệu, không phải chỗ báo bận. */
.btn.htmx-request::after,button.row.htmx-request::after,
form.htmx-request .btn[type=submit]::after{
  content:"";position:absolute;inset:auto 0 0;height:3px;opacity:0;
  background:linear-gradient(90deg,transparent,currentColor 45%,currentColor 55%,transparent);
  animation:cho-may-chu var(--d-spin) var(--e-out) var(--d-wait) infinite}
@keyframes cho-may-chu{
  from{opacity:.65;transform:translateX(-100%)}
  to{opacity:.65;transform:translateX(100%)}}

/* Ô tìm thì đổi màu cái kính lúp, không khóa gì hết. Cũng chờ 400ms. */
.search:has(.input.htmx-request) > .ic{
  color:var(--c-accent);animation:kinh-lup var(--d-spin) var(--e-out) var(--d-wait) infinite}
@keyframes kinh-lup{50%{opacity:.35}}

/* --- con trỏ chuột ----------------------------------------------------
   Ứng dụng này dựng cho điện thoại, nhưng nó là PWA nên vẫn có người mở
   trên máy bàn. `hover:hover` để máy cảm ứng không dính trạng thái treo
   sau khi chạm — lỗi kinh điển của việc viết :hover mà không rào. */
@media (hover:hover) and (pointer:fine){
  .btn:hover{opacity:.9}
  .row:hover,.iconbtn:hover{background:var(--c-sunken)}
  .chip:hover:not([aria-current="true"]){border-color:var(--c-ink-3);color:var(--c-ink)}
  .tabbar a:hover:not([aria-current="page"]){color:var(--c-ink-2)}
}

/* --- ít chuyển động ---------------------------------------------------
   Tắt phần trang trí, GIỮ phần mang thông tin. Vòng quay "đang tải" và
   vạch chờ máy chủ không phải hiệu ứng đẹp mắt — chúng là câu trả lời cho
   "máy có nhận không?", tắt đi thì màn hình lại im lặng đúng như cũ. */
@media (prefers-reduced-motion:reduce){
  *{animation-duration:var(--d-off) !important;animation-iteration-count:1 !important;
    transition-duration:var(--d-off) !important}
  .ptr--spin,
  .btn.htmx-request::after,button.row.htmx-request::after,
  form.htmx-request .btn[type=submit]::after,
  .search:has(.input.htmx-request) > .ic{
    animation-duration:var(--d-spin) !important;animation-iteration-count:infinite !important}
}
