/* ==========================================================================
   "Cối Xay Gió" (#/farm) — nông trại hành động Sunnyside; bản CHẠY SONG SONG của kế
   hoạch thay client My Kingdom (pha A). Cùng kỹ thuật với .kg-root: MỌI luật nằm dưới
   .farm-root, full-bleed bằng margin âm huỷ padding của .view, chiều cao trừ chrome
   của app theo đúng con số My Room đã đo (60px thanh trên / 56px thanh tab — xem
   css/ispeak.css:806-819, css/kingdom.css:74-93; chép lại chứ không tự đoán lại).

   File này NGẮN có chủ đích: toàn bộ game vẽ trong một canvas 480x270 phóng nguyên tỷ
   lệ, nên canvas không cần CSS trang trí — nó cần đúng chỗ đứng và phép phóng sạch
   (engine tự tính k nguyên, CSS chỉ cấm trình duyệt làm nhoè). DOM chỉ giữ những gì
   nhiều chữ: HUD lớp (pha B), panel server (pha C), toast — khung dựng sẵn từ hôm nay
   để pha sau thêm nội dung mà không đổi cấu trúc.
   ========================================================================== */
.farm-root {
  --bg: #1a1522;
  margin: -18px -16px -64px;
  background: var(--bg);
  color: #fff;
  font-family: var(--font, system-ui), sans-serif;
}

/* Cột cố định, không cuộn — cùng lý lẽ và cùng con số với .kg-root #kgApp. */
.farm-root #fmApp {
  position: relative;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 60px);                /* Safari cũ không hiểu dvh — giữ làm đường lùi */
  height: calc(100dvh - 60px);
  min-height: 320px;
  overflow: hidden;
}
/* (G43) Sàn 320px của #fmApp là sàn của MÁY ĐỨNG. Điện thoại nằm ngang chỉ còn
   375 - 60 = 315px, nên cái sàn ấy đẩy 5px cuối của sân (tức mép dưới joystick và nút
   đánh) xuống dưới khung nhìn. Máy thấp thì bỏ sàn — sân bé còn hơn nút bị cắt. */
@media (max-height: 430px) {
  .farm-root #fmApp { min-height: 0; }
}
@media (max-width: 719px) {
  /* Dưới 720px thanh nav trên ẩn, tabbar dưới hiện — vẫn 56px, chỉ đổi chỗ. */
  .farm-root #fmApp { height: calc(100vh - 124px); }
  .farm-root #fmApp { height: calc(100dvh - 60px - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom)); }
}

/* Sân khấu: canvas đứng giữa, nền tối lấp phần thừa khi khung không đúng 16:9. */
.farm-root #fmStage {
  position: relative;   /* (G43) gốc toạ độ của #fmPop — xem chú thích ở khối toast */
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.farm-root #fmCanvas {
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  background: var(--bg);
}

/* HUD lớp — pha A còn trống (thuộc tính hidden trong shell); khung để sẵn. */
.farm-root #fmHud {
  display: flex;
  align-items: center;
  flex-wrap: wrap;   /* màn hẹp: cụm nút gói xuống dòng thay vì bị #fmApp overflow cắt mất */
  gap: 8px;
  row-gap: 4px;
  padding: 6px 10px;
  font-size: 14px;
}

/* Panel DOM cho bề mặt server (pha C). z-index 60 = tầng bảng của các game khác;
   TRẦN CỨNG là 70 (--z-imm của nút thoát toàn màn hình) — không luật nào ở đây được
   chạm tới nó. */
.farm-root #fmPanel {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 8, 16, 0.55);
}

/* Toast xếp chồng — đứng trên panel một bậc, vẫn dưới nút thoát.
   (G43) Neo vào #fmStage chứ không vào #fmApp: neo cũ đo từ mép trên của #fmApp, tức
   là toast rơi ĐÈ LÊN thanh chip #fmHud (94px trên máy dọc) — che đúng cái tên lớp và
   kho tài nguyên. #fmStage không tạo ngữ cảnh xếp lớp mới (position: relative mà
   z-index: auto), nên z 65 ở đây vẫn so bì với #fmPanel z 60 như trước. */
.farm-root #fmPop {
  position: absolute;
  top: 10px;
  /* (G43) left/right thay cho left:50% + translateX: với left:50% thì bề ngang khả
     dụng của thẻ chỉ còn NỬA sân, nên một toast lỗi 55 ký tự gói thành bốn dòng hẹp.
     Căn giữa nay là việc của align-items. */
  left: 8px;
  right: 8px;
  z-index: 65;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  pointer-events: none;
}

