:root {
  --bg: #0f1419;
  --panel: #1a2332;
  --panel-border: #2d3a4f;
  --accent: #3d8bfd;
  --slot: #243044;
  --slot-hover: #2c3b55;
  --text: #e6edf3;
  --muted: #8b9cb3;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(61, 139, 253, 0.28);
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: "Segoe UI", system-ui, sans-serif;
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

#top {
  height: 48px;
  background: var(--panel);
  border-bottom: 1px solid var(--panel-border);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
  flex-shrink: 0;
}
#top button {
  background: var(--accent);
  color: white;
  border: none;
  padding: 6px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}
#top button:hover { filter: brightness(1.1); }
#top button:disabled { opacity: 0.5; cursor: default; filter: none; }
#status { font-size: 13px; color: var(--muted); }
#goldAmount {
  margin-left: auto;
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #e0c34a;
}
#fpsCounter { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
#tickTimer { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

#main {
  flex: 1;
  display: flex;
  min-height: 0;
}

#game-wrap {
  flex: 1;
  position: relative;
  overflow: hidden;
  background: #0a0e14;
}
canvas#world {
  display: block;
  width: 100%;
  height: 100%;
}

#sidebar {
  width: 460px;
  background: var(--panel);
  border-left: 1px solid var(--panel-border);
  display: flex;
  flex-direction: column;
  padding: 14px;
  gap: 14px;
  flex-shrink: 0;
}

.panel-title {
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  margin-bottom: 8px;
  text-align: center;
}

.sidebar-section {
  flex: 0.8;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

#minimap-wrap {
  background: #0d1117;
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 6px;
  flex: 1;
  min-height: 0;
}
canvas#minimap {
  display: block;
  width: 100%;
  height: 100%;
  background: #15202b;
  border-radius: 4px;
}
/* XP globe HUD — top-right corner of the game view itself, right next to
   where the minimap sits in the sidebar, RS3-style. Fixed size (not tied
   to any panel's own resizing) since it just needs enough room for the
   orb plus the "+N" text rising above it. */
canvas#xpGlobe {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 60px;
  height: 120px;
  pointer-events: none;
  z-index: 10;
}

.half-panel {
  flex: 1.2;
  background: #0d1117;
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
  min-height: 0;
}

#chatlog {
  flex: 1;
  overflow-y: auto;
  font-size: 12px;
  color: var(--text);
}
#chatlog div { margin-bottom: 3px; }
#chatlog div.chat-hidden { display: none; }

.chat-input-row {
  display: flex;
  margin-top: 6px;
}
.chat-input-row input {
  flex: 1;
  min-width: 0;
  background: #0d1117;
  border: 1px solid var(--panel-border);
  color: var(--text);
  font-size: 13px;
  padding: 8px 10px;
  border-radius: 5px;
}

.filter-row {
  display: flex;
  gap: 4px;
  margin-bottom: 6px;
}
.filter-btn {
  flex: 1;
  font-size: 10px;
  padding: 3px 0;
  background: #0d1117;
  border: 1px solid var(--panel-border);
  border-radius: 4px;
  color: var(--muted);
  cursor: pointer;
  text-transform: uppercase;
  letter-spacing: 0.4px;
}
.filter-btn.active {
  background: #1f6feb;
  border-color: #1f6feb;
  color: #ffffff;
}

/* Always-visible 24-slot (4x6) inventory grid, docked under the minimap —
   no stacking, so each slot is either empty or exactly one item, no qty
   badge needed here (unlike the bank, which does stack). Rows are sized to
   their (square) content rather than stretched to fill the panel — on a
   tall desktop window that leftover vertical space just becomes a gap above
   the toggle buttons instead of stretching every slot into a tall rectangle.
   align-content:start packs the grid to the top of that space. */
.inv-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: min-content;
  align-content: start;
  gap: 6px;
}
.inv-slot {
  background: var(--slot);
  border: 1px solid #1c2a3a;
  border-radius: 5px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  cursor: default;
  user-select: none;
  aspect-ratio: 1 / 1;
}
/* A material's icon (see game.js's materialIconHtml — hand-drawn <img> or
   a plain-emoji <span> fallback), wherever it shows up: the inventory
   grid, the bank grid, and a MaterialShop's own buy list. One shared class
   so its size is one number to change, not one per call site — an <img>
   doesn't inherit its parent's font-size the way an emoji <span> does, so
   this sets both explicitly rather than relying on inheritance for one and
   an explicit width for the other. Square source art (see
   MATERIAL_ICON_SIZE) means width alone sizes the image; height follows
   via its own aspect ratio. */
