/* ==========================================================================
   "My Kingdom" — vương quốc pixel top-down (#/kingdom). Cùng kỹ thuật với .cats-root và
   .rpg-root: MỌI luật nằm dưới .kg-root, biến màu TỰ KHAI LẠI (không đụng :root của app),
   và full-bleed bằng margin âm huỷ padding của .view.

   "Mọi luật nằm dưới .kg-root" ở đây không còn là nếp đẹp mà là ĐIỀU KIỆN: từ lúc HUD
   chuyển sang tông kem sáng, một luật thoát ra ngoài sẽ dội nền sáng vào cả app tối.
   tools/test.mjs khẳng định điều đó, đúng cách nó đã khẳng định cho .fg-root.

   KHUNG VÀ ICON đến từ css/kingdom-ui.css — file SINH TỰ ĐỘNG bởi
   tools/make-kingdom-atlas.mjs, chở ảnh Sunnyside dưới dạng data: URI và chỉ số icon.
   File đó chở DỮ LIỆU, file này chở Ý ĐỊNH: chỗ nào dùng khung nào, màu gì, cỡ bao nhiêu.
   Cùng đường chia mà atlas-map.js / sprites.js đã dùng.

   BẢNG MÀU LÀ ĐO ĐƯỢC, không phải chọn tay: #181425 (viền), #ffffff + #c0cbdc (khung box),
   #ead4aa + #c28569 (nhãn) đọc thẳng ra từ pixel của bộ art. Nhờ vậy chrome và bản đồ nằm
   trong cùng một bảng màu thay vì "gần giống".

   BỐ CỤC là một CỘT CỐ ĐỊNH (100dvh trừ chrome của app), không cuộn: thanh tài nguyên / sân
   khấu / thẻ nhóm / dải cửa hàng. Sân khấu là `flex: 1`, nên mọi thứ đặt DƯỚI nó đều ăn vào
   chiều cao bản đồ — đúng cái bẫy js/room/shell.js:52-58 đã ghi lại một lần, và là lý do
   chip đồng hồ sản xuất là overlay chứ không phải một hàng riêng.
   ========================================================================== */
.kg-root {
  --ink: #181425; --line: #181425;
  --panel: #c0cbdc; --panel-b: #ffffff;
  --cream: #ead4aa; --tan: #c28569;
  --bg: #181425;
  --accent: #8fc45c; --accent-d: #5b8a34; --gold: #f0c760; --danger: #7d2b2b;
  /* Tương phản trên nền panel (#c0cbdc) là ĐO ĐƯỢC, không chọn theo mắt: --good cũ
     (#3f7d3a) chỉ được 3.05:1 và --dim cũ (#5a5a72) được 4.08:1 — cả hai dưới ngưỡng
     4.5 của chữ thường, tức là mờ đi trên màn hình điện thoại ngoài sáng. Hai giá trị
     dưới đây cho 5.68 và 5.70. */
  --good: #275022; --dim: #454559;
  margin: -18px -16px -64px;
  color: var(--ink);
  background: var(--bg);
  font-family: var(--font, system-ui), sans-serif;
}

/* Khung 9-slice dùng chung. PHÓNG THEO BỘI SỐ NGUYÊN: ảnh nguồn 3px/mảnh đã được
   tools/make-kingdom-atlas.mjs phóng nearest ×2 lúc dựng, nên `--kg-box-slice` là 6 và
   viền cũng đúng 6px — trình duyệt không phóng gì cả. Đó là bản CSS của fitSprite, và là
   lý do không giao việc ấy cho `image-rendering: pixelated`: thuộc tính đó không được bảo
   đảm áp dụng cho border-image, mà nhoè ở border-image thì nhoè đúng bốn góc. */
