/* dashboard.css - CSS rieng cua dashboard.html (dung chung: css/common.css) */
/* ---- Tokens ---- */
:root{--glass:rgba(16,26,60,0.55);--glass-strong:rgba(12,20,50,0.78);--glass-brd:rgba(150,180,255,0.18);--glass-brd-strong:rgba(160,190,255,0.4);--sun:#ffcf6b;--sun-warm:#ff9d3d;--accent:#5fd3ff;--accent-2:#8f7bff;--good:#63e6a8;--shadow-soft:0 18px 50px -18px rgba(0,0,0,0.7), 0 2px 8px rgba(0,0,0,0.4);--font-display:"Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;--font-body:"Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:"Share Tech Mono", ui-monospace, "Consolas", monospace;--jelly:cubic-bezier(.34,1.56,.64,1);
  /* ⚠️ NEON CUA BUONG LAI PHAI THEO TONG DEN TRE CHON.
     13 cho trong file nay tung go cung `rgba(var(--neon-rgb),…)`, ma css/cockpit.css chi
     ghi de `--accent/--accent-2/--cyan/--purple` — nen doi den xong thi vien, goc
     neon va quang sang VAN mau cyan. Chu du an bat duoc dung chuyen do.
     `rgba()` khong nhan bien mau hex, nen phai giu rieng bo ba RGB. Gia tri mac
     dinh la dung mau cu (56,189,248) de tong `cockpit-cyan` khong doi mot pixel. */
  --neon-rgb:56,189,248;
  --neon-hi-rgb:120,220,255;}
html,body{min-height:100%;}
body{min-height:100vh;overflow-x:hidden;background:#060c22;}
/* NỀN: ảnh khoang lái phủ kín màn hình.
   Ảnh nằm trong <picture> ở dashboard.html (AVIF/WebP + 2 độ phân giải) —
   ở đây chỉ lo bố cục; ::after là lớp phủ tối, nằm trên ảnh do đứng sau trong DOM. */
.bg-photo{position:fixed;inset:0;z-index:0;pointer-events:none;background:#060c22;}
.bg-photo picture{display:block;width:100%;height:100%;}
.bg-photo img{width:100%;height:100%;object-fit:cover;object-position:center;display:block;}
/* Lớp phủ tối nhẹ để chữ + card nổi rõ trên nền ảnh */
.bg-photo::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg, rgba(6,12,34,.45) 0%, rgba(6,12,34,.28) 40%, rgba(6,12,34,.55) 100%);}
a{text-decoration:none;}
:focus-visible{outline:2px solid var(--accent);}

/* ============================================================
   HEADER = BẢNG TRẠNG THÁI TÀU
   ============================================================ */
.statusbar{
  position:sticky;top:0;z-index:30;display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  padding:12px clamp(20px,4vw,54px);
  background:linear-gradient(180deg, rgba(10,16,44,.9), rgba(10,16,44,.55));
  border-bottom:1px solid rgba(95,211,255,.25);
  box-shadow:0 6px 30px -14px rgba(0,0,0,.8), inset 0 -1px 0 rgba(95,211,255,.12);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
}
/* Logo dùng chung ở `css/common.css`; ô vuông gradient + <b>AstroQ</b> đã bỏ
   vì wordmark có sẵn chữ đó. Giữ `gap` cho nhãn HULL đứng cách logo. */
.ship-id{display:flex;align-items:center;gap:11px;--logo-h:32px;}
.ship-id .hull{font-family:var(--font-mono);font-size:10px;letter-spacing:.12em;color:var(--good);border:1px solid rgba(99,230,168,.35);padding:2px 7px;border-radius:6px;}

.gauges{display:flex;align-items:center;gap:8px;flex:1;justify-content:center;flex-wrap:wrap;}
.gauge{display:flex;align-items:center;gap:8px;padding:7px 11px;border-radius:12px;
  background:rgba(8,14,38,.6);border:1px solid var(--glass-brd);}
/* ⚠️ MÀN RỘNG: HÀNG ĐỒNG HỒ KHÔNG ĐƯỢC XUỐNG DÒNG — và đây là một lỗi ĐO ĐƯỢC,
   không phải chuyện gọn mắt. `probe_decal.py` canh "dán hình lên bảng tên tàu thì
   header KHÔNG cao thêm một pixel". Hình dán rộng 36px, và ở 1440px với tên tàu +
   số dư 3 chữ số thì 4 đồng hồ cần 827px trong khi chỗ còn lại chỉ 815 ⇒ hàng
   xuống 2 dòng và header nhảy **69px → 105px**. Tức là một món trang trí làm cả
   thanh trạng thái đổi hình dạng.
   Chữa ở gốc: màn rộng thì cấm xuống dòng, và cho ĐÚNG ô Toạ độ co lại (nó là
   thông số tàu giả lập, dài nhất và ít giá trị nhất). Ba ô kia `flex:none` nên
   không bao giờ bị bóp — số dư Thiên thạch tím và XP là số thật của trẻ.
   ⚠️ Ellipsis ở đây là VAN AN TOÀN cho ca cực đoan (số dư 5 chữ số), không phải
      trạng thái thường ngày: sau khi siết `gap`/`padding`, 4 ô còn 797px. */