/* Ô nhập từ 16px trở lên, nếu không iOS tự phóng to cả trang khi chạm vào. */
.farm-root input, .farm-root textarea, .farm-root select { font-size: 16px; }

/* ---- body.s8-play: đang chơi ⇒ thanh tab dưới bị ẩn (xem css/ispeak.css) -------------
   Cùng lý lẽ với css/kingdom.css:485-497: 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. */
@media (max-width: 979px) {
  body.s8-play .farm-root { margin-bottom: 0; }
}
@media (max-width: 719px) {
  body.s8-play .farm-root #fmApp {
    height: calc(100vh - 60px);
    height: calc(100dvh - 60px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  }
}

/* ---- body.s8-imm: toàn màn hình ⇒ thanh TRÊN cũng đi (xem css/ispeak.css) ------------
   Chép khuôn css/kingdom.css:550-575, cùng từng lý do: thanh trên biến mất thì 60px
   phải trả lại cho sân, phần tai thỏ nó nuốt hộ phải có người nhận (gốc .farm-root),
   và 56px góc trên-phải nhường cho nút thoát #immExit của vỏ app. */
body.s8-play.s8-imm .farm-root { padding-top: env(safe-area-inset-top); }
/* 56px = 48px nút #immExit + 8px lề. Con số gốc ở css/ispeak.css, khối .s8-imm-exit. */
body.s8-play.s8-imm .farm-root #fmHud { padding-right: calc(env(safe-area-inset-right) + 56px); }
body.s8-play.s8-imm .farm-root #fmApp {
  height: calc(100vh - 60px);
  height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
}
@media (max-width: 719px) {
  /* Dòng `vh` GIỮ NGUYÊN 124 đã đo: iOS 14.3 không có dvh, và `100vh` ở đó là khung
     nhìn khi thanh Safari đã co — lớn hơn khung thật. Thà thừa một dải trống. */
  body.s8-play.s8-imm .farm-root #fmApp {
    height: calc(100vh - 124px);
    height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  }
}

/* ---- (pha B) chip tài nguyên LỚP + thẻ báo ------------------------------------------
   HUD là DOM vì nó là mặt của VÍ LỚP — tách hẳn bề mặt với xu vườn vẽ trong canvas
   (hai đồng "xu" không bao giờ đứng cùng một lớp hình). Luật mới nằm CUỐI file. */