.kg-root .kg-9,
.kg-root .kg-9p {
  border: 6px solid transparent;
  border-image-slice: var(--kg-box-slice) fill;
  border-image-width: 6px;
  border-image-repeat: repeat;
  background: none;
}
.kg-root .kg-9 { border-image-source: var(--kg-box); }
/* Nút CHÍNH đổi bằng một COLOURWAY KHÁC của cùng hình học 9-slice (bộ `lt_`, kem ấm), chứ
   không tô màu bằng CSS. Lý do là kỹ thuật chứ không phải khẩu vị: `border-image` có `fill`
   vẽ đè miền giữa, nên `background-color` bị che hẳn — và mọi mẹo còn lại (inset
   box-shadow, một lớp ::before) đều là đánh cược với thứ tự vẽ của chính cái khung.
   Bộ art ship sẵn ba colourway đúng để làm việc này; dùng cái thứ hai rẻ hơn một mẹo. */
.kg-root .kg-9p { border-image-source: var(--kg-boxp); }
/* Nhãn ba mảnh ngang — dùng cho chip: cao CỐ ĐỊNH đúng bằng ảnh (×2 = 26px) nên mảnh giữa
   chỉ lát NGANG, không bị kéo dọc. Kéo dọc một dải pixel art là cách nhanh nhất làm nó nhoè. */
.kg-root .kg-lbl {
  border: 0 solid transparent;
  border-left-width: 8px; border-right-width: 8px;
  border-image-source: var(--kg-label);
  border-image-slice: 0 var(--kg-label-slice) fill;
  border-image-width: 0 8px;
  border-image-repeat: repeat;
  height: var(--kg-label-h);
  background: none;
}

.kg-root #kgApp {
  display: flex;
  flex-direction: column;
  /* KHUNG CỐ ĐỊNH, KHÔNG cuộn — nhưng KHÔNG dùng `position: fixed; inset: 0` như màn chơi
     của Biệt Đội Mèo (cats.css:167-181). Trò kia ẩn hẳn chrome của app vì nó là một trận
     đấu; My Kingdom là một màn để ghé vào giữa hai lượt luyện nói, nên thanh nav phải còn
     đó — và khi thanh nav còn đó thì chiều cao phải TRỪ nó đi.
     Con số 60px (topbar) và 56px (tabbar) là con số My Room đã đo và đang dùng
     (css/ispeak.css:806-819); chép lại chứ không tự đoán lại. */
  height: calc(100vh - 60px);                /* Safari cũ không hiểu dvh — giữ làm đường lùi */
  height: calc(100dvh - 60px);
  min-height: 440px;
  overflow: hidden;
}
@media (max-width: 719px) {
  /* Dưới 720px thanh nav trên ẩn đi và tabbar dưới hiện ra — vẫn là 56px, chỉ đổi chỗ.
     Cộng cả hai safe-area vì iPhone tai thỏ ăn mất một dải ở cả hai đầu. */
  .kg-root #kgApp { height: calc(100vh - 124px); }
  .kg-root #kgApp { height: calc(100dvh - 60px - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }
}

/* ---------------- thanh tài nguyên ---------------- */
.kg-root #kgTop {
  display: flex; align-items: center; gap: 8px;
  /* KHÔNG cộng safe-area-inset-top ở đây: thanh nav của app đã đứng trên đầu và đã lo
     phần tai thỏ rồi. Cộng lần nữa là chừa chỗ cho một cái tai thỏ không tồn tại. */
  padding: 7px 9px;
  background: var(--panel);
  border-bottom: 3px solid var(--line);
  flex: 0 0 auto;
}
/* Ví KHÔNG đeo khung, và đó là một quyết định đo được chứ không phải bỏ sót. Đo trên
   375px: sau bốn nút chỉ còn ~198px cho ví, mà năm nhóm icon+số đã chiếm ~202px. Mỗi
   khung nhãn ăn thêm 16px (mảnh trái + mảnh phải), nên đeo khung — dù một cái chung cho
   cả ví — là ví phải CUỘN ngang ngay từ ván đầu, khi các con số còn có hai chữ số.
   Một cái ví phải kéo mới thấy hết là HUD tệ hơn một cái ví không viền, nên viền là thứ
   bị cắt. Khung nhãn chuyển sang thẻ báo việc, nơi nó chỉ có một dòng và luôn vừa.
   Năm nhóm đứng một hàng, không xuống dòng: xuống dòng là đẩy thanh trên từ 51px lên
   105px, và 54px ấy ăn thẳng vào chiều cao bản đồ — đúng cái bẫy chú thích đầu file ghi. */