.material-icon { width: 40px; font-size: 34px; flex-shrink: 0; }

/* Skills tab — same "inventory-style" stacked-card grid as .inv-grid
   above, just 5 columns (not 6) since each card carries more than an icon
   (level + XP progress too), so needs a bit more room per cell. */
.skills-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-auto-rows: min-content;
  align-content: start;
  gap: 6px;
}
.skill-card {
  background: var(--slot);
  border: 1px solid #1c2a3a;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 8px 2px;
  cursor: pointer;
  user-select: none;
}
.skill-card:hover { background: var(--slot-hover); }
/* font-size sizes the emoji fallback (an inline <span> ignores width);
   width sizes a hand-drawn material <img> instead (see game.js's own
   skillIconHtml) — the two coexist harmlessly on one class since each only
   affects the element type it actually applies to. */
.skill-card-icon { font-size: 26px; width: 30px; }
.skill-card-level { font-size: 12px; font-weight: 700; color: var(--text); }
.skill-card-xp { font-size: 9px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* A sell shop is open and this item isn't in its catalog (see
   renderInventory/currentSellShopItemIds) — dimmed rather than hidden, so
   it's still obvious what you're carrying, just not what this particular
   shop will take. */
.inv-slot.not-sellable { opacity: 0.35; }

.bank-grid {
  display: grid;
  grid-template-columns: repeat(10, 56px);
  gap: 6px;
  max-height: 50vh;
  /* overflow-y alone still made the x-axis compute to 'auto' too (per the
     CSS overflow spec, a non-visible value on one axis forces 'visible' on
     the other to become 'auto'), so any tiny rounding mismatch showed up as
     a horizontal scrollbar. Pin it to hidden explicitly — the panel below
     no longer has a fixed width that could mismatch the grid anyway. */
  overflow-y: auto;
  overflow-x: hidden;
  margin-bottom: 12px;
}
/* #bankOverlay has no fixed width (unlike most overlays) — it shrink-wraps
   to the grid's own exact 10-column width instead of a hand-picked number,
   so the two can never drift out of sync. A bit of extra side padding on
   top of .overlay-panel's base 16px just gives it some visible breathing
   room rather than being a knife's-edge-exact fit. */
#bankOverlay {
  padding-left: 24px;
  padding-right: 24px;
}
/* Bank's own filter row (unlike chat's) is icon-sized, not stretched full
   width — "All" stays text, the three category buttons show their skill
   icon instead (see the title attribute for the hover name) — so the whole
   row sits centered as a compact group rather than spanning edge to edge. */
#bankOverlay .filter-row {
  justify-content: center;
  gap: 8px;
  margin-bottom: 14px;
}
#bankOverlay .filter-btn {
  flex: none;
  padding: 6px 16px;
  font-size: 16px;
}
/* Sized for this button's own compact padding above — see
   .skill-card-icon's own comment for why font-size (the emoji fallback)
   and width (a hand-drawn material <img>) coexist on one class. */
.bank-filter-icon { font-size: 16px; width: 22px; }
.bank-slot {
  width: 56px;
  height: 56px;
  background: var(--slot);
  border: 1px solid #1c2a3a;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  cursor: pointer;
  position: relative;
}
.bank-slot:hover { background: var(--slot-hover); }
.bank-slot-qty {
  position: absolute;
  bottom: 2px;
  right: 4px;
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Small floating action menu for a single inventory/bank slot click (e.g.
   "Deposit 1" / "Deposit All", "Withdraw 1" / "Withdraw 12" / "Withdraw
   All") — positioned in JS next to whichever slot was clicked. Fixed, not
   absolute, so it isn't affected by any scrolling ancestor. */
.slot-menu {
  position: fixed;
  display: none;
  flex-direction: column;
  gap: 2px;
  background: rgba(13, 17, 23, 0.97);
  border: 1px solid var(--panel-border);
  border-radius: 6px;
  padding: 4px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.5);
  z-index: 30;
}
.slot-menu.visible { display: flex; }
.slot-menu button {
  background: var(--slot);
  color: var(--text);
  border: none;
  padding: 6px 14px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
  text-align: left;
  white-space: nowrap;
}
.slot-menu button:hover { background: var(--slot-hover); }

/* Clothing popup — same row-button look as .item-row, but clickable and
   with a "worn" state (see rebuildClosetList/updateClosetUI). */
.closet-grid {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 280px;
  max-height: 50vh;
  overflow-y: auto;
  margin-bottom: 12px;
}
.closet-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--slot);
  border-radius: 5px;
  border: 1px solid #1c2a3a;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
}
.closet-item:hover { background: var(--slot-hover); }
/* Was just a subtle border-color swap before — easy to miss at a glance.
   A visibly thicker border plus a tinted fill and bolder name reads as
   "currently worn" without needing a whole separate badge/icon. */