.farm-root .fm-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.10);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}
.farm-root .fm-pop {
  padding: 4px 10px;
  border-radius: 8px;
  background: rgba(20, 16, 30, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.25);
  font-size: 14px;
  font-weight: 700;
}
.farm-root .fm-pop-err { border-color: #d66; color: #ffb3b3; }
.farm-root .fm-pop-ok { border-color: #6c6; }

/* ---- (pha C) panel server: thẻ công trình, Đình làng, Thánh Chiến, goblin -----------
   Chữ nhiều = DOM. Bảng màu lấy từ khối .kg-root (đo từ art Sunnyside, xem
   css/kingdom.css đầu file): nền kem #ead4aa, mực #181425, panel #c0cbdc.
   Vùng chạm: nút chính 44px, nút nhỏ/tab 36px — cùng con số css/kingdom.css:499-548. */
.farm-root .fm-modal {
  width: min(560px, calc(100% - 24px));
  max-height: calc(100% - 24px);
  overflow-y: auto;
  background: #ead4aa;
  color: #181425;
  border: 3px solid #181425;
  border-radius: 10px;
  padding: 14px;
  font-size: 15px;
  line-height: 1.45;
}
.farm-root .fm-modal h3 { margin: 0 0 6px; font-size: 18px; }
.farm-root .fm-muted { margin: 4px 0; opacity: 0.82; font-size: 14px; }
.farm-root .fm-lvl { font-size: 12px; opacity: 0.8; margin-left: 6px; }
.farm-root .fm-row { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
.farm-root .fm-btn {
  display: block;
  width: 100%;
  min-height: 44px;
  margin: 6px 0;
  border: 2px solid #181425;
  border-radius: 8px;
  background: #fff;
  color: #181425;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}
.farm-root .fm-row .fm-btn { width: auto; flex: 1; }
.farm-root .fm-btn-p { background: #8fc45c; }
.farm-root .fm-btn-d { background: #e6b8b8; }
.farm-root .fm-poor { opacity: 0.55; }
.farm-root .fm-btn-s {
  min-height: 36px;
  border: 2px solid #181425;
  border-radius: 8px;
  background: #fff;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  margin-left: 6px;
}
.farm-root .fm-tabs { display: flex; gap: 4px; flex-wrap: wrap; margin: 8px 0; }
.farm-root .fm-tab {
  min-height: 36px;
  padding: 4px 10px;
  border: 2px solid #181425;
  border-radius: 8px;
  background: #c0cbdc;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.farm-root .fm-tab.on { background: #8fc45c; }
.farm-root .fm-list { list-style: none; margin: 6px 0; padding: 0; }
.farm-root .fm-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 4px;
  border-bottom: 1px solid rgba(24, 20, 37, 0.18);
}
.farm-root .fm-list li.done { opacity: 0.6; }
.farm-root .fm-list li span { display: flex; flex-direction: column; min-width: 0; }
.farm-root .fm-list li small { opacity: 0.7; font-size: 13px; }
.farm-root .fm-land { list-style: none; margin: 6px 0; padding: 0; }
.farm-root .fm-land li { padding: 3px 0; opacity: 0.65; }
.farm-root .fm-land li.on { opacity: 1; }
.farm-root .fm-avas { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; margin: 8px 0; }
.farm-root .fm-ava {
  border: 2px solid #181425;
  border-radius: 8px;
  background: #fff;
  padding: 2px;
  cursor: pointer;
}
.farm-root .fm-ava.on { background: #8fc45c; }
/* (G43) canvas 40px trong lưới 6 cột tràn khỏi ô của nó dưới ~376px — kẹp theo ô. */
.farm-root .fm-ava canvas { image-rendering: pixelated; display: block; margin: 0 auto; max-width: 100%; height: auto; }
.farm-root .fm-hbtn {
  min-height: 36px;
  min-width: 40px;
  margin-left: 4px;
  padding: 0 10px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 15px;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}
.farm-root .fm-hbtn:first-of-type { margin-left: auto; }

/* ---- (pha D) banner raid: góc dưới-trái sân, node ổn định (shape-guard) ------------- */
.farm-root .fm-raidbar {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 55;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 10px;
  background: rgba(90, 16, 16, 0.88);
  border: 2px solid #e0b080;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}
.farm-root .fm-raidbar .fm-btn-s { margin-left: 0; }
.farm-root .fm-raidgo { background: #8fc45c; min-height: 40px; }

/* ---- (đợt chữ-to 08/2026) sân rộng: shell mang s8-wide ⇒ .view thành .view-wide,
   trần 760px thành 1360px và measure() của engine đạt k=2 — chữ canvas to gấp đôi mà
   không đổi một con số font nào. `.view-wide` padding là `0 0 64px` nên ba lề âm
   trên/trái/phải phải về 0, đáy giữ -64px (hai khung cùng padding-bottom).
   Viết `:is()` chứ không phải `.view-wide .farm-root`: luật phạm vi hoá của file này
   đòi mọi selector top-level bắt đầu bằng .farm-root — cùng khuôn với .gw-root. */
.farm-root:is(.view-wide *) { margin-top: 0; margin-left: 0; margin-right: 0; }

/* ---- (G2 + G43) BÀN PHÍM ẢO MOBILE: joystick MOBA + cụm nút -------------------------
   touch.js chỉ mở [hidden] trên máy trỏ thô; desktop không thấy gì. Lớp phủ tự nó
   KHÔNG ăn chạm (pointer-events none) — chỉ joystick và nút ăn, để chạm canvas (chọn
   hotbar, bắt đầu ở title) vẫn xuyên qua. touch-action none trên cả sân: thiếu nó thì
   kéo joystick là kéo cả trang (bài học css/gang.css:74-76). z-index 56: trên banner
   raid (55), dưới panel (60), xa trần cứng 70.

   (G43) SÁU nút thay MƯỜI BA. Bản cũ có 10 nút skill trên ba cung đồng tâm + 3 nút cột
   trái; đo trên máy thật: 375x667 chúng phủ 306px của một cái sân cao 513px, và ở màn
   NGANG 667x375 thì 📖/💬/s6..s10 nằm HẲN trên thanh #fmHud — hai lớp cùng ăn một cú
   chạm, nút HUD thành nút chết. Nay: joystick + ⚔️ + BA Ô KỸ NĂNG + E + 💨 + 🗺️ + ô
   nhân vật. Nội dung ba ô do người chơi chọn trong bảng Nhân vật (js/farm/charpanel.js).

   HAI BIẾN CHỊU LỰC. --fmjoy (cỡ joystick) và --fmjoyb (mép dưới của joystick) không
   phải để cho gọn: hàng ô-nhân-vật + bản đồ neo theo CHÍNH hai biến ấy, nên đổi cỡ
   joystick ở một chỗ là cả cột trái đi theo, không bao giờ lệch nhau nửa pixel. */
.farm-root #fmStage, .farm-root #fmTouch { touch-action: none; overscroll-behavior: none; }
.farm-root {
  --fmjoy: 104px;
  /* --fmbot do engine.measure() ghi: dải đen dưới canvas + chiều cao hàng công cụ.
     max() nên trên điện thoại (canvas lấp kín sân) con số cũ thắng, còn trên tablet
     dọc — nơi canvas bị kẹp ở 540x840 và nằm giữa hai dải đen — cả cột trái tự nhấc
     lên khỏi hàng công cụ. Chưa có biến (khung chưa đo xong) thì rơi về số cũ. */
  --fmjoyb: max(18px, var(--fmbot, 18px));
}
.farm-root #fmTouch {
  position: absolute;
  inset: 0;
  z-index: 56;
  pointer-events: none;
}
.farm-root #fmTouch[hidden] { display: none; }
/* (G43) CÓ BẢNG MỞ = LỚP PHỦ TẮT. Bảng canvas (chọn map, cửa hàng, nhà tù) vẽ TRONG
   canvas nên nằm DƯỚI z 56: trước đây cụm nút phủ đúng giữa bảng chọn map — và vì nút
   ăn chạm trước, bấm vào dòng "Chiến trường" là bấm vào cái nút nằm trên nó. Mà bảng
   chọn map là cửa đi lại DUY NHẤT trên điện thoại. engine gọi G.touch.setVeiled mỗi
   khung; display: none để không một khe hở nào còn nuốt chạm. */
.farm-root #fmTouch.fm-veil { display: none; }
.farm-root .fm-joy {
  position: absolute;
  left: calc(18px + env(safe-area-inset-left));
  bottom: calc(var(--fmjoyb) + env(safe-area-inset-bottom));
  width: var(--fmjoy);
  height: var(--fmjoy);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 2px solid rgba(255, 255, 255, 0.25);
  pointer-events: auto;
  touch-action: none;
}
.farm-root .fm-joy-knob {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 48px;
  height: 48px;
  margin: -24px 0 0 -24px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.28);
  border: 2px solid rgba(255, 255, 255, 0.5);
}

/* (G26 + G43) CỤM PHẢI XẾP THEO CUNG KIỂU MOBA, không phải lưới 3 cột.
   Lưới cũ xếp 6 nút thành 2 hàng 3 cột đều tăm tắp: ngón cái phải RỜI khỏi nút đánh
   rồi ngắm mới bấm được skill, và không có gì nói cho người chơi biết nút nào là nút
   chính. Bố cục dưới đây chép đúng nếp của thể loại: một nút đánh TO ở sát góc, ba ô
   kỹ năng nằm trên MỘT CUNG quanh nó trong tầm quét của cùng ngón cái, hai nút phụ
   (tương tác / lộn tránh) đẩy ra vòng ngoài để không bao giờ bấm nhầm.

   Toạ độ là KẾT QUẢ TÍNH chứ không ướm mắt: tâm nút đánh ở (52, 50) tính từ góc
   dưới-phải của khung; ba ô nằm trên cung bán kính 90 tại 16 / 52 / 88 độ (bước 36 độ).
   BÁN KÍNH VÀ CỠ NÚT RÀNG NHAU: dây cung giữa hai ô liền nhau là 2R.sin(18 độ) =
   0,618R, nên R 90 với nút 50 cho khe 5,6px — thấy được, và bài kiểm tính lại chính
   hai con số ấy từ CSS nên nắn một cái mà quên cái kia là đỏ.
   VÌ SAO BA CHỨ KHÔNG BỐN Ô: nút thứ tư trên cùng cung rơi ở 124 độ, tức right = -13 —
   NGOÀI mép phải khung. Muốn bốn thì phải nới cụm thêm ~50px, mà cụm 222 + joystick
   104 đã vừa khít 361px. Con số 3 là con số của cái điện thoại, không phải khẩu vị.

   KHUNG KHÔNG ĂN CHẠM (pointer-events: none), chỉ từng nút ăn. Lưới cũ để cả khung
   ăn chạm nên mọi khe hở giữa nút cũng nuốt cú chạm — mà ngay dưới cụm này là hàng
   công cụ vẽ ở đáy canvas, thứ người chơi phải chạm được để đổi cuốc/kiếm/hạt. */
.farm-root .fm-btns {
  position: absolute;
  right: calc(8px + env(safe-area-inset-right));
  bottom: calc(max(8px, var(--fmbot, 8px)) + env(safe-area-inset-bottom));
  width: 222px;
  height: 166px;
  pointer-events: none;
  touch-action: none;
}
.farm-root .fm-tbtn {
  position: absolute;
  width: 50px;
  height: 50px;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.5);
  background: radial-gradient(circle at 50% 32%, rgba(92, 104, 148, 0.92), rgba(22, 18, 36, 0.94) 78%);
  color: #fff;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  display: grid;
  place-items: center;
  pointer-events: auto;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45), inset 0 0 0 2px rgba(0, 0, 0, 0.3);
  transition: transform 70ms linear, filter 70ms linear;
}
/* Phản hồi khi ĐÈ: trên cảm ứng không có con trỏ nên nút phải tự nói là nó đã nhận —
   thiếu cái này thì lúc mạng/khung hình khựng, người chơi bấm lại lần hai. */
.farm-root .fm-tbtn:active { transform: scale(0.92); filter: brightness(1.35); }

/* Nút đánh: TO nhất, ấm màu nhất, sát góc nhất — ba tín hiệu cùng nói một điều. */
.farm-root .fm-atk {
  right: 10px;
  bottom: 8px;
  width: 84px;
  height: 84px;
  font-size: 32px;
  border-color: rgba(255, 196, 140, 0.8);
  background: radial-gradient(circle at 50% 30%, rgba(228, 118, 72, 0.95), rgba(118, 32, 28, 0.95) 76%);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5), inset 0 0 0 3px rgba(0, 0, 0, 0.3),
              0 0 14px rgba(255, 140, 70, 0.35);
}

/* Ba ô kỹ năng trên cung — xanh lạnh để tách hẳn khỏi nút đánh ấm. */
.farm-root .fm-skill {
  width: 50px;
  height: 50px;
  font-size: 22px;
  border-color: rgba(150, 200, 255, 0.62);
  background: radial-gradient(circle at 50% 30%, rgba(74, 116, 186, 0.92), rgba(18, 24, 52, 0.94) 78%);
}
.farm-root .fm-s1 { right: 114px; bottom: 50px; }
.farm-root .fm-s2 { right: 82px; bottom: 96px; }
.farm-root .fm-s3 { right: 30px; bottom: 115px; }

/* Hai nút phụ: vòng ngoài, xám trung tính — chúng không phải nút đánh nhau. Mỗi nút
   một luật ĐẦY ĐỦ (không gộp selector) vì bài kiểm hình học đọc từng khối theo tên. */
.farm-root .fm-e { right: 172px; bottom: 8px; font-size: 20px; }
.farm-root .fm-roll { right: 172px; bottom: 64px; }

/* ---- (G43) trạng thái của MỘT Ô KỸ NĂNG ---------------------------------------------
   Bốn trạng thái, và mỗi cái phải tự nói ra lý do NGAY TRÊN NÚT: thuộc tính title là
   thứ chỉ rê chuột mới đọc được, tức là trên điện thoại nó không tồn tại.
   * ô trống: dấu cộng mờ, không viền sáng — một lời mời, không phải một cái nút chết
   * còn khoá: mờ + huy hiệu "LvN" (skills.js ghi data-lv)
   * phép ngoài Chiến trường: mờ + dấu kiếm ở góc (data-off="war"), VẪN bấm được để cú
     bấm ăn được cái toast giải thích
   * họ PHÉP mang màu tím (data-fam="war") — giữ đúng mã màu hai-họ-nút của G31 dù nay
     hai họ dùng chung ba cái nút */
.farm-root .fm-skill[disabled] { opacity: 0.5; }
.farm-root .fm-skill[data-lv]::before {
  content: attr(data-lv);
  position: absolute;
  left: 50%;
  bottom: -3px;
  transform: translateX(-50%);
  padding: 0 4px;
  border-radius: 6px;
  background: rgba(12, 10, 22, 0.9);
  color: #ffd75e;
  font-size: 10px;
  font-weight: 700;
  line-height: 13px;
  pointer-events: none;
}
.farm-root .fm-skill[data-off="war"] { opacity: 0.62; }
.farm-root .fm-skill[data-off="war"]::before {
  content: "⚔";
  position: absolute;
  right: -2px;
  top: -4px;
  padding: 0 3px;
  border-radius: 6px;
  background: rgba(12, 10, 22, 0.9);
  color: #d8b8ff;
  font-size: 11px;
  font-weight: 700;
  line-height: 14px;
  pointer-events: none;
}
/* (G32) --fmicon do skills.js đặt khi icon PNG đã về: lớp nền TRÊN là icon 32px căn
   giữa (chưa đặt biến thì lớp là none — y nguyên nút cũ), lớp DƯỚI giữ gradient của
   họ nút. Đường ảnh đi qua biến CSS thay vì ghim thẳng vào đây: số phiên bản file (v1)
   sống trong warfxatlas.js sinh tự động, css không phải đổi theo mỗi lần dựng atlas.
   image-rendering pixelated: icon pixel-art phóng nguyên nét, không nhoè. */
.farm-root .fm-skill[data-fam="war"] {
  border-color: rgba(214, 160, 255, 0.62);
  background: var(--fmicon, none) center / 32px 32px no-repeat,
    radial-gradient(circle at 50% 30%, rgba(138, 84, 196, 0.92), rgba(34, 18, 52, 0.94) 78%);
  image-rendering: pixelated;
}
.farm-root .fm-skill[data-fam="melee"] {
  background: var(--fmicon, none) center / 32px 32px no-repeat,
    radial-gradient(circle at 50% 30%, rgba(74, 116, 186, 0.92), rgba(18, 24, 52, 0.94) 78%);
  image-rendering: pixelated;
}

/* ---- (G9) vành cooldown của ô kỹ năng ------------------------------------------------
   skills.js ghi biến --fmcd (phần trăm CÒN LẠI, 0-100) lên từng nút; VÀNH conic sáng
   quét theo chiều kim đồng hồ rồi thu dần về 0 — mask radial khoét rỗng tâm để icon
   luôn đọc được (đĩa đặc đè lên chữ inline là nút chìm trong bóng tối suốt cd 20s của
   khiên). Không z-index mới (lớp phủ là con của nút, đã nằm trong #fmTouch z 56).
   (G26) KHÔNG đặt lại position ở đây: nút đã absolute, tức đã là gốc toạ độ cho
   ::after. Cũng không overflow: hidden — huy hiệu cấp ::before thò xuống mép nút 3px. */
.farm-root .fm-skill { overflow: visible; }
.farm-root .fm-skill::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(rgba(255, 215, 94, 0.9) calc(var(--fmcd, 0) * 1%), transparent 0);
  -webkit-mask: radial-gradient(closest-side, transparent 62%, #000 64%);
  mask: radial-gradient(closest-side, transparent 62%, #000 64%);
  pointer-events: none;
}

/* ---- (G43) HÀNG CỘT TRÁI: ô nhân vật + nút bản đồ -----------------------------------
   Bản cũ xếp ba nút bản-đồ/hướng-dẫn/kỹ-năng thành một CỘT DỌC cao 362px ở mép trái —
   nó leo tới hai phần ba sân trên máy dọc, và ở màn ngang thì hai nút trên cùng chui
   hẳn ra khỏi #fmApp (bị overflow: hidden cắt mất, tức là nút vẽ ra mà không ai bấm
   được). Nay còn HAI nút, xếp thành một HÀNG NGANG ngay trên joystick — cao thêm đúng
   58px, không hơn. Neo theo --fmjoy/--fmjoyb nên joystick co lại là hàng tự tụt theo.
   pointer-events phải khai LẠI: .fm-tbtn thừa hưởng nó từ .fm-btns, hai nút này ở ngoài. */
.farm-root .fm-hero {
  position: absolute;
  left: calc(18px + env(safe-area-inset-left));
  bottom: calc(var(--fmjoyb) + var(--fmjoy) + 8px + env(safe-area-inset-bottom));
  width: 56px;
  height: 56px;
  padding: 0;
  border: 2px solid rgba(255, 215, 94, 0.75);
  border-radius: 12px;
  background: radial-gradient(circle at 50% 25%, rgba(92, 104, 148, 0.95), rgba(22, 18, 36, 0.95) 80%);
  color: #fff;
  display: grid;
  place-items: center;
  pointer-events: auto;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45), inset 0 0 0 2px rgba(0, 0, 0, 0.3);
}
.farm-root .fm-hero:active { transform: scale(0.94); filter: brightness(1.3); }
.farm-root .fm-hero-art {
  width: 48px;
  height: 48px;
  image-rendering: pixelated;
  pointer-events: none;
}
.farm-root .fm-hero-lv {
  position: absolute;
  left: 50%;
  bottom: -7px;
  transform: translateX(-50%);
  padding: 0 5px;
  border-radius: 6px;
  background: rgba(12, 10, 22, 0.92);
  border: 1px solid rgba(255, 215, 94, 0.5);
  color: #ffd75e;
  font-size: 10px;
  font-weight: 700;
  line-height: 14px;
  white-space: nowrap;
  pointer-events: none;
}
/* Nút bản đồ: cửa đi lại DUY NHẤT trên điện thoại từ khi bỏ cổng đi bộ — nên nó KHÔNG
   nằm trong cụm .fm-btns (một nút lạ trong cụm là một nút rơi khỏi cung), mà đứng cạnh
   ô nhân vật, tầm ngón cái trái, xa cụm nút đánh nhau bên phải. */
.farm-root .fm-map {
  position: absolute;
  left: calc(82px + env(safe-area-inset-left));
  bottom: calc(var(--fmjoyb) + var(--fmjoy) + 11px + env(safe-area-inset-bottom));
  pointer-events: auto;
  font-size: 20px;
}

/* ---- (G43) banner raid nhường chỗ cho cần điều khiển --------------------------------
   .fm-raidbar (js/farm/raid.js) neo góc dưới-trái, đúng chỗ joystick đứng: z 55 nằm
   dưới lớp phủ 56 nên cái nút "Chống trả" của nó chìm hẳn dưới vành cần. Trên máy cảm
   ứng đẩy nó lên TRÊN cả cột trái (joystick + hàng ô nhân vật = --fmjoy + 66).
   Chọn được cụm này bằng dấu ~ vì raid.js appendChild vào #fmApp, tức banner là em
   SAU #fmTouch; và [hidden] của #fmTouch giữ cho desktop nguyên như cũ. */
.farm-root #fmTouch:not([hidden]) ~ .fm-raidbar {
  bottom: calc(var(--fmjoyb) + var(--fmjoy) + 66px + env(safe-area-inset-bottom));
  max-width: calc(100% - 20px);
}

/* ---- (G13) MÀN DỌC: nhường chỗ cho sân chơi ----------------------------------------
   Trên điện thoại dọc, thanh công cụ gói thành ba hàng ăn gần 300px và cụm nút cảm ứng
   đè lên hàng công cụ vẽ ở đáy canvas (hàng ấy cao ~40px thật). Hai khối dưới đây chữa
   đúng hai chuyện đó — không đụng desktop. */
@media (max-width: 480px) {
  .farm-root #fmHud { gap: 5px; row-gap: 3px; padding: 4px 6px; font-size: 12px; }
  .farm-root .fm-chip { gap: 3px; padding: 2px 5px; font-size: 11px; }
  /* (G43) 30px là chiều cao của một nút mà đầu ngón tay trượt: nay thanh chip chỉ
     còn MỘT hàng nên 36px không còn đắt (hàng cao 44px, vẫn dưới nửa của 94px cũ). */
  .farm-root .fm-hbtn { min-height: 36px; min-width: 36px; margin-left: 3px; padding: 0 8px; font-size: 13px; }
}
@media (orientation: portrait) {
  .farm-root { --fmjoyb: max(58px, var(--fmbot, 58px)); }
  .farm-root .fm-btns { bottom: calc(max(56px, var(--fmbot, 56px)) + env(safe-area-inset-bottom)); }
}

/* (G26 + G43) MÁY HẸP: thu cả cụm thay vì xếp lại. Cụm 222 + joystick 104 + hai lề 26
   là vừa khít 361px; dưới ngưỡng đó chúng chạm nhau. Thu bằng transform giữ nguyên
   hình cung (xếp lại theo một bố cục thứ hai là hai thứ phải nuôi song song), và hệ số
   0.9 được chọn theo TRẦN DƯỚI của vùng chạm chứ không cho đẹp: nút nhỏ nhất
   50 x 0.9 = 45px, vẫn trên mức 44 mà tay người cần. Joystick co theo bằng biến — nó
   là vùng KÉO chứ không phải nút bấm nên 88px vẫn thoải mái, và 16px tiết kiệm được ở
   đây là 16px khe hở giữa cần và nút E trên máy 320px. */
@media (max-width: 360px) {
  .farm-root { --fmjoy: 88px; }
  .farm-root .fm-btns { transform: scale(0.9); transform-origin: 100% 100%; }
}

/* ---- (G43) THANH #fmHud MỘT HÀNG trên máy hẹp ---------------------------------------
   Đo thật trên 375x667: 6 chip + 6 nút gói thành BA HÀNG cao 94px — 18% màn hình cho
   một dải thông tin, và nó ăn đúng vào chiều cao của sân. flex-wrap: nowrap + cuộn
   ngang giữ đủ MỌI chip/nút (không xoá thông tin của ai) mà chỉ tốn một hàng 34px.
   Cuộn ngang an toàn ở đây: touch-action: none chỉ đặt trên #fmStage/#fmTouch, thanh
   này không nằm trong hai chỗ ấy. */
@media (max-width: 719px) {
  .farm-root #fmHud {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .farm-root #fmHud::-webkit-scrollbar { display: none; }
  /* KHÔNG cho co: flex-shrink mặc định là 1, nên trong một hàng nowrap mọi chip bị ép
     hẹp hơn nội dung và chữ chồng lên nhau (đo thật: "Việc làm" đè lên "Bản đồ"). */
  .farm-root #fmHud > * { flex: 0 0 auto; }
  /* Tên lớp là chuỗi người dùng tự đặt (bản solo tự sinh dài 40 ký tự): cắt bằng ba
     chấm chứ không để một cái chip dài hơn cả màn hình. */
  .farm-root .fm-chip { max-width: 45vw; overflow: hidden; text-overflow: ellipsis; display: inline-block; }
  /* nút hành động ĐỨNG TRƯỚC chip thông tin: hàng cuộn thì cái nằm bên phải là cái
     phải kéo mới thấy, mà "Xây"/"Việc làm" là thứ người chơi bấm, chip chỉ để đọc. */
  .farm-root .fm-hbtn { order: -1; }
  .farm-root .fm-hbtn:first-of-type { margin-left: 0; }
}

/* ---- (G43) BẢNG NHÂN VẬT (#fmChar, js/farm/charpanel.js) ----------------------------
   Cùng tầng và cùng bảng màu với #fmPanel (modal DOM của bề mặt server) — hai bảng
   không bao giờ mở cùng lúc (hiến chương một-modal-một-lúc, guard chéo ở charpanel.open
   và panels.onClick), nên chúng dùng chung z 60. Bảng này KHÔNG cần server: nó là cửa
   duy nhất tới ba ô kỹ năng, phải mở được cả khi học sinh chưa vào lớp. */
.farm-root #fmChar {
  position: absolute;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 8, 16, 0.55);
}
.farm-root #fmChar[hidden] { display: none; }
.farm-root .fm-char-top { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.farm-root .fm-char-art {
  width: 64px;
  height: 64px;
  flex: 0 0 auto;
  image-rendering: pixelated;
  border: 2px solid #181425;
  border-radius: 10px;
  background: #c0cbdc;
}
.farm-root .fm-char-meta { min-width: 0; flex: 1; }
.farm-root .fm-char-meta h3 { margin: 0 0 2px; }
.farm-root .fm-xp {
  height: 8px;
  margin: 4px 0;
  border-radius: 4px;
  background: rgba(24, 20, 37, 0.22);
  overflow: hidden;
}
.farm-root .fm-xp i { display: block; height: 100%; background: #e8a33d; }
/* Ba ô: lưới 3 cột cố định — chúng là BẢN SAO của ba nút trên sân, thứ tự và số lượng
   phải đọc ra ngay là "ô 1 / ô 2 / ô 3". */
.farm-root .fm-slots { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin: 6px 0 10px; }
.farm-root .fm-slot {
  min-height: 72px;
  padding: 4px 2px;
  border: 2px solid #181425;
  border-radius: 10px;
  background: #fff;
  color: #181425;
  font: inherit;
  display: grid;
  place-items: center;
  gap: 1px;
  cursor: pointer;
}
.farm-root .fm-slot.on { background: #8fc45c; box-shadow: inset 0 0 0 2px rgba(24, 20, 37, 0.45); }
.farm-root .fm-slot i { font-size: 11px; font-style: normal; opacity: 0.7; }
.farm-root .fm-slot b { font-size: 22px; line-height: 1.1; }
.farm-root .fm-slot small { font-size: 11px; line-height: 1.15; text-align: center; }
.farm-root .fm-slot-em { opacity: 0.45; }
/* Danh sách 10 kỹ năng: MỖI DÒNG LÀ MỘT NÚT cao 56px — bảng cũ (canvas) có dòng 15px
   ở khổ nhìn dọc, tức 22px thật, dưới xa mức 44px mà đầu ngón tay cần. */
.farm-root .fm-skilllist li { padding: 0; align-items: stretch; }
.farm-root .fm-skilllist li.on { background: rgba(143, 196, 92, 0.32); }
.farm-root .fm-skilllist li.lock { opacity: 0.62; }
.farm-root .fm-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 56px;
  padding: 6px 4px;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.farm-root .fm-pick-ico { flex: 0 0 30px; font-size: 22px; text-align: center; }
.farm-root .fm-pick-txt { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.farm-root .fm-pick-txt b { font-size: 14px; }
.farm-root .fm-pick-txt small { font-size: 12px; opacity: 0.78; }
.farm-root .fm-pick-num { opacity: 0.62; }
.farm-root .fm-pick em {
  flex: 0 0 auto;
  font-style: normal;
  font-size: 12px;
  font-weight: 700;
  padding: 4px 7px;
  border: 2px solid #181425;
  border-radius: 8px;
  background: #fff;
}
.farm-root .fm-tag-on { background: #8fc45c; }
.farm-root .fm-tag-lv { background: #e6d3b8; opacity: 0.8; }