.kg-root .kg-res {
  display: flex; align-items: center; gap: 5px;
  flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
  min-width: 0; flex: 1;
}
.kg-root .kg-res::-webkit-scrollbar { display: none; }
.kg-root .kg-r {
  display: inline-flex; align-items: center; gap: 2px; flex: 0 0 auto;
  font-size: 13px; line-height: 1; white-space: nowrap;
  color: var(--ink);
}
.kg-root .kg-r b { font-variant-numeric: tabular-nums; font-weight: 700; }

.kg-root .kg-top-actions { display: flex; gap: 5px; flex: 0 0 auto; }
.kg-root .kg-tbtn {
  position: relative;
  width: 34px; height: 34px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; cursor: pointer;
  color: var(--ink);
}
.kg-root .kg-tbtn:active { transform: translateY(1px); }
.kg-root .kg-badge {
  position: absolute; top: -6px; right: -6px;
  min-width: 17px; padding: 0 4px;
  font-size: 10px; line-height: 17px; font-weight: 700;
  color: var(--ink); background: var(--gold);
  border: 1px solid var(--line);
  border-radius: 999px;
}

/* ---------------- sân khấu ---------------- */
.kg-root #kgStage { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; background: var(--bg); }
/* touch-action: none chứ KHÔNG phải manipulation. Từ lúc bản đồ kéo được, mọi cú vuốt
   trên canvas là của game; để mặc thì trình duyệt cuộn trang trước và touchmove tới tay ta
   đã muộn. Đây cũng là lý do touchmove nghe với passive:false — preventDefault chỉ có
   nghĩa khi trình duyệt chưa quyết định thay mình. */
.kg-root #kgCanvas { display: block; width: 100%; height: 100%; touch-action: none; }

/* Chip đồng hồ là một CỘT ba dòng ngắn, không phải một hàng: xếp hàng ngang thì ba nhóm
   (tốc độ / đã tích / còn bao lâu) cộng lại rộng 353px trên một sân khấu rộng 375px —
   tức là nó chắn gần hết mép trên của bản đồ. Cột thì rộng ~90px và nằm gọn một góc.
   Vì cao thay đổi theo nội dung nên nó dùng khung `kg-9` (co dãn được cả hai chiều) chứ
   không dùng khung nhãn (cao cố định 26px). */