@media (min-width:721px){
  .gauges{flex-wrap:nowrap;}
  .gauge{flex:none;}
  .gauge.coord{flex:0 1 auto;min-width:0;}
  .gauge.coord .g-val{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
}
/* Máy tính bảng (721–1100px): BỎ HẲN ô Toạ độ thay vì để nó bị cắt cụt. Đo trên
   iPad mini/Pro dọc: ô đó cần 188px mà chỉ còn 111–128px, nên hiện ra
   "7.36°N 142…" — một chuỗi cụt đọc ra như trang bị lỗi, trong khi nội dung nó
   mang là toạ độ GIẢ LẬP sinh ngẫu nhiên mỗi 2,5 giây. Thà bỏ hẳn một thứ trang
   trí hơn là bày ra một dòng trông như hỏng. */
@media (max-width:1100px){ .gauge.coord{display:none;} }
.gauge .g-label{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.16em;color:var(--ink-faint);}
.gauge img{width:18px;height:18px;object-fit:contain;filter:drop-shadow(0 0 6px rgba(143,123,255,.6));}
.gauge .g-val{font-family:var(--font-display);font-weight:700;font-size:14px;color:#fff;}
.gauge .g-val.mono{font-family:var(--font-mono);font-size:12px;letter-spacing:.04em;color:var(--accent);}
.gauge .bar{position:relative;width:52px;height:6px;border-radius:6px;background:rgba(255,255,255,.1);overflow:hidden;}
.gauge .bar i{position:absolute;inset:0;width:50%;border-radius:6px;display:block;
  background:linear-gradient(90deg,var(--accent),var(--accent-2));transition:width .5s var(--ease);}
.gauge.o2 .bar i{background:linear-gradient(90deg,#63e6a8,#3fd6c9);}
.gauge.fuel{border-color:rgba(192,132,252,.42);background:rgba(26,16,44,.55);}
.gauge.fuel .g-val{color:#e6d3ff;}
/* Huy hiệu "Thiên thạch tím" — gem tím phát sáng */
.gem-badge{position:relative;width:24px;height:24px;flex:none;display:grid;place-items:center;border-radius:8px;
  background:radial-gradient(circle at 50% 38%, rgba(192,132,252,.45), rgba(143,123,255,.12) 68%, transparent);
  box-shadow:0 0 14px rgba(192,132,252,.7), inset 0 0 10px rgba(192,132,252,.4);animation:gemPulse 2.6s ease-in-out infinite;}
.gem-badge img{width:17px;height:17px;object-fit:contain;filter:drop-shadow(0 0 5px rgba(192,132,252,.95));}
@keyframes gemPulse{0%,100%{box-shadow:0 0 12px rgba(192,132,252,.5), inset 0 0 8px rgba(192,132,252,.35);}
  50%{box-shadow:0 0 22px rgba(192,132,252,.95), inset 0 0 13px rgba(192,132,252,.6);}}
/* Flight Streak — hổ phách phát sáng */
.gauge.streak{border-color:rgba(251,146,60,.42);background:rgba(38,22,8,.55);}
.gauge.streak .g-val{color:#ffd7a6;}
.gauge.streak .streak-ic{color:#fb923c;display:inline-flex;font-size:16px;
  filter:drop-shadow(0 0 8px rgba(251,146,60,.9));animation:flameFlick 1.5s ease-in-out infinite;}
.gauge.streak .streak-unit{font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:#eaa668;}
@keyframes flameFlick{0%,100%{transform:translateY(0) scale(1);}50%{transform:translateY(-1px) scale(1.09);}}

/* ⚠️ DÃY NÚT VI|EN + NÚT ĐĂNG XUẤT ĐÃ BỎ KHỎI HEADER (15/08/2026) — chúng nay là
   hai MENU THẢ (`css/user-menu.css`). Rule `.lang-switch` / `.logout-btn` cũ đã
   xoá theo chứ không để lại: CSS không còn ai dùng là bẫy cho người sửa sau, và
   `.lang-switch` bên trong tấm thả có kiểu riêng khai ở user-menu.css. */
.controls{display:flex;align-items:center;gap:8px;}
/* Ruột của nút menu avatar. Vỏ (viên thuốc, viền, quầng sáng) do `.um-btn` ở
   css/user-menu.css lo — ở đây chỉ còn phần riêng của dashboard. */
.user-menu .avatar{width:34px;height:34px;border-radius:50%;flex:none;overflow:hidden;display:grid;place-items:center;font-family:var(--font-display);font-weight:700;color:#04060f;background:linear-gradient(135deg,var(--accent),var(--accent-2));box-shadow:0 0 12px rgba(var(--neon-rgb),.4);}
.user-menu .avatar img{width:100%;height:100%;object-fit:cover;display:block;}
.user-menu .meta{display:flex;flex-direction:column;line-height:1.12;padding-right:3px;text-align:left;}
.user-menu .meta .nm{font-family:var(--font-display);font-weight:600;font-size:13px;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink);}
.user-menu .meta .role{font-family:var(--font-mono);font-size:9px;color:var(--ink-faint);letter-spacing:.14em;text-transform:uppercase;}
/* Màn hẹp: giấu tên + cấp, chỉ còn ảnh đại diện. Ở 390px hàng header phải chứa
   logo + 2 gauge + 2 nút menu; giữ tên ở đây là header xuống thêm một hàng, mà
   chính chiều cao header là thứ đang đẩy 6 thẻ khu vực xuống dưới màn hình.
   Tên vẫn còn — nó nằm ngay trong lời chào "Chào mừng trở lại, …" bên dưới. */
@media (max-width:640px){ .user-menu .meta{display:none;} }

/* ============================================================
   MAIN
   ============================================================ */
main{position:relative;z-index:10;max-width:1120px;margin:0 auto;padding:clamp(18px,3vw,34px) clamp(20px,4vw,54px) 70px;}

/* ---- HERO: tiêu đề chào mừng (đưa lên sát mép khung kính) ---- */
.hero{display:flex;flex-direction:column;align-items:center;text-align:center;padding:0 0 6px;}
.hero .eyebrow{font-size:12px;color:var(--accent);}
.hero h1{font-family:var(--font-display);font-weight:700;font-size:clamp(24px,4.4vw,40px);margin:8px 0 0;line-height:1.1;
  text-shadow:0 2px 30px rgba(95,150,255,.4);}
.hero p{color:var(--ink-soft);font-size:14.5px;line-height:1.6;margin:10px 0 0;max-width:560px;}
@keyframes spin{to{transform:rotate(360deg);}}
@keyframes barFlow{from{background-position:0 0;}to{background-position:200% 0;}}

/* ⚠️⚠️ ĐẶT LẠI `text-align` Ở ĐÂY, ĐỪNG DỰA VÀO MẶC ĐỊNH — và đây là lỗi ĐO ĐƯỢC,
   không phải chuyện thẩm mỹ. `.hero` khai `text-align:center` và bảng này nằm TRONG
   nó, nên chữ thừa hưởng canh giữa; mọi khối hai dòng (số trên, nhãn dưới) vì thế
   canh giữa theo dòng DÀI HƠN, tức mỗi khối một mép trái.
   Đo được trước khi sửa: ô "0%" lệch **37px** (nhãn "ĐỘ CHÍNH XÁC QUIZ" dài mà số
   ngắn) · ô "Hồ sơ" +25px · ô "Thành tích" −31px · khối XP −33px. Bốn khối xếp
   trong một bảng mà mỗi khối một mép thì đọc ra như bố cục hỏng — chủ dự án bắt được
   ở ô "0%", chỗ lệch nhiều nhất.
   ⚠️ Chữa ở CẤP BẢNG, không vá từng khối: vá lẻ thì khối thứ năm thêm sau lại lệch,
      và người thêm nó không có cách nào biết. Mọi thứ trong bảng này vốn canh trái;
      thứ cần canh giữa đều đang dùng flex nên không phụ thuộc `text-align`.
   Sau khi sửa: cả bốn khối lệch **0px**, đo ở desktop · điện thoại 390px · bản EN. */
/* ⚠️ DẢI MỎNG, KHÔNG PHẢI PANEL CAO (15/08/2026). Nó nằm TRÊN 6 thẻ khu vực nên
   mỗi pixel nó chiếm là một pixel đẩy thứ trẻ vào đây để làm xuống dưới màn hình.
   Đo được: bản cũ cao **481px**; bản này ~120px. Ba thứ tạo ra chênh lệch đó —
   ba ô số nằm NGANG thay vì xếp dọc · bỏ khoảng đệm thừa · gộp cấp bậc + thanh XP
   + số hành tinh vào một cột hẹp. Sửa gì ở đây thì đo lại bằng
   `scratchpad/probe_hub_layout.py`, đừng để nó cao trở lại. */
.stats-hud{position:relative;z-index:1;margin:22px auto 0;width:min(880px,100%);text-align:left;
  display:grid;grid-template-columns:1.12fr 1fr;gap:14px;padding:14px 16px 13px;border-radius:18px;
  border:1px solid rgba(var(--neon-rgb),.32);
  background:linear-gradient(180deg, rgba(11,15,25,.62), rgba(13,19,34,.5));
  -webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);
  box-shadow:0 26px 62px -26px rgba(0,0,0,.78), 0 0 46px -14px rgba(var(--neon-rgb),.4), inset 0 0 32px -18px rgba(var(--neon-hi-rgb),.6);}
/* ⚠️ MỐC 900px chứ không phải 640px — số đo, không phải ước lượng. Ở iPad mini
   dọc (768px) panel chia 2 cột thì cột phải chỉ còn ~85px cho mỗi ô, và
   `audit_viewports` bắt được **"0 phút" bị cắt còn "0 ph…"** (sw 48 > cw 37). Một
   con số bị cắt là một con số nói sai. Dưới 900px thì xếp dọc, ba ô được cả bề
   rộng. */
@media(max-width:900px){.stats-hud{grid-template-columns:1fr;}}
.stats-hud .panel-tag{position:absolute;top:-10px;left:20px;display:inline-flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:9px;letter-spacing:.2em;text-transform:uppercase;color:var(--accent);
  background:#0b0f19;border:1px solid rgba(var(--neon-rgb),.42);padding:3px 10px;border-radius:999px;box-shadow:0 0 14px rgba(var(--neon-rgb),.35);}
.stats-hud .panel-tag .led{width:6px;height:6px;border-radius:50%;background:var(--good);box-shadow:0 0 8px var(--good);animation:blink 1.6s steps(1,end) infinite;}

/* --- Cột trái: Tiến trình hành tinh / thiên hà --- */
.sh-left{display:flex;flex-direction:column;gap:8px;padding:2px 14px 0 2px;border-right:1px solid rgba(120,160,255,.14);}
@media(max-width:900px){.sh-left{border-right:none;border-bottom:1px solid rgba(120,160,255,.14);padding:2px 0 10px;}}
.xp-head{display:flex;align-items:center;gap:10px;}
.xp-orb{width:36px;height:36px;flex:none;border-radius:11px;display:grid;place-items:center;font-size:22px;color:var(--accent);
  background:rgba(var(--neon-rgb),.14);border:1px solid rgba(var(--neon-rgb),.42);box-shadow:inset 0 0 16px -6px rgba(var(--neon-rgb),.9);}
.xp-orb .lic{animation:spin 16s linear infinite;}
.xp-title{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint);}
.xp-level{font-family:var(--font-display);font-weight:700;font-size:15.5px;color:#fff;margin-top:1px;}
.xp-level #planet-name{color:var(--accent);}
.xp-bar{position:relative;height:8px;border-radius:8px;background:rgba(255,255,255,.09);overflow:hidden;box-shadow:inset 0 0 8px rgba(0,0,0,.5);}
.xp-bar i{position:absolute;inset:0;width:0;border-radius:8px;display:block;
  background:linear-gradient(90deg,var(--accent),#c084fc);background-size:200% 100%;
  box-shadow:0 0 14px rgba(192,132,252,.7);transition:width .85s var(--ease);animation:barFlow 3.5s linear infinite;}
.xp-meta{display:flex;justify-content:space-between;font-family:var(--font-mono);font-size:10px;letter-spacing:.06em;color:var(--ink-soft);}
.xp-meta b{color:#c084fc;font-weight:700;}
.galaxy-row{display:flex;align-items:center;gap:9px;margin-top:0;padding-top:8px;border-top:1px dashed rgba(150,180,255,.18);}
.galaxy-row .lic{color:#c084fc;font-size:18px;filter:drop-shadow(0 0 6px rgba(192,132,252,.7));}
.galaxy-row .gx-txt{font-size:12.5px;color:var(--ink-soft);}
.galaxy-row .gx-txt b{color:#fff;font-family:var(--font-display);}
.gx-pips{display:flex;gap:4px;margin-left:auto;}
.gx-pips span{width:9px;height:9px;border-radius:50%;background:rgba(255,255,255,.12);border:1px solid rgba(120,160,255,.3);}
.gx-pips span.on{background:radial-gradient(circle,#c084fc,#8f7bff);box-shadow:0 0 8px rgba(192,132,252,.85);border-color:transparent;}

/* --- Cột phải: 3 mini stat tiles + đường vào Kho Thành Tích --- */
.sh-right{display:flex;flex-direction:column;gap:10px;justify-content:center;}
/* ⚠️ BA Ô NẰM NGANG, không xếp dọc. Xếp dọc là 3 × 60px = 180px chiều cao cho ba
   con số — chính nó chiếm phần lớn 481px của bản cũ. Nằm ngang thì cả cột phải
   cao ~58px, và ba con số vẫn đọc được vì mỗi ô chỉ có icon + số + nhãn ngắn. */
.sh-right #stat-tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;}
/* ⚠️ BẢNG PHI HÀNH GIA (`.ptiles`/`.ptile`) ĐÃ BỎ 15/08/2026 — ba ô Hồ sơ ·
   Thành tích · Mẫu vật nay là ba mục trong menu thả sau avatar, cùng chỗ với
   Kho Trang Trí, bảng bố mẹ và Đăng xuất. Kiểu dáng ở `css/user-menu.css`.
   ⚠️ XOÁ HẲN chứ không để lại: rule không còn ai dùng là bẫy cho người sửa sau
      (họ sẽ tưởng lưới đó vẫn còn và đi sửa nó). Lý do dời + số đo: xem chú
      thích ở đầu js/user-menu.js và trong dashboard.html.
   Ba tông màu theo trang đích (cyan · hổ phách · tím) KHÔNG mất — chúng đi
   theo sang `.um-item.um-profile/.um-awards/.um-vault`. */

/* ══ MẪU VẬT GẮN TRÊN VÁCH BUỒNG LÁI (16/08/2026) ═══════════════════════════
   Chủ dự án: *"nó bồng bềnh nhưng gắn vào khung của khoang tàu 2 bên"*.

   ⚠️ GẮN VÀO MÉP MÀN, KHÔNG GẮN VÀO CỘT KHUNG VẼ TRONG ẢNH. Nghe thì cột khung
      mới đúng ý, nhưng đo ra nó KHÔNG ĐỨNG YÊN: `object-fit:cover` cắt mỗi khổ
      một kiểu nên cột đó rơi vào những chỗ khác nhau trên màn, và Kho Trang Trí
      sắp bán NỀN — đổi nền là cột đi chỗ khác hoặc không còn cột nào. Bám mép màn
      thì đúng ở mọi khổ và với mọi nền, không cần khai dữ liệu cho từng ảnh.
   ⚠️ TABLET DỌC KHÔNG CÓ TÍNH NĂNG NÀY, và đó là số đo chứ không phải lựa chọn:
      ở tỉ lệ 0.75 thì cover chỉ giữ 26%..74% bề rộng ảnh, tức **hai cột khung
      buồng lái bị cắt mất hoàn toàn** — không có "hai bên" nào để mà gắn. Nằm
      ngang thì giữ được 6%..94%, cột còn nguyên. */
.desk-float{position:fixed;inset:0;z-index:1;pointer-events:none;display:none;
  /* ⚠️ CỠ MÓC BỊ KẸP THEO CẢ BỀ RỘNG LẪN CHIỀU CAO, và vế chiều cao mới là vế giữ
     cho 3 móc không đè lên nhau. Chỉ theo bề rộng thì một màn 1600×720 cho móc
     148px → 5×148 + 4 khe = 796px trên một khung cao 720px, tức hai móc cuối tràn
     khỏi màn. `(100vh - 120px)/3 - 14px` = phần mỗi móc được chia sau khi chừa
     60px trên/dưới (tránh thanh trạng thái và hai huy hiệu neo góc dưới) và 14px
     khe. Sàn 64px vẫn thắng ở màn rất thấp — ở đó cột ngắn lại chứ không tràn. */
  --dfw:clamp(64px, min(calc((100vw - 1120px) * .30 + 34px),
                        calc((100vh - 120px) / 3 - 14px)), 148px);}
.desk-float[hidden]{display:none;}

/* Một VÁCH = một cột móc, canh giữa theo chiều dọc. Xếp bằng flex chứ không gán
   `top` cho từng móc: cỡ móc co giãn theo màn, nên mọi bộ toạ độ cố định đều đè
   nhau ở một khổ nào đó — đúng lỗi mà vế chiều cao ở trên vừa chặn.
   ⚠️ `transform` ở đây KHÔNG phá `position:fixed` của `.desk-float`: nó nằm ở con,
      không phải tổ tiên. Bẫy "tổ tiên có transform nuốt fixed" đã trả giá hai lần
      (menu avatar 15/08, và chính phép kiểm [c] của `probe_desk.py`). */
.dfw{position:absolute;top:50%;transform:translateY(-50%);
  display:flex;flex-direction:column;gap:14px;}
.dfw--L{left:0;} .dfw--R{right:0;}
/* Móc trống: ô vô hình GIỮ CHỖ, không phải ô bị bỏ đi. Xem ghi chú ở `paintDesk`. */
.dfp-void{width:var(--dfw);height:var(--dfw);visibility:hidden;pointer-events:none;}
.dfp{position:relative;pointer-events:auto;
  width:var(--dfw);height:var(--dfw);
  display:grid;place-items:center;text-decoration:none;
  border:1px solid rgba(192,132,252,.34);
  background:radial-gradient(circle at 50% 42%, rgba(192,132,252,.16), rgba(15,23,42,.55));
  box-shadow:inset 0 0 26px -12px rgba(192,132,252,.95), 0 12px 30px -18px rgba(0,0,0,.9);
  transition:transform .35s var(--jelly),border-color .25s,box-shadow .25s;}
/* Mép phía vách DẸT, mép phía trong bo tròn — hình dáng nói ra rằng nó đang tựa
   vào một cái gì đó chứ không lơ lửng. */
.dfw--L .dfp{border-left:none; border-radius:0 999px 999px 0;}
.dfw--R .dfp{border-right:none; border-radius:999px 0 0 999px;}
/* Hai con vít giữ khoang vào vách. Không có chúng thì cái mép dẹt đọc ra như hình
   bị cắt cụt ở mép màn chứ không như đồ được bắt vào tường. */
.dfp::before, .dfp::after{content:"";position:absolute;width:5px;height:5px;
  border-radius:50%;background:rgba(150,180,255,.5);
  box-shadow:0 0 6px -1px rgba(192,132,252,.9);}
.dfp::before{top:24%;} .dfp::after{bottom:24%;}
.dfw--L .dfp::before, .dfw--L .dfp::after{left:7px;}
.dfw--R .dfp::before, .dfw--R .dfp::after{right:7px;}
/* Chỗ để dành: nét đứt, KHÔNG có chữ. Phải đọc ra là "còn chỗ", không phải nút. */
.dfp--empty{background:rgba(15,23,42,.28);border-style:dashed;
  border-color:rgba(150,180,255,.3);box-shadow:none;}
.dfp--empty::before, .dfp--empty::after{opacity:.4;}
.dfp--empty .dp-plus{font-family:var(--font-display);font-weight:700;
  font-size:calc(var(--dfw) * .28);color:var(--ink-faint);}
.dfp--empty:hover{border-color:rgba(192,132,252,.75);}

/* ⚠️⚠️ BỒNG BỀNH LÀ MẪU VẬT BÊN TRONG, KHÔNG PHẢI CẢ CÁI KHOANG. Một khoang bắt
   vít vào vách mà tự nhún nhảy là sai vật lý và đọc ra như lỗi vẽ; mẫu vật trôi
   trong khoang kính thì đúng — và đó cũng là ngôn ngữ hình `specimen-vault.html`
   đã dùng (`.pod .sp` với `podFloat`). Ba khoang lệch pha nhau: cùng pha thì ba
   món nhấp nhô đồng loạt, đọc ra như cả buồng lái đang rung.
   `common.css` đã tắt mọi animation ở `prefers-reduced-motion`, không khai lại. */
.dfp .dp-sp{font-size:calc(var(--dfw) * .5);line-height:1;
  filter:drop-shadow(0 0 16px rgba(192,132,252,.75));
  animation:dfpFloat 6.5s ease-in-out infinite;}
@keyframes dfpFloat{0%,100%{transform:translateY(0);}50%{transform:translateY(-9px);}}
/* Lệch pha theo vị trí trên vách. Năm nhịp lẻ nhau, KHÔNG chia hết cho nhau —
   trùng chu kỳ thì cứ vài giây cả cột lại nhấp nhô đồng loạt một lần. */
.dfw > :nth-child(1) .dp-sp{animation-duration:6.5s;animation-delay:0s;}
.dfw > :nth-child(2) .dp-sp{animation-duration:7.8s;animation-delay:-2.4s;}
.dfw > :nth-child(3) .dp-sp{animation-duration:5.9s;animation-delay:-4.1s;}
.dfw > :nth-child(4) .dp-sp{animation-duration:7.1s;animation-delay:-1.3s;}
.dfw > :nth-child(5) .dp-sp{animation-duration:6.2s;animation-delay:-3.5s;}
.dfw--R > :nth-child(1) .dp-sp{animation-delay:-1.7s;}
.dfw--R > :nth-child(3) .dp-sp{animation-delay:-0.8s;}
.dfp:hover{border-color:rgba(192,132,252,.85);
  box-shadow:inset 0 0 30px -10px rgba(192,132,252,1), 0 14px 34px -16px rgba(0,0,0,.95);}
.dfp:hover .dp-sp{filter:drop-shadow(0 0 26px rgba(192,132,252,1));}
.dfp:active{transform:scale(.97);}

/* ⚠️ NGƯỠNG 1180px LÀ SÀN CỨNG, ĐO RA CHỨ KHÔNG CHỌN CHO ĐẸP. `main` rộng tối đa
   1120px và padding là `clamp(20px,4vw,54px)`, nên lề thật = 41px @1024 · 77px
   @1180 · 177px @1366. Với khoang tối thiểu 64px thì 1180 còn cách chữ 13px, còn
   1024 ra **−23px** tức khoang đè lên chữ.
   ⇒ CÓ: desktop · laptop · iPad Air/Pro NẰM NGANG.
   ⇒ KHÔNG: iPad mini nằm ngang (1024), mọi tablet DỌC (cột khung bị cover cắt mất),
     điện thoại. Chỗ MẤT có ý thức — chủ dự án chốt *"có những tính năng mình sẽ ko
     thể hiện được ở mobile"*; phép kiểm [6] canh đúng điều đó. */
@media (min-width:1180px){
  .desk-float{display:block;}
  .desk-float[hidden]{display:none;}
}
  .desk-float[hidden]{display:none;}
}

/* ══ DẢI NHẮC MẤT PHIÊN ĐĂNG NHẬP (thêm 13/08/2026) ═══════════════════════════
   Vì sao có: 13/08 đo được ví thật của chủ dự án là 21 tt trong khi HUD hiện 866.
   `Economy` cộng LẠC QUAN vào cache mỗi lượt quiz/game rồi chờ `setFromServer()`
   ghi đè; hai số lệch 845 nghĩa là mọi lời gọi `/me/*` đã trả `auth` từ lâu — tức
   trẻ vẫn chơi, vẫn thấy tt tăng, mà KHÔNG THỨ GÌ được lưu. App im lặng suốt quãng
   đó; chỉ tới khi mở một trang CẦN token (cửa hàng) nó mới lộ, và lộ bằng một câu
   dễ đọc nhầm thành lỗi.

   ⚠️ CỐ Ý KHÔNG mượn `.banner` của `css/page-shell.css`: dashboard không nạp file
      đó, mà nạp vào là kéo theo cả khung trang nội dung (nền, `.wrap`, `.hub-top`,
      `.hero`, `.panel`) — đè lên bố cục buồng lái. Đây là bản riêng, 6 dòng.

   ⚠️ BẪY `[hidden]` — LẦN THỨ CHÍN (`#time-ok` · `.lk-btn` · `.pt-empty` ·
      `.co-steps` · `#wl-done` · `.exp-ctl` · `.brag` · `.deskrow` · nay `.sess`).
      Khai `display:flex` thì phải khai lại `[hidden]{display:none}`.

   ⚠️ TÔNG HỔ PHÁCH, KHÔNG ĐỎ. Đây không phải lỗi của đứa trẻ và cũng chưa mất gì
      — việc vẫn nằm trong hàng chờ. Đỏ đọc ra thành "hỏng rồi", đúng thứ khiến trẻ
      đóng app; cùng nguyên tắc đã chốt cho `.sum-note` ở quiz và cột "cần luyện
      thêm" ở trang phụ huynh.
   ⚠️ KHÔNG animation. Một dải nhấp nháy ở đây là GIỤC — cùng loại với thứ điều ④
      của `Services/Daily.cs` đã cấm. Nói một lần, nói rõ, rồi để yên. */
/* ⚠️ `grid-column` GIỮ LẠI dù dải này nay là con TRỰC TIẾP của <main> (15/08/2026,
   dời lên trên 6 thẻ khu vực): thuộc tính đó bị bỏ qua khi cha không phải lưới,
   nên nó vô hại, và giữ thì dải vẫn đúng nếu ai đó đặt lại nó vào một lưới.
   `width` + `margin` là phần MỚI: trước đây bề rộng do bảng Thống Kê quyết. */
.sess{grid-column:1 / -1;width:min(880px,100%);margin:18px auto 0;
  display:flex;align-items:flex-start;gap:10px;flex-wrap:wrap;
  margin-top:11px;padding:11px 14px;border-radius:14px;
  border:1px solid rgba(255,207,107,.34);background:rgba(255,207,107,.09);
  color:#ffe3bd;font-size:13px;line-height:1.5;}
.sess[hidden]{display:none;}
.sess .ic{font-size:17px;line-height:1.35;flex:none;}
.sess .tx{flex:1;min-width:0;}
.sess .tx b{color:#fff;font-weight:700;}
/* Câu về việc ĐÃ MẤT tách dòng riêng và nhạt hơn: nó là chuyện đã rồi, không phải
   việc trẻ làm được gì. Câu hành động ở trên mới là thứ cần đọc trước. */
.sess .lost{display:block;margin-top:3px;font-size:12px;color:rgba(255,227,189,.72);}
/* Vùng chạm 48px, KHÔNG 44 — luật 10 mục 6 CLAUDE.md: 44 là mốc TỐI THIỂU của
   WCAG 2.5.5, đặt đúng mốc là không còn biên an toàn nào. */
.sess .go{flex:none;display:inline-flex;align-items:center;min-height:48px;padding:0 16px;
  border-radius:12px;font-family:var(--font-display);font-weight:700;font-size:13px;
  color:#1b1204;text-decoration:none;background:linear-gradient(135deg,#ffd98a,#ffb43f);
  box-shadow:0 10px 22px -14px rgba(255,180,63,.9);}
.sess .go:hover{filter:brightness(1.06);}
.sess .go[hidden]{display:none;}   /* bẫy [hidden] lần thứ chín — xem ghi chú trên */
.sess button.go{border:0;cursor:pointer;font-family:var(--font-display);}

/* ─── Dải MỜI BẤM LINK KÍCH HOẠT (04/09/2026) ───────────────────────────────────
   Dùng lại toàn bộ `.sess` ở trên; đây chỉ là lớp sơn phân biệt. Lý do dải này tồn
   tại nằm ở khối chú thích cạnh `#verify` trong dashboard.html.
   ⚠️ TÔNG TÍM, không hổ phách: nó nói về THIÊN THẠCH TÍM đang chờ trong hòm thư, và
      hai dải có thể cùng hiện (mất phiên + chưa kích hoạt) — cùng màu thì đọc ra
      thành một dải bị lặp. Vẫn KHÔNG animation, cùng lý do đã ghi cho `.sess`. */
.sess--gift{border-color:rgba(192,132,252,.36);background:rgba(192,132,252,.10);color:#e9dcff;}
.sess--gift .tx b{color:#fff;}
.sess--gift .go{color:#1a0f2b;background:linear-gradient(135deg,#dcc4ff,#a855f7);
  box-shadow:0 10px 22px -14px rgba(168,85,247,.9);}
@media (max-width:560px){ .sess .go{width:100%;justify-content:center;} }
/* ⚠️ `min-width:0` LÀ BẮT BUỘC khi ba ô nằm ngang — lỗi ĐO ĐƯỢC, không phải phòng
   xa. Ô lưới mặc định không co xuống dưới bề rộng nội dung, mà nhãn `.st-lbl` khai
   `nowrap`; nên ở 390px cột thứ ba bị đẩy **ra ngoài mép phải và cắt cụt** (ảnh
   chụp: ô "0/0 HUY HIỆU" mất một nửa). */
.stat-tile{display:flex;align-items:center;gap:9px;padding:9px 10px;border-radius:12px;min-width:0;
  border:1px solid rgba(120,160,255,.18);background:rgba(15,23,42,.44);
  transition:transform .3s var(--jelly),border-color .25s,box-shadow .25s;}
.stat-tile:hover{transform:translateY(-3px);border-color:var(--tile-brd);
  box-shadow:0 12px 26px -14px rgba(0,0,0,.7), 0 0 22px -6px var(--tile-glow);}
.stat-tile .st-ic{width:30px;height:30px;flex:none;border-radius:9px;display:grid;place-items:center;font-size:16px;
  color:var(--tile-c);background:var(--tile-bg);border:1px solid var(--tile-brd);box-shadow:inset 0 0 14px -6px var(--tile-c);}
/* `flex:1` để nhãn được dùng hết chỗ còn lại trước khi phải cắt bằng ellipsis —
   khối này vốn co theo nội dung nên nhãn dài bị cắt sớm hơn cần thiết.
   (Canh trái do `.stats-hud` lo — xem ghi chú ở đó.) */
.stat-tile .st-body{display:flex;flex-direction:column;line-height:1.15;
  min-width:0;flex:1;}
.stat-tile .st-val{font-family:var(--font-display);font-weight:700;font-size:15px;color:#fff;white-space:nowrap;}
/* ⚠️ NHÃN XUỐNG DÒNG, KHÔNG CẮT BẰNG ELLIPSIS. Từ khi ba ô nằm ngang thì mỗi ô
   chỉ còn ~126px ở 1440px, và `audit_viewports` bắt được "Độ chính xác Quiz" bị
   cắt còn "Độ chính xác…" (89 > 64px) trên 6 khổ màn. Nhãn là thứ nói con số bên
   trên là con số CỦA CÁI GÌ — cắt nó đi thì con số mất nghĩa. Thà ô cao thêm một
   dòng. */
.stat-tile .st-lbl{font-family:var(--font-mono);font-size:8.2px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint);line-height:1.2;}
/* Màn hẹp: ô chỉ còn ~100px nên nhãn phải được XUỐNG DÒNG thay vì cắt bằng
   ellipsis — "ĐỘ CHÍNH XÁC QUIZ" cắt thành "ĐỘ CHÍNH XÁC…" thì mất đúng chữ nói
   nó là con số của cái gì. Ô cao thêm một dòng, đổi lại đọc được. */
@media (max-width:640px){
  .stat-tile{padding:8px;gap:7px;}
  .stat-tile .st-ic{width:26px;height:26px;font-size:14px;border-radius:8px;}
}
/* Dãy chấm hành tinh: bỏ ở màn hẹp. Nó lặp lại đúng thứ "0/8" ngay bên trái đã
   nói, mà lại là thứ đẩy hàng đó tràn ra ngoài. */
@media (max-width:560px){ .gx-pips{display:none;} }
.tile-time{--tile-c:#38bdf8;--tile-bg:rgba(var(--neon-rgb),.14);--tile-brd:rgba(var(--neon-rgb),.42);--tile-glow:rgba(var(--neon-rgb),.6);}
.tile-acc{--tile-c:#fb923c;--tile-bg:rgba(251,146,60,.14);--tile-brd:rgba(251,146,60,.42);--tile-glow:rgba(251,146,60,.6);}
.tile-badge{--tile-c:#c084fc;--tile-bg:rgba(192,132,252,.14);--tile-brd:rgba(192,132,252,.42);--tile-glow:rgba(192,132,252,.6);}

/* ============================================================
   CONSOLE DƯỚI — Radar kỹ năng + Nhật ký hoạt động
   ============================================================ */
.console-row{display:grid;grid-template-columns:310px 1fr;gap:22px;margin-top:26px;}
@media(max-width:760px){.console-row{grid-template-columns:1fr;}}
.console{position:relative;border-radius:16px;padding:15px 18px 17px;
  border:1px solid rgba(52,211,153,.3);
  background:linear-gradient(180deg, rgba(6,16,20,.66), rgba(8,18,26,.5));
  -webkit-backdrop-filter:blur(15px);backdrop-filter:blur(15px);
  box-shadow:0 22px 48px -24px rgba(0,0,0,.78), inset 0 0 32px -16px rgba(52,211,153,.55);}
.console .con-tag{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:#5ff2c0;}
.console .con-tag .b{color:rgba(120,255,210,.55);}
/* Radar */
.radar-wrap{display:grid;place-items:center;padding:6px 0 2px;}
.radar-svg{width:100%;max-width:236px;height:auto;overflow:visible;filter:drop-shadow(0 0 10px rgba(52,211,153,.35));}
.rr-ring{fill:none;stroke:rgba(95,240,192,.16);stroke-width:1;}
.rr-axis{stroke:rgba(95,240,192,.2);stroke-width:1;}
.rr-fill{fill:rgba(var(--neon-rgb),.22);stroke:#38bdf8;stroke-width:1.6;filter:drop-shadow(0 0 6px rgba(var(--neon-rgb),.85));}
.rr-dot{fill:#c084fc;filter:drop-shadow(0 0 5px rgba(192,132,252,.95));}
.rr-label{fill:#9fe9d4;font-family:var(--font-mono);font-size:8px;letter-spacing:.04em;}
.rr-sweep-g{transform-box:view-box;transform-origin:100px 100px;animation:radarSweep 4s linear infinite;}
/* Đuôi quạt mờ dần về phía SAU thanh sáng — gradient `#rr-tail` do buildRadar() dựng
   trong chính SVG (toạ độ userSpaceOnUse nên nó quay cùng nhóm). Xem ghi chú ở
   buildRadar: dấu của đỉnh đuôi dính với chiều quay của `radarSweep`. */
.rr-wedge{fill:url(#rr-tail);}
.rr-sweep{stroke:rgba(120,255,210,.9);stroke-width:1.4;filter:drop-shadow(0 0 6px rgba(95,240,192,.95));}
@keyframes radarSweep{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
/* Nhật ký */
.log-list{display:flex;flex-direction:column;}
.log-item{display:flex;align-items:center;gap:11px;padding:9px 4px;border-bottom:1px solid rgba(120,160,255,.1);}
.log-item:last-child{border-bottom:none;}
.log-item .lg-ic{width:30px;height:30px;flex:none;border-radius:9px;display:grid;place-items:center;font-size:15px;color:#5ff2c0;
  background:rgba(52,211,153,.12);border:1px solid rgba(52,211,153,.32);}
.log-item .lg-body{flex:1;min-width:0;}
.log-item .lg-text{font-size:13px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.log-item .lg-time{font-family:var(--font-mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-faint);white-space:nowrap;}

/* ---- CARDS = MÀN HÌNH HUD SCI-FI ---- */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:22px;margin-top:26px;}
.hud{
  --neon:rgba(var(--neon-rgb),.4); --neonS:rgba(var(--neon-rgb),.95);
  position:relative;display:flex;flex-direction:column;
  border-radius:16px;padding:20px 22px;
  border:1px solid var(--neon);
  /* Kính mờ bán trong suốt + 4 góc neon sci-fi (8 vạch) */
  background:
    linear-gradient(var(--neonS),var(--neonS)) left 12px top 12px/17px 2px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) left 12px top 12px/2px 17px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) right 12px top 12px/17px 2px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) right 12px top 12px/2px 17px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) left 12px bottom 12px/17px 2px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) left 12px bottom 12px/2px 17px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) right 12px bottom 12px/17px 2px no-repeat,
    linear-gradient(var(--neonS),var(--neonS)) right 12px bottom 12px/2px 17px no-repeat,
    linear-gradient(180deg, rgba(15,23,42,.66), rgba(15,23,42,.5));
  box-shadow:0 22px 50px -24px rgba(0,0,0,.7), 0 0 26px -4px var(--neon), inset 0 0 34px -14px var(--neonS);
  -webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);
  transition:transform .35s var(--jelly), box-shadow .35s var(--ease), border-color .35s var(--ease);
  animation:hudPulse 4s ease-in-out infinite;
}
@keyframes hudPulse{0%,100%{box-shadow:0 22px 50px -24px rgba(0,0,0,.7), 0 0 20px -6px var(--neon), inset 0 0 34px -16px var(--neonS);}
  50%{box-shadow:0 22px 50px -24px rgba(0,0,0,.7), 0 0 34px -2px var(--neon), inset 0 0 40px -12px var(--neonS);}}
/* Lưới tech mờ */
.hud::before{content:"";position:absolute;inset:0;pointer-events:none;opacity:.5;border-radius:inherit;
  background-image:linear-gradient(rgba(120,160,255,.06) 1px, transparent 1px), linear-gradient(90deg, rgba(120,160,255,.06) 1px, transparent 1px);
  background-size:22px 22px;-webkit-mask:linear-gradient(180deg,#000,transparent 78%);mask:linear-gradient(180deg,#000,transparent 78%);}
.hud:hover{transform:translateY(-8px) scale(1.02);border-color:rgba(125,211,252,.85);
  box-shadow:0 34px 66px -22px rgba(0,0,0,.75), 0 0 50px 0 var(--neonS), inset 0 0 44px -10px var(--neonS);animation:none;}

/* `line-height` ĐẶT CỨNG, không để `normal`: chữ có dấu tiếng Việt ("ETA: SẮP TỚI")
   cao hơn chữ không dấu ("SIG: STRONG") một phần pixel, nên hàng chân của card này
   dày hơn card kia → `.hud p{flex:1}` đẩy nút lệch 1px so với card bên cạnh trong
   cùng một hàng. Không nhìn ra bằng mắt nhưng đo là thấy, và cố định lại thì rẻ. */
.hud-line{display:flex;align-items:center;justify-content:space-between;gap:8px;
  font-family:var(--font-mono);font-size:9.5px;line-height:1.5;letter-spacing:.14em;
  color:var(--ink-faint);text-transform:uppercase;}
.hud-line.top{margin-bottom:14px;}
.hud-line.bot{margin-top:16px;border-top:1px dashed rgba(150,180,255,.2);padding-top:10px;}
.hud-line .ok{color:var(--good);}
.hud-line .led{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--good);box-shadow:0 0 8px var(--good);animation:blink 1.6s steps(1,end) infinite;margin-right:6px;}

.hud .ic{width:60px;height:60px;border-radius:18px;display:grid;place-items:center;font-size:28px;
  background:var(--ic-bg, rgba(95,211,255,.16));border:1px solid var(--neon);box-shadow:inset 0 0 18px -6px var(--neon);
  margin-bottom:14px;transition:transform .35s var(--jelly);}
/* Icon sticker choán chỗ ít hơn emoji ở cùng cỡ chữ (emoji là glyph, vẽ tràn
   gần hết ô em; icon này là SVG 1em nên đúng 1em). Đo trên ảnh chụp: 28px
   trong ô 60px chỉ lấp 46%, nhìn ra là một hình nhỏ lọt giữa ô rỗng. Nới
   riêng ở đây chứ không sửa `.sic` dùng chung — cỡ ô là chuyện của TRANG. */
.hud .ic .sic{width:38px;height:38px;}
.hud:hover .ic{transform:scale(1.1) rotate(-6deg);}
.hud .tag{font-family:var(--font-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint);}
.hud h3{font-family:var(--font-display);font-weight:700;font-size:20px;margin:4px 0 0;}
.hud p{color:var(--ink-soft);font-size:13.5px;line-height:1.55;margin:11px 0 16px;flex:1;}
/* `.hud .cost` (badge "5 {tt} / lượt") đã XOÁ ngày 30/07/2026 cùng với badge ở
   card Khu Huấn Luyện — không card nào trên trang này còn hiện phí nữa. Badge phí
   của từng game vẫn còn và vẫn cần: nó ở `.gcard .cost` trong css/games.css, chỗ
   duy nhất mỗi thẻ hiện đúng phí của chính game đó. */

/* Viền/góc neon dùng chung màu cyan (rgba(var(--neon-rgb),...)); chỉ đổi tông icon + nút cho từng khu vực */
.card--quiz   {--ic-bg:rgba(var(--neon-rgb),.18);}
.card--game   {--ic-bg:rgba(255,157,61,.18);}
.card--map    {--ic-bg:rgba(143,123,255,.2);}
.card--mission{--ic-bg:rgba(74,222,128,.18);}
.card--lab    {--ic-bg:rgba(244,114,182,.16);}
/* `.card--codex` (04/08/2026) — trước là `.card--archive` của thẻ "Thư Viện Thiên
   Văn"; thẻ đó đã nhường chỗ MOD-06 cho Sổ Tay Thuật Ngữ (trang có thật). Giữ
   nguyên tông hổ phách. */
.card--codex  {--ic-bg:rgba(255,207,107,.16);}

/* ---- Khu vực CHƯA CÓ TRANG (Phòng Nghiên Cứu) ----
   Ba việc, và cả ba đều cần thiết để card không nói dối:
     · đèn + nhãn đổi sang HỔ PHÁCH (`.standby`) chứ không xanh: `SYS.ONLINE` màu
       xanh cạnh một cái nút bấm không được là hai thông điệp ngược nhau;
     · đèn NGỪNG nhấp nháy — nhấp nháy là dấu hiệu "đang hoạt động";
     · bỏ luôn `hudPulse` và hiệu ứng nhấc lên khi hover: card không bấm được thì
       đừng mời trẻ bấm.
   ⚠️ Cố ý KHÔNG dùng `filter:grayscale()` để làm mờ: trên nền gradient sáng nó cho
   ra một khối xám SÁNG HƠN card bình thường (đã đo trên ảnh chụp ở nút Mặt Trăng của
   màn tổng kết nhiệm vụ), tức là hút mắt vào đúng cái không dùng được. */
.hud.soon{animation:none;opacity:.82;--neon:rgba(255,207,107,.3);--neonS:rgba(255,207,107,.6);}
.hud.soon:hover{transform:none;border-color:var(--neon);
  box-shadow:0 22px 50px -24px rgba(0,0,0,.7),0 0 26px -4px var(--neon),
             inset 0 0 34px -14px var(--neonS);}
.hud.soon:hover .ic{transform:none;}
.hud-line .standby{color:var(--sun);}
.hud-line .led.standby{background:var(--sun);box-shadow:0 0 8px var(--sun);animation:none;}
/* Nút "Sắp ra mắt": nét đứt trong suốt, không gradient (xem ghi chú ở trên) */
.hud .jelly-btn[disabled]{cursor:default;background:none;box-shadow:none;transform:none;filter:none;
  color:var(--ink-faint);border:1px dashed rgba(255,207,107,.34);}

/* `border:1px solid transparent` chứ KHÔNG phải `border:none`: nút "Sắp ra mắt" bên
   dưới thêm viền nét đứt 1px, mà chiều cao nút là `auto` nên `box-sizing:border-box`
   không cứu — viền làm nút cao thêm 2px và nút của 2 card khoá lệch 3px so với nút
   card bên cạnh trong cùng một hàng (đo được: 47px vs 45px). Chừa sẵn 1px trong suốt
   ở đây thì mọi nút cùng cỡ, biến thể chỉ đổi MÀU viền. */
.jelly-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;
  border:1px solid transparent;width:100%;
  padding:13px 18px;border-radius:14px;font-family:var(--font-display);font-weight:600;font-size:15px;color:#04060f;
  background:linear-gradient(120deg,var(--accent),var(--accent-2));box-shadow:0 12px 26px -12px var(--neon);
  transition:transform .3s var(--jelly),filter .2s;}
/* ⚠️ SAU KHI CO TONG DEN, HAI NUT NAY KHONG DUOC GIU MAU RIENG NUA.
   `.card--game` tung go `var(--sun)` (ho phach) va `.card--map` go cung `#a48bff`,
   nen doi den xong thi 4 nut doi mau con 2 nut dung yen — chu du an bat duoc:
   "button 'bat dau luyen' van mau vang nhu cu" va "'Mo ban do' dang 1 kieu rieng".
   ⚠️ Ban sac rieng cua tung khu VAN CON, chi la no khong nam o nut: moi card co
   `--ic-bg` rieng cho o icon (6 tong khac nhau). Giu mau rieng o nut la mot bang
   mau THU HAI phai bao tri cho moi tong den moi. */
.jelly-btn:hover{filter:brightness(1.07);transform:translateY(-2px);}
.jelly-btn:active{transform:scale(.93);}

/* ============ MODALS + TOAST ============ */
.modal{z-index:60;padding:20px;background:rgba(4,6,20,.66);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);pointer-events:none;transition:opacity .3s var(--ease),visibility .3s;}
.modal.show{pointer-events:auto;}
.modal-card{width:100%;max-width:420px;text-align:center;position:relative;
  background:var(--glass-strong);border:1px solid var(--glass-brd-strong);
  -webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);
  border-radius:26px;box-shadow:var(--shadow-soft), 0 0 40px -8px rgba(95,211,255,.4);padding:32px 28px 24px;
  transform:translateY(12px) scale(.96);transition:transform .35s var(--jelly);}
.modal.show .modal-card{transform:none;}
.modal-ic{font-size:44px;line-height:1;}
.modal-card h3{font-family:var(--font-display);font-weight:700;font-size:22px;margin:12px 0 0;}
.modal-card p{color:var(--ink-soft);font-size:14px;line-height:1.6;margin:12px 0 0;}
.modal-actions{flex-wrap:wrap;margin-top:22px;}
.m-btn{border:1px solid var(--glass-brd);background:rgba(255,255,255,.06);color:var(--ink);cursor:pointer;
  border-radius:16px;padding:12px 20px;font-family:var(--font-display);font-weight:600;font-size:14px;
  transition:transform .25s var(--jelly),background .16s,border-color .16s;}
.m-btn:hover{background:rgba(255,255,255,.12);border-color:var(--glass-brd-strong);}
.m-btn:active{transform:scale(.93);}
.m-btn.primary{background:linear-gradient(120deg,var(--accent),var(--accent-2));color:#04060f;border-color:transparent;}

/* ⚠️ MÁY TÍNH BẢNG & HẸP HƠN: HÀNG ĐỒNG HỒ XUỐNG MỘT DÒNG RIÊNG.
   Không phải cho gọn mắt — để chiều cao header KHÔNG phụ thuộc bề rộng của bảng
   tên tàu. `probe_decal.py` đo được: khi ba khối (tên tàu · đồng hồ · hai menu)
   cùng chen một hàng ở 820px thì chỉ cần dán thêm một hình 36px là điểm xuống
   dòng đổi và header nhảy **125px ↔ 117px** — tức một món trang trí làm cả thanh
   trạng thái đổi hình dạng. Tách hàng thì bố cục cố định, dán gì cũng không đổi. */
@media (max-width:1100px){
  .gauges{order:3;width:100%;justify-content:flex-start;}
}
@media (max-width:720px){
  .hero p{max-width:100%;}
  /* ⚠️ GIẤU HAI ĐỒNG HỒ TELEMETRY Ở MÀN HẸP (15/08/2026) — có chủ đích, và chỉ
     đúng hai cái này. `O₂` và `TỌA ĐỘ` là thông số TÀU GIẢ LẬP (`tickTelemetry()`
     sinh số ngẫu nhiên mỗi 2,5 giây), tức trang trí bối cảnh chứ không phải số
     liệu của trẻ. Bốn đồng hồ xuống 2 hàng làm header cao **257px** trên 390×844
     — đo được bằng `scratchpad/probe_hub_layout.py` — và chính chiều cao đó là
     một phần lý do 6 thẻ khu vực bị đẩy khỏi màn hình đầu tiên.
     ⛔ ĐỪNG giấu `.gauge.fuel` (Thiên thạch tím) hay `.gauge.streak` (XP): đó là
        HAI CON SỐ THẬT của trẻ, và tt là thứ nó phải biết trước khi bấm vào một
        mini-game có phí. */
  .gauge.o2,.gauge.coord{display:none;}
}

/* ============================================================
   MÀN HẸP — LƯỚI THẺ 2 CỘT, THẺ RÚT GỌN.
   Khuôn quen của màn hình chính trong game/app cho trẻ: một lưới ô to, đủ để
   nhìn thấy MỌI lựa chọn cùng lúc thay vì cuộn qua từng thẻ một.
   Số đo: ở 390px, lưới 1 cột cho mỗi thẻ ~360px chiều cao ⇒ cả 6 thẻ trải
   2168px, tức trẻ phải cuộn 3 màn hình mới xem hết mình có những khu nào.
   ⚠️ RÚT GỌN chỉ ở ≤560px, và chỉ bỏ những phần NGƯỜI LỚN cần: đoạn mô tả, nhãn
      thể loại, hàng chỉ số chân thẻ. Icon + TÊN KHU + nút vào vẫn còn nguyên —
      đó là ba thứ trẻ dùng để chọn. Từ 561px trở lên thẻ vẫn đầy đủ.
   ============================================================ */
@media (max-width:900px){
  .cards{grid-template-columns:repeat(2,1fr);gap:14px;}
}
@media (max-width:560px){
  /* Hai đồng hồ còn lại xếp CHUNG một hàng thay vì mỗi cái một hàng: bỏ nhãn chữ,
     giữ biểu tượng + con số. Nhãn "THIÊN THẠCH"/"KINH NGHIỆM" là thứ dài nhất
     trong ô, mà ảnh thiên thạch tím và ngọn lửa XP đã là dấu hiệu riêng — tên đầy
     đủ vẫn còn ở `title` (trình đọc màn hình và chuột dừng đều đọc được).
     Đo được: header 169px → 128px trên 390×844. */
  .gauge .g-label{display:none;}
  .gauge{padding:6px 11px;}
  /* Câu "Chọn một khu vực để bắt đầu…" ẩn ở cỡ này: nó nói lại đúng thứ lưới thẻ
     ngay bên dưới đang bày ra, mà nó chiếm 3 dòng (~90px) — tức là nó đẩy chính
     mấy cái thẻ nó đang giới thiệu ra khỏi màn hình. Vẫn còn nguyên ở màn rộng. */
  .hero p{display:none;}
  .hero h1{font-size:clamp(21px,6.4vw,28px);}
  .cards{gap:12px;}
  .hud{padding:14px 13px;border-radius:14px;}
  .hud .ic{width:48px;height:48px;border-radius:14px;margin-bottom:10px;}
  .hud .ic .sic{width:30px;height:30px;}
  .hud h3{font-size:15.5px;line-height:1.2;}
  /* Mô tả + nhãn thể loại + hàng chỉ số chân thẻ: ẩn ở cỡ này (xem lý do trên).
     `.hud p` đang giữ `flex:1` để đẩy nút xuống đáy, nên phải cho `h3` nhận vai
     đó — không thì nút của thẻ tên ngắn nhảy lên cao hơn thẻ tên dài cùng hàng. */
  .hud p,.hud .tag,.hud .hud-line.bot{display:none;}
  .hud h3{flex:1;margin-bottom:10px;}
  .hud-line.top{margin-bottom:10px;font-size:8.5px;letter-spacing:.1em;}
  /* Nhãn nút dài nhất ("Vào phòng thí nghiệm", "Xem bảng nhiệm vụ") vẫn xuống 2
     dòng ở cỡ này, và ĐỂ NGUYÊN như vậy: rút chữ cho vừa một dòng là đổi nội
     dung theo bề rộng màn hình (hai câu cho một nút), còn thu nhỏ nữa thì chữ
     dưới 12px. Nút cao thấp khác nhau trong cùng một hàng không sao — chúng canh
     đáy thẻ, và cả hàng vẫn bằng nhau nhờ lưới. */
  .jelly-btn{padding:10px 10px;font-size:12.5px;border-radius:12px;}
}

/* Nhật ký rỗng — chưa có hoạt động nào. Thà một dòng thật còn hơn 4 dòng bịa:
   trước 29/07/2026 khối này là dữ liệu cắm cứng ("Mở khoá huy hiệu Nhà Du Hành
   2 phút trước"), giờ chỉ hiện huy hiệu THẬT vừa mở theo `earnedAt` từ server. */
.log-empty{color:var(--ink-faint);font-size:12.8px;line-height:1.6;padding:14px 4px;}

/* Toast — khung ở css/common.css, đây chỉ đặt vị trí + màu.
   Đặt dưới thanh trạng thái (sticky, cao ~58px) để không đè lên các gauge. */
.toast{top:78px;transform:translateX(-50%) translateY(-10px);z-index:95;
  padding:12px 18px;font-size:13.5px;background:rgba(13,20,48,.95);
  border:1px solid rgba(95,211,255,.5);box-shadow:0 18px 40px -18px rgba(0,0,0,.85);
  -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);
  transition:opacity .28s var(--ease),transform .28s var(--ease);}
@media (max-width:560px){ .toast{top:auto;bottom:18px;} }

/* Vùng chạm trên thiết bị cảm ứng: khối `@media (pointer:coarse)` cũ ở đây chỉ
   nói về `.logout-btn` và `.ptile` — hai thứ không còn tồn tại từ 15/08/2026 —
   nên đã bỏ. Vùng chạm của hai menu thả khai ở `css/user-menu.css` (`.um-btn`
   48px, `.um-item` 48px, màn hẹp 52px), vẫn là 48 chứ không 44: 44 là mốc TỐI
   THIỂU của WCAG 2.5.5, đặt đúng mốc là không còn biên an toàn nào (quy tắc 10
   mục 6 — nút VI/EN của explorer đã chập chờn 683/1 rồi 684/0 vì lý do đó). */

/* ⚠️ `.pt-row` (hai dòng liên kết Kho Trang Trí + Dành cho bố mẹ) ĐÃ BỎ
   15/08/2026 — cả hai nay là mục trong menu thả sau avatar. Sự phân biệt
   "của trẻ" và "của người lớn" mà nét đứt từng mang KHÔNG mất: nó đi theo
   sang `.um-item.um-parent` (vẫn nét đứt, vẫn chữ nhạt) ở css/user-menu.css. */

/* ⚠️⚠️ CHỜ NGÔN NGỮ — xem khối chú thích dài ở thẻ `<script>` đặt lớp này trong
   `dashboard.html`. Chỉ bật cho trẻ chọn EN, và chỉ tới khi `applyLang` chạy xong
   (hoặc 4 giây, đường cứu). Che bằng `visibility` chứ KHÔNG bằng `display:none`:
   `visibility` giữ nguyên bố cục nên lúc bỏ che không có cú nhảy nào; `display:none`
   thì cả trang dựng lại và ta vừa đổi một lỗi loé chữ thành một lỗi nhảy bố cục.
   Nền + ảnh khoang lái VẪN hiện — trẻ thấy trang đang tới, chỉ chưa thấy chữ. */
/* ⚠️ `main`, KHÔNG phải `.wrap`: trang này không có `.wrap` (bản đầu tôi viết
   `.lang-wait .wrap` và nó che ĐÚNG KHÔNG GÌ CẢ — phép đo mới lộ ra, vì bộ chọn
   trong bộ đo cũng không tìm thấy tiêu đề). Header cố ý KHÔNG che: chữ hiện của nó
   là số liệu buồng lái và mã toạ độ, không phụ thuộc ngôn ngữ; phần chữ có dịch của
   nó (`data-i18n-title`, menu thả) đều đang ẩn. */
.lang-wait main{visibility:hidden;}