.closet-item.worn {
  border: 2px solid var(--accent);
  background: rgba(61, 139, 253, 0.16);
  font-weight: 700;
}
.closet-icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
.color-swatch {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 1px solid rgba(255,255,255,0.3);
}

/* ---------- Shops ---------- */
.shop-gold {
  font-size: 13px;
  font-weight: 600;
  color: #e0c34a;
  margin-bottom: 10px;
}
.shop-hint {
  font-size: 13px;
  color: var(--muted);
  max-width: 260px;
  margin-bottom: 14px;
}
/* .shop-item builds on .closet-item's own row look (icon + name) and is a
   real <button> now (not a plain div with an inline Buy button inside it)
   — clicking anywhere on an unowned row opens the buy-confirm popup (see
   showBuyConfirm); an owned row is just disabled, no click action. */
.shop-item {
  justify-content: space-between;
}
.shop-item.owned { opacity: 0.6; cursor: default; }
.shop-item-name { flex: 1; text-align: left; }
.shop-price {
  color: #e0c34a;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.shop-owned-tag {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  flex-shrink: 0;
}

/* Buy-confirmation popup — stacks on top of #buyShopOverlay (see its own
   comment in index.ejs). */
#buyConfirmCanvas {
  display: block;
  /* Fixed CSS size regardless of the backing buffer's actual pixel
     dimensions — see renderBuyConfirmPreview's own DPR handling, which
     sizes the buffer itself larger on a HiDPI screen. */
  width: 170px;
  height: 170px;
  margin: 0 auto 10px;
  /* Same green as the Town zone's own dark ground tile (see ZONES[0].tileB
     in game.js) rather than this UI's usual near-black slot background —
     a lot of clothing/pets (Black T-Shirt, Black Crab, Black Penguin, ...)
     are dark enough to disappear against that, and this reads as "the
     world you're actually standing in" instead of an arbitrary color. */
  background: #63a049;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
}
.buy-confirm-price {
  font-size: 15px;
  font-weight: 700;
  color: #e0c34a;
  margin-bottom: 14px;
}

#travelMapCanvas {
  display: block;
  width: 380px;
  height: 260px;
  margin: 0 auto 10px;
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  cursor: default;
}
.buy-confirm-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
}
.buy-confirm-actions .panel-close-btn { flex: 1; }

/* ---------- Overlays ---------- */
.overlay-panel {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: rgba(13, 17, 23, 0.94);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 16px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.55);
  z-index: 20;
  display: none;
  text-align: center;
  max-width: 94vw;
}
.overlay-panel.visible { display: block; }

/* Chat — anchored bottom-left of the game view (RuneScape-style) instead of
   centered like the other panels, and left-aligned specifically so it
   never overlaps the combat bar, which is centered along the same bottom
   edge. Toggled via the top-bar Chat button or Enter (see toggleChat). */
.chat-panel {
  position: absolute;
  left: 16px;
  bottom: 16px;
  width: 380px;
  height: 260px;
  background: rgba(13, 17, 23, 0.94);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  padding: 10px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.55);
  z-index: 20;
  display: none;
  flex-direction: column;
}
.chat-panel.visible { display: flex; }

#loginOverlay .panel-title { margin-bottom: 14px; }
#loginOverlay input {
  display: block;
  width: 100%;
  background: #0d1117;
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 14px;
  margin-bottom: 10px;
}
#loginOverlay .panel-close-btn { width: 100%; margin-top: 4px; }
/* Single message slot for the login card — connecting/disconnected status
   and a real error both just replace this same element's text, whichever
   is current, rather than two separate divs that could both show text at
   once. Muted by default (plain status, e.g. "Connected – joining…" —
   nothing has gone wrong); .error (set by showLoginOverlay, never plain
   setStatus) switches it to red for an actual failure/close reason. */
.login-message {
  min-height: 16px;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 6px;
}
.login-message.error { color: #e85d4c; }

/* Nothing but the login card itself until you've actually joined — the
   rest of the UI (top bar, sidebar, chat, world canvases) only makes sense
   once you're actually in the world, and showing it empty/idle behind the
   login card just looked cluttered. body.logged-in is added/removed by
   hideLoginOverlay/showLoginOverlay — see their own comments. Uses the ID
   selectors here (not classes) specifically so this reliably overrides
   .chat-panel.visible, which is otherwise the default state of #chatOverlay
   in the markup. */
body:not(.logged-in) #top,
body:not(.logged-in) #sidebar,
body:not(.logged-in) #chatOverlay,
body:not(.logged-in) canvas#world,
body:not(.logged-in) canvas#xpGlobe {
  display: none;
}