.kg-root .kg-rate {
  position: absolute; top: 6px; right: 6px;
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  padding: 2px 4px;
  font-size: 11px; line-height: 1.25;
  color: var(--ink);
  pointer-events: none;
}
.kg-root .kg-rate-l,
.kg-root .kg-rate-b,
.kg-root .kg-rate-t { display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
.kg-root .kg-rate-b { font-weight: 700; }
.kg-root .kg-rate-full .kg-rate-t { color: var(--danger); font-weight: 700; }

/* Chip tên vương quốc, góc trên-TRÁI của sân khấu, đối xứng với đồng hồ sản xuất bên phải.
   max-width 48% chứ không để tự do: tên do học sinh đặt (tối đa 24 ký tự, kd_rename kẹp ở
   server), và một cái tên dài đúng 24 ký tự viết hoa sẽ chạy sang tận chip đồng hồ nếu
   không chặn. Quá thì cắt bằng ellipsis — mất vài chữ cuối vẫn đọc ra được vương quốc nào,
   còn đè lên đồng hồ thì hỏng cả hai. */
.kg-root .kg-title {
  position: absolute; top: 6px; left: 6px;
  max-width: 48%;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 6px;
  font-size: 12px; line-height: 1.2; font-weight: 700; cursor: pointer;
  color: var(--ink); text-align: left;
}
.kg-root .kg-title-n {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.kg-root .kg-title-m { font-weight: 400; opacity: .75; flex: 0 0 auto; }
/* Chấm nhắc việc: có nhiệm vụ chưa nhận, hoặc có phiếu chưa bỏ. KHÔNG hiện số — con số ở
   đây phải cộng hai thứ khác loại lại với nhau, và "3" thì không nói được là ba nhiệm vụ
   hay ba cuộc bỏ phiếu. Một chấm nói đúng cái nó biết: có việc ở trong. */
.kg-root .kg-title-dot {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 999px;
  background: var(--gold); border: 1px solid var(--line);
}

.kg-root .kg-pop-wrap {
  position: absolute; left: 0; right: 0; top: 6px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  pointer-events: none;
}
/* Thẻ báo việc đeo khung nhãn: một dòng, ngắn, và cao cố định 26px đúng bằng ảnh nên
   mảnh giữa chỉ lát ngang. `nowrap` là điều kiện của khung ấy — xuống dòng trong một cái
   hộp cao cố định là chữ tràn ra ngoài viền. */
.kg-root .kg-pop {
  max-width: 92%;
  display: inline-flex; align-items: center; gap: 3px;
  padding: 0 2px;
  font-size: 13px; white-space: nowrap; color: var(--ink);
  animation: kg-pop-in .18s ease-out;
}
.kg-root .kg-pop-ok { color: var(--good); }
.kg-root .kg-pop-err { color: var(--danger); }
@keyframes kg-pop-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .kg-root .kg-pop { animation: none; } }

.kg-root .kg-mode {
  position: absolute; left: 8px; right: 8px; bottom: 8px;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 5px 8px;
  font-size: 13px; color: var(--ink);
}
.kg-root .kg-mode-x {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 10px; font-size: 12px; cursor: pointer;
  color: var(--ink);
}

/* ---------------- thẻ nhóm + dải cửa hàng ---------------- */
.kg-root #kgCats {
  display: flex; gap: 4px; flex: 0 0 auto;
  padding: 6px 8px 0;
  background: var(--tan);
  border-top: 3px solid var(--line);
}
/* Thẻ nhóm chưa chọn dùng #3d2415 chứ KHÔNG dùng --cream: kem trên nền nâu vàng chỉ được
   2.12:1 — đọc được trong nhà và biến mất ngoài sân. Nâu đậm cho 4.69:1; thẻ đang chọn là
   --ink + đậm, 5.88:1. Chênh lệch giữa hai trạng thái vẫn rõ mà không cần đổi màu nền. */
.kg-root .kg-cat {
  flex: 1; padding: 6px 4px;
  font-size: 12px; cursor: pointer;
  color: #3d2415; background: transparent;
  border: none;
}
.kg-root .kg-cat.on { color: var(--ink); font-weight: 700; }

.kg-root #kgShop { flex: 0 0 auto; background: var(--tan); }
.kg-root #kgShopStrip {
  display: flex; gap: 6px;
  padding: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
.kg-root .kg-card {
  flex: 0 0 auto; width: 78px;
  display: flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 4px 3px; cursor: pointer;
  color: var(--ink);
}
.kg-root .kg-card.on { box-shadow: 0 0 0 3px var(--accent); }
/* "Không đủ" là MỜ ĐI, không phải disabled: chạm vào vẫn cầm được món lên và bóng trên
   bản đồ vẫn hiện — người chơi cần thấy nó chiếm bao nhiêu đất TRƯỚC khi đi kiếm gỗ. */
