* { box-sizing: border-box; }

/* ============ Slate & Ember design tokens ============
   The whole UI reads from this one palette. Panels are translucent near-black glass
   (the location backdrop blurs through them — see the chrome block at the end of this
   file), with a single warm ember accent. Change these to retheme every gump at once. */
:root {
  --shade: #10131a;                       /* page ground + darkest wells (old #1c140d) */
  --panel: rgba(15, 17, 22, 0.85);        /* gump/panel glass (old #2b1f14) */
  --panel-deep: #12151c;                  /* gradient partner for --panel (old #241a10) */
  --well: #0b0d12;                        /* inputs, tracks, inset areas (old #16100a) */
  --edge: rgba(255, 158, 88, 0.24);       /* panel + control borders (old bronze #8a6d3b) */
  --accent: #ffb578;                      /* headers, highlights, hovers (old gold #c9a227) */
  --accent-strong: #f08a42;               /* primary button gradient top */
  --accent-deep: #d97a35;                 /* primary button gradient bottom */
  --accent-bright: #ffc98f;               /* button hover */
  --ink: #d4d8de;                         /* body text (old cream #e8d9b8) */
  --ink-dim: #c3c8d0;                     /* secondary text */
  --muted: #9aa1ab;                       /* labels, captions */
  --on-accent: #16110b;                   /* dark text on ember fills */
}

body {
  margin: 0;
  font-family: system-ui, 'Segoe UI', sans-serif;
  background: var(--shade);
  color: var(--ink);
  cursor: url('/assets/sprites/cursors/default.png') 2 2, auto;
}

/* The main game view's town/dungeon backdrop — a procedurally-generated scene per
   location (see tools/generateBackdrops.js), swapped live on arrival by
   public/js/travel.js. Scoped to .game-page (not the shared `body` rule above, which
   the login/register pages also use) via an inline custom property set server-side in
   views/game.ejs from the character's current location, updated client-side with no
   page reload. More specific than the plain `body` rule above, so it wins even though
   that rule's `background` shorthand implicitly resets background-image too. */
body.game-page {
  background-image: var(--backdrop-url, none);
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
}

/* The "open road" scene shown while a trip is in progress, cross-faded over the real
   backdrop above. Toggled by public/js/travel.js on the travelDestination false<->true
   edge, which also sets --transition-duration inline per phase (a slow 7s fade out on
   departure, a snappier fade back in on arrival) — so only the property list lives here.
   Fixed below every gump (gumpWindow.js's z-index counter starts at 950) so panels stay
   fully usable mid-trip. */
.travel-backdrop-overlay {
  position: fixed;
  inset: 0;
  z-index: 5;
  background-image: url('/assets/backdrops/traveling.png');
  background-size: cover;
  background-position: 50% 50%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 3s ease;
}

.travel-backdrop-overlay.visible {
  opacity: 1;
  /* Slow drift so the "traveling" screen doesn't feel like a static freeze-frame during
     longer trips — purely decorative, no fixed duration tie to the trip length itself. */
  animation: travel-backdrop-drift 24s ease-in-out infinite alternate;
}

@keyframes travel-backdrop-drift {
  from { background-position: 50% 50%; }
  to { background-position: 54% 46%; }
}

.travel-backdrop-countdown {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  text-shadow: 0 2px 12px rgba(0, 0, 0, 0.85), 0 0 30px rgba(0, 0, 0, 0.5);
  /* The overlay itself stays pointer-events:none so the scene never blocks clicks on
     whatever's behind it while not visible — but that also swallows clicks on the
     Cancel Travel button now living in here, since pointer-events:none cascades to
     children. Re-enabling it just on this block (not the whole overlay) keeps that
     click-through behavior everywhere except the actual countdown/button area. */
  pointer-events: auto;
}

.travel-backdrop-destination {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 22px;
  color: var(--ink);
  margin-bottom: 6px;
}

.travel-backdrop-time {
  font-size: 40px;
  font-weight: bold;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
}

/* Mirrors #travel-cancel-btn's disabled cutoff (public/js/travel.js), just sized to
   read clearly against the full-screen backdrop scene instead of the World Map panel's
   compact 12px version — still a secondary action under the big countdown, not the
   focal point, so it stops short of the primary-CTA treatment (.death-overlay-teleport-btn). */
.travel-backdrop-cancel-btn {
  display: block;
  margin: 18px auto 0;
  padding: 8px 24px;
  background: rgba(16, 19, 26, 0.65);
  border: 1px solid var(--edge);
  border-radius: 5px;
  color: var(--accent);
  font-weight: bold;
  font-size: 15px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.travel-backdrop-cancel-btn:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.travel-backdrop-cancel-btn:disabled {
  background: rgba(16, 19, 26, 0.65);
  border-color: rgba(255, 255, 255, 0.12);
  color: var(--muted);
  cursor: not-allowed;
}

.travel-backdrop-cancel-window {
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted);
}

/* Chat stays fully visible and usable while traveling — its normal in-flow spot near
   the bottom of the page is exactly what the full-screen travel-backdrop-overlay
   (z-index: 5) covers. Pinned to a corner above it only while a trip is actually in
   progress (public/js/travel.js toggles this class alongside the overlay's own
   `visible` one) — snaps back to its ordinary stacked position the instant you arrive. */
.chat-panel.travel-pinned {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 6;
  width: 400px;
  margin: 0;
}

/* Dark parchment track, bronze thumb that lights up gold on hover — matches every other
   scrollable panel's palette (journal/chat log, bank/backpack, skills gump) instead of
   leaving the browser's default light-grey bars clashing with the theme. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--edge) var(--well);
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track {
  background: var(--well);
}

*::-webkit-scrollbar-thumb {
  background: var(--edge);
  border-radius: 5px;
  border: 2px solid var(--well);
}

*::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

.auth-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  padding: 40px 16px;
  background:
    radial-gradient(ellipse at 50% 0%, rgba(255, 255, 255, 0.05), transparent 60%),
    var(--shade);
}

.auth-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  width: 100%;
  max-width: 380px;
}

.auth-logo {
  width: 180px;
  height: 180px;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.6));
}

.dev-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 16px;
  background: rgba(201, 122, 39, 0.15);
  border: 1px solid #c97a27;
  border-radius: 6px;
  color: #e8a765;
  font-size: 13px;
  font-weight: bold;
  text-align: center;
  justify-content: center;
  letter-spacing: 0.3px;
}

/* Shared "parchment card" look for both the login box and the Testers Wanted box —
   a gold double-border and subtle inner glow, distinct from the flat game-panel style
   used once you're actually in the game (this is the first thing anyone sees). */
.auth-box, .testers-box {
  width: 100%;
  background: linear-gradient(180deg, var(--panel), var(--panel-deep));
  border: 1px solid var(--edge);
  outline: 1px solid rgba(255, 158, 88, 0.25);
  outline-offset: -5px;
  padding: 28px 32px;
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  text-align: center;
}

.auth-box h1, .testers-box h2 {
  margin: 0 0 4px;
  color: var(--accent);
  font-size: 22px;
  letter-spacing: 0.5px;
}

.testers-box h2 { font-size: 18px; }

.auth-subtitle {
  margin: 0 0 20px;
  font-size: 13px;
  color: var(--muted);
}

.auth-box form, .testers-box form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: left;
}

.auth-box label, .testers-box label {
  display: flex;
  flex-direction: column;
  font-size: 13px;
  color: var(--ink-dim);
  gap: 5px;
}

.auth-box input, .auth-box textarea, .testers-box input, .testers-box textarea {
  padding: 9px 10px;
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: 4px;
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
}

.auth-box input:focus, .auth-box textarea:focus, .testers-box input:focus, .testers-box textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(255, 158, 88, 0.25);
}

.auth-box textarea, .testers-box textarea {
  resize: vertical;
  min-height: 60px;
}

.auth-box button, .testers-box button {
  margin-top: 6px;
  padding: 10px;
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  border-radius: 4px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 14px;
  letter-spacing: 0.3px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.auth-box button:hover, .testers-box button:hover { background: var(--accent); }

.auth-box p, .testers-box p { font-size: 13px; }

.auth-box a { color: var(--accent); }

.error { color: #e25c4f; }

.success-message { color: #7fd1a8; font-size: 13px; }

.field-feedback {
  margin: -8px 0 0;
  font-size: 12px;
  min-height: 14px;
}

.password-checklist {
  list-style: none;
  margin: -8px 0 0;
  padding: 0;
  font-size: 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rule-met { color: #7fd1a8; }
.rule-unmet { color: var(--muted); }

.auth-note {
  font-size: 12px;
  color: var(--muted);
  text-align: center;
}

.testers-box {
  border-color: rgba(255, 255, 255, 0.12);
}

.testers-box p.auth-subtitle { margin-bottom: 16px; }

.game-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 20px;
  background:
    radial-gradient(ellipse 260px 100% at 90px 50%, rgba(255, 181, 120, 0.14), transparent 70%),
    var(--panel);
  border-bottom: 3px solid var(--edge);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35), 0 1px 0 rgba(255, 181, 120, 0.12) inset;
  /* Sticky (was static) since the header crest hangs off this bar (.header-crest near
     the end of this file) — pinning the header keeps the XP bar and attack/armor/
     weight badges it carries always on screen, matching the always-visible guarantee
     they had inside the old position:fixed Status gump. z-index above the scrolling
     panels but below floating gumps (gumpWindow.js starts its shared z counter at 950). */
  position: sticky;
  top: 0;
  z-index: 60;
}

/* The character name/vitals used to live here (nameplate.ejs, inline) — that content is
   now the always-visible, draggable Status gump (statusGump.ejs) instead, so the header
   itself just needs the wordmark in its place. */
.game-header-title {
  display: flex;
  align-items: center;
}

/* logo-480.png already bakes in the "TIS" wordmark (stone/gold emblem) — a plain
   drop-shadow glow plus a hover lift is enough to make it read as the page's anchor
   point instead of another flat text label next to the nav links. */
.game-header-logo {
  height: 46px;
  width: auto;
  display: block;
  /* The logo PNG is a square canvas with an opaque near-black background, so this
     actually clips visible corners (it would be a no-op on transparent-background art). */
  border-radius: 10px;
  filter: drop-shadow(0 0 10px rgba(255, 181, 120, 0.45)) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7));
  transition: filter 0.25s ease, transform 0.25s ease;
}

.game-header-logo:hover {
  filter: drop-shadow(0 0 16px rgba(255, 181, 120, 0.8)) drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7));
  transform: scale(1.06);
}

.game-header-right {
  display: flex;
  align-items: center;
  gap: 20px;
}

.game-header-nav {
  display: flex;
  align-items: center;
  gap: 16px;
}

.game-header-nav a {
  color: var(--accent);
  text-decoration: none;
  font-size: 14px;
}

.game-header-nav a:hover { text-decoration: underline; }

.game-header-nav-button {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 14px;
  font-family: inherit;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  padding: 0;
}

.game-header-nav-button:hover { text-decoration: underline; }

/* The nameplate — character name plus a compact Hits/Mana/Stam bar stack, styled
   after the classic UO overhead status bars. Bar colors match their classic UO
   status-window counterparts (red/blue/green); no combat system moves current
   below max yet, see calculateVitals in services/combatStats.js. */
.nameplate {
  /* relative so spell FX (spellCast.js's player-targeted spawnFx) can anchor over the
     vitals bars rather than the whole floating gump. */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.nameplate-name {
  font-size: 20px;
  color: var(--accent);
  font-weight: bold;
}

.nameplate-bars {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 190px;
}

.nameplate-bar-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
}

.nameplate-bar-tag {
  width: 30px;
  flex-shrink: 0;
  font-size: 9px;
  text-transform: uppercase;
  color: var(--muted);
}

.nameplate-bar {
  flex: 1;
  height: 7px;
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 3px;
  overflow: hidden;
}

.nameplate-bar-fill {
  height: 100%;
}