/* NPC dialogue box — RuneScape-style: anchored near the bottom of the
   game view rather than centered, with the speaker's name as a header
   and a "click to continue" affordance instead of an explicit close
   button, since dialogue is meant to be advanced/dismissed by clicking
   the box itself. */
#dialogueBox {
  position: absolute;
  left: 50%;
  bottom: 28px;
  transform: translateX(-50%);
  width: 520px;
  max-width: 90vw;
  background: rgba(13, 17, 23, 0.96);
  border: 1px solid var(--panel-border);
  border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.55);
  z-index: 20;
  display: none;
  cursor: pointer;
  user-select: none;
}
#dialogueBox.visible { display: block; }
#dialogueName {
  background: var(--accent);
  color: white;
  font-weight: 700;
  font-size: 13px;
  padding: 8px 14px;
  border-radius: 8px 8px 0 0;
}
#dialogueText {
  padding: 16px 14px 8px;
  font-size: 14px;
  line-height: 1.5;
  min-height: 2.4em;
}
#dialogueContinue {
  padding: 0 14px 10px;
  text-align: right;
  font-size: 11px;
  color: var(--muted);
  animation: dialoguePulse 1.4s ease-in-out infinite;
}
@keyframes dialoguePulse {
  0%, 100% { opacity: 0.5; }
  50% { opacity: 1; }
}

/* Combat bar — placeholder equipment shell shown only while actively
   fighting. No real equipment/crafting system behind these yet. */
#combatBar {
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  display: none;
  gap: 8px;
  padding: 8px;
  background: rgba(13, 17, 23, 0.9);
  border: 1px solid var(--panel-border);
  border-radius: 10px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.55);
  z-index: 20;
}
#combatBar.visible { display: flex; }
.combat-slot {
  width: 44px;
  height: 44px;
  background: var(--slot);
  border: 1px solid #1c2a3a;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.combat-slot-special { border-color: #7a5a1f; color: #e0c34a; }

.item-list { max-height: 50vh; overflow-y: auto; text-align: left; margin-bottom: 12px; }
.panel-hidden { display: none; }
/* Stretches to fill the sidebar panel (like .inv-grid's flex:1) so the
   toggle buttons underneath stay pinned to the bottom instead of riding up
   against however few skills there are. */
#skillsList { flex: 1; min-height: 0; max-height: none; margin-bottom: 0; }
.category-header {
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin: 12px 0 4px;
}
.category-header:first-child { margin-top: 0; }
.item-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--slot);
  border-radius: 5px;
  border: 1px solid #1c2a3a;
  font-size: 13px;
  margin-bottom: 5px;
}
.item-icon { font-size: 16px; width: 22px; text-align: center; flex-shrink: 0; }
.item-name { flex: 1; }
.item-qty { color: var(--muted); font-variant-numeric: tabular-nums; }
.empty-hint { color: var(--muted); font-size: 13px; padding: 10px 0; }

.panel-close-btn {
  background: var(--accent);
  color: white;
  border: none;
  padding: 6px 20px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}

.menu-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 12px;
}
.menu-btn {
  background: var(--slot);
  color: var(--text);
  border: 1px solid #1c2a3a;
  padding: 10px 14px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
}
.menu-btn:hover { background: var(--slot-hover); }

.account-info-row {
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 6px;
}

/* A server chat message (see OP.CHAT/isServer), a sell/buy result, and a
   few other one-off notices all pop through here instead of just sitting
   in the chat log. Each call to showToast gets its own stacked element
   (see #toastContainer) rather than overwriting one shared one — more
   than one toast-worthy thing can genuinely land close together (a shop
   purchase result alongside an unrelated server chat line, say), and a
   single shared slot would just have the second call erase the first
   before it was ever readable. Fixed + centered so it reads immediately
   without having to glance at the chat panel; z-index above .slot-menu
   (30, currently the highest in this file) so it never sits dimmed behind
   an open panel. */
#toastContainer {
  position: fixed;
  top: 50px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
  z-index: 31;
}
.toast-item {
  background: rgba(13, 17, 23, 0.94);
  border: 1px solid var(--panel-border);
  color: var(--text);
  padding: 8px 18px;
  border-radius: 10px;
  font-size: 14px;
  opacity: 0;
  white-space: nowrap;
  transition: opacity 0.25s ease;
}
.toast-item.visible { opacity: 1; }