.kg-root .kg-card.kg-poor { opacity: .5; }
.kg-root .kg-card-art { width: 48px; height: 48px; image-rendering: pixelated; }
.kg-root .kg-card-n { font-size: 11px; line-height: 1.2; text-align: center; }
.kg-root .kg-card-c,
.kg-root .kg-card-r {
  display: flex; align-items: center; justify-content: center; gap: 2px;
  font-size: 10px; line-height: 1.3; white-space: nowrap;
}
.kg-root .kg-card-r { color: #4a4a63; }

/* ---------------- modal ---------------- */
.kg-root #kgModalWrap {
  position: fixed; inset: 0; z-index: 60;
  display: none; align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(24, 20, 37, .62);
}
.kg-root #kgModalWrap.on { display: flex; }
.kg-root #kgModal {
  width: 100%; max-width: 380px; max-height: 80dvh; overflow-y: auto;
  padding: 14px;
  color: var(--ink);
}
.kg-root #kgModal h3 { margin: 0 0 10px; font-size: 17px; }
.kg-root #kgModal p { margin: 0 0 8px; font-size: 14px; }
.kg-root .kg-muted { color: var(--dim); font-size: 13px; display: flex; align-items: center; gap: 4px; }
.kg-root .kg-btn {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  width: 100%; margin-top: 8px; padding: 7px;
  font-size: 14px; cursor: pointer;
  color: var(--ink);
}
.kg-root .kg-btn-p { font-weight: 700; }
.kg-root .kg-btn-d { color: var(--danger); }
.kg-root .kg-help { margin: 0 0 4px; padding-left: 18px; font-size: 13px; line-height: 1.6; }
.kg-root .kg-land { margin: 0 0 8px; padding: 0; list-style: none; font-size: 14px; }
.kg-root .kg-land li {
  display: flex; align-items: center; gap: 4px;
  padding: 5px 0; border-bottom: 1px solid rgba(24, 20, 37, .25); color: var(--dim);
}
.kg-root .kg-land li.on { color: var(--good); }

/* ---------------- bảng điều khiển của lớp (v2) ---------------- */
/* Một danh sách dùng chung cho nhiệm vụ / biểu quyết / nhật ký / đóng góp. Bốn màn ấy đều
   là "một hàng, chữ bên trái, số hoặc nút bên phải", nên chúng dùng CHUNG một bộ lớp thay
   vì mỗi màn một bộ: bốn bộ lớp gần giống nhau là bốn chỗ để lệch nhau về khoảng cách, và
   người dùng thì đọc chúng trong cùng một hộp thoại nên lệch là thấy ngay.
   max-height + cuộn: nhật ký dài vô hạn, còn #kgModal thì không được cao hơn màn hình. */