.nameplate-bar-fill.nameplate-bar-hits { background: #e25c4f; }
.nameplate-bar-fill.nameplate-bar-mana { background: #5d8ef0; }
.nameplate-bar-fill.nameplate-bar-stam { background: #8fc84f; }

.nameplate-bar-value {
  width: 42px;
  flex-shrink: 0;
  text-align: right;
  font-size: 10px;
  color: var(--ink-dim);
}

/* The Status gump — same floating-window chrome as every other gump (see the comment
   above .skills-gump), just wrapping the .nameplate content instead of living fixed in
   the header. No close button, so no matching -close rule is needed. */
.status-gump {
  width: 220px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.status-gump .nameplate {
  padding: 10px 14px;
}

.game-main {
  display: flex;
  gap: 40px;
  padding: 30px;
  flex-wrap: wrap;
}

.journal-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px;
  margin: 0 30px 30px;
}

.journal-panel h2 {
  margin-top: 0;
  color: var(--accent);
  font-size: 16px;
}

.journal-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.journal-tab {
  padding: 4px 12px;
  background: var(--shade);
  border: 1px solid var(--edge);
  border-radius: 3px;
  color: var(--muted);
  font-size: 12px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.journal-tab.active {
  background: var(--edge);
  color: var(--on-accent);
}

.journal-log {
  height: 160px;
  overflow-y: auto;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 8px;
  font-size: 13px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.journal-entry.journal-fail { color: #e25c4f; }
.journal-entry.journal-success { color: #7fd1a8; }
.journal-entry.journal-skill { color: #6ec6ff; }
.journal-entry.journal-stat { color: #4caf50; }
.journal-entry.journal-info { color: var(--ink); }
.journal-entry.journal-vendor { color: #e0a854; font-style: italic; }
.journal-entry.journal-devtool { color: #d896ff; text-shadow: 0 0 4px rgba(216, 150, 255, 0.6); }
.journal-entry.journal-combat { color: #e07b39; }
.journal-entry.journal-chatter { color: #9fb88a; font-style: italic; }
.journal-entry.journal-loot { color: #f4c430; font-weight: bold; }
/* A GM /broadcast (sockets/adminHandlers.js) — deliberately the loudest line in the
   Journal, gold-on-glow like .journal-loot but bordered so it reads as an announcement
   rather than a drop. */
.journal-entry.journal-broadcast {
  display: block;
  color: var(--accent-bright);
  font-weight: bold;
  text-shadow: 0 0 6px rgba(255, 158, 88, 0.6);
  border-left: 2px solid var(--accent);
  padding-left: 6px;
  margin: 2px 0;
}

/* Death: no auto-res anymore — the world goes greyscale until "Res" is said in Local
   chat. Scoped to #game-world (header/main/journal/chat), NOT <body> — a CSS filter on
   an ancestor establishes a new containing block for any position:fixed descendant
   (same as transform/perspective), so a body-level filter would make every draggable
   gump (all position:fixed via gumpWindow.js, lock button or not — Status, Bank,
   Stable, Skills, Travel, Guild, DevTools, Combat, GroundLoot, Spellbook) drift with
   page scroll instead of staying pinned to the viewport. The gumps and the death banner
   live outside this wrapper (see views/game.ejs) and stay full-color/stationary. */
.game-world.is-dead { filter: grayscale(1); }

.death-overlay {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 18px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}

.death-overlay-title { color: #e25c4f; font-weight: bold; font-size: 15px; }
.death-overlay-text { color: var(--ink); font-size: 13px; }

.death-overlay-teleport-btn {
  padding: 6px 14px;
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  border-radius: 4px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 13px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}
.death-overlay-teleport-btn:hover { background: var(--accent); }

.journal-action-link {
  display: inline-block;
  margin-left: 4px;
  padding: 1px 8px;
  font-size: 11px;
  font-family: inherit;
  color: var(--on-accent);
  background: var(--accent);
  border: 1px solid var(--edge);
  border-radius: 10px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  line-height: 1.6;
}

.journal-action-link:hover { background: var(--accent-bright); }

.chat-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px;
  margin: 0 30px 30px;
}

.chat-panel h2 {
  margin-top: 0;
  color: var(--accent);
  font-size: 16px;
}

.chat-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 8px;
}

.chat-tab {
  padding: 4px 12px;
  background: var(--shade);
  border: 1px solid var(--edge);
  border-radius: 3px;
  color: var(--muted);
  font-size: 12px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.chat-tab.active {
  background: var(--edge);
  color: var(--on-accent);
}

.chat-log {
  height: 120px;
  overflow-y: auto;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 8px;
  font-size: 13px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 10px;
}

.chat-entry-name {
  font-weight: bold;
}

/* Each channel gets its own color so a message's origin is unmistakable at a glance —
   Local reads as plain speech, Global a cool blue, Guild the classic UO guild-chat
   green (a slightly different shade from the Journal's stat-gain green, since they're
   unrelated systems that just happen to share a palette family). */
.chat-entry.chat-channel-local { color: var(--ink); }
.chat-entry.chat-channel-local .chat-entry-name { color: var(--accent); }
.chat-entry.chat-channel-global { color: #5bc8ea; }
.chat-entry.chat-channel-global .chat-entry-name { color: #8fdcf2; }
.chat-entry.chat-channel-guild { color: #6fcf6f; }
.chat-entry.chat-channel-guild .chat-entry-name { color: #9be89b; }

.chat-form {
  display: flex;
  gap: 8px;
}

.chat-form input {
  flex: 1;
  padding: 6px 8px;
  background: var(--shade);
  border: 1px solid var(--edge);
  color: var(--ink);
  font-family: inherit;
  border-radius: 3px;
}

.chat-form button {
  padding: 6px 14px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.equipped-panel-wrap, .inventory-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px;
}

.equipped-panel-wrap h2, .inventory-panel h2 {
  margin-top: 0;
  color: var(--accent);
  font-size: 16px;
}

.equipped-panel {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  width: fit-content;
  padding: 8px;
  background: rgba(0, 0, 0, 0.2);
  border: 2px dashed rgba(255, 255, 255, 0.12);
  border-radius: 4px;
}

/* A rough paperdoll silhouette: helm at the top, neck tucked in next to chest
   (shrunk so it doesn't sit too far from the helm), cloak on the other side,
   weapon+shield flanking the legs, and arms/gloves/rings on the bottom row.
   Both rings share the single slot-rings tile — they're the same-sized small
   accessory boxes side by side rather than one full-size gear slot each.
   Placement is by grid-area, not DOM order, so equipped.ejs is free to render
   these slots in whatever order is convenient. */
.equip-paperdoll {
  display: grid;
  grid-template-columns: repeat(3, 44px);
  grid-template-rows: 44px 44px 44px 44px;
  gap: 8px;
  grid-template-areas:
    "slot-mount     slot-head      slot-binding"
    "slot-neck      slot-chest     slot-cloak"
    "slot-weapon    slot-legs      slot-shield"
    "slot-arms      slot-hands     slot-rings";
}

.equip-slot-box[data-slot="mount"] { grid-area: slot-mount; }
.equip-slot-box[data-slot="head"] { grid-area: slot-head; }
/* The Virtue Binding — blessed and permanently worn (see routes/auth.js), so it never
   shows the empty-slot silhouette/faded-out treatment every other box gets; a soft
   ember glow instead marks it as "always there", matching the header crest XP bar's
   own ember accent (see .xp-bar-fill below) since they track the same progression. */
.equip-slot-box[data-slot="binding"] {
  grid-area: slot-binding;
  box-shadow: 0 0 6px rgba(255, 158, 88, 0.35);
}
.equip-slot-box[data-slot="neck"] {
  grid-area: slot-neck;
  width: 30px;
  height: 26px;
  justify-self: center;
}
.equip-slot-box[data-slot="neck"] .item-icon { width: 26px; height: 26px; }
.equip-slot-box[data-slot="chest"] { grid-area: slot-chest; }
.equip-slot-box[data-slot="arms"] { grid-area: slot-arms; }
.equip-slot-box[data-slot="hands"] { grid-area: slot-hands; }
.equip-slot-box[data-slot="legs"] { grid-area: slot-legs; }
.equip-slot-box[data-slot="cloak"] { grid-area: slot-cloak; }
.equip-slot-box[data-slot="weapon"] { grid-area: slot-weapon; }
.equip-slot-box[data-slot="shield"] { grid-area: slot-shield; }
.equip-slot-box[data-slot="ring1"],
.equip-slot-box[data-slot="ring2"] {
  grid-area: slot-rings;
  width: 20px;
  height: 26px;
}
.equip-slot-box[data-slot="ring1"] { justify-self: start; }
.equip-slot-box[data-slot="ring2"] { justify-self: end; }
.equip-slot-box[data-slot="ring1"] .item-icon,
.equip-slot-box[data-slot="ring2"] .item-icon { width: 18px; height: 18px; }
.equip-slot-box[data-slot="ring1"] .equip-slot-placeholder-icon,
.equip-slot-box[data-slot="ring2"] .equip-slot-placeholder-icon { width: 16px; height: 16px; }
.equip-slot-box[data-slot="neck"] .equip-slot-placeholder-icon { width: 22px; height: 22px; }

/* Every empty slot shows a faint silhouette of what belongs there (tools/generateSprites.js)
   instead of a plain text label — the label itself only shows up as a hover tooltip
   (public/js/tooltip.js reads the box's own data-slot-label). Same warm background tint
   for every empty slot, not just Mount, for a consistent "nothing here yet" look. */
.equip-slot-box.equip-slot-empty {
  background: rgba(255, 255, 255, 0.03);
}
.equip-slot-placeholder-icon {
  width: 30px;
  height: 30px;
  opacity: 0.35;
  filter: grayscale(1);
  pointer-events: none;
}

/* Blacked out while a two-handed weapon is worn — there's no free hand for a shield. */
.equip-slot-box.equip-slot-disabled {
  background: #000;
  border-color: rgba(255, 255, 255, 0.05);
  opacity: 0.55;
  pointer-events: none;
}

/* Food + the 3 Potions — always the bottom row, deliberately separate from the
   paperdoll grid above so they never shuffle position as other gear changes. */
.equip-consumables {
  display: grid;
  grid-template-columns: repeat(4, 44px);
  gap: 8px;
}

.food-threshold-control {
  width: 100%;
  max-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}

.food-threshold-control label {
  font-size: 11px;
  color: var(--muted);
  text-align: center;
}

.food-threshold-control label span {
  color: var(--accent);
  font-weight: bold;
}

.food-threshold-control input[type="range"] {
  width: 100%;
  accent-color: var(--accent-strong);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.item-slot {
  position: relative;
  width: 44px;
  height: 44px;
}

.equip-slot-box {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: 3px;
}

.equip-slot-box.drag-over {
  border-color: var(--accent);
  background: rgba(255, 158, 88, 0.2);
}

.equip-badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #3f9d4a;
  color: #f2fff2;
  font-size: 10px;
  font-weight: bold;
  line-height: 14px;
  text-align: center;
  border: 1px solid #1c4d1c;
  pointer-events: none;
}

.backpack-panel {
  position: relative;
  background-image: url('/assets/sprites/backpack_bg.png');
  background-size: cover;
  border: 1px solid var(--edge);
  border-radius: 4px;
  overflow: hidden;
}

.backpack-panel.drag-over {
  outline: 2px solid var(--accent);
}

.current-location.drag-over {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.item-icon {
  width: 44px;
  height: 44px;
  image-rendering: pixelated;
  cursor: url('/assets/sprites/cursors/grab.png') 16 16, grab;
}

.backpack-item {
  position: absolute;
}

.item-icon:active { cursor: url('/assets/sprites/cursors/grabbing.png') 16 16, grabbing; }

.item-icon[data-is-container="true"] { cursor: url('/assets/sprites/cursors/default.png') 2 2, pointer; }

.item-icon.drag-over {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* In use by a craft batch right now (services/craftBatchState.js's isItemDefLocked) —
   dimmed and un-draggable (public/js/dragdrop.js's createItemImg sets draggable=false
   alongside this), with a cursor that reads as "can't touch this" instead of the usual
   grab hand. */
.item-icon[data-locked="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Every icon of the same stackable item type, shown while dragging (public/js/
   dragdrop.js's dragstart listener) — a distinct green/glow rather than drag-over's
   static orange outline, since a match can also be the one thing under the cursor at
   the same time and the two need to read as different cues. */
.item-icon.stack-match {
  outline: 2px solid #7fd1a8;
  outline-offset: 1px;
  animation: stack-match-pulse 1s ease-in-out infinite;
}

@keyframes stack-match-pulse {
  0%, 100% { box-shadow: 0 0 4px 1px rgba(127, 209, 168, 0.6); }
  50% { box-shadow: 0 0 8px 3px rgba(127, 209, 168, 0.9); }
}

.stack-badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  min-width: 14px;
  height: 14px;
  padding: 0 2px;
  border-radius: 3px;
  background: var(--panel);
  color: var(--ink);
  font-size: 10px;
  font-weight: bold;
  line-height: 14px;
  text-align: center;
  border: 1px solid var(--edge);
  pointer-events: none;
}

/* Same stack-count look as .stack-badge, just mirrored to the opposite corner (bottom-
   left instead of bottom-right) so it doesn't collide with the equipped-slot "E" badge
   sharing that corner — mainly seen on the Food slot, where auto-eat quietly works
   through the stack and this is the only way to tell how much is left without opening
   the backpack to check. */
.equip-stack-badge {
  position: absolute;
  bottom: -2px;
  left: -2px;
  min-width: 14px;
  height: 14px;
  padding: 0 2px;
  border-radius: 3px;
  background: var(--panel);
  color: var(--ink);
  font-size: 10px;
  font-weight: bold;
  line-height: 14px;
  text-align: center;
  border: 1px solid var(--edge);
  pointer-events: none;
}

.activities-panel, .stats-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px;
  width: 236px;
}

.stats-panel h2 {
  margin: 0;
  color: var(--accent);
  font-size: 16px;
}

.stats-icon-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 12px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.stats-icon-badge {
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 12px;
  color: var(--accent);
  white-space: nowrap;
}

.stats-icon-glyph {
  font-size: 13px;
}

.stats-row {
  display: grid;
  grid-template-columns: 14px 1fr 24px;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  font-size: 14px;
}

.stat-value {
  text-align: right;
}

.stat-arrow {
  padding: 0;
  width: 14px;
  height: 14px;
  line-height: 12px;
  font-size: 8px;
  background: var(--shade);
  color: var(--muted);
  border: 1px solid var(--edge);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.stat-arrow-up, .stat-arrow-down {
  background: var(--accent);
  color: var(--on-accent);
  font-weight: bold;
}

.stats-total {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
  color: var(--muted);
  text-align: right;
}

#weight-value.overweight {
  color: #e25c4f;
  font-weight: bold;
}

.activities-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.activities-header h2 {
  margin: 0;
  color: var(--accent);
  font-size: 16px;
}

.skills-toggle {
  width: 22px;
  height: 22px;
  padding: 0;
  background: var(--shade);
  color: var(--accent);
  border: 1px solid var(--edge);
  border-radius: 3px;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.activities-list {
  margin-top: 10px;
  max-height: 320px;
  overflow-y: auto;
}

/* ---- Player-arranged skill groups (public/js/activities.js's renderSkillGroups) ----
   Same collapse idiom as .crafting-section (chevron rotates via .collapsed on the
   parent, body display:none), sized down for the 236px-wide panel. */
.skills-group-header {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 4px 4px 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  user-select: none;
}

.skills-group-toggle {
  font-size: 9px;
  transition: transform 0.15s ease;
}

.skills-group.collapsed .skills-group-toggle {
  transform: rotate(-90deg);
}

.skills-group.collapsed .skills-group-body {
  display: none;
}

.skills-group-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.skills-group-remove {
  padding: 0 5px;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.skills-group-remove:hover { color: var(--accent); }

/* Keeps an empty custom group a real drop target instead of a 0-height sliver. */
.skills-group-body {
  min-height: 8px;
}

/* Drag-to-arrange affordances: the dragged row inserts above a highlighted row, or
   appends into a highlighted group. */
.activity-row.row-drop-target {
  box-shadow: 0 -2px 0 var(--accent);
}

.skills-group.group-drop-target .skills-group-header {
  color: var(--on-accent);
  background: var(--edge);
}

.activity-row[draggable="true"] { cursor: url('/assets/sprites/cursors/point.png') 4 2, grab; }

/* An activity you can't start from where you're standing (or mid-travel) — dimmed, not
   hidden; the server rejects an ineligible Start with its own clean error either way. */
.activity-row.ineligible {
  opacity: 0.45;
}

.skills-add-group-btn {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 4px 0;
  background: none;
  border: 1px dashed var(--edge);
  border-radius: 3px;
  color: var(--muted);
  font-size: 11px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.skills-add-group-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
}

.activity-row {
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.activity-row:last-child { border-bottom: none; }

/* The skill-value and button columns are fixed widths deliberately — .activity-main is
   its own independent grid per row, so a max-content button column sizes off that row's
   own label text and ends up at a different X per row, breaking alignment down a
   section. The label column is 1fr (not a fixed px) so it always yields whatever the
   panel's actual width leaves over after the fixed columns + gaps, instead of the old
   fixed-104px label colliding with the button column at the panel's real 236px width
   (104 + 66 + gaps alone left the Start/Stop button almost no room to render in). Long
   labels still truncate via .activity-label's ellipsis, same as always. */
.activity-main {
  display: grid;
  grid-template-columns: 1fr 38px 44px;
  align-items: center;
  gap: 6px;
}

.activity-label {
  font-size: 14px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.activity-skill {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
}

/* A Tinkering tool's active bonus (config/tinkering.js) — sectioned off from the raw
   skill value with its own tinted pill rather than sitting inline as plain bracketed
   text, so "this part is a bonus, not the trained value" reads at a glance. Same green
   the rest of the UI already uses for a stat/skill gain (.journal-stat). Shared by the
   persistent Activities panel and the full Skills gump alike. */
.skill-bonus-badge {
  padding: 0 4px;
  border-radius: 3px;
  background: rgba(76, 175, 80, 0.18);
  border: 1px solid rgba(76, 175, 80, 0.4);
  color: #4caf50;
  font-size: 10px;
  font-weight: bold;
  line-height: 1.5;
}

.activity-toggle {
  width: 100%;
  padding: 4px 0;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 11px;
  text-align: center;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.activity-toggle.active {
  background: #3f9d4a;
  color: #f2fff2;
}

/* Static crafting skills (Blacksmithing/Carpentry/Tinkering/Alchemy/Cooking) never get
   a Start button — a craft batch only ever begins from its own gump — but sit in the
   exact same 44px grid slot .activity-toggle occupies, always in the "Stop" look since
   there's no other state to show (views/partials/activities.ejs). */
.craft-stop-btn {
  width: 100%;
  padding: 4px 0;
  background: #3f9d4a;
  border: none;
  border-radius: 3px;
  color: #f2fff2;
  font-weight: bold;
  font-size: 11px;
  text-align: center;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.activity-progress {
  height: 3px;
  margin-top: 6px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 2px;
  overflow: hidden;
  visibility: hidden;
}

/* Craft-kind rows (Cooking/Smelting/Carpentry Plank) never get the .active class — that's
   reserved for toggleable tick activities (Start/Stop) — but they do resolve real
   per-attempt waits (craft:attemptStart/attemptResult, see public/js/activities.js), so
   their bar needs to stay visible unconditionally rather than only while .active. */
.activity-row.active .activity-progress,
.activity-row[data-craft-kind] .activity-progress,
.crafting-progress-row .activity-progress {
  visibility: visible;
}

.crafting-progress-row {
  margin-bottom: 10px;
}

.activity-progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: #3f9d4a;
}

.activity-progress-fill.failed {
  background: #e25c4f;
}

/* Positioning (fixed/left/top/z-index) is now applied inline by initGumpWindow()
   (public/js/gumpWindow.js) so this gump can float and be dragged anywhere, rather than
   sitting behind a full-viewport dimmed modal backdrop. */
.skills-gump {
  width: 260px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.skills-gump-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  border-bottom: 2px solid rgba(255, 255, 255, 0.12);
  color: var(--accent);
  font-weight: bold;
  /* Grab cursor signals every gump's title bar is draggable (public/js/gumpWindow.js) —
     shared here since every gump reuses this exact header class, same grab/grabbing
     cursor pair .item-icon already uses for item drag-and-drop. */
  cursor: url('/assets/sprites/cursors/grab.png') 16 16, grab;
}

.skills-gump-header:active {
  cursor: url('/assets/sprites/cursors/grabbing.png') 16 16, grabbing;
}

.skills-gump-close {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 18px;
  line-height: 1;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  padding: 0 4px;
}

/* Injected into every gump's header by public/js/gumpWindow.js (not hand-placed per
   .ejs) — locking disables that gump's drag-start so it can't be nudged by an
   accidental grab once it's positioned where you want it. */
.gump-lock-btn {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  padding: 0 4px;
  opacity: 0.75;
}

.gump-lock-btn:hover { opacity: 1; color: var(--accent); }
.gump-lock-btn.locked { color: var(--accent); opacity: 1; }

/* Swaps the draggable grab cursor for a plain pointer once locked, so the header
   itself visually confirms it won't move before you even try. */
.gump-locked .skills-gump-header {
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}
.gump-locked .skills-gump-header:active {
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.skills-gump-body {
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px 14px;
}

.skills-gump-category {
  margin-top: 10px;
  padding-top: 6px;
  font-size: 11px;
  text-transform: uppercase;
  color: var(--muted);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.skills-gump-category:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.skills-gump-row {
  display: flex;
  justify-content: space-between;
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 14px;
}

.skills-gump-row:last-child { border-bottom: none; }

.skills-gump-value {
  color: var(--muted);
}

.skills-gump-total {
  margin: 0 14px 12px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 13px;
  color: var(--accent);
  text-align: right;
}

/* See the comment above .skills-gump — positioning now comes from initGumpWindow(). */
.travel-gump {
  width: min(900px, 90vw);
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.travel-gump-map {
  position: relative;
  line-height: 0;
}

.travel-gump-map-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 0 0 4px 4px;
}

.travel-map-pin {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: none;
  border: none;
  padding: 0;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.travel-map-pin-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #e25c4f;
  border: 2px solid var(--panel);
  box-shadow: 0 0 0 1px var(--ink);
}

/* Dungeons get their own pin color — a warning at a glance before you ever open the
   description text, same "flag drives a visual tell" idea as the equip-slot-disabled
   blackout for a worn two-handed weapon. */
.travel-map-pin.dungeon .travel-map-pin-dot {
  background: #6b2d8f;
}

.travel-map-pin:hover .travel-map-pin-dot {
  background: #f0c88a;
}

.travel-map-pin.current .travel-map-pin-dot {
  background: #3f9d4a;
  cursor: default;
}

.travel-map-pin.recall-hidden {
  display: none;
}

.travel-map-pin-label {
  font-size: 11px;
  font-weight: bold;
  color: #fff;
  text-shadow: 0 0 3px #000, 0 0 3px #000, 0 1px 2px #000;
  white-space: nowrap;
}

/* Random-plot house pin — a hand-drawn CSS house glyph (roof + walls + door) rather
   than a plain dot, so it reads as distinct from every town/dungeon pin at a glance.
   Only ever rendered for the house's own owner (see travelMapGump.ejs). */
.house-map-pin {
  position: absolute;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: none;
  border: none;
  padding: 0;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.house-map-pin-icon {
  position: relative;
  width: 14px;
  height: 11px;
  background: #cbb08a;
  border: 1px solid var(--ink);
  border-top: none;
  box-shadow: 0 0 0 1px var(--panel);
}

.house-map-pin-icon::before {
  content: '';
  position: absolute;
  top: -8px;
  left: -1px;
  width: 0;
  height: 0;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-bottom: 8px solid #8a3a2b;
}

.house-map-pin-icon::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 5px;
  width: 4px;
  height: 7px;
  background: #4a3420;
}

.house-map-pin:hover .house-map-pin-icon {
  background: #f0c88a;
}

.house-map-pin.current .house-map-pin-icon {
  background: #3f9d4a;
}

.travel-map-tooltip {
  display: none;
  position: fixed;
  z-index: 1600;
  background: var(--panel);
  border: 1px solid var(--edge);
  color: var(--ink);
  padding: 4px 8px;
  font-size: 13px;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
}

/* Every gump now floats (see public/js/gumpWindow.js) rather than sitting behind a
   full-viewport dimmed modal, so the Backpack/Activities/etc. stay visible and
   interactive underneath any of them — not just the Bank, which is where this
   originally started (drag-and-drop deposits need the Backpack visible at the same
   time). Positioning itself comes from initGumpWindow()'s inline styles.

   The Bank gets its own richer "treasure chest" identity rather than the plain
   parchment-book look every other gump shares — a full ornate stone-and-brass gump
   frame (public/assets/sprites/bank_gump_frame.png, a commissioned piece, not
   procedurally drawn) applied as a CSS border-image, its "BANK" title baked right into
   the top slice. border-image-slice's `fill` keyword also paints the frame's own dark
   interior texture across the whole padding/content box as a base layer, so the header
   strip/gold row/weight row/item-scatter panel all sit directly on that same provided
   art with nothing else layered on top of it. */
.bank-gump {
  position: relative;
  border-style: solid;
  border-width: 40px 14px 16px 14px;
  border-image-source: url('/assets/sprites/bank_gump_frame.png');
  border-image-slice: 15% 10% 11% 10% fill;
  border-image-width: 40px 14px 16px 14px;
  border-image-repeat: stretch;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
}

/* The frame's top slice already reads "BANK" in gold — no need for our own header
   text/background duplicating it. This strip exists purely as the drag handle (see
   public/js/gumpWindow.js) and to host the close button, pinned over the border-image's
   title area rather than occupying content-box space of its own. */
.bank-gump .skills-gump-header {
  position: absolute;
  top: -40px;
  left: 0;
  right: 0;
  height: 40px;
  padding: 0;
  border: none;
  background: transparent;
  justify-content: flex-end;
}

.bank-gump .skills-gump-header span {
  display: none;
}

.bank-gump .skills-gump-close {
  margin: 6px 10px 0 0;
}

.bank-panel {
  position: relative;
  background: transparent;
  overflow: hidden;
  border-radius: 6px;
}

/* No more full-viewport modal — combat now floats in the main view (see
   public/js/gumpWindow.js) so Backpack/Activities stay usable mid-fight, matching real
   UO where combat never blocks the rest of the UI. */
.combat-gump {
  width: 320px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  padding-bottom: 14px;
}

.combat-gump-body {
  padding: 8px 16px 0;
}

.combat-monster-display {
  position: relative;
  text-align: center;
  padding: 16px 0 8px;
}

.combat-monster-sprite {
  width: 132px;
  height: 132px;
  image-rendering: pixelated;
}

.combat-hp-bar-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
}

.combat-hp-bar {
  flex: 1;
}

/* Qualified with .travel-progress-fill (always present together on the same element —
   see combatGump.ejs) rather than left as a bare single class: .travel-progress-fill's
   own background: var(--accent) is declared LATER in this file, and two single-class
   selectors of equal specificity resolve by source order, not which one "sounds more
   specific" — so a bare .combat-hp-fill-monster rule here was silently losing that
   fight and rendering as plain accent-orange the whole time, never actually red. Same
   root cause as the .auto-battle-btn.active fix elsewhere in this file; fixed the same
   way, by out-specificity-ing the later rule instead of relying on source order. */
.travel-progress-fill.combat-hp-fill-monster {
  /* A distinctly hostile red — was rendering as plain accent-orange (see above), which
     didn't read as "the mob's HP" at a glance next to every other orange-accented bar
     in the game. */
  background: #d9271c;
  /* combat.js sets width directly on every combat:tick/combat:start — this transition
     is what turns each of those instant jumps into a smooth drain/refill instead of a
     hard snap, matching the "more fluid, less ticky" bars requested. combat:start's own
     jump to a fresh monster's 100% is a rare exception; a ~250ms fill-in there reads
     fine as "the bar arriving", not wrong. */
  transition: width 250ms ease-out;
}

.combat-hp-value {
  font-size: 12px;
  color: var(--ink-dim);
  white-space: nowrap;
}

/* Section caption above the two per-combatant rows below — on their own, "You"/
   "Monster" next to a bare bar with no numbers don't read as an attack-speed timer at a
   glance. This names the whole block once. */
.combat-swing-section-label {
  margin-top: 10px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.combat-swing-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
}

.combat-swing-label {
  width: 46px;
  flex-shrink: 0;
  font-size: 10px;
  text-transform: uppercase;
  color: var(--muted);
}

.combat-swing-bar {
  flex: 1;
}

/* Same .travel-progress-fill-qualified fix as .combat-hp-fill-monster above — these two
   were equally silently losing their own background to .travel-progress-fill's later
   rule, rendering as plain accent-orange instead of blue/amber. */
.travel-progress-fill.combat-swing-fill-player {
  background: #5d8ef0;
}

.travel-progress-fill.combat-swing-fill-monster-swing {
  background: #c97a27;
}

.combat-respawn-row {
  text-align: center;
  padding: 28px 0 24px;
}

.combat-respawn-label {
  font-size: 13px;
  color: var(--ink-dim);
}

.combat-respawn-countdown {
  margin-top: 8px;
  font-size: 28px;
  font-weight: bold;
  color: var(--accent);
}

@keyframes damage-popup-float {
  0% { transform: translate(-50%, 0); opacity: 1; }
  100% { transform: translate(-50%, -28px); opacity: 0; }
}

.damage-popup {
  position: absolute;
  top: 0;
  left: 50%;
  font-weight: bold;
  font-size: 16px;
  pointer-events: none;
  animation: damage-popup-float 800ms ease-out forwards;
}

.damage-popup-monster { color: #ffe08a; text-shadow: 0 0 3px #000; }
.damage-popup-player { color: #e25c4f; text-shadow: 0 0 3px #000; }

/* ============ Combat & spell FX (public/js/combat.js's spawnFx) ============
   All CSS-only transient effects, same append/animationend-remove lifecycle as
   .damage-popup above. The .fx-anchor is a zero-size point pinned over the monster
   sprite's center (132px sprite + 16px top padding => center ~82px); each family's
   .fx-inner centers itself off that point with negative margins so its keyframes can
   use transform purely for motion. Spell tints come from --fx-color (set inline by
   public/js/spellCast.js's SPELL_FX map); weapon effects carry their own colors. */
.fx-anchor {
  position: absolute;
  left: 50%;
  top: 82px;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 5;
}

/* Player-targeted spells (heal/buff/cure) anchor over the Status gump's vitals
   instead of a monster — see spellCast.js's MONSTER_TARGETED_EFFECTS split. */
.fx-anchor.spell-fx-on-player { top: 46px; }

.fx-inner { position: absolute; }

/* --- Monster hit feedback: a landed blow flashes the sprite; macing judders it. --- */
@keyframes sprite-hit-flash {
  0% { filter: brightness(1); }
  30% { filter: brightness(2.1) saturate(0.55); }
  100% { filter: brightness(1); }
}
.sprite-hit-flash { animation: sprite-hit-flash 260ms ease-out; }

@keyframes sprite-hit-shake {
  0%, 100% { transform: translate(0, 0); }
  20% { transform: translate(-4px, 1px); }
  40% { transform: translate(4px, -1px); }
  60% { transform: translate(-3px, 0); }
  80% { transform: translate(2px, 0); }
}
.sprite-hit-shake { animation: sprite-hit-shake 320ms ease-out, sprite-hit-flash 260ms ease-out; }

/* --- Weapon-skill swing effects (combat.js's WEAPON_FX_CLASSES). A miss plays the
   same swing ghosted — the whiff reads as "swung and missed", not "nothing happened". */
.fx-anchor.weapon-fx-miss { opacity: 0.3; }

/* Swordsmanship: a steel streak sweeping diagonally across the sprite. */
@keyframes weapon-fx-slash {
  0% { transform: rotate(45deg) translateX(-70px) scaleX(0.4); opacity: 0; }
  20% { opacity: 1; }
  100% { transform: rotate(45deg) translateX(70px) scaleX(1); opacity: 0; }
}
.weapon-fx-slash .fx-inner {
  width: 104px;
  height: 5px;
  margin: -3px 0 0 -52px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, #eef2f7, transparent);
  box-shadow: 0 0 8px rgba(238, 242, 247, 0.8);
  animation: weapon-fx-slash 420ms ease-in forwards;
}

/* Fencing: a thin lance stabbing straight in from the left. */
@keyframes weapon-fx-thrust {
  0% { transform: translateX(-85px) scaleX(0.5); opacity: 0; }
  55% { transform: translateX(0) scaleX(1); opacity: 1; }
  100% { transform: translateX(10px) scaleX(0.7); opacity: 0; }
}
.weapon-fx-thrust .fx-inner {
  width: 80px;
  height: 3px;
  margin: -2px 0 0 -40px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, #dfe6ee 65%, #ffffff);
  box-shadow: 0 0 6px rgba(223, 230, 238, 0.8);
  animation: weapon-fx-thrust 340ms cubic-bezier(0.5, 0, 0.9, 0.4) forwards;
}

/* Macing: a blunt-impact shockwave ring (paired with the sprite judder above). */
@keyframes weapon-fx-smash {
  0% { transform: scale(0.3); opacity: 1; }
  100% { transform: scale(2.4); opacity: 0; }
}
.weapon-fx-smash .fx-inner {
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  border: 3px solid #f0d9a8;
  box-shadow: 0 0 10px rgba(240, 217, 168, 0.7), inset 0 0 6px rgba(240, 217, 168, 0.5);
  animation: weapon-fx-smash 380ms ease-out forwards;
}

/* Archery: an arrow flying in from the left, vanishing on impact. */
@keyframes weapon-fx-arrow {
  0% { transform: translateX(-95px); opacity: 0; }
  15% { opacity: 1; }
  85% { transform: translateX(-4px); opacity: 1; }
  100% { transform: translateX(0); opacity: 0; }
}
.weapon-fx-arrow .fx-inner {
  width: 42px;
  height: 3px;
  margin: -2px 0 0 -42px;
  border-radius: 2px;
  background: linear-gradient(90deg, #8a6a42, #c9a86a);
  animation: weapon-fx-arrow 300ms linear forwards;
}
.weapon-fx-arrow .fx-inner::after {
  content: '';
  position: absolute;
  right: -7px;
  top: -3px;
  border-left: 8px solid #dfe6ee;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

/* Wrestling: a compact bare-knuckle burst right on the target. */
@keyframes weapon-fx-punch {
  0% { transform: scale(0.4); opacity: 0.9; }
  100% { transform: scale(1.5); opacity: 0; }
}
.weapon-fx-punch .fx-inner {
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0));
  animation: weapon-fx-punch 300ms ease-out forwards;
}

/* --- Spell resolution effects (spellCast.js's SPELL_FX) — a small set of keyframe
   families tinted per spell via --fx-color rather than 23 bespoke animations. --- */

/* bolt: Magic Arrow / Energy Bolt — a streak of raw energy flying in. */
@keyframes spell-fx-bolt {
  0% { transform: translateX(-95px) scaleX(0.6); opacity: 0; }
  20% { opacity: 1; }
  80% { transform: translateX(0) scaleX(1); opacity: 1; }
  100% { transform: translateX(6px) scaleX(0.5); opacity: 0; }
}
.spell-fx-bolt .fx-inner {
  width: 64px;
  height: 4px;
  margin: -2px 0 0 -32px;
  border-radius: 3px;
  background: linear-gradient(90deg, transparent, var(--fx-color));
  box-shadow: 0 0 10px var(--fx-color);
  animation: spell-fx-bolt 380ms ease-in forwards;
}

/* orb: Fireball — a ball that flies in and bursts. */
@keyframes spell-fx-orb {
  0% { transform: translateX(-90px) scale(0.6); opacity: 0; }
  15% { opacity: 1; }
  70% { transform: translateX(0) scale(1); opacity: 1; }
  100% { transform: translateX(0) scale(2.4); opacity: 0; }
}
.spell-fx-orb .fx-inner {
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff2d0 15%, var(--fx-color) 55%, transparent 75%);
  box-shadow: 0 0 14px var(--fx-color);
  animation: spell-fx-orb 550ms ease-in forwards;
}

/* lightning: a jagged bolt striking down from above the sprite, with a flicker. */
@keyframes spell-fx-lightning {
  0% { transform: scaleY(0); opacity: 0; }
  25% { transform: scaleY(1); opacity: 1; }
  40% { opacity: 0.4; }
  55% { opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; }
}
.spell-fx-lightning .fx-inner {
  width: 26px;
  height: 104px;
  margin: -104px 0 0 -13px;
  transform-origin: top;
  background: var(--fx-color);
  clip-path: polygon(52% 0, 68% 0, 44% 38%, 62% 38%, 30% 100%, 46% 55%, 30% 55%);
  filter: drop-shadow(0 0 8px var(--fx-color));
  animation: spell-fx-lightning 450ms ease-out forwards;
}

/* pulse: Harm / Mind Blast — an expanding sphere of force centered on the target. */
@keyframes spell-fx-pulse {
  0% { transform: scale(0.4); opacity: 0.95; }
  100% { transform: scale(2); opacity: 0; }
}
.spell-fx-pulse .fx-inner {
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border-radius: 50%;
  background: radial-gradient(circle, var(--fx-color) 30%, transparent 70%);
  box-shadow: 0 0 16px var(--fx-color);
  animation: spell-fx-pulse 520ms ease-out forwards;
}

/* pillar: Flame Strike — a column of fire erupting up through the target. */
@keyframes spell-fx-pillar {
  0% { transform: scaleY(0); opacity: 0.95; }
  55% { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; }
}
.spell-fx-pillar .fx-inner {
  width: 44px;
  height: 118px;
  margin: -96px 0 0 -22px;
  transform-origin: bottom;
  border-radius: 40% 40% 8px 8px;
  background: linear-gradient(to top, var(--fx-color), rgba(255, 240, 190, 0.9) 35%, transparent);
  filter: drop-shadow(0 0 12px var(--fx-color));
  animation: spell-fx-pillar 650ms ease-out forwards;
}

/* sink: Weaken / Curse / Poison — energy draining down through the target. */
@keyframes spell-fx-sink {
  0% { transform: translateY(-16px) scale(1); opacity: 0.9; }
  100% { transform: translateY(26px) scale(0.65); opacity: 0; }
}
.spell-fx-sink .fx-inner {
  width: 64px;
  height: 26px;
  margin: -13px 0 0 -32px;
  border-radius: 50%;
  border: 3px solid var(--fx-color);
  border-left-color: transparent;
  border-right-color: transparent;
  box-shadow: 0 0 10px var(--fx-color);
  animation: spell-fx-sink 600ms ease-in forwards;
}

/* ring: Paralyze / Protection / Magic Reflection / Invisibility — a rune-circle
   snapping shut around its target. */
@keyframes spell-fx-ring {
  0% { transform: scale(1.9); opacity: 0; }
  35% { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.15); opacity: 0; }
}
.spell-fx-ring .fx-inner {
  width: 52px;
  height: 52px;
  margin: -26px 0 0 -26px;
  border-radius: 50%;
  border: 3px solid var(--fx-color);
  box-shadow: 0 0 12px var(--fx-color), inset 0 0 8px var(--fx-color);
  animation: spell-fx-ring 620ms ease-out forwards;
}

/* rise: Heal / Greater Heal / Cure / Bless — motes of light drifting upward (the
   box-shadow copies fake two extra sparkles off the one element). */
@keyframes spell-fx-rise {
  0% { transform: translateY(14px) rotate(45deg); opacity: 0; }
  25% { opacity: 1; }
  100% { transform: translateY(-34px) rotate(45deg); opacity: 0; }
}
.spell-fx-rise .fx-inner {
  width: 9px;
  height: 9px;
  margin: -5px 0 0 -5px;
  background: var(--fx-color);
  box-shadow:
    -16px 12px 0 -2px var(--fx-color),
    14px 18px 0 -3px var(--fx-color),
    0 0 10px var(--fx-color);
  animation: spell-fx-rise 700ms ease-out forwards;
}

/* See the comment above .skills-gump — positioning now comes from initGumpWindow(). */
.ground-loot-gump {
  width: 300px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
  padding-bottom: 14px;
}

.ground-loot-item-list {
  max-height: 260px;
  overflow-y: auto;
  padding: 8px 16px 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ground-loot-item-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.ground-loot-item-name {
  flex: 1;
  font-size: 13px;
}

.ground-loot-take-btn, .ground-loot-take-all-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.ground-loot-take-all-btn {
  display: block;
  margin: 12px 16px 0;
}

.bank-panel.drag-over {
  outline: 2px solid var(--accent);
}

.bank-gold-row {
  display: flex;
  justify-content: space-between;
  margin: 10px 14px 0;
  font-size: 14px;
}

.bank-gold-row #bank-gold-value {
  color: #f0d878;
  font-weight: bold;
}

.bank-weight-row {
  display: flex;
  justify-content: space-between;
  margin: 10px 14px 0;
  font-size: 14px;
}

.bank-weight-row .overweight, #bank-weight-value.overweight {
  color: #e25c4f;
  font-weight: bold;
}

.bank-weight-bar {
  height: 8px;
  margin: 6px 14px 0;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 4px;
  overflow: hidden;
}

.bank-weight-bar-fill {
  height: 100%;
  background: var(--edge);
}

.bank-weight-bar-fill.overweight {
  background: #e25c4f;
}

.construction-site-body {
  padding: 4px 0 14px;
  width: 320px;
}

.construction-site-name {
  margin: 0 14px 8px;
  color: var(--accent);
  font-size: 15px;
  font-weight: bold;
}

.construction-site-drop-zone {
  margin: 16px 14px 0;
  padding: 18px 10px;
  border: 2px dashed rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}

.construction-site-drop-zone.drag-over {
  border-color: var(--accent);
  color: var(--ink);
  background: rgba(255, 158, 88, 0.08);
}

.construction-site-status {
  margin: 10px 14px 0;
  font-size: 12px;
  color: #7fd1a8;
  text-align: center;
}

/* The Guild gump — reuses the skills-gump-header/close chrome but needs its own
   wider body to fit a member roster with per-row management buttons, or a charter's
   signature list, or the open-charter browser, depending on which of the four views
   currently applies (see public/js/guilds.js). */
/* See the comment above .skills-gump — positioning now comes from initGumpWindow(). */
.guild-gump {
  /* A Guildmaster's member row can show rank badge + name + up to three action buttons
     (Promote/Demote, Make GM, Kick) at once — 380px left barely 350px of usable content
     width after the gump body's own padding, forcing that row to wrap awkwardly. 460px
     matches this game's other button/row-dense gumps (.admin-gump at 440px, .trade-gump/
     .crafting-gump at 420px). */
  width: 460px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}

.guild-gold-label {
  font-size: 12px;
  color: var(--muted);
  font-weight: normal;
  margin-left: 8px;
}

.guild-gump-body {
  max-height: 70vh;
  overflow-y: auto;
  padding: 14px;
}

.guild-gump-body h3 {
  margin: 14px 0 6px;
  color: var(--accent);
  font-size: 15px;
}

.guild-gump-body h3:first-child { margin-top: 0; }

.guild-hint {
  font-size: 12px;
  color: var(--muted);
  margin: 0 0 10px;
}

.guild-invite-banner {
  background: rgba(255, 158, 88, 0.12);
  border: 1px solid rgba(255, 158, 88, 0.5);
  border-radius: 4px;
  padding: 10px;
  margin-bottom: 14px;
  font-size: 13px;
}

.guild-invite-banner p { margin: 0 0 8px; }

.guild-member-list, .guild-charter-signers, .guild-open-charter-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}

.guild-member-row, .guild-open-charter-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  font-size: 13px;
}

.guild-charter-signer-row {
  padding: 5px 8px;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  font-size: 13px;
}

.guild-member-rank {
  font-size: 10px;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  flex-shrink: 0;
}

.guild-rank-guildmaster { background: var(--accent); color: var(--on-accent); }
.guild-rank-officer { background: #6ec6ff; color: var(--on-accent); }
.guild-rank-member { background: rgba(255, 255, 255, 0.08); color: var(--ink); }

.guild-member-name {
  flex: 1;
}

.guild-member-actions {
  display: flex;
  gap: 4px;
}

.guild-open-charter-row span { flex: 1; }

.guild-action-btn {
  padding: 3px 8px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-size: 11px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.guild-action-btn.guild-action-danger, .guild-action-danger {
  background: #e25c4f;
  color: #f2e2e2;
}

.guild-sign-btn {
  padding: 4px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-size: 12px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.guild-inline-form {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}

.guild-inline-form input {
  flex: 1;
  padding: 6px 8px;
  background: var(--shade);
  border: 1px solid var(--edge);
  color: var(--ink);
  font-family: inherit;
  border-radius: 3px;
}

.guild-inline-form button {
  padding: 6px 14px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.guild-management-buttons {
  display: flex;
  gap: 8px;
}

.guild-management-buttons button {
  padding: 6px 12px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-size: 13px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.guild-management-buttons button.guild-action-danger {
  background: #e25c4f;
  color: #f2e2e2;
}

/* The [TAG] prefix on the nameplate — the classic UO guild-chat green, matching
   .chat-entry.chat-channel-guild so a tag reads the same wherever it shows up. */
.nameplate-guild-tag {
  color: #6fcf6f;
  font-weight: bold;
  margin-right: 4px;
}

.world-map-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px;
  /* Wide enough that "Traveling to <longest destination name>… (M:SS)" never teeters
     right on the wrap boundary — at 236px it could flip between 1 and 2 lines as the
     countdown's digit width shifted tick to tick, shoving the Cancel Travel button
     below it up and down every second (read as "flickering"). */
  width: 270px;
}

.server-reboot-overlay {
  position: fixed;
  inset: 0;
  z-index: 5000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.85);
}

.server-reboot-panel {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: 32px 48px;
  text-align: center;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
}

.server-reboot-title {
  color: var(--accent);
  font-size: 22px;
  font-weight: bold;
  margin-bottom: 12px;
}

.server-reboot-message {
  color: var(--ink);
  font-size: 16px;
}

.current-location {
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.current-location-label {
  font-size: 11px;
  text-transform: uppercase;
  color: var(--muted);
}

.current-location-name {
  font-size: 16px;
  color: var(--accent);
  font-weight: bold;
}

.current-location-desc {
  font-size: 12px;
  margin-top: 4px;
  color: var(--ink);
}

.vendor-stub {
  margin-top: 6px;
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
}

.blacksmith-note {
  margin-top: 6px;
  font-size: 11px;
  font-style: italic;
  color: var(--accent);
}

.dungeon-note {
  margin-top: 6px;
  font-size: 11px;
  font-style: italic;
  font-weight: bold;
  color: #b361e0;
}

.stable-note {
  margin-top: 6px;
  font-size: 11px;
  font-style: italic;
  color: #6fa8c9;
}

/* Regional karma ("Standing") — same row+bar shape as .bank-weight-row/.bank-weight-bar,
   just scoped to the World Map panel's current-location block instead of the Bank gump. */
.karma-row {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
  font-size: 12px;
  color: var(--ink-dim);
}

.karma-row .low-standing, #karma-value.low-standing {
  color: #e25c4f;
  font-weight: bold;
}

.karma-bar {
  height: 6px;
  margin-top: 3px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 3px;
  overflow: hidden;
}

.karma-bar-fill {
  height: 100%;
  background: #7fd1a8;
}

.karma-bar-fill.low-standing {
  background: #e25c4f;
}

.stable-roster {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 14px 14px;
  width: 360px;
}

.stable-mount-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.stable-mount-icon {
  width: 44px;
  height: 44px;
  image-rendering: pixelated;
}

.stable-mount-info {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.stable-mount-name {
  font-size: 13px;
}

.stable-mount-bonus {
  font-size: 11px;
  color: var(--muted);
}

.stable-mount-price {
  font-size: 12px;
  color: var(--accent);
  white-space: nowrap;
}

.stable-buy-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

/* The Spellbook gets the Bank/Trade ornate-frame treatment with its own commissioned-
   lineage art — spellbook_gump_frame.png derives from the trade frame (same 700x467
   proportions, so the same slice tuning), retitled "SPELLBOOK" with a mystical pass:
   the corner smoke hue-shifted arcane violet, a faint rune ring embossed across the
   interior. Without this override it inherited .bank-gump's frame verbatim and opened
   with "BANK" carved over a spellbook. Everything below re-themes the controls to
   match — arcane violet, not the shared ember chip (see the grouped button rules near
   the end of this file, which deliberately exclude .spellbook-cast-btn). */
.spellbook-gump {
  width: 400px;
  border-image-source: url('/assets/sprites/spellbook_gump_frame.png');
  border-image-slice: 15% 9% 13% 9% fill;
  border-width: 44px 16px 18px 16px;
  border-image-width: 44px 16px 18px 16px;
}

.spellbook-roster {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 14px 14px;
  max-height: 420px;
  overflow-y: auto;
}

.spellbook-circle-heading {
  margin-top: 8px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #b79ce8;
  border-bottom: 1px solid rgba(160, 120, 230, 0.22);
  padding-bottom: 2px;
}

/* A small arcane star leads each circle heading — text-based, no sprite needed. */
.spellbook-circle-heading::before {
  content: '✦ ';
  color: rgba(183, 156, 232, 0.6);
}

.spellbook-circle-heading:first-child {
  margin-top: 0;
}

.spellbook-spell-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 6px;
  margin: 0 -6px;
  border-radius: 4px;
}

.spellbook-spell-row:hover {
  background: rgba(139, 99, 235, 0.08);
}

/* Unlearned spells sit dimmed on the page — readable as "in the book, not yet yours"
   at a glance, before the Not-learned label is even read. */
.spellbook-spell-row[data-known="false"] {
  opacity: 0.55;
}

.spellbook-spell-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.spellbook-spell-name {
  font-size: 13px;
}

/* The power words are the mystical part — set in the frame's arcane violet rather
   than the generic muted grey. */
.spellbook-spell-words {
  font-size: 11px;
  color: #9d86cc;
  font-style: italic;
}

.spellbook-spell-mana {
  font-size: 11px;
  color: #8aa6c9;
  white-space: nowrap;
}

.spellbook-cast-btn {
  padding: 3px 12px;
  background: rgba(139, 99, 235, 0.12);
  border: 1px solid rgba(168, 130, 255, 0.45);
  border-radius: 5px;
  color: #cdb8f5;
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.spellbook-cast-btn:hover:not(:disabled) {
  background: #8b63eb;
  border-color: #a882ff;
  color: #16110b;
  box-shadow: 0 0 10px rgba(139, 99, 235, 0.55);
}

.spellbook-cast-btn:disabled {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.1);
  color: #6d7480;
}

.spellbook-locked-label {
  font-size: 11px;
  color: #6d7480;
  font-style: italic;
  white-space: nowrap;
}

/* Generic crafting gump (Blacksmithing today, Tailoring/Tinkering later) — see
   views/partials/craftingGump.ejs/public/js/crafting.js. */
.crafting-gump {
  width: 420px;
}

.crafting-gump-body {
  padding: 10px 14px 14px;
  overflow-y: auto;
  min-height: 0; /* lets this flex child actually shrink/scroll instead of forcing .crafting-gump to grow to fit it */
}

.crafting-material-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.crafting-material-row label {
  font-size: 13px;
  color: var(--ink-dim);
}

.crafting-material-select {
  flex: 1;
  background: var(--well);
  border: 1px solid var(--edge);
  color: var(--ink);
  border-radius: 4px;
  padding: 4px 6px;
  font-size: 13px;
}

/* Floats at the top of .crafting-gump-body's own scroll area (position: sticky is
   relative to that ancestor, the nearest scrolling container) instead of scrolling away
   with the recipe list below it — so the preview stays visible the whole time you're
   browsing a long Weapons/Armor list. Needs a near-opaque background (matching --panel,
   not the previous much-more-transparent rgba(0,0,0,0.25)) so scrolled-under recipe rows
   don't show through once it's actually stuck in place. */
.crafting-preview-panel {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 60px;
  padding: 8px;
  margin-bottom: 10px;
  background: var(--panel);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  position: sticky;
  top: 0;
  z-index: 2;
}

.crafting-preview-icon {
  width: 44px;
  height: 44px;
  image-rendering: pixelated;
  flex-shrink: 0;
}

.crafting-preview-placeholder {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
}

.crafting-preview-stats {
  font-size: 12px;
  color: var(--ink-dim);
  line-height: 1.5;
}

.crafting-preview-name {
  font-weight: bold;
  color: var(--accent);
}

/* Collapsible sections — no prior precedent in this codebase; the header's arrow
   flips via .collapsed on the parent, body collapses via display:none. */
.crafting-section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 4px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.crafting-section-toggle {
  transition: transform 0.15s ease;
}

.crafting-section.collapsed .crafting-section-toggle {
  transform: rotate(-90deg);
}

.crafting-section.collapsed .crafting-section-body {
  display: none;
}

.crafting-recipe-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 4px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.crafting-recipe-row.selected {
  background: rgba(255, 158, 88, 0.12);
}

.crafting-recipe-info {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.crafting-recipe-name {
  font-size: 13px;
}

.crafting-recipe-cost {
  font-size: 11px;
  color: var(--muted);
}

.crafting-qty-input {
  width: 44px;
}

.crafting-craft-btn {
  padding: 3px 8px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 11px;
  white-space: nowrap;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

/* The trade window shares .bank-gump's ornate-frame treatment but needs its OWN frame
   art — otherwise it inherits bank_gump_frame.png and shows the "BANK" title over a
   trade. This commissioned trade_gump_frame.png has "TRADE" baked into its top slice,
   same as the bank piece; slices are tuned to this image's 1536x1024 proportions (its
   title bar ~15% down, ornate side rails ~9% in, footer ~13% up). Everything else —
   the absolutely-positioned header/drag handle, hidden header text — is inherited from
   the .bank-gump rules above unchanged. */
.trade-gump {
  width: 420px;
  border-image-source: url('/assets/sprites/trade_gump_frame.png');
  border-image-slice: 15% 9% 13% 9% fill;
  border-width: 44px 16px 18px 16px;
  border-image-width: 44px 16px 18px 16px;
}

.trade-pending {
  padding: 16px 14px;
  text-align: center;
}

.trade-pending-message {
  margin: 0 0 12px;
  font-size: 14px;
}

.trade-pending-actions {
  display: flex;
  justify-content: center;
  gap: 10px;
}

.trade-pending-actions .trade-accept-btn,
.trade-pending-actions .trade-cancel-btn {
  width: auto;
  margin: 0;
}

.trade-columns {
  display: flex;
  gap: 10px;
  padding: 10px 14px 0;
}

.trade-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.trade-column-header {
  font-size: 12px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}

.trade-item-list {
  flex: 1;
  min-height: 140px;
  max-height: 220px;
  background: var(--shade);
  border: 1px solid var(--edge);
  border-radius: 4px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  overflow-y: auto;
}

.trade-item-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 4px;
  border-radius: 3px;
}

.trade-item-list-mine .trade-item-row {
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.trade-item-list-mine .trade-item-row:hover {
  background: rgba(255, 158, 88, 0.15);
}

.trade-item-list-mine.drag-over {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.trade-item-row .item-icon {
  width: 28px;
  height: 28px;
}

.trade-item-name {
  font-size: 12px;
  flex: 1;
}

.trade-their-status {
  margin-top: 8px;
  font-size: 12px;
  color: #7fd1a8;
  min-height: 16px;
}

.trade-accept-btn, .trade-cancel-btn {
  display: block;
  width: calc(100% - 28px);
  margin: 10px 14px 14px;
  padding: 6px 10px;
  border: none;
  border-radius: 3px;
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.trade-column .trade-accept-btn {
  width: 100%;
  margin: 8px 0 0;
}

.trade-accept-btn {
  background: var(--edge);
  color: var(--on-accent);
}

.trade-accept-btn:disabled {
  background: #4a3d28;
  color: #8a7a5a;
  cursor: default;
}

.trade-cancel-btn {
  background: rgba(226, 92, 79, 0.18);
  color: var(--ink);
}

.admin-gump {
  width: 440px;
}

.admin-broadcast-row {
  display: flex;
  gap: 8px;
  padding: 10px 14px 0;
}

.admin-broadcast-input {
  flex: 1;
  padding: 6px 8px;
  background: var(--shade);
  border: 1px solid var(--edge);
  color: var(--ink);
  font-family: inherit;
  font-size: 12px;
  border-radius: 3px;
}

.admin-broadcast-send-btn {
  padding: 6px 14px;
  font-family: inherit;
  font-size: 12px;
  background: var(--accent);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.admin-broadcast-send-btn:hover { background: var(--accent-bright); }

.admin-player-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  max-height: 260px;
  overflow-y: auto;
}

.admin-player-row {
  display: grid;
  grid-template-columns: 1.2fr 1fr 0.5fr 1fr auto;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  background: var(--shade);
  border: 1px solid var(--edge);
  border-radius: 4px;
  font-size: 11px;
}

.admin-player-name {
  font-weight: bold;
  color: var(--accent);
}

.admin-player-location, .admin-player-status, .admin-player-age {
  color: var(--muted);
}

/* Age reads as a short "3d"/"5h"/"12m" duration — right-aligned so it doesn't look like
   a stray word wedged between Location and Status. */
.admin-player-age {
  text-align: right;
}

.admin-player-actions {
  display: flex;
  gap: 3px;
}

.admin-player-action-btn {
  padding: 2px 5px;
  font-size: 10px;
  background: var(--edge);
  color: var(--on-accent);
  border: none;
  border-radius: 3px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.admin-player-action-btn:hover {
  background: var(--accent);
}

.admin-inspect {
  margin-top: 8px;
  padding: 10px 14px 14px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.admin-inspect-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
  font-weight: bold;
  color: var(--accent);
}

.admin-inspect-close {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 16px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.admin-inspect-gold {
  font-size: 12px;
  color: #f0d878;
  margin-bottom: 8px;
}

.admin-inspect-section-label {
  font-size: 11px;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 8px;
}

.admin-inspect-items {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 6px 0;
}

.admin-inspect-row {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--shade);
  border: 1px solid var(--edge);
  border-radius: 4px;
  padding: 3px 6px;
  font-size: 11px;
}

.admin-inspect-row .item-icon {
  width: 24px;
  height: 24px;
}

.admin-inspect-empty {
  font-size: 11px;
  color: #6d7480;
  font-style: italic;
}

.dungeon-roster {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.dungeon-monster-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.dungeon-monster-icon {
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
}

.dungeon-monster-name {
  flex: 1;
  font-size: 13px;
}

.dungeon-attack-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

/* The post-kill respawn wait for a manual (non-Auto-Battle) fight — public/js/combat.js
   moves this under whichever roster row was actually last fought rather than it living
   in one fixed spot (Element.after()). The whole roster dims/disables while it's up
   (.on-cooldown below) since the wait is a real dungeon-wide cooldown blocking every
   monster, not just the one just killed. */
.dungeon-roster-cooldown {
  text-align: center;
  padding: 6px 0;
  font-size: 12px;
  font-weight: bold;
  color: var(--accent);
}

.dungeon-roster.on-cooldown .dungeon-attack-btn {
  opacity: 0.4;
  pointer-events: none;
}

.auto-battle-btn {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 5px 10px;
  background: var(--edge);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.auto-battle-btn.active {
  background: var(--accent);
  border-color: #e8c14a;
  box-shadow: 0 0 6px rgba(255, 158, 88, 0.5);
  /* Without this, .active only wins the background — color falls through to the later
     "flips solid on hover" chip rule below (.auto-battle-btn { color: var(--accent) }),
     same value as this rule's own background, so the label goes invisible the instant
     Auto Battle turns on (fixed only once you actually hover it, since :hover's own
     color: var(--on-accent) happens to out-cascade that rule). */
  color: var(--on-accent);
}

.ground-loot-list {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ground-loot-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.ground-loot-label {
  flex: 1;
  font-size: 12px;
  font-style: italic;
  color: var(--muted);
}

.ground-loot-view-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.my-house-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  margin-top: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.my-house-label {
  flex: 1;
  font-size: 12px;
  color: var(--accent);
}

.my-house-action-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.travel-progress {
  margin-top: 10px;
}

.travel-progress-label {
  font-size: 13px;
  margin-bottom: 6px;
}

.travel-countdown {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.travel-progress-bar {
  height: 6px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 3px;
  overflow: hidden;
}

.bandage-progress-row,
.spell-cast-progress-row {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

/* public/js/buffBar.js rebuilds this row's children on every character:update rather
   than toggling display — :not(:empty) keeps the divider/spacing collapsed to nothing
   when no buff is active instead of leaving a bare empty gap in the Status gump. */
.buff-bar-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.buff-bar-row:not(:empty) {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.buff-bar-icon {
  width: 24px;
  height: 24px;
  image-rendering: pixelated;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.travel-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
}

.travel-cancel-btn {
  display: block;
  margin: 8px 0 0;
  padding: 5px 12px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.house-view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}

.house-view-title {
  font-size: 14px;
  font-weight: bold;
  color: var(--accent);
}

.house-view-leave-btn {
  padding: 3px 10px;
  background: var(--edge);
  border: none;
  border-radius: 3px;
  color: var(--on-accent);
  font-weight: bold;
  font-size: 12px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.house-view-floorplan {
  position: relative;
  max-width: 100%;
  overflow: auto;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 3px;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
  background-size: 32px 32px;
}

.house-view-chest {
  position: absolute;
  width: 32px;
  height: 32px;
  background: url('/assets/sprites/bank_chest.png') center / contain no-repeat;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.house-view-chest.dragging {
  opacity: 0.6;
}

.travel-cancel-btn:hover:not(:disabled) { background: var(--accent); }

.travel-cancel-btn:disabled {
  background: #4a3e28;
  color: #8a7a5c;
  cursor: not-allowed;
}

/* #open-bags-area intentionally has no layout rules of its own — each .bag-panel-wrap
   child is position:fixed (public/js/gumpWindow.js) and self-positions via inline
   left/top, so this element is purely a DOM append target now, not a visual container. */

.bag-panel-wrap {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 10px;
  height: fit-content;
}

.bag-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
  color: var(--accent);
  font-size: 14px;
}

.bag-panel-close {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 16px;
  line-height: 1;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  padding: 0 4px;
}

.bag-panel {
  position: relative;
  background-image: url('/assets/sprites/backpack_bg.png');
  background-size: cover;
  border: 1px solid var(--edge);
  border-radius: 4px;
  overflow: hidden;
}

.bag-panel.drag-over {
  outline: 2px solid var(--accent);
}

.item-tooltip {
  display: none;
  position: fixed;
  /* Must always beat any gump's z-index, including a gump that's been dragged/clicked
     to the front repeatedly — gumpWindow.js's shared z-index counter starts at 950 and
     climbs indefinitely with every bringToFront(), so a fixed 1000 here eventually gets
     buried (most noticeably on whichever gump — often the Bank — gets clicked most).
     10000 is comfortably out of that counter's realistic reach for a single session. */
  z-index: 10000;
  background: var(--panel);
  border: 1px solid var(--edge);
  color: var(--ink);
  padding: 4px 8px;
  font-size: 13px;
  border-radius: 4px;
  white-space: nowrap;
  pointer-events: none;
}

.tooltip-heal {
  color: #4caf50;
  font-weight: bold;
}

.tooltip-ar {
  color: #7fb3d5;
  font-weight: bold;
}

.tooltip-mount {
  color: #d4af7a;
  font-weight: bold;
}

.tooltip-blessed {
  color: var(--accent);
  font-weight: bold;
}

.tooltip-active {
  color: #4caf50;
  font-weight: bold;
}

.tooltip-gold {
  color: #e0c14c;
  font-weight: bold;
}

.tooltip-magic {
  color: #a06bd6;
  font-weight: bold;
}

.tooltip-crafter {
  color: #7fa8d9;
  font-style: italic;
}

.tooltip-decay {
  color: #9aa1ab;
  font-style: italic;
}

.toast {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%) translateY(-10px);
  z-index: 2000;
  background: var(--panel);
  border: 1px solid var(--edge);
  color: var(--ink);
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 14px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s, transform 0.2s;
}

.toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.toast-success { border-color: #3f9d4a; }
.toast-error { border-color: #e25c4f; }
/* GM /broadcast (public/js/adminBroadcast.js) — wider and brighter than a normal toast
   so it can't be mistaken for routine craft-success feedback. */
.toast-broadcast {
  border-color: var(--accent);
  border-width: 2px;
  color: var(--accent-bright);
  font-weight: bold;
  box-shadow: 0 0 16px rgba(255, 158, 88, 0.35);
}

/* Virtue Binding level-up (public/js/bindingLevelUp.js) — same server-wide reach as a
   GM /broadcast above, but a slow pulsing glow (rather than a flat one) keeps a player
   milestone from reading as a staff announcement. */
.toast-levelup {
  border-color: var(--accent);
  border-width: 2px;
  color: var(--accent-bright);
  font-weight: bold;
  background: linear-gradient(180deg, rgba(255, 181, 120, 0.12), var(--panel));
  animation: levelup-toast-pulse 1.4s ease-in-out infinite;
}

@keyframes levelup-toast-pulse {
  0%, 100% { box-shadow: 0 0 14px rgba(255, 158, 88, 0.4); }
  50% { box-shadow: 0 0 26px rgba(255, 158, 88, 0.8); }
}

/* Shared right-click menu (public/js/contextMenu.js) — a Local speaker's name in chat
   (Trade), and every non-weapon/armor item icon (Split Stack, Smelt/Cook/Carve, etc).
   Same fixed, above-every-gump z-index reasoning as .item-tooltip above. */
.context-menu {
  display: none;
  position: fixed;
  z-index: 10000;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 4px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
  min-width: 130px;
  padding: 4px 0;
}

.context-menu-item {
  display: block;
  width: 100%;
  padding: 7px 14px;
  background: none;
  border: none;
  color: var(--ink);
  font-family: inherit;
  font-size: 13px;
  text-align: left;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.context-menu-item:hover {
  background: rgba(255, 158, 88, 0.15);
  color: var(--accent);
}

/* Themed "how many?" dialog (public/js/contextMenu.js's promptItemAmount) — replaces
   the browser's native window.prompt() behind every batch/split action (Split Stack,
   Smelt/Cook/Carve x, Sell/Buy how many). A dim full-screen backdrop centers one small
   glass panel, same look language as every other gump. Higher z-index than the context
   menu (10000) since a quantity prompt is often triggered *from* a context menu item. */
.prompt-modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 10001;
  background: rgba(0, 0, 0, 0.55);
  align-items: center;
  justify-content: center;
}

.prompt-modal {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 260px;
  padding: 20px 22px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-top-color: rgba(255, 158, 88, 0.45);
  border-radius: 8px;
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.prompt-modal-label {
  color: var(--ink);
  font-size: 14px;
  text-align: center;
}

.prompt-modal-input {
  padding: 8px;
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: 4px;
  color: var(--ink);
  font-family: inherit;
  font-size: 16px;
  text-align: center;
}

.prompt-modal-input:focus {
  outline: none;
  border-color: var(--accent);
}

.prompt-modal-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
}

.prompt-modal-btn {
  padding: 7px 20px;
  border-radius: 4px;
  font-family: inherit;
  font-size: 13px;
  font-weight: bold;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.prompt-modal-confirm {
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  color: var(--on-accent);
}

.prompt-modal-confirm:hover {
  background: var(--accent-bright);
}

.prompt-modal-cancel {
  background: var(--edge);
  border: 1px solid var(--edge);
  color: var(--ink);
}

.prompt-modal-cancel:hover {
  background: rgba(255, 158, 88, 0.15);
  color: var(--accent);
}

.wiki-nav {
  display: flex;
  gap: 16px;
  padding: 10px 20px;
  background: var(--panel);
  border-bottom: 3px solid var(--edge);
}

.wiki-nav a {
  color: var(--accent);
  text-decoration: none;
  font-size: 14px;
}

.wiki-nav a:hover { text-decoration: underline; }

.wiki-main {
  max-width: 900px;
  margin: 0 auto;
  padding: 30px;
}

.wiki-main h1 {
  color: var(--accent);
  margin-top: 0;
}

.wiki-list {
  line-height: 2;
}

.wiki-list a { color: var(--accent); }

.wiki-section {
  background: var(--panel);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 16px 20px;
  margin-bottom: 24px;
}

.wiki-section h2 {
  color: var(--accent);
  margin-top: 0;
}

.wiki-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 12px;
}

.wiki-table th, .wiki-table td {
  text-align: left;
  padding: 6px 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  font-size: 14px;
}

.wiki-table th {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 11px;
}

.wiki-icon {
  width: 32px;
  height: 32px;
  image-rendering: pixelated;
}

.wiki-heal {
  color: #4caf50;
  font-weight: bold;
}

/* Testers Ring dev tool — deliberately more "extra" than every other gump (shimmering
   gradient frame, glowing title text) since this is meant to be the reusable base look
   for future dev/test tooling rather than a one-off. */
.devtools-gump-overlay {
  position: fixed;
  inset: 0;
  z-index: 1600;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(10, 4, 20, 0.65);
}

.devtools-gump-frame {
  padding: 3px;
  border-radius: 14px;
  background: linear-gradient(135deg, #ff8a8a, #ffe08a, #8ad4ff, #c88aff, #ff8a8a);
  background-size: 300% 300%;
  animation: devtools-shimmer 6s ease infinite;
  box-shadow: 0 0 32px rgba(200, 140, 255, 0.55), 0 8px 24px rgba(0, 0, 0, 0.6);
}

@keyframes devtools-shimmer {
  0% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}

.devtools-gump {
  width: 320px;
  max-height: 80vh;
  overflow-y: auto;
  background: #1a1024;
  border-radius: 11px;
}

.devtools-gump-header {
  position: relative;
  padding: 14px 16px;
  text-align: center;
  border-bottom: 1px solid rgba(216, 150, 255, 0.35);
}

.devtools-gump-title {
  font-size: 17px;
  font-weight: bold;
  background: linear-gradient(90deg, #ff8a8a, #ffe08a, #8ad4ff, #c88aff, #ff8a8a);
  background-size: 300% 100%;
  animation: devtools-shimmer 6s ease infinite;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: 0 0 14px rgba(200, 140, 255, 0.5);
}

.devtools-gump-close {
  position: absolute;
  top: 8px;
  right: 12px;
  background: none;
  border: none;
  color: #d896ff;
  font-size: 20px;
  line-height: 1;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  padding: 0 4px;
}

.devtools-gump-body {
  padding: 8px 16px 16px;
}

.devtools-gump-section-label {
  margin-top: 10px;
  padding-top: 6px;
  font-size: 11px;
  text-transform: uppercase;
  color: #a888c9;
  border-top: 1px solid rgba(216, 150, 255, 0.25);
}

.devtools-gump-section-label:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.devtools-gump-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 0;
}

.devtools-gump-row label {
  flex: 1;
  font-size: 13px;
  color: #e8d9ff;
}

.devtools-gump-input {
  width: 64px;
  background: #2a1f38;
  border: 1px solid #6b4a8f;
  color: #f0e8ff;
  border-radius: 4px;
  padding: 3px 6px;
  font-size: 13px;
}

.devtools-gump-set {
  background: linear-gradient(135deg, #8a5fc9, #c88aff);
  border: none;
  color: #1a1024;
  font-weight: bold;
  border-radius: 4px;
  padding: 4px 10px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
  font-size: 12px;
}

.devtools-gump-set:hover {
  filter: brightness(1.15);
}

.devtools-contact-row {
  padding: 8px 0;
  border-bottom: 1px solid rgba(216, 150, 255, 0.2);
  font-size: 12px;
}

.devtools-contact-row:last-child { border-bottom: none; }

.devtools-contact-name {
  color: #e8d9ff;
  font-weight: bold;
}

.devtools-contact-email {
  color: #a888c9;
}

.devtools-contact-message {
  margin-top: 3px;
  color: #c9b8e8;
  white-space: pre-wrap;
  word-break: break-word;
}

.devtools-contact-empty {
  color: #a888c9;
  font-size: 12px;
  padding: 6px 0;
}

/* ============ Slate & Ember chrome pass ============
   Everything below deliberately sits at the END of the file so it wins the cascade over
   the per-panel rules above at equal specificity — one grouped elevation/typography
   treatment instead of re-editing every gump rule individually. */

/* Panel glass: blur the location backdrop through every floating surface, one soft
   elevation shadow, and an ember hairline across the top edge (border-top-color only —
   the border itself is already 1px var(--edge)). */
.activities-panel, .stats-panel, .world-map-panel, .journal-panel, .chat-panel,
.equipped-panel-wrap, .inventory-panel, .bag-panel-wrap, .status-gump, .skills-gump,
.combat-gump, .guild-gump, .ground-loot-gump, .travel-gump, .context-menu,
.server-reboot-panel, .death-overlay, .travel-map-tooltip, .toast {
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  border-radius: 8px;
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  border-top-color: rgba(255, 158, 88, 0.45);
}

/* Data panels get the modern uppercase micro-header; the serif stays reserved for
   identity (names and places) below. */
.journal-panel h2, .chat-panel h2, .equipped-panel-wrap h2, .inventory-panel h2,
.stats-panel h2, .activities-header h2, .skills-gump-header, .skills-gump-header span {
  font-family: system-ui, 'Segoe UI', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Serif is an identity marker now, not the default voice: character names, place
   names, and the wordmark keep it. */
.game-header-title, .nameplate-name, .current-location-name,
.auth-box h1, .testers-box h2, .server-reboot-title {
  font-family: Georgia, 'Times New Roman', serif;
  letter-spacing: 0.02em;
  text-transform: none;
}

/* Vitals read at a glance: each bar fill casts its own colored glow. */
.nameplate-bar-fill.nameplate-bar-hits { box-shadow: 0 0 8px rgba(226, 92, 79, 0.55); }
.nameplate-bar-fill.nameplate-bar-mana { box-shadow: 0 0 8px rgba(93, 142, 240, 0.55); }
.nameplate-bar-fill.nameplate-bar-stam { box-shadow: 0 0 8px rgba(143, 200, 79, 0.5); }
.nameplate-bar { background: rgba(255, 255, 255, 0.07); border-color: transparent; }

/* Active tabs flip to a solid ember chip — the tokenized var(--edge) background the
   bulk pass left here is translucent, which reads as disabled rather than selected. */
.journal-tab.active, .chat-tab.active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.journal-tab, .chat-tab { color: var(--muted); }

/* Primary CTA buttons carry a soft ember bloom so they read as the one warm thing on
   an otherwise cool panel. */
.death-overlay-teleport-btn, .auth-box button, .testers-box button {
  box-shadow: 0 4px 14px rgba(240, 138, 66, 0.3);
  border-radius: 6px;
}

/* Secondary action buttons — the game's workhorse control (Attack, Craft, Cast, Buy,
   Take, Cancel Travel…). Formerly solid bronze with dark text; now a quiet ember
   outline chip that flips solid on hover. Grouped here once rather than re-editing
   each rule above. */
/* .spellbook-cast-btn deliberately absent — the Spellbook's controls carry their own
   arcane-violet identity (see the .spellbook-* block) matching its mystical frame art,
   not the shared ember chip. */
.chat-form button, .activity-toggle, .ground-loot-take-btn, .ground-loot-take-all-btn,
.guild-action-btn, .guild-sign-btn, .guild-inline-form button,
.guild-management-buttons button, .stable-buy-btn,
.crafting-craft-btn, .admin-player-action-btn, .dungeon-attack-btn, .auto-battle-btn,
.ground-loot-view-btn, .my-house-action-btn, .travel-cancel-btn, .house-view-leave-btn,
.trade-accept-btn {
  background: rgba(255, 158, 88, 0.10);
  border: 1px solid rgba(255, 158, 88, 0.4);
  border-radius: 5px;
  color: var(--accent);
}

.chat-form button:hover, .activity-toggle:hover, .ground-loot-take-btn:hover,
.ground-loot-take-all-btn:hover, .guild-action-btn:hover, .guild-sign-btn:hover,
.guild-inline-form button:hover, .guild-management-buttons button:hover,
.stable-buy-btn:hover:not(:disabled),
.crafting-craft-btn:hover:not(:disabled), .admin-player-action-btn:hover,
.dungeon-attack-btn:hover, .auto-battle-btn:hover, .ground-loot-view-btn:hover,
.my-house-action-btn:hover, .travel-cancel-btn:hover:not(:disabled),
.house-view-leave-btn:hover, .trade-accept-btn:hover:not(:disabled) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

/* Disabled state must still beat the grouped chip style above (same specificity,
   declared later). */
.stable-buy-btn:disabled, .crafting-craft-btn:disabled,
.travel-cancel-btn:disabled, .trade-accept-btn:disabled {
  background: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.1);
  color: #6d7480;
}

/* The bank weight meter fill was tokenized to the translucent edge color — give it a
   real ember fill instead. */
.bank-weight-bar-fill { background: var(--accent-strong); }

/* The header Log Out button never had a rule of its own — it was rendering as a raw
   browser-default button. Styled as a quiet outline chip matching the secondary
   buttons above. */
.game-header form button {
  padding: 5px 12px;
  font-family: inherit;
  font-size: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 5px;
  color: var(--muted);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.game-header form button:hover {
  border-color: rgba(255, 158, 88, 0.5);
  color: var(--accent);
}

/* Crafting gumps (Blacksmithing/Carpentry/Tinkering) reuse .bank-gump's markup for the
   overlay plumbing, but the Bank's commissioned chest frame (with "BANK" baked into the
   art) is the Bank's identity, not theirs — reset the border-image and give them the
   standard glass chrome. Their own .skills-gump-header supplies the correct title. */
.crafting-gump {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--edge);
  border-top-color: rgba(255, 158, 88, 0.45);
  border-image: none;
  background: var(--panel);
  border-radius: 8px;
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  /* No longer user-resizable (see crafting.js) — capped instead of growing unbounded.
     The scroll lives on .crafting-gump-body below, not here, so the header (title,
     drag handle, close button) stays put instead of scrolling out of reach along with
     a long recipe list. */
  max-height: 80vh;
}

/* Unlike Bank/Trade, there's no border-image art with a baked-in title for this panel
   (reset above) — revert the inherited .bank-gump header treatment (absolutely
   positioned 40px above the panel, title text hidden — meant to overlay that art's own
   40px-tall decorative top border, which this panel doesn't have) back to a normal,
   visible, in-flow title bar. Without this, the header renders off-panel with no
   visible title, drag handle, or reachable close button. */
.crafting-gump .skills-gump-header {
  position: static;
  flex-shrink: 0;
  height: auto;
  padding: 10px 14px;
  border-bottom: 2px solid rgba(255, 255, 255, 0.12);
  background: transparent;
  justify-content: space-between;
}

.crafting-gump .skills-gump-header span {
  display: block;
}

.crafting-gump .skills-gump-close {
  margin: 0;
}

/* The qty input borrows .devtools-gump-input's purple identity in markup — keep the
   sharing, override the skin to match every other slate input. */
.crafting-qty-input {
  background: var(--well);
  border: 1px solid var(--edge);
  color: var(--ink);
  font-family: inherit;
}

/* ============ Backpack-as-bag ============
   The main Backpack sheds the glass panel entirely — it renders as the bag itself
   (backpack_gump.png, drawn by tools/generateSprites.js's drawBackpackGump). The
   padding here MUST match that file's BAG_PAD_* constants (56/34/30/34): it positions
   the item grid exactly over the art's open interior. Sub-bags and the Bank keep
   their existing looks. */
.inventory-panel {
  background: none;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 0;
}

.inventory-panel h2 { margin: 0 0 6px 12px; }

.backpack-bag {
  background: url('/assets/sprites/backpack_gump.png') top left / 100% 100% no-repeat;
  padding: 56px 34px 30px;
  width: fit-content;
  /* drop-shadow follows the bag's silhouette (flap wings and all), where box-shadow
     would draw a rectangle around the transparent canvas. */
  filter: drop-shadow(0 14px 24px rgba(0, 0, 0, 0.55));
}

.backpack-bag .backpack-panel {
  background-image: none;
  border: none;
  border-radius: 10px;
  overflow: visible;
}

/* The World Map header button was a 22px icon squint — now a solid ember chip, the
   one always-visible call-to-action on the main screen. */
.travel-map-open-btn {
  padding: 5px 12px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--on-accent);
  box-shadow: 0 3px 10px rgba(240, 138, 66, 0.3);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.travel-map-open-btn:hover { background: var(--accent-bright); }

/* ============ Status gump ============
   Now just the always-visible essentials: the character's name in the title bar
   (serif override near the end of this file), vitals bars, transient progress rows,
   and the buff bar — stat training moved to the Skills panel, the badge strip to the
   header crest. */
.status-gump { width: 248px; }

/* Ember hairline divider between the gump's sections, brightest in the middle. */
.status-divider {
  height: 1px;
  margin: 12px 4px 10px;
  background: linear-gradient(90deg, transparent, rgba(255, 158, 88, 0.55) 50%, transparent);
}

.status-stats .stats-row { padding: 5px 0; }
.status-stats .stats-row + .stats-row { border-top: 1px solid rgba(255, 255, 255, 0.05); }

.status-stats .stats-total {
  border-top: none;
  padding-top: 4px;
  font-variant-numeric: tabular-nums;
}

/* Thin meter under the Total line showing how close to the stat cap you are. */
.stats-total-bar {
  height: 4px;
  margin-top: 5px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.stats-total-fill {
  height: 100%;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  box-shadow: 0 0 6px rgba(255, 158, 88, 0.45);
}

/* Attack / Armor / Weight as three equal columns split by vertical dividers. */
.status-gump .stats-icon-row {
  justify-content: stretch;
  gap: 0;
  margin-top: 0;
  padding-top: 2px;
  border-top: none;
}

.status-gump .stats-icon-badge {
  flex: 1;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

.status-gump .stats-icon-badge + .stats-icon-badge {
  border-left: 1px solid rgba(255, 255, 255, 0.1);
}

/* ============ "On the Ground" panel ============
   Same glass chrome as the other game-main panels; also a drop target — dragdrop.js
   treats a drop anywhere on it as item:drop. */
.ground-panel {
  width: 248px;
  align-self: flex-start;
  padding: 16px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-top-color: rgba(255, 158, 88, 0.45);
  border-radius: 8px;
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
}

.ground-panel h2 {
  margin: 0 0 8px;
  font-family: system-ui, 'Segoe UI', sans-serif;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.ground-panel-hint {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
  margin-bottom: 8px;
}

.ground-panel.drag-over {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Collapsible Stats section inside the Status gump — same chevron/.collapsed pattern
   as .crafting-section-header, scoped to this gump. Replaces the divider that used to
   sit between the vitals and the stat rows (its border-bottom plays that role now). */
.status-stats-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 2px;
  margin-top: 10px;
  font-family: system-ui, 'Segoe UI', sans-serif;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.status-stats-toggle {
  transition: transform 0.15s ease;
  font-size: 9px;
}

.status-stats-section.collapsed .status-stats-toggle {
  transform: rotate(-90deg);
}

.status-stats-section.collapsed .status-stats {
  display: none;
}

/* Total line, sans the old meter — tightened so it doesn't eat vertical space. */
.status-stats .stats-total {
  margin-top: 2px;
  padding-top: 2px;
  font-size: 12px;
}

/* ============ Market Place ============
   The market gump (views/partials/marketGump.ejs, public/js/market.js) reuses the
   crafting gumps' glass chrome (.crafting-gump) for its shell; everything below is the
   market-specific interior: page tabs, the 128x96 table tiles (tools/generateSprites.js's
   market/market_table*.png as each tile's header art), listing rows, and the two
   fixed-position modals (sell settings + one-time welcome). */
.market-gump {
  width: 720px;
}

.market-page-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 10px;
}

.market-page-tab {
  flex: 1;
  padding: 7px 10px;
  font-family: inherit;
  font-size: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--edge);
  border-radius: 5px;
  color: var(--ink-dim);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.market-page-tab:hover { border-color: rgba(255, 158, 88, 0.5); color: var(--accent); }
.market-page-tab.active {
  border-color: var(--accent);
  color: var(--accent);
  background: rgba(255, 158, 88, 0.08);
}

/* The premium (Front Row) tab wears its awning's colors so the tier reads at a glance. */
.market-page-tab-premium {
  border-color: rgba(138, 36, 48, 0.8);
  color: #e8b4bc;
}
.market-page-tab-premium.active {
  border-color: #d4af37;
  color: #f0d878;
  background: rgba(138, 36, 48, 0.25);
}

.market-my-table-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  margin-bottom: 10px;
  font-size: 12px;
  color: var(--accent);
  background: rgba(255, 158, 88, 0.07);
  border: 1px solid rgba(255, 158, 88, 0.35);
  border-radius: 5px;
}

.market-abandon-btn {
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(201, 60, 60, 0.6);
  border-radius: 4px;
  color: #e08a8a;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}
.market-abandon-btn:hover { background: rgba(201, 60, 60, 0.15); }

.market-table-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}

/* One rentable table: the generated stall art as a header band, listings beneath.
   A drop target (see dragdrop.js's .market-table branch) — the drag-over highlight
   matches every other drop zone's. */
.market-table {
  border: 1px solid var(--edge);
  border-radius: 6px;
  background:
    url('/assets/sprites/market/market_table.png') no-repeat top center / 128px 96px,
    rgba(0, 0, 0, 0.25);
  padding-top: 96px;
  min-height: 120px;
}

.market-table-premium {
  background-image: url('/assets/sprites/market/market_table_premium.png');
  border-color: rgba(138, 36, 48, 0.6);
}

.market-table.vacant { opacity: 0.75; }
.market-table.mine { border-color: var(--accent); }
.market-table.drag-over {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(255, 158, 88, 0.35);
  opacity: 1;
}

.market-table-banner {
  padding: 4px 8px;
  font-size: 11px;
  color: var(--ink-dim);
  background: rgba(0, 0, 0, 0.35);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.market-table.mine .market-table-banner { color: var(--accent); }

.market-table-listings {
  padding: 4px 6px 6px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.market-listing-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 4px;
  border-radius: 4px;
  font-size: 12px;
}

.market-listing-row:hover { background: rgba(255, 255, 255, 0.05); }
.market-listing-row.pending { opacity: 0.6; font-style: italic; }

.market-listing-icon {
  width: 28px;
  height: 28px;
  image-rendering: pixelated;
  flex-shrink: 0;
}

.market-listing-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink);
}

.market-listing-price {
  color: var(--gold, #d4af37);
  white-space: nowrap;
  font-size: 11px;
}

.market-listing-btn {
  padding: 2px 8px;
  font-family: inherit;
  font-size: 11px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--edge);
  border-radius: 4px;
  color: var(--ink-dim);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}
.market-listing-btn:hover { border-color: rgba(255, 158, 88, 0.5); color: var(--accent); }
.market-buy-btn {
  border-color: rgba(255, 158, 88, 0.45);
  color: var(--accent);
}

/* World Map panel's market note/button — same quiet-note styling family as
   .stable-note/.blacksmith-note (which inherit generic styles), plus a real button. */
.market-note {
  margin-top: 6px;
  font-size: 12px;
  color: var(--accent);
  font-style: italic;
}

.market-open-btn {
  margin-top: 6px;
  width: 100%;
  padding: 7px 10px;
  font-family: inherit;
  font-size: 12px;
  font-weight: bold;
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--on-accent);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

/* Fixed-position modal shell shared by the sell-settings and welcome popups — the
   game's first true modal (a centered dialog over a dimmed backdrop), distinct from
   the draggable gumps: setting a price is a focused decision, not a window to park. */
.market-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.55);
}

.market-modal {
  width: 360px;
  background: var(--panel);
  border: 1px solid var(--edge);
  border-top-color: rgba(255, 158, 88, 0.45);
  border-radius: 8px;
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.6);
}

.market-modal .skills-gump-header { cursor: default; }

.market-modal-body {
  padding: 12px 14px 14px;
  font-size: 13px;
  color: var(--ink-dim);
}

.market-modal-body p { margin: 0 0 10px; line-height: 1.5; }
.market-modal-body a { color: var(--accent); }

.market-sell-item-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.market-sell-item-icon {
  width: 36px;
  height: 36px;
  image-rendering: pixelated;
}

.market-sell-item-name { font-weight: bold; color: var(--ink); }

.market-sell-price-label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  margin-bottom: 10px;
}

#market-sell-price-input { width: 120px; padding: 5px 8px; }

.market-sell-mode-row {
  display: flex;
  gap: 14px;
  margin-bottom: 12px;
  font-size: 12px;
}

.market-sell-mode-row label {
  display: flex;
  align-items: center;
  gap: 5px;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.market-modal-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

.market-confirm-btn {
  padding: 7px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: bold;
  background: linear-gradient(180deg, var(--accent-strong), var(--accent-deep));
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--on-accent);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.market-cancel-btn {
  padding: 7px 14px;
  font-family: inherit;
  font-size: 12px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--edge);
  border-radius: 5px;
  color: var(--ink-dim);
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

/* ============ Header crest ============
   A small banner hanging from the game header's center (views/partials/headerCrest.ejs)
   carrying the XP bar and the attack/armor/weight badges — moved out of the Status
   gump, which now stays tight around the vitals. The ::before/::after flare wings are
   quarter-circle concave fills that curve the crest's shoulders out into the header
   rail, so it reads as one continuous forged piece rather than a box floating under a
   bar. */
.header-crest {
  position: absolute;
  top: calc(100% + 3px); /* below the header's 3px ember rail, hanging from it */
  left: 50%;
  /* --crest-offset-x is a horizontal-only drag offset from center, set by
     public/js/headerCrest.js and persisted per character — 0px (dead center) until
     the player drags it. Only the X term ever changes; top/vertical position stays
     fixed to the header rail regardless. */
  transform: translateX(calc(-50% + var(--crest-offset-x, 0px)));
  z-index: 5;
  width: 250px;
  padding: 8px 16px 10px;
  background: linear-gradient(180deg, #171b23 0%, rgba(15, 17, 22, 0.9) 100%);
  border: 1px solid rgba(255, 158, 88, 0.35);
  border-top: none;
  border-radius: 0 0 20px 20px;
  box-shadow:
    0 10px 24px rgba(0, 0, 0, 0.5),
    0 0 14px rgba(255, 158, 88, 0.08),
    inset 0 -1px 0 rgba(255, 181, 120, 0.1);
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  cursor: url('/assets/sprites/cursors/grab.png') 16 16, grab;
}

.header-crest.crest-dragging {
  cursor: url('/assets/sprites/cursors/grabbing.png') 16 16, grabbing;
  user-select: none;
}

/* Flare wings. radial-gradient paints everything OUTSIDE a quarter-circle centered on
   the wing's bottom-inner corner, leaving a smooth concave curve from the header edge
   down onto the crest's side. Solid fill (not the translucent panel glass) — a
   translucent wing over the scrolling backdrop would read as a seam, not a curve. */
.header-crest::before,
.header-crest::after {
  content: '';
  position: absolute;
  top: 0;
  width: 20px;
  height: 20px;
  pointer-events: none;
}

.header-crest::before {
  left: -20px;
  background: radial-gradient(circle 20px at 0 100%, transparent 19px, #171b23 20px);
}

.header-crest::after {
  right: -20px;
  background: radial-gradient(circle 20px at 100% 100%, transparent 19px, #171b23 20px);
}

/* The XP bar — the reserved "thin glowing ember meter" design, now tracking the
   Virtue Binding live (public/js/bindingXpBar.js drives #xp-bar-fill's width off
   character:update; the slow breathing glow keeps it feeling alive even sitting at a
   fixed percentage between ticks). */
.xp-bar {
  height: 6px;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

.xp-bar-fill {
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  box-shadow: 0 0 6px rgba(255, 158, 88, 0.45);
  animation: xp-ember-breathe 3s ease-in-out infinite;
  transition: width 0.4s ease;
}

@keyframes xp-ember-breathe {
  0%, 100% { box-shadow: 0 0 6px rgba(255, 158, 88, 0.45); filter: brightness(1); }
  50% { box-shadow: 0 0 12px rgba(255, 158, 88, 0.8); filter: brightness(1.15); }
}

/* The level-up flourish (public/js/bindingXpBar.js) — the bar's own container gets a
   brighter halo around it for the moment the fill sits pinned at 100% before draining
   down to the new level's progress, so the "you just leveled" beat reads clearly even
   on the thin header crest bar. */
.xp-bar.xp-bar-levelup {
  border-color: var(--accent);
  box-shadow: 0 0 14px rgba(255, 158, 88, 0.85);
}

/* Badge strip inside the crest — .stats-icon-row's base rule was written for its old
   Status-gump home (top divider + wide margin); tighter here under the XP bar. */
.header-crest .stats-icon-row {
  margin-top: 7px;
  padding-top: 0;
  border-top: none;
}

/* ============ Stats section in the Skills panel ============
   The stat-training rows (moved from the Status gump — views/partials/activities.ejs)
   restyled to sit flush with the panel's collapsible skill groups: chevron-first
   header like .skills-group-header, not the old space-between gump layout. The
   .collapsed behavior itself comes from the existing .status-stats-section rules. */
.skills-stats-section {
  margin-bottom: 4px;
}

.skills-stats-section .status-stats-header {
  justify-content: flex-start;
  gap: 6px;
  margin-top: 0;
  padding: 6px 4px 4px 0;
  font-size: 11px;
  letter-spacing: 0.05em;
}

/* ============ Skills panel: fixed height + animated collapses ============
   The Skills panel and the Equipped panel read as a mismatched pair whenever one's
   content made it taller than the other (expanding the Stats section, wrapping to
   separate flex lines at narrow widths) — both now share one fixed height, with the
   skill list flexing to absorb whatever the Stats section above it takes. 460px is the
   Equipped panel's natural content height (vitals block + slot grid + food slider); if
   that panel's contents ever change shape, retune both numbers together. */
.equipped-panel-wrap,
.activities-panel {
  height: 460px;
}

.activities-panel {
  display: flex;
  flex-direction: column;
}

.activities-panel .activities-list {
  flex: 1 1 auto;
  min-height: 0; /* lets the flex item actually shrink below its content height */
  max-height: none; /* the fixed panel height governs now, not the old 320px cap */
}

/* Slide-and-fade collapse for the skill groups and the Stats section — replaces the
   old instant display:none. The grid-template-rows 1fr->0fr transition animates the
   body's height without needing a measured max-height; the body keeps display:block
   while collapsed (overridden from the earlier rules) so there's something to animate,
   with overflow:hidden clipping it and opacity fading it on the way. */
.skills-group,
.status-stats-section {
  display: grid;
  grid-template-rows: auto 1fr;
  transition: grid-template-rows 0.28s ease;
}

.skills-group.collapsed,
.status-stats-section.collapsed {
  grid-template-rows: auto 0fr;
}

.skills-group-body,
.status-stats-section .status-stats {
  overflow: hidden;
  opacity: 1;
  transition: opacity 0.22s ease;
}

.skills-group.collapsed .skills-group-body,
.status-stats-section.collapsed .status-stats {
  display: block;
  opacity: 0;
  /* Overrides .skills-group-body's 8px empty-drop-target min-height — a collapsed
     group must actually reach 0 or the 0fr row gets propped open. */
  min-height: 0;
}



/* ============ Vitals inside the Equipped panel ============
   The Status gump's chrome is fully stripped — the vitals block (bars + progress rows
   + buff bar) is part of the Equipped Items panel now, sitting above the paperdoll
   grid (views/partials/equipped.ejs); an ember hairline below it hands off to the item
   slots. The character's name lives in the header crest. */
.status-gump-overlay {
  position: static;
}

.status-gump-overlay .status-gump {
  width: auto;
  background: none;
  border: none;
  border-radius: 0;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  margin-bottom: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid rgba(255, 158, 88, 0.18);
}

.status-gump-overlay .nameplate {
  padding: 2px 2px 0;
}

/* The character's name in the header crest — serif identity font over the XP bar. */
.header-crest-name {
  text-align: center;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 15px;
  font-weight: bold;
  letter-spacing: 0.02em;
  color: var(--accent);
  text-shadow: 0 1px 6px rgba(255, 181, 120, 0.35), 0 2px 3px rgba(0, 0, 0, 0.7);
  margin-bottom: 6px;
}

/* The Virtue Binding's level, sitting right after the name atop its own XP bar —
   smaller and unshadowed so it reads as a badge, not a second name. */
.header-crest-level {
  font-family: 'Segoe UI', sans-serif;
  font-size: 11px;
  font-weight: normal;
  letter-spacing: normal;
  color: var(--accent-bright);
  text-shadow: none;
  opacity: 0.85;
}

/* The header crest hangs ~42px into the content area over the centered bag — this
   drops the bag below it with a small breathing gap (crest bottom sits at ~133px from
   the viewport top with game-main's 30px padding putting the bag at 91px). */
.inventory-panel {
  margin-top: 52px;
}

/* Inspect Binding gump (views/partials/bindingGump.ejs, public/js/binding.js) — same
   "standard glass chrome" shell the Crafting gump uses (see .crafting-gump above),
   just its own width for an 8-virtue grid rather than a scrolling recipe list. */
.binding-gump {
  display: flex;
  flex-direction: column;
  width: 620px;
  border: 1px solid var(--edge);
  border-top-color: rgba(255, 158, 88, 0.45);
  border-image: none;
  background: var(--panel);
  border-radius: 8px;
  backdrop-filter: blur(7px);
  -webkit-backdrop-filter: blur(7px);
  box-shadow: 0 16px 38px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  max-height: 85vh;
}

.binding-gump .skills-gump-header {
  position: static;
  flex-shrink: 0;
  height: auto;
  padding: 10px 14px;
  border-bottom: 2px solid rgba(255, 255, 255, 0.12);
  background: transparent;
  justify-content: space-between;
}

.binding-gump .skills-gump-header span { display: block; }
.binding-gump .skills-gump-close { margin: 0; }

.binding-gump-body {
  padding: 14px;
  overflow-y: auto;
}

.binding-summary {
  margin-bottom: 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.binding-summary-level {
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 16px;
  font-weight: bold;
  color: var(--accent);
  margin-bottom: 6px;
}

/* A wider, taller restatement of the header crest's own ember bar (.xp-bar/.xp-bar-fill
   in game.css) — same design, just promoted to a size worth reading progress off of. */
.binding-summary-xp-bar {
  height: 10px;
  border-radius: 5px;
  margin-bottom: 8px;
}

.binding-summary-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: var(--ink);
  margin-bottom: 6px;
}

.binding-summary-hint {
  font-size: 12px;
  color: var(--ink-dim);
  font-style: italic;
}

.binding-virtues {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.binding-virtue {
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: 10px;
}

.binding-virtue-header {
  display: flex;
  flex-direction: column;
  margin-bottom: 8px;
}

.binding-virtue-label {
  font-weight: bold;
  color: var(--accent);
  font-size: 13px;
}

.binding-virtue-theme {
  font-size: 10px;
  color: var(--ink-dim);
}

.binding-virtue-tiers {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.binding-tier-node {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  background: rgba(0, 0, 0, 0.2);
  border: 1px solid var(--edge);
  border-radius: 4px;
  padding: 5px 8px;
  color: var(--ink-dim);
  font-size: 11px;
  text-align: left;
  cursor: url('/assets/sprites/cursors/point.png') 4 2, pointer;
}

.binding-tier-node:disabled {
  cursor: default;
  opacity: 0.55;
}

.binding-tier-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
  background: rgba(255, 255, 255, 0.15);
  border: 1px solid var(--edge);
}

/* Already spent — a filled ember dot, full-brightness label. */
.binding-tier-unlocked {
  color: var(--ink);
  border-color: rgba(255, 158, 88, 0.45);
}
.binding-tier-unlocked .binding-tier-dot {
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  box-shadow: 0 0 6px rgba(255, 158, 88, 0.5);
  border-color: transparent;
}

/* The one node a talent point can actually buy right now — a bright ring invites the
   click; still resolves to :disabled (see the EJS) when no points are left to spend. */
.binding-tier-next:not(:disabled) {
  color: var(--accent-bright);
  border-color: var(--accent);
}
.binding-tier-next:not(:disabled) .binding-tier-dot {
  border-color: var(--accent);
  box-shadow: 0 0 4px rgba(255, 158, 88, 0.4);
}
.binding-tier-next:not(:disabled):hover {
  background: rgba(255, 158, 88, 0.12);
}

/* Locked behind an earlier tier in the same virtue not being spent yet. */
.binding-tier-locked {
  opacity: 0.5;
}