.kg-root .kg-list { margin: 0 0 8px; padding: 0; list-style: none; max-height: 46vh; overflow-y: auto; }
.kg-root .kg-list li {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 0; font-size: 13px; line-height: 1.35;
  border-bottom: 1px solid var(--line);
}
.kg-root .kg-list li:last-child { border-bottom: 0; }
.kg-root .kg-list .kg-l-main { flex: 1 1 auto; min-width: 0; }
.kg-root .kg-list .kg-l-sub { display: block; font-size: 11px; color: var(--dim); }
.kg-root .kg-list .kg-l-side {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 3px;
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.kg-root .kg-list li.done .kg-l-main { color: var(--dim); text-decoration: line-through; }
.kg-root .kg-btn-s {
  flex: 0 0 auto; padding: 3px 8px; font-size: 12px; line-height: 1.2;
  cursor: pointer; color: var(--ink);
}
/* Hai nút biểu quyết đứng CẠNH nhau và ĐỒNG cỡ, không phải một nút chính một nút phụ.
   "Đồng ý" to hơn "Không đồng ý" là thiết kế đang bỏ phiếu hộ người dùng — mà ở đây một
   phiếu chống là đủ dừng cả đề nghị, nên hai lựa chọn phải nặng bằng nhau trên màn hình. */
.kg-root .kg-vote2 { display: flex; gap: 6px; }
.kg-root .kg-vote2 .kg-btn { flex: 1 1 0; margin: 0; }
/* Huy hiệu cấp trên thẻ công trình. Không dùng icon: "cấp" là một con SỐ, và bộ icon
   Sunnyside không có chữ số nào. */
.kg-root .kg-lvl {
  display: inline-block; padding: 1px 6px; margin-left: 6px;
  font-size: 11px; font-weight: 700; line-height: 1.4;
  color: var(--ink); background: var(--gold);
  border: 1px solid var(--line); border-radius: 999px;
}
/* Lưới chọn nhân vật — BA cột, mỗi cột một tông áo, mỗi hàng một kiểu tóc.
   Bản 12 nhân vật xếp 4 cột (một hàng = một tông) và điều đó đúng khi có 4 kiểu tóc. Sáu
   kiểu tóc thì lưới phải xoay 90°, vì 6 cột không còn vừa: đo trên 375px theo đúng phép
   tính ở dòng 376 dưới đây — 375 − 32 (đệm #kgModalWrap) − 12 (viền kg-9) − 28 (đệm
   #kgModal) = 303px lòng trong; 6 cột với 5 khe 6px còn 45,5px mỗi ô, mà một ô cần 40px
   hình cộng 12px viền 9-slice cộng 6px đệm = 58px. Ba cột cho 87px mỗi ô — dư thoải mái,
   và mười tám nhân vật xếp sáu hàng. Sáu hàng CÓ cuộn trên máy thấp, và đó là đánh đổi
   đúng: #kgModal đã có `overflow-y: auto` sẵn, còn một hàng tràn ngang thì không có gì đỡ.
   Đổi số cột ở đây thì PHẢI đổi chiều chia trong js/kingdom/sprites.js:villagerSprite —
   hai chỗ ấy là một quyết định viết ở hai file. */
.kg-root .kg-avas {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 0 0 8px;
}
.kg-root .kg-ava {
  display: flex; align-items: center; justify-content: center;
  padding: 3px; cursor: pointer; background: none;
}
.kg-root .kg-ava canvas { display: block; image-rendering: pixelated; }
/* Ô đang chọn: một vòng vàng, KHÔNG đổi nền. Đổi nền là đổi cả tông màu da của nhân vật
   đứng trên nó, và người chơi sẽ tưởng mình vừa chọn một nhân vật khác. */
.kg-root .kg-ava.on { outline: 2px solid var(--gold); outline-offset: -2px; }

.kg-root .kg-tabs { display: flex; gap: 4px; margin: 0 0 8px; flex-wrap: wrap; }
.kg-root .kg-tab {
  padding: 4px 8px; font-size: 12px; font-weight: 700; cursor: pointer;
  color: var(--dim); background: none; border: 0; border-bottom: 2px solid transparent;
}
.kg-root .kg-tab.on { color: var(--ink); border-bottom-color: var(--gold); }

.kg-root .kg-crops { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.kg-root .kg-crop {
  display: flex; flex-direction: column; gap: 1px;
  padding: 7px; text-align: left; cursor: pointer;
  color: var(--ink);
  font-size: 11px;
}
.kg-root .kg-crop b { font-size: 13px; }
.kg-root .kg-crop.kg-poor { opacity: .5; }

/* Hình cây THẬT trong nút chọn: một ô 32px = 16px nguồn ×2. Bội số nguyên, cùng luật với
   chú thích cỡ icon ngay dưới và với fitSprite trong engine.js.
   Nó đứng trong một HÀNG ĐẦU cùng với TÊN, không phải một hàng riêng và cũng không đứng
   cạnh cả khối chữ. Đo trên máy 375px: 375 − 32 (đệm của #kgModalWrap) = 343; trừ 12 viền
   kg-9 và 28 đệm của #kgModal còn 303; chia đôi trừ khe 6 ra 148,5; trừ tiếp 12 viền và 14
   đệm của chính cái nút — mỗi nút chỉ còn 122,5px lòng trong. Cho canvas đứng cạnh CẢ KHỐI
   CHỮ là ép "Yield: 360 grain" xuống 84,5px, mà dòng ấy đo được 88–91px ở 11px: nó tràn qua
   khung 9-slice, và border-image thì không cắt. Đứng cạnh mỗi cái TÊN thì hai dòng lúa giữ
   nguyên bề ngang cũ, còn tên dài xuống dòng MIỄN PHÍ vì hàng đầu đã cao 32px sẵn.
   KHÔNG có bản @media 900px như .kg-card-art: thẻ hàng to lên vì chính .kg-card to lên
   (78→92px), còn #kgModal thì max-width 380px ở MỌI khổ màn nên cái nút không hề rộng thêm —
   phóng hình ở đó chỉ ăn mất bề ngang của cái tên, và 48 cũng không phải bội ×2 của hộp 32. */
.kg-root .kg-crop { min-width: 0; }
.kg-root .kg-crop-h { display: flex; align-items: center; gap: 6px; min-width: 0; }
.kg-root .kg-crop-art { flex: 0 0 auto; width: 32px; height: 32px; image-rendering: pixelated; }
.kg-root .kg-crop-h b { min-width: 0; overflow-wrap: anywhere; line-height: 1.15; }

/* Modal cây đang lớn: hộp 48px = 16 ×3, vì ở đây vẽ khung HIỆN TẠI chứ không phải khung chín
   — và khung to nhất trong cả sáu nấc là hướng dương nấc 4, 13×19, tức 26×38 ở ×2. Hộp 32
   không chứa nổi nó, và neo đáy sẽ cắt mất ngọn mà không báo gì. */
.kg-root .kg-crop-big { display: block; width: 48px; height: 48px; margin: 0 0 8px; image-rendering: pixelated; }

/* Icon to hơn ở nút bấm: CÙNG một dải ảnh, chỉ đổi biến cỡ ô — và 20px trên nguồn 16px
   không phải bội số nguyên, nên nút dùng 32px (×2) còn chip giữ 16px (×1). Hai nấc, cả
   hai đều nguyên. */
.kg-root .kg-tbtn .kg-i { --kg-icon-cell: 16px; }

/* Từ 900px trở lên dải cửa hàng rộng ra và thẻ to hơn — không đổi bố cục, chỉ bớt chật.
   Khổ điện thoại KHÔNG đánh đổi lấy cột phụ: đó là nơi phần lớn học sinh ngồi. */
@media (min-width: 900px) {
  .kg-root .kg-card { width: 92px; }
  .kg-root .kg-card-art { width: 60px; height: 60px; }
  .kg-root .kg-card-n { font-size: 12px; }
}

/* Hai nút zoom, dán góc dưới-phải CỦA SÂN KHẤU. Không nhét vào thanh trên vì thanh ấy đã
   chật (xem chú thích ở .kg-res), và vì zoom là thao tác về BẢN ĐỒ nên nó thuộc về bản đồ.
   Trên 375px, thanh chế độ chiếm mép dưới khi đang cầm công trình — nên hai nút này lùi
   lên trên nó một đoạn thay vì tranh chỗ. */
.kg-root .kg-zoom {
  position: absolute; right: 6px; bottom: 52px;
  display: flex; flex-direction: column; gap: 5px;
}
.kg-root .kg-zbtn {
  width: 34px; height: 34px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 700; line-height: 1; cursor: pointer;
  color: var(--ink);
}
.kg-root .kg-zbtn:active { transform: translateY(1px); }

/* ---- body.s8-play: đang chơi ⇒ thanh tab dưới bị ẩn (xem css/ispeak.css) -------------
   Cùng lý lẽ với forge.css: luật mobile ở trên trừ 56px của thanh tab khỏi chiều cao sân,
   thanh tab không còn thì phải trả lại. Nằm ở FILE NÀY vì test "mọi luật của kingdom nằm
   dưới .kg-root" cũng cấm .kg-root xuất hiện trong ispeak.css. */
@media (max-width: 979px) {
  body.s8-play .kg-root { margin-bottom: 0; }
}
@media (max-width: 719px) {
  body.s8-play .kg-root #kgApp {
    height: calc(100vh - 60px);
    height: calc(100dvh - 60px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  }
}
