/* Apple ][e palette + blocky text. The original 280x192 hi-res had
   black, white, green, violet, orange, blue. Lo-res added more, but we keep
   the iconic five for the recreation. Text screen was 40x24 monochrome white. */

:root {
  --bg: #000;
  --fg: #ffffff;            /* monochrome text screen white-on-black */
  --fg-dim: #aaaaaa;
  --green:  #41ff00;        /* hi-res green */
  --violet: #c750ff;        /* hi-res violet */
  --orange: #ff7a00;        /* hi-res orange */
  --blue:   #2050ff;        /* hi-res blue */
  --selected: #ffd400;      /* yellow highlight (not on Apple but readable) */
  --crt-glow: 0 0 2px rgba(255,255,255,0.4);
}

* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; height: 100%;
  background: #111;
  color: var(--fg);
  font-family: 'Menlo', 'Consolas', 'Courier New', monospace;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

video {
  width: 400px;
  height: auto; /* maintain aspect ratio */
  margin-top: 40px;
  display: block;
}

/* BUILD 327: video moved INSIDE the connect-grid left column so it
   stacks above the title/subtitle/copyright. The old #screen-connect
   > video rule positioned the video as a sibling above the grid,
   which forced the page to scroll. This new rule scopes the video
   into the left column with auto-shrink for narrow viewports. */
.connect-logo-video {
  display: block;
  width: 100%;
  max-width: 360px;
  margin: 0 auto 14px;
  /* BUILD 388.1: NO border-radius. The logo's black background is
     supposed to fade seamlessly into the page; rounded corners cut
     curved slivers out of that black and expose the page behind.
     See CLAUDE.md uiPatterns.richardsryLogo. */
}

#screen-frame {
  display: flex;
  flex-direction: column;
  height: 100vh;
  align-items: center;
  justify-content: center;
  background: radial-gradient(ellipse at center, #1a1a1a 0%, #000 100%);
  padding: 12px;
}

/* Row container: main CRT screen on the left, chat panel on the right. */
#screen-row {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 24px;
  width: 100%;
  max-width: 1240px;
}

/* The main "monitor" area, ~4:3-ish like Apple ][e screens. */
#screen {
  position: relative;
  flex: 1 1 auto;
  max-width: 880px;
  aspect-ratio: 4 / 3;
  background: #000;
  border: 4px solid #444;
  box-shadow: 0 0 0 8px #222, 0 0 30px rgba(0,255,128,0.05) inset;
  padding: 24px 28px;
  overflow: hidden;
  text-shadow: var(--crt-glow);
}

/* Hide the chat chassis entirely until the user has joined.
   BUILD 327: also hide the dice stage + dice-toggle button on the
   login / mode-picker screens. The dice stage is a global
   draggable widget that shouldn't appear until the player is
   actually in a game, and the DICE: OFF/ON pill in the footer is
   only meaningful once combat exists. The body.is-joined flag flips
   when the socket connects to a room, so this rule covers both
   the login form AND the play-mode picker (both pre-join). */
body:not(.is-joined) #chat-panel { display: none; }
body:not(.is-joined) #dice-stage { display: none !important; }
body:not(.is-joined) #dice-toggle { display: none !important; }
body:not(.is-joined) #screen-row { justify-content: center; }
/* BUILD 327: the connect screen sometimes overflowed the viewport
   on shorter laptop displays, surfacing a vertical scrollbar.
   Constrain the frame to the viewport height and let inner
   columns scroll only if their content genuinely overflows. */
html, body { overflow: hidden; height: 100%; }
body:not(.is-joined) #screen-connect { overflow: auto; }

/* ----- Connect-screen lobby (live list of joined players) ----- */
#connect-lobby {
  margin-top: 18px;
  padding: 10px 14px;
  border: 1px dotted #444;
  max-width: 380px;
}
#connect-lobby h3 {
  color: var(--orange);
  letter-spacing: 0.14em;
  margin: 0 0 6px;
  font-size: 13px;
}
#connect-players-list {
  list-style: none;
  padding: 0;
  margin: 0;
  font-size: 12px;
}
#connect-players-list li { padding: 2px 0; }

/* Chat panel: same chassis treatment as the main screen, matched height. */
#chat-panel {
  position: relative;
  width: 280px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: #000;
  border: 4px solid #444;
  box-shadow: 0 0 0 8px #222;
  padding: 6px 8px 8px;
  /* Match the screen's aspect-driven height. */
  align-self: stretch;
}
#chat-header {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.12em;
  font-size: 13px;
  padding-bottom: 4px;
  border-bottom: 1px dotted #444;
  text-align: center;
}
/* BUILD 258: room code shown next to PARTY CHAT for private rooms.
   Bright yellow so the leader can grab it at a glance to share. */
.chat-room-code {
  color: var(--yellow);
  font-weight: bold;
  letter-spacing: 0.16em;
  margin-left: 4px;
}
/* BUILD 265: READY-check panel under the chat header. One row per
   connected player. Title strip is muted; rows are tighter than the
   chat lines below. Ticked rows turn green so the leader can scan
   who's still missing at a glance. */
#ready-panel {
  border-bottom: 1px dotted #444;
  padding: 4px 6px 6px;
  font-size: 12px;
}
#ready-panel:empty { display: none; }
.ready-panel-title {
  color: var(--yellow);
  font-weight: bold;
  letter-spacing: 0.10em;
  font-size: 11px;
  margin-bottom: 4px;
  text-align: center;
}
.ready-panel-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 2px 2px;
  cursor: default;
}
.ready-panel-row.is-ready { background: rgba(80, 180, 80, 0.18); }
.ready-panel-cb {
  margin: 0;
  accent-color: var(--orange);
}
.ready-panel-cb:not(:disabled) { cursor: pointer; }
.ready-panel-name {
  flex: 1 1 auto;
  font-weight: bold;
  letter-spacing: 0.06em;
}
.ready-panel-you {
  font-size: 10px;
  color: var(--orange);
  letter-spacing: 0.10em;
}
/* BUILD 270: leader footer line in the ready panel. The leader has
   no checkbox -- they make the call to enter -- but they're still
   named here so everyone sees who's about to lead the descent. */
.ready-panel-leader {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px dotted #444;
  font-size: 10px;
  color: var(--yellow);
  letter-spacing: 0.10em;
  text-align: center;
}
/* BUILD 266: in-game modal overlay (replacement for browser alert /
   confirm popups). Backdrop dims the whole viewport; card is centered
   and styled to match the game's monospace + amber-on-black look. */
.game-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'VT323', 'Courier New', monospace;
}
.game-modal-card {
  background: #0d0d0d;
  border: 2px solid var(--orange);
  box-shadow: 0 0 0 1px #000, 0 0 28px rgba(255, 165, 0, 0.35);
  padding: 18px 22px 16px;
  min-width: 320px;
  max-width: 560px;
  color: #e6e6c8;
}
.game-modal-card.tone-danger { border-color: #f44; box-shadow: 0 0 0 1px #000, 0 0 28px rgba(255, 60, 60, 0.45); }
.game-modal-title {
  color: var(--yellow);
  letter-spacing: 0.16em;
  font-weight: bold;
  font-size: 18px;
  text-align: center;
  border-bottom: 1px dotted #444;
  padding-bottom: 8px;
  margin-bottom: 12px;
}
.game-modal-body {
  font-size: 16px;
  line-height: 1.4;
  letter-spacing: 0.06em;
  text-align: center;
  white-space: pre-wrap;
  padding: 8px 0 14px;
}
.game-modal-footer {
  display: flex;
  justify-content: center;
  gap: 14px;
  margin-top: 4px;
}
.game-modal-footer button {
  font-family: inherit;
  font-size: 16px;
  letter-spacing: 0.10em;
  padding: 6px 18px;
  background: #181818;
  color: var(--yellow);
  border: 1px solid #555;
  cursor: pointer;
  min-width: 80px;
}
.game-modal-footer button.game-modal-ok {
  border-color: var(--orange);
  color: var(--orange);
  font-weight: bold;
}
.game-modal-footer button.game-modal-ok:hover { background: var(--orange); color: #000; }
.game-modal-footer button.game-modal-cancel:hover { background: #333; }
.game-modal-footer button:focus { outline: 1px dashed var(--yellow); outline-offset: 2px; }

/* BUILD 266: Edge-of-Town redesign. Top half: title + menu on the
   left, edge_of_town.png in the upper-right (mirrors cemetery layout).
   Bottom half: a large descent panel housing the only ENTER MAZE
   button in the entire game. The button is the call to action --
   styled like an ominous portal -- and goes amber-glow when the
   party is ready, dim when something is blocking it. */
#edge-header .town-header-left { flex: 1 1 auto; }
.town-header-art.edge-art {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
}
.town-header-art.edge-art img.town-building {
  max-width: 300px;
  max-height: auto-fit;
  border: 0;
}
.edge-descent {
  margin-top: 18px;
  display: flex;
  justify-content: center;
}
.edge-descent-frame {
  width: min(640px, 95%);
  padding: 18px 24px 22px;
  background:
    radial-gradient(ellipse at center, rgba(255, 165, 0, 0.08) 0%, rgba(0,0,0,0) 70%),
    #0a0a0a;
  border: 2px solid var(--orange);
  box-shadow: 0 0 0 1px #000, 0 0 32px rgba(255, 165, 0, 0.18) inset, 0 0 24px rgba(255, 165, 0, 0.18);
  text-align: center;
}
.edge-descent-runes {
  color: var(--yellow);
  letter-spacing: 0.36em;
  font-weight: bold;
  font-size: 18px;
  text-shadow: 0 0 8px rgba(255, 220, 64, 0.45);
  margin-bottom: 12px;
}
.edge-descent-status {
  color: #c0c0a8;
  font-size: 13px;
  letter-spacing: 0.12em;
  min-height: 20px;
  margin-bottom: 14px;
}
.edge-enter-btn {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 16px 36px;
  background: #181208;
  color: var(--orange);
  border: 2px solid var(--orange);
  box-shadow: 0 0 0 1px #000, 0 0 22px rgba(255, 165, 0, 0.45);
  font-family: inherit;
  font-size: 22px;
  letter-spacing: 0.20em;
  font-weight: bold;
  cursor: pointer;
  transition: transform 80ms ease, box-shadow 120ms ease, background 120ms ease;
}
.edge-enter-btn:hover:not(:disabled) {
  background: var(--orange);
  color: #000;
  box-shadow: 0 0 0 1px #000, 0 0 36px rgba(255, 165, 0, 0.85);
  transform: translateY(-1px);
}
.edge-enter-btn:active:not(:disabled) { transform: translateY(1px); }
.edge-enter-btn:disabled,
.edge-enter-btn.is-blocked {
  background: #1a1a1a;
  color: #6a5230;
  border-color: #6a5230;
  box-shadow: 0 0 0 1px #000;
  cursor: not-allowed;
  opacity: 0.7;
}
.edge-enter-icon {
  font-size: 26px;
  line-height: 1;
}
.edge-enter-label { font-size: 22px; }
.edge-enter-sub {
  font-size: 11px;
  letter-spacing: 0.16em;
  color: var(--yellow);
  font-weight: normal;
}
.edge-enter-btn:disabled .edge-enter-sub { color: #8a7a4a; }

/* BUILD 267: dice roller stage. Re-themed for the amber/CRT aesthetic
   of the game (the source styles were purple/teal). The stage is the
   draggable window that holds the spinning d6/d20/etc. We attach it
   to <body> as a singleton; each player drags it where it fits on
   their screen and the offset is saved per-userId in localStorage. */
.dice-stage {
  z-index: 9100;
  overflow: hidden;
  border: 2px solid var(--orange);
  border-radius: 8px;
  background:
    radial-gradient(circle at 48% 42%, rgba(255, 165, 0, 0.10), transparent 9rem),
    linear-gradient(145deg, #0d0d0d, #050505);
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.55), 0 0 22px rgba(255, 165, 0, 0.25);
  transition: opacity 160ms ease, visibility 160ms ease, transform 160ms ease;
}
.dice-stage svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.dice-stage.is-roll-hidden {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transform: scale(0.96);
}
.dice-surface,
.dice-layer {
  position: relative;
  width: 100%;
  height: 100%;
}
.dice-surface::before {
  position: absolute;
  inset: 6px;
  border: 1px solid rgba(255, 200, 100, 0.20);
  border-radius: 5px;
  content: "";
  pointer-events: none;
}
.dice-surface::after {
  position: absolute;
  left: 10%; right: 10%;
  bottom: 9%; height: 22%;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(0, 0, 0, 0.42), transparent 68%);
  content: "";
  pointer-events: none;
}
.dice-layer { z-index: 1; }
.die-wrapper {
  position: absolute;
  filter: drop-shadow(0 12px 14px rgba(0, 0, 0, 0.45));
}
.dice-window-draggable { cursor: grab; }
.dice-window-draggable.is-dragging {
  cursor: grabbing;
  box-shadow: 0 24px 54px rgba(0, 0, 0, 0.65), 0 0 28px rgba(255, 165, 0, 0.5);
}
/* Dice header strip on top of the stage -- shows the roll label
   ("PETE attacks GOBLIN: 1d8+2") and a drag handle hint. */
.dice-stage-label {
  position: absolute;
  top: 4px; left: 0; right: 0;
  text-align: center;
  font-family: 'VT323', 'Courier New', monospace;
  font-size: 11px;
  color: var(--yellow);
  letter-spacing: 0.12em;
  pointer-events: none;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
  z-index: 2;
}
.dice-stage-total {
  position: absolute;
  bottom: 4px; left: 4px; right: 4px;
  text-align: center;
  font-family: 'VT323', 'Courier New', monospace;
  /* BUILD 277: sized to fit "DICE (3) : 4+5+3 + 2 = 14" inside the
     200px-ish stage. Was 18px + 0.18em letter-spacing which clipped
     anything past a 3-character total. */
  font-size: 14px;
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.06em;
  pointer-events: none;
  text-shadow: 0 0 4px #000, 0 0 8px rgba(255, 165, 0, 0.4);
  z-index: 2;
  opacity: 0;
  transition: opacity 200ms ease;
  white-space: nowrap;
  overflow: visible;
}
.dice-stage-total.show { opacity: 1; }

/* BUILD 269: announcer strip welded to the bottom of the dice stage.
   Single line of text, same orange border, ~28px tall. Because the
   stage is position:fixed, this absolute child rides along with it
   when the user drags the stage. The negative top:100% offset hugs
   the stage bottom edge and the border-top:0 makes the seam invisible
   so the two read as one connected window. */
.dice-announcer {
  position: absolute;
  top: 100%;
  left: -2px;       /* compensate for the stage's own border so edges align */
  right: -2px;
  /* BUILD 277: min-height + auto-wrap so long formulas don't get
     truncated to "DICE (3) : 4+5+3..." with ellipsis. The strip
     grows to fit the breakdown text. */
  min-height: 28px;
  padding: 4px 8px;
  background: #0d0d0d;
  border: 2px solid var(--orange);
  border-top: 0;
  border-radius: 0 0 8px 8px;
  text-align: center;
  font-family: 'VT323', 'Courier New', monospace;
  font-size: 14px;
  letter-spacing: 0.06em;
  color: var(--yellow);
  text-shadow: 0 0 6px rgba(255, 165, 0, 0.35);
  pointer-events: none;
  z-index: 9101;
  overflow: visible;
  white-space: normal;
  line-height: 20px;
}
.dice-announcer:empty { display: none; }
.dice-stage.is-roll-hidden .dice-announcer { visibility: hidden; opacity: 0; }
/* Dimmed intro mode -- the very first roll after the leader flips
   the dice ON. Tones down the stage so it reads as "preview / hint"
   rather than a real roll. */
.dice-stage.is-intro { opacity: 0.55; filter: saturate(0.7); }
.dice-stage.is-intro .dice-announcer { color: #c0a060; }

/* BUILD 267: dice on/off pill in the footer. Only the leader's
   clicks land server-side -- followers see the state but can't flip. */
#dice-toggle {
  font-family: 'VT323', 'Courier New', monospace;
  font-size: 11px;
  letter-spacing: 0.10em;
  padding: 2px 8px;
  border: 1px solid #555;
  border-radius: 3px;
  background: #1a1a1a;
  color: #888;
  cursor: pointer;
}
#dice-toggle.is-on { color: var(--orange); border-color: var(--orange); }
#dice-toggle.leader-only:not(.am-leader) { cursor: not-allowed; opacity: 0.6; }
#dice-toggle:hover.am-leader { background: #2a2010; }
#chat-log {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  padding: 6px 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
}
#chat-log .chat-line {
  display: inline-block;
  max-width: 92%;
  padding: 3px 6px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.04);
  border-left: 2px solid;
  word-wrap: break-word;
}
#chat-log .chat-line.mine {
  align-self: flex-start;
  text-align: left;
  border-left-width: 2px;
  border-right-width: 0;
}
#chat-log .chat-line.theirs {
  align-self: flex-end;
  text-align: right;
  border-left-width: 0;
  border-right: 2px solid;
}
#chat-log .chat-line.system {
  align-self: center;
  text-align: center;
  color: var(--fg-dim);
  font-style: italic;
  background: transparent;
  border: none;
  font-size: 11px;
}
#chat-log .chat-line .name {
  font-weight: bold;
  letter-spacing: 0.04em;
  margin-right: 4px;
}
#chat-log .chat-line.theirs .name { margin-right: 0; margin-left: 4px; }
#chat-input-row {
  display: flex;
  gap: 4px;
  border-top: 1px dotted #444;
  padding-top: 6px;
  flex-shrink: 0;
}
#chat-input {
  flex: 1 1 auto;
  background: #000;
  color: var(--green);
  border: 1px solid #333;
  font-family: inherit;
  font-size: 12px;
  padding: 4px 6px;
  letter-spacing: 0.04em;
  text-transform: none;
}
#chat-input:focus { outline: 1px solid var(--green); }
#chat-send {
  padding: 4px 10px;
  font-size: 11px;
  margin: 0;
}

/* On narrower screens stack vertically so the game still fits. */
@media (max-width: 1100px) {
  #screen-row { flex-direction: column; }
  #chat-panel { width: 100%; height: 200px; }
}

/* Subtle scanlines */
#screen::after {
  content: "";
  position: absolute; inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    rgba(0,0,0,0) 0px,
    rgba(0,0,0,0) 2px,
    rgba(0,0,0,0.18) 3px
  );
  pointer-events: none;
}

/* Castle screen hero strip: menu (left) | video (center) | image
   (right). Flex with explicit child sizes so nothing wraps even on
   narrow viewports. The menu column is sized to fit the longest
   menu line; the video and the castle image share the remaining
   horizontal space, with the image capped so it doesn't dominate. */
/* BUILD 189: 3-column grid layout.
   - #castle-stage is the positioning root and houses the absolutely
     positioned backdrop image PLUS the foreground grid.
   - #castle-image is the BACKDROP -- pinned upper-right, 50% smaller
     than before, behind the grid via z-index. Other rows can float
     over the bottom of it.
   - #castle-grid is the foreground 3-column grid:
       row 1: [menu]   [video]    [empty/image-behind]
       row 2: [divider+title spans cols 1-2]   [empty/image-behind]
       row 3: [party-summary spans all 3 columns]
*/
#castle-stage {
  position: relative;
  margin: 0 0 6px;
}
#castle-image {
  position: absolute;
  top: 0;
  right: 0;
  width: 30%;
  max-width: 260px;
  height: auto;
  z-index: 0;
  pointer-events: none;
  border: 0;
  background: transparent;
  image-rendering: auto;
}
#castle-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(300px, auto) minmax(220px, 1fr) minmax(180px, 1fr);
  grid-template-areas:
    "menu  video  imgA"
    "title title  imgB"
    "party party  party";
  gap: 8px 16px;
  align-items: start;
}
.castle-menu-cell  { grid-area: menu; }
.castle-video-cell { grid-area: video; display: flex; align-items: flex-start; justify-content: center; }
.castle-image-cell    { grid-area: imgA; min-height: 1px; }
.castle-image-cell-2  { grid-area: imgB; min-height: 1px; }
.castle-title-cell { grid-area: title; }
.castle-party-row  { grid-area: party; }
#castle-grid #castle-menu { margin: 0; }
#castle-grid #castle-logo {
  width: 220px;
  max-width: 100%;
  height: auto;
  background: transparent;
  display: block;
}

#castle-title {
  color: var(--orange);
  font-size: 28px;
  letter-spacing: 0.16em;
  margin: 4px 0 4px;
  text-align: center;
  /* Subtle dark halo so the title still reads when the orange
     backdrop bleeds through underneath. */
  text-shadow: 0 0 6px rgba(0,0,0,0.85), 0 1px 0 #000;
}

/* Fancy divider between menu and title. A thin orange line fades to
   transparent at each end, with a small diamond ornament capping both
   sides. The diamonds are drawn as inline SVG backgrounds; the line is
   a single ::before pseudo-element. Small, refined, no asset files. */
.castle-divider {
  position: relative;
  height: 14px;
  margin: 6px auto 2px;
  width: 90%;
  background-image:
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='12' viewBox='0 0 14 12'><polygon points='7,1 12,6 7,11 2,6' fill='%23ff9a3a' stroke='%23ffd28a' stroke-width='0.6'/></svg>"),
    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='12' viewBox='0 0 14 12'><polygon points='7,1 12,6 7,11 2,6' fill='%23ff9a3a' stroke='%23ffd28a' stroke-width='0.6'/></svg>");
  background-repeat: no-repeat, no-repeat;
  background-position: left center, right center;
  background-size: 14px 12px, 14px 12px;
}
.castle-divider::before {
  content: '';
  position: absolute;
  left: 18px;
  right: 18px;
  top: 50%;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(255, 154, 58, 0.9) 25%,
    rgba(255, 210, 138, 0.95) 50%,
    rgba(255, 154, 58, 0.9) 75%,
    transparent 100%);
  box-shadow: 0 0 4px rgba(255, 154, 58, 0.45);
}

/* Party-summary row floats below the menu/video row. The right edge of
   each player row may overlap the castle backdrop, so we give the rows
   a dark halo to keep the text legible. */
#castle-grid .castle-party-row,
#castle-grid #castle-menu li {
  text-shadow: 0 0 5px rgba(0,0,0,0.85);
}
/* Castle stage pins to the top of the screen. */
#screen-castle #castle-stage { margin-top: 0; }

.screen { display: none; }
/* When visible, each section fills the screen's content box and scrolls
   internally so long lists (e.g. roster + party + buttons) never push
   navigation off-screen. The global #screen-commands bar sits outside
   #screen and stays put. */
.screen.visible {
  display: block;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 6px;
}
.screen.visible::-webkit-scrollbar { width: 8px; }
.screen.visible::-webkit-scrollbar-track { background: #000; }
.screen.visible::-webkit-scrollbar-thumb { background: #2a4a2a; border-radius: 2px; }

h1 {
  font-size: 32px;
  margin: 8px 0 4px;
  color: var(--orange);
  letter-spacing: 0.18em;
}
h2 {
  font-size: 18px;
  margin: 6px 0 12px;
  color: var(--green);
  letter-spacing: 0.12em;
}
h3 { color: var(--violet); margin: 4px 0; font-size: 15px; }
p, label, li { line-height: 1.45; }

.copy { color: var(--fg-dim); font-size: 11px; margin: 0 0 4px; }
.build-tag {
  color: var(--orange);
  font-size: 10px;
  letter-spacing: 0.18em;
  margin: 0 0 14px;
  opacity: 0.85;
}
#build-footer {
  color: var(--orange);
  font-size: 10px;
  letter-spacing: 0.14em;
  opacity: 0.7;
  margin-left: 12px;
}

.prompt-block {
  margin-top: 24px;
}
.prompt-block input {
  background: #000;
  border: 1px solid var(--green);
  color: var(--green);
  font-family: inherit;
  font-size: inherit;
  padding: 4px 8px;
  width: 220px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.prompt-block input:focus { outline: 2px solid var(--orange); }

button {
  background: #000;
  color: var(--green);
  border: 1px solid var(--green);
  font-family: inherit;
  font-size: inherit;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 4px 10px;
  cursor: pointer;
  margin: 4px 6px 4px 0;
}
button:hover { background: var(--green); color: #000; }
button:disabled { color: #555; border-color: #333; cursor: not-allowed; background: #000; }

ul.menu { list-style: none; padding: 0; margin: 16px 0; }
ul.menu li {
  padding: 4px 8px;
  cursor: pointer;
  font-size: 16px;
  border-left: 4px solid transparent;
  /* BUILD 190: keep menu items on one line each. "BOLTAC'S TRADING POST"
     was wrapping on the castle screen because the cell width was just
     under the natural width with letter-spacing. */
  white-space: nowrap;
}
ul.menu li:hover {
  border-left-color: var(--orange);
  color: var(--orange);
}

.status { color: var(--fg-dim); margin-top: 16px; }
.error  { color: var(--orange); }
.note   { color: var(--violet); }

/* ----- Shared town header strip (Tavern / Inn / Shop / Temple) -----
   Each town location uses the same header layout: a big orange
   title on the left, a stacked building+host image cluster on the
   right. The .town-host overlaps the LEFT edge of .town-building
   at a configurable percentage of the building's height. */
.town-header {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 12px;
}
.town-header-left { flex: 1 1 auto; }
.town-title {
  color: var(--orange);
  font-size: 36px;
  letter-spacing: 0.14em;
  margin: 0 0 8px;
  line-height: 1.1;
}
.town-header-art {
  position: relative;
  flex: 0 0 auto;
  padding-left: 90px;
}
.town-building {
  display: block;
  width: 320px;
  max-width: 320px;
  height: auto;
}
.town-host {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 50%;
  width: auto;
  z-index: 2;
  pointer-events: none;
}
/* "Short" host: smaller overlap (~20% of building height). Kept
   around for any host that wants the modest overlay treatment. */
.town-host-short {
  height: 20%;
  bottom: 8px;
}
/* Bishop overlay: was 20% (short), bumped 3x to 60%. The portrait
   can overflow the building's left edge since it has a transparent
   background. */
.town-host-bishop {
  height: 60%;
  bottom: 4px;
}
/* Boltac and wife overlay: same 3x bump to 60%. The transparent
   background lets them overlap into the storefront art. */
.town-host-boltac {
  height: 60%;
  bottom: 4px;
}

/* Inline message strip for town screens (replaces browser alert()
   popups, which some players have blocked). Sits under the page
   title. Fades content in. */
.town-messages {
  min-height: 18px;
  margin: 0 0 8px;
  padding: 0;
}
.town-messages .town-msg {
  display: block;
  margin: 2px 0;
  padding: 4px 8px;
  border-left: 3px solid var(--orange);
  background: rgba(255, 122, 0, 0.05);
  color: var(--green);
  letter-spacing: 0.04em;
}
.town-messages .town-msg.error {
  border-left-color: var(--red);
  color: var(--red);
}

/* ----- Tavern ----- */
/* Header strip: title block on the left, hero art on the right.
   The art is a stacked pair -- the tavern building image as the
   background, and the owner Gilgamesh standing slightly in front
   of its left edge to welcome the party in. */
#tavern-header {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 12px;
}
#tavern-header-left { flex: 1 1 auto; }
#tavern-title {
  color: var(--orange);
  font-size: 36px;
  letter-spacing: 0.14em;
  margin: 0 0 8px;
  line-height: 1.1;
}
#tavern-header-art {
  position: relative;
  flex: 0 0 auto;
  /* Reserve enough horizontal room for the building image plus the
     portion of Gilgamesh's portrait that overhangs to the left.
     Height grows naturally to the building image so Gilgamesh's
     "height: 50%" lands at exactly half the building. */
  padding-left: 90px;
}
#tavern-building {
  display: block;
  width: 320px;
  max-width: 320px;
  height: auto;
}
/* Gilgamesh portrait overlaps the LEFT edge of the tavern building.
   Height = 50% of the header-art column (which sizes to the tavern
   building), so he's automatically about half the building's
   height. Absolutely positioned bottom-left so he stands in front
   of the doorway welcoming the party in. */
#tavern-gilgamesh {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 50%;
  width: auto;
  z-index: 2;
  pointer-events: none;
}

/* Roster + Current Party each get a thin bordered card so they
   read as two distinct sections in the same column. */
#tavern-sections {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 12px;
}
#tavern-roster-section,
#tavern-slots-section {
  border: 1px solid #444;
  padding: 8px 12px;
  background: rgba(20, 20, 20, 0.3);
}
.tavern-section-title {
  margin: 0 0 6px;
  color: var(--orange);
  letter-spacing: 0.1em;
  font-size: 14px;
}
#tavern-roster, #tavern-slots {
  margin: 0;
}
.roster-row, .slot-row {
  display: flex; gap: 12px; align-items: center;
  border-bottom: 1px dotted #333;
  padding: 4px 0;
}
.slot-row .slot-num { color: var(--orange); width: 26px; }
.slot-row .slot-empty { color: var(--fg-dim); }

.char-line { font-size: 13px; }
.char-line .name { color: var(--violet); }
.char-line .race { color: var(--fg-dim); }
.char-line .hp { color: var(--green); }
.char-line .hp.low { color: var(--orange); }
.char-line .ac { color: var(--blue); }
.char-line .status-bad { color: #ff5050; }

/* ----- Wizardry-style party roster ----- */
.roster-table {
  font-family: 'Menlo','Consolas','Courier New',monospace;
  font-size: 13px;
  margin: 6px 0;
}
.roster-row {
  display: grid;
  /* BUILD 190: widened NAME by ~6 chars (170 -> 230), trimmed ACR a hair
     (84 -> 72) and bumped AC to fit "-10" cleanly. column-gap dropped to
     6px so LVL sits closer to A-CLS-R per request. */
  grid-template-columns: 32px 26px 230px 72px 36px 44px 56px 1fr;
  column-gap: 6px;
  /* BUILD 192: top-align each cell so the multi-line NAME (name on row
     1, age in gray on row 2) doesn't push everything else down to the
     middle of the cell. */
  align-items: start;
  padding: 3px 4px;
  /* Brighter hairline separator so consecutive same-owner rows (which
     share the tinted background colour) still read as distinct rows
     instead of one solid bar. */
  border-bottom: 1px solid #2c2c2c;
}
.roster-row.roster-header {
  color: var(--orange);
  border-bottom: 1px solid #444;
  font-weight: bold;
  letter-spacing: 0.08em;
}
.roster-row .col-avatar { display: flex; align-items: flex-start; justify-content: center; }
.roster-row .col-avatar svg { display: block; }
.roster-row .col-num    { color: var(--orange); text-align: right; }
/* BUILD 192: NAME cell stacks vertically:
     line 1 = c.name + (owner) in violet
     line 2 = "XX.X YRS" in muted gray
   This keeps the age from awkwardly wrapping mid-token when the name
   alone fills the column. */
.roster-row .col-name {
  color: var(--violet);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
}
.roster-row .col-name .col-name-line {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.roster-row .col-name .col-age-suffix {
  display: block;
  color: #888;
  font-size: 11px;
  letter-spacing: 0.04em;
  margin-top: 1px;
}

/* BUILD 191: compact up/down reorder buttons inside the col-num cell.
   Used by camp so the leader can shuffle the marching order without
   leaving the dungeon. Stacked vertically; each button is 14px tall. */
.roster-row .col-num { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
.roster-row .col-num-arrows {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
  line-height: 1;
}
.roster-row .col-num-arrow {
  width: 16px; height: 12px;
  padding: 0; margin: 0;
  font-size: 8px; line-height: 1;
  background: #1a1a1a;
  color: var(--orange);
  border: 1px solid #444;
  cursor: pointer;
}
.roster-row .col-num-arrow:hover:not(:disabled) {
  background: #2a2a2a;
  border-color: var(--orange);
}
.roster-row .col-num-arrow:disabled {
  color: #444;
  cursor: default;
  background: #0f0f0f;
}

/* BUILD 191: tavern roster head-strip with search filter + count. */
.tavern-roster-headstrip {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin: 4px 0 6px;
}
.tavern-roster-headstrip .tavern-section-title { margin: 0; }
/* BUILD 259: compact numeric level filter in the tavern head strip. */
#tavern-roster-level-filter {
  width: 70px;
  text-align: center;
  background: #0e160a;
  color: var(--fg-bright);
  border: 1px solid var(--line-2);
  padding: 3px 6px;
  font-family: inherit;
  font-size: 12px;
}
#tavern-roster-level-filter:focus {
  outline: none;
  border-color: var(--orange);
}
#tavern-roster-filter {
  flex: 1 1 220px;
  min-width: 200px;
  padding: 4px 8px;
  font-size: 12px;
  letter-spacing: 0.05em;
  background: #0c0c0c;
  border: 1px solid #555;
  color: var(--green);
}
#tavern-roster-filter:focus {
  outline: 1px solid var(--orange);
  border-color: var(--orange);
}
#tavern-roster-count {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--violet);
}

/* Single "+ ADD TO PARTY" button per roster row. Wider than the old
   6-slot grid so it reads as one decisive action. */
.tavern-add-btn {
  background: #1a1a1a;
  color: var(--green);
  border: 1px solid var(--green);
  padding: 4px 12px;
  font-weight: bold;
  letter-spacing: 0.08em;
  cursor: pointer;
}
.tavern-add-btn:hover:not(:disabled) {
  background: var(--green);
  color: #000;
}
.tavern-add-btn:disabled {
  color: #555;
  border-color: #333;
  cursor: default;
}

/* BUILD 190: castle "PARTY GOLD POOL:" line. Label is muted, number
   rides UI.gpSpan() so it shows in bold gold with comma separators
   and a "GP" suffix matching every other gold figure in the game. */
.party-gold-line {
  display: flex; align-items: baseline; gap: 8px;
  margin: 6px 0 2px;
}
.party-gold-line .party-gold-label {
  color: var(--violet);
  letter-spacing: 0.06em;
  font-weight: bold;
}
.roster-row .col-acr    { color: var(--fg-dim); }
.roster-row .col-lvl    { color: var(--orange); text-align: right; font-weight: bold; }
.roster-row .col-ac     { color: var(--blue);  text-align: right; }
.roster-row .col-hp     { color: var(--green); text-align: right; }
.roster-row .col-status { color: var(--green); }
.roster-row .col-status.low-hp { color: var(--orange); }
/* Per-status color rules. The class lives on the row AND the col-status
   span (rosterRow puts it in both places); we drive color from either
   so callers that prefer to set just the cell still work. */
.roster-row.status-bad        .col-status, .col-status.status-bad        { color: #ff5050; }
.roster-row.status-poisoned   .col-status, .col-status.status-poisoned   { color: #4dd44d; }
.roster-row.status-stoned     .col-status, .col-status.status-stoned     { color: #b8a8c8; }
.roster-row.status-paralyzed  .col-status, .col-status.status-paralyzed  { color: #c750ff; }
.roster-row.status-asleep     .col-status, .col-status.status-asleep     { color: #80c8ff; }
.roster-row.status-afraid     .col-status, .col-status.status-afraid     { color: #ffd400; }
.roster-row.status-silenced   .col-status, .col-status.status-silenced   { color: #9078ff; }
.roster-row.status-confused   .col-status, .col-status.status-confused   { color: #ff80c0; }
.roster-row.status-blinded    .col-status, .col-status.status-blinded    { color: #555555; }
/* HP-graded OK tiers (label is "OK" / "ALIVE" / "NOT GOOD" / "CRITICAL"). */
.roster-row.status-ok-full   .col-status, .col-status.status-ok-full   { color: #41ff00; }   /* green */
.roster-row.status-ok-warn   .col-status, .col-status.status-ok-warn   { color: #ffd400; }   /* yellow */
.roster-row.status-alive     .col-status, .col-status.status-alive     { color: #ffa040; }   /* light orange */
.roster-row.status-notgood   .col-status, .col-status.status-notgood   { color: #c86010; }   /* dark orange */
.roster-row.status-critical  .col-status, .col-status.status-critical  { color: #ff6060; }   /* light red */
.roster-row.status-dead      .col-status, .col-status.status-dead      { color: #8a0000; font-weight: bold; }
.roster-row.status-ashes     .col-status, .col-status.status-ashes     { color: #666; }
/* Standalone status classes (used on a bare span or div outside a
   roster row, e.g. inside a combat-char card or inspect card).
   Afflictions get bold so they're impossible to miss next to plain
   HP-grade labels in the same column. */
.status-ok-full    { color: #41ff00; }
.status-ok-warn    { color: #ffd400; }
.status-alive      { color: #ffa040; }
.status-notgood    { color: #c86010; }
.status-critical   { color: #ff6060; }
.status-dead       { color: #8a0000; font-weight: bold; }
.status-ashes      { color: #666; }
.status-bad        { color: #ff5050; font-weight: bold; }
.status-poisoned   { color: #4dd44d; font-weight: bold; }
.status-stoned     { color: #b8a8c8; font-weight: bold; }
.status-paralyzed  { color: #c750ff; font-weight: bold; }
.status-asleep     { color: #80c8ff; font-weight: bold; }
.status-afraid     { color: #ffd400; font-weight: bold; }
.status-silenced   { color: #9078ff; font-weight: bold; }
.status-confused   { color: #ff80c0; font-weight: bold; }
.status-blinded    { color: #555555; font-weight: bold; }
.roster-row.empty .col-name { color: var(--fg-dim); font-style: italic; }
.roster-row.front-row .col-num { color: var(--orange); font-weight: bold; }
.roster-row.back-row  .col-num { color: var(--violet); }
.roster-row button { margin-left: 4px; padding: 2px 8px; font-size: 11px; }
/* Action rows nested *inside* a roster-row span the full grid. */
.roster-row .roster-actions, .roster-row .roster-claim-row {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
  padding: 2px 0 4px 30px;
}
/* Action rows that are SIBLINGS of a roster-row (e.g. the Tavern's
   ASSIGN-TO buttons) need their own visible style. */
.roster-entry { border-bottom: 1px dotted #1d1d1d; padding-bottom: 4px; margin-bottom: 4px; }
.roster-entry > .roster-claim-row {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 4px 0 6px 30px;
  align-items: center;
}
.roster-entry > .roster-claim-row .claim-prompt {
  color: var(--orange); letter-spacing: 0.06em; font-size: 12px;
}
.roster-entry > .roster-claim-row button {
  padding: 4px 10px; font-size: 12px; margin: 0;
}
.roster-row .gold { color: var(--green); font-size: 11px; margin-right: 6px; }
.roster-section-label {
  margin-top: 6px;
  padding: 2px 4px;
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--fg-dim);
  text-transform: uppercase;
}

/* ----- Town panels ----- */
.town-controls .character-card {
  border: 1px solid #333;
  padding: 8px 10px;
  margin: 6px 0;
}
.town-controls .character-card:hover { border-color: var(--green); }

.shop-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 18px; }
.shop-inv-row { display: flex; gap: 6px; align-items: center; padding: 2px 0; border-bottom: 1px dotted #222; }
.shop-inv-row .equipped { color: var(--orange); }
/* Item description column shown next to each item name everywhere
   (Boltac's, inspect, camp) so the player can see weapon damage,
   AC bonus, or potion effect inline. */
.item-desc {
  color: var(--green);
  font-size: 11px;
  letter-spacing: 0.04em;
  opacity: 0.85;
  margin-left: auto;
  padding-right: 6px;
}
.shop-buy-row .item-desc { margin-left: 8px; }
.shop-character-panel, .shop-buy-panel { margin-top: 10px; }
.shop-character-card {
  border: 1px solid #333;
  padding: 6px 8px;
  margin: 4px 0;
  cursor: pointer;
}
.shop-character-card:hover { border-color: var(--green); }
.shop-character-card.focused { border-color: var(--orange); background: #1a1108; }
.shop-character-card.eligible { border-color: var(--green); }
.shop-character-card.dim { opacity: 0.45; }
/* Boltac's: name a few points larger so it stands out from the
   tabular numbers underneath. */
.shop-character-card .name { color: var(--violet); font-size: 18px; letter-spacing: 0.04em; }
.shop-buy-row {
  display: flex; gap: 8px; align-items: center;
  padding: 4px 8px; border: 1px solid #1f1f1f; margin: 2px 0;
  cursor: pointer;
}
/* Thicker, brighter border around the Boltac inventory area so it
   reads as the "store shelf" -- separated from the party character
   panel above it. */
.shop-buy-panel {
  border: 2px solid #a06800;
  border-radius: 4px;
  padding: 10px 12px;
  background: rgba(80, 50, 0, 0.08);
  margin-top: 14px;
}

/* ====================================================================
   BUILD 202: NEW 3-column Boltac's layout.
   Top of #shop-controls is the PARTY POOL banner (unchanged).
   Below it, .shop3-body fills the remaining width with three columns:
     col 1 -- party cards (click to focus)
     col 2 -- focused character's inventory
     col 3 -- Boltac's catalog filtered to that character
   Each column has its own scroll so the party + inventory stay pinned
   while the catalog scrolls below them.
   ==================================================================== */
.shop3-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) minmax(0, 1.3fr);
  gap: 10px;
  margin-top: 10px;
  /* BUILD 203: stretch fully across the screen so the body's right
     edge lines up with the Boltac trading-post painting in the
     header strip above. */
  width: 100%;
  box-sizing: border-box;
  /* Generous fixed height -- works inside the 880x660 screen frame.
     Each column scrolls internally; the rest of the shop chrome
     (header strip + party-pool banner + back button) sits outside. */
  height: 360px;
}
.shop3-col {
  border: 1px solid #555;
  border-radius: 4px;
  background: rgba(10,10,10,0.45);
  padding: 8px 10px;
  overflow-y: auto;
  overflow-x: hidden;
}
.shop3-col-title {
  margin: 0 0 8px;
  color: var(--orange);
  letter-spacing: 0.10em;
  font-size: 13px;
  border-bottom: 1px dotted #444;
  padding-bottom: 4px;
}

/* ---------- COL 1: PARTY ----------
 * BUILD 244: 2-column character card. Left half is a full-height
 * portrait, right half is the info stack (name, gold, vitals, AC,
 * HP, XP). Gives the avatar more visual weight while keeping every
 * field readable in one glance. */
.shop3-party { padding: 8px; }
.shop3-char-card {
  display: grid;
  grid-template-columns: 96px 1fr;
  align-items: stretch;
  gap: 10px;
  padding: 8px;
  margin: 0 0 10px;
  border: 1px solid #333;
  border-radius: 4px;
  background: #0c0c0c;
  cursor: pointer;
  transition: border-color 0.10s, background 0.10s;
  text-align: left;
  min-height: 120px;
}
.shop3-char-card:hover  { border-color: var(--green); background: #181818; }
.shop3-char-card.focused {
  border-color: var(--orange);
  background: #1a1108;
  box-shadow: 0 0 8px rgba(255,154,58,0.35);
}
.shop3-portrait {
  display: flex;
  align-items: stretch;
  justify-content: center;
  /* Black frame so the portrait reads as a portrait, not just a
     floating sprite. */
  background: #000;
  border: 1px solid #222;
  border-radius: 3px;
  overflow: hidden;
}
.shop3-portrait img, .shop3-portrait svg {
  width: 100%;
  height: 100%;
  display: block;
  image-rendering: pixelated;
  object-fit: contain;
}
.shop3-info {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  justify-content: flex-start;
}
.shop3-name {
  color: var(--violet);
  font-weight: bold;
  letter-spacing: 0.06em;
  font-size: 13px;
  line-height: 1.1;
}
.shop3-owner {
  font-size: 10px;
  color: #888;
  letter-spacing: 0.04em;
  margin-bottom: 2px;
}
.shop3-stat-line {
  font-size: 11px;
  letter-spacing: 0.04em;
}
/* Gold gets a touch more emphasis since it sits at the top of the
   info stack as the headline number a shopper checks. */
.shop3-stat-gold {
  font-size: 13px;
  font-weight: 600;
  padding: 2px 0;
  margin-bottom: 2px;
}
.shop3-stat-vitals {
  color: #c0a060;
  letter-spacing: 0.06em;
  font-weight: 600;
  margin-bottom: 2px;
}
.shop3-stat-label { color: var(--violet); }
.shop3-stat-val   { color: var(--green); font-weight: bold; }

/* ---------- COL 2 + COL 3: ITEM CARDS ---------- */
.shop3-item-card {
  display: grid;
  /* BUILD 203: icon column bumped 56 -> 68px (~+20%) to match the
     enlarged item art. */
  grid-template-columns: 68px 1fr auto;
  gap: 8px;
  align-items: center;
  padding: 6px 8px;
  margin: 0 0 6px;
  border: 1px solid #2a2a2a;
  border-radius: 4px;
  background: #0c0c0c;
  cursor: pointer;
  transition: border-color 0.10s, background 0.10s;
}
.shop3-item-card:hover {
  border-color: var(--orange);
  background: #1a1108;
}
.shop3-item-card.equipped {
  border-color: var(--green);
  background: rgba(20, 40, 20, 0.25);
}
.shop3-item-icon {
  /* BUILD 203: 56 -> 68 (~+20%) so item art is easier to read. */
  width: 68px; height: 68px;
  display: flex; align-items: center; justify-content: center;
}
.shop3-item-icon img, .shop3-item-icon svg {
  max-width: 60px;
  max-height: 60px;
  width: auto;
  height: 60px;
  object-fit: contain;
  display: block;
}
.shop3-item-text {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0;
}
.shop3-item-name {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.04em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.shop3-item-desc {
  color: #aaa;
  font-size: 11px;
  letter-spacing: 0.02em;
  line-height: 1.25;
}
.shop3-item-actions {
  display: flex; flex-direction: column;
  align-items: flex-end; gap: 2px;
}
.shop3-price { font-size: 11px; }
.shop3-too-poor {
  color: #ff7070;
  font-size: 10px;
  letter-spacing: 0.06em;
}

/* ----- BUILD 206: Boltac's section split (GEAR vs AMMUNITION) and
   ranged weapon detail rows. ----- */
.shop3-section { display: contents; }              /* default: flow inline */
.shop3-section-ammo {
  display: block;
  margin-top: 10px;
  padding: 8px;
  border: 1px solid #654221;
  border-radius: 3px;
  background: rgba(80, 50, 20, 0.18);
}
.shop3-section-title {
  margin: 0 0 6px;
  font-size: 12px;
  letter-spacing: 0.12em;
  color: #d8a04a;
  border-bottom: 1px dotted #654221;
  padding-bottom: 3px;
}
.shop3-item-sub {
  font-size: 10px;
  letter-spacing: 0.06em;
  margin-top: 2px;
  color: #8aa;
}
.shop3-item-sub-ranged { color: #d8a04a; font-weight: 600; letter-spacing: 0.1em; }
.shop3-item-sub-ammo   { color: #d8a04a; font-weight: 600; letter-spacing: 0.1em; }
.shop3-item-sub-camp {
  display: inline-block;
  margin-top: 2px;
  padding: 1px 6px;
  border: 1px solid #5a7a3a;
  background: rgba(60, 100, 40, 0.25);
  color: #b0d080;
  font-size: 10px;
  letter-spacing: 0.08em;
  border-radius: 2px;
}
.shop3-ranged-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 8px;
  margin-top: 2px;
  font-size: 10px;
  line-height: 1.35;
}
.shop3-ranged-key { color: #888; letter-spacing: 0.06em; }
.shop3-ranged-val { color: #c8b48c; }
.shop3-popup-stat-head {
  margin-top: 6px;
  padding-top: 4px;
  border-top: 1px dotted #444;
  color: #d8a04a;
  letter-spacing: 0.12em;
  font-weight: 600;
}

/* BUILD 206: inventory stack count badge. Sits in the lower-right
   corner of the item's icon when the player has more than one of
   the same item. Used on both the inspect screen (large icons) and
   Boltac's character inventory column (medium icons). */
.inv-icon-stack {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.inv-icon-stack-count {
  position: absolute;
  right: -2px;
  bottom: -2px;
  min-width: 18px;
  padding: 0 4px;
  background: rgba(0, 0, 0, 0.85);
  border: 1px solid #d8a04a;
  border-radius: 9px;
  color: #ffd97a;
  font-size: 11px;
  line-height: 14px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-align: center;
  box-shadow: 0 1px 3px rgba(0,0,0,0.5);
  pointer-events: none;
}

/* BUILD 330: ranged-weapon AMMO badge -- small red circle in the
   top-right corner of the inventory icon showing currentShots
   remaining. Pairs with .inv-icon-stack-count (gold, bottom-right);
   the two indicators are spatially separated so they read as
   distinct concepts ("how many copies do I have" vs "how many shots
   does this quiver hold").
   .ammo-refill-pulse is added briefly when a Boltac's purchase
   refills the quiver -- a green halo + bulge tells the player their
   gold turned into arrows. */
.inv-icon-ammo-count {
  position: absolute;
  right: -4px;
  top: -4px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  box-sizing: border-box;
  background: #c41818;
  border: 1px solid #ff5050;
  border-radius: 10px;
  color: #ffffff;
  font-size: 11px;
  line-height: 18px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-align: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.25);
  pointer-events: none;
  transform-origin: center center;
  transition: transform 120ms ease-out;
}
@keyframes ammo-refill-surge {
  0%   { transform: scale(1);    box-shadow: 0 0 0  0    rgba(65,255,0,0.0),  0 1px 4px rgba(0,0,0,0.55); }
  20%  { transform: scale(1.85); box-shadow: 0 0 18px 6px rgba(65,255,0,0.85), 0 1px 4px rgba(0,0,0,0.55); background: #1c8c1c; border-color:#7fff5f; }
  60%  { transform: scale(1.25); box-shadow: 0 0 14px 4px rgba(65,255,0,0.65), 0 1px 4px rgba(0,0,0,0.55); background: #1c8c1c; border-color:#7fff5f; }
  100% { transform: scale(1);    box-shadow: 0 0 0  0    rgba(65,255,0,0.0),  0 1px 4px rgba(0,0,0,0.55); }
}
.inv-icon-ammo-count.ammo-refill-pulse {
  animation: ammo-refill-surge 900ms ease-out;
}

/* ====================================================================
   Item detail popup (any shop item card opens this)
   ==================================================================== */
.shop3-popup-overlay {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.78);
  z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  animation: shop3-popup-fade 0.18s ease-out;
}
@keyframes shop3-popup-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.shop3-popup-panel {
  position: relative;
  background: #0a0a0a;
  border: 2px solid var(--orange);
  border-radius: 6px;
  padding: 24px 28px 18px;
  width: 460px;
  max-width: calc(100vw - 40px);
  max-height: calc(100vh - 60px);
  overflow-y: auto;
  box-shadow: 0 0 30px rgba(255,154,58,0.4);
}
.shop3-popup-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 28px;
  height: 28px;
  background: transparent;
  border: 1px solid #555;
  color: #aaa;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  padding: 0;
}
.shop3-popup-close:hover { border-color: var(--orange); color: var(--orange); }
.shop3-popup-art {
  display: flex; justify-content: center;
  margin: 4px auto 12px;
  width: 100%;
}
.shop3-popup-art img, .shop3-popup-art svg {
  max-width: 240px;
  max-height: 240px;
  height: auto;
  width: auto;
  object-fit: contain;
  image-rendering: auto;
  display: block;
}
.shop3-popup-name {
  margin: 0 0 6px;
  color: var(--orange);
  text-align: center;
  letter-spacing: 0.10em;
}
.shop3-popup-desc {
  margin: 0 0 12px;
  color: #ccc;
  text-align: center;
  line-height: 1.4;
}
.shop3-popup-stats {
  border-top: 1px dotted #444;
  padding-top: 8px;
  font-size: 12px;
  color: var(--green);
  letter-spacing: 0.04em;
  display: flex; flex-direction: column; gap: 3px;
}
.shop3-popup-price-row { margin-top: 6px; }
.shop3-popup-close-btn {
  display: block;
  margin: 14px auto 0;
  background: #1a1a1a;
  color: var(--orange);
  border: 1px solid var(--orange);
  padding: 6px 18px;
  letter-spacing: 0.08em;
  font-weight: bold;
  cursor: pointer;
}
.shop3-popup-close-btn:hover { background: var(--orange); color: #000; }
.shop-buy-panel > h3 {
  margin: 0 0 8px;
  color: #ffc060;
  letter-spacing: 0.12em;
}
/* Sell cell: icon button sits next to the gold-styled price tag. */
.shop-sell-cell { white-space: nowrap; text-align: center; }
.shop-sell-price {
  display: inline-block;
  margin-left: 2px;
  font-size: 11px;
  vertical-align: middle;
}
/* Per-character inventory table inside a shop card -- compact rows so
   the card stays narrow. */
.shop-inv-table .inv-cell-name { font-size: 12px; padding-top: 4px; }
.shop-inv-table .inv-cell-desc { font-size: 10px; padding-bottom: 4px; }
.shop-buy-btn { vertical-align: middle; }

/* ===== Training Grounds two-column layout ===== */
/* The page splits into a persistent welcome card on the left (~20%
   of the width) and the active creation-stage panel on the right
   (~80%). The left card shows the sergeant, decoration, name input
   and NEXT button; the right column fills in race / alignment /
   stat-roll / class panels as the player progresses.

   Training-specific header tweaks: pull the header bottom-margin
   down and the title down so the two-column grid below tucks
   right against the title, keeping the name entry visible without
   scrolling. */
.training-header { margin-bottom: 4px; }
.training-header .town-title { margin-bottom: 0; }
/* BUILD 378 / 379: crest image under the "Training Grounds" title.
   The width/height attributes are stamped by an inline onload in the
   HTML (30% of the PNG's natural dimensions), so CSS just gives it
   a block context + a subtle drop shadow. No size cap here -- the
   raw scale is the source of truth. */
.training-header .training-title-crest {
  display: block;
  margin: 6px 0 0;
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.55));
}
#training-controls { margin-top: 4px; }

.training-grid {
  display: grid;
  grid-template-columns: minmax(220px, 22%) 1fr;
  gap: 18px;
  align-items: start;
  margin-top: 0;
}
.training-welcome {
  border: 2px solid #a06800;
  border-radius: 6px;
  background: rgba(40, 25, 4, 0.55);
  padding: 12px 12px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  box-shadow: 0 0 8px rgba(255, 200, 80, 0.18) inset;
}
/* BUILD 378: .training-deco (shield+sword SVG above the sergeant) is
   retired. The crest image moved to the page header under the title
   -- see .training-title-crest. Rule kept as a defensive no-op in case
   any cached HTML still emits the class; sizes to 0 so it doesn't
   affect layout. */
.training-deco { display: none; }
.training-sergeant {
  /* 3x the earlier overlay (~80px tall → ~240px) and flipped on the
     horizontal axis so the drill sergeant faces RIGHT toward the
     name input rather than off the left edge of the card. */
  width: auto;
  max-width: 240px;
  height: 240px;
  display: block;
  transform: scaleX(-1);
  margin: 4px 0 8px;
  filter: drop-shadow(0 4px 4px rgba(0,0,0,0.5));
}
.training-welcome-text {
  color: var(--fg);
  font-size: 12px;
  line-height: 1.4;
  margin: 4px 0 12px;
  letter-spacing: 0.04em;
}
.training-name-box {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-bottom: 10px;
}
.training-name-label {
  color: var(--orange);
  letter-spacing: 0.16em;
  font-size: 11px;
  font-weight: bold;
}
.training-name-input {
  /* Ornate input box that matches the warm-brown frame of the
     welcome card. Bigger than the default form input so it reads
     as the primary call to action. */
  width: 100%;
  max-width: 200px;
  padding: 8px 10px;
  background: #1a0e02;
  color: #ffd400;
  border: 2px solid #a06800;
  border-radius: 4px;
  font-family: inherit;
  font-size: 14px;
  letter-spacing: 0.10em;
  text-align: center;
  text-transform: uppercase;
  box-shadow: 0 0 6px rgba(255, 200, 80, 0.25) inset;
  outline: none;
}
.training-name-input:focus { border-color: #ffd400; }
.training-name-input:disabled {
  color: #cfa040;
  border-color: #7a5018;
  opacity: 0.85;
}
.training-btn-row {
  display: flex;
  justify-content: center;
  width: 100%;
}
.training-next-btn {
  padding: 8px 24px;
  font-size: 14px;
  letter-spacing: 0.14em;
  font-weight: bold;
  background: #1f1100;
  color: var(--orange);
  border: 2px solid var(--orange);
  border-radius: 4px;
  cursor: pointer;
}
.training-next-btn:hover { background: var(--orange); color: #000; }
.training-reset-btn {
  margin-top: 4px;
  font-size: 11px;
  color: var(--fg-dim);
  background: transparent;
  border: 1px dotted var(--fg-dim);
  padding: 4px 8px;
  cursor: pointer;
}
.training-reset-btn:hover { color: var(--orange); border-color: var(--orange); }

/* Right column -- the active stage panel. Bordered to visually
   echo the welcome card. */
.training-right {
  border: 1px solid #444;
  border-radius: 6px;
  background: rgba(20, 20, 20, 0.4);
  padding: 12px 14px;
  min-height: 320px;
}
.training-await {
  color: var(--fg-dim);
  font-style: italic;
  letter-spacing: 0.08em;
  text-align: center;
  padding: 60px 12px;
}

/* BUILD 209: Temple of Cant rebuild.
   The old heart-only icon button (with the priest-portrait mouseover
   popup) is replaced with a wide green-text/green-outline button that
   reads "HEAL THYSELF: <cost>" -- heart on the left, gold-coin on the
   right of the cost. The character's gold purse sits to the right of
   the button so the player can see at a glance what they have to
   spend. No hover popup; the monk chant sound effect still plays on
   click (handled in townView.js). */
.temple-pool-line {
  margin: 4px 0;
  letter-spacing: 0.04em;
}
.temple-pool-label {
  color: var(--fg-dim);
  letter-spacing: 0.06em;
}
.temple-white { color: #e8e8e8; }

.temple-heal-group {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: flex-end;
}
.temple-heal-self-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  background: #0a1a08;
  border: 1px solid var(--green);
  border-radius: 3px;
  color: var(--green);
  cursor: pointer;
  font-family: 'Courier New', monospace;
  font-size: 12px;
  letter-spacing: 0.06em;
  line-height: 1.1;
  white-space: nowrap;
}
.temple-heal-self-btn:hover {
  background: #14301a;
  box-shadow: 0 0 6px rgba(80, 220, 80, 0.45);
}
.temple-heal-self-btn:disabled,
.temple-heal-self-btn.temple-heal-full {
  cursor: default;
  opacity: 0.55;
  background: #0a1a08;
  box-shadow: none;
}
.temple-heal-self-btn.temple-heal-poor {
  border-color: var(--orange);
  color: var(--orange);
  background: #1a1108;
}
.temple-heal-self-btn .temple-btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
}
.temple-heal-self-btn .temple-btn-icon svg {
  width: 18px;
  height: 18px;
  display: block;
}
.temple-heal-self-btn .temple-btn-label { letter-spacing: 0.08em; font-weight: 700; }
.temple-heal-self-btn .temple-btn-cost  {
  color: #ffd97a;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.temple-purse {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: #ffd97a;
}
.temple-purse-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
}
.temple-purse-icon svg { width: 18px; height: 18px; display: block; }
.shop-buy-row:hover { border-color: var(--green); }
.shop-buy-row.focused { border-color: var(--orange); background: #1a1108; }
.shop-buy-row.eligible { border-color: var(--green); }
.shop-buy-row.dim { opacity: 0.45; }

/* ----- Boltac's polish (BUILD 131) ----- */
/* Golden-yellow GP amounts everywhere in the shop. The amount and the
   "GP" suffix share the same gold color so they read as a unit. */
.gp-amount { color: #ffd400; font-weight: bold; }
.gp-amount .gp-suffix { color: #ffd400; font-weight: bold; margin-left: 2px; }

/* Pool-gold banner at the top of the shop. */
.shop-pool {
  display: flex; gap: 12px; align-items: center;
  padding: 6px 10px;
  border: 1px solid #2d2d2d;
  background: rgba(0,0,0,0.3);
  margin: 4px 0 8px;
}
.shop-pool .pool-label { color: var(--orange); letter-spacing: 0.10em; }
.shop-pool .pool-hint  { color: #888; font-size: 11px; margin-left: auto; }

/* BUILD 204: drop the legacy 120px right padding that was reserving
   space for the procedural Boltac portrait (long-removed when the
   painted boltac_and_wife.png moved into the header strip). That
   padding was creating a visible gap between the right edge of the
   shop content and the screen frame's scrollbar -- both the PARTY
   POOL banner and the 3-column body now extend edge-to-edge. */
#shop-controls { position: relative; }
.boltac-portrait {
  position: absolute;
  top: 4px; right: 6px;
  width: 96px; height: 120px;
}
.boltac-portrait svg { display: block; }
.boltac-name {
  position: absolute;
  top: 130px; right: 6px;
  width: 96px; text-align: center;
  font-size: 11px; color: var(--orange); letter-spacing: 0.12em;
}

/* Item icon column inside both inventory rows and buy rows. The inner
   <svg> is 18x18 (set by ItemIcons.build); the wrapper just reserves
   space and centers it. */
.shop-inv-row .inv-icon,
.shop-buy-row .buy-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
}
.shop-inv-row .inv-icon svg,
.shop-buy-row .buy-icon svg,
.inspect-inv-row .inv-icon svg,
.inspect-equip-chip .chip-icon svg {
  display: block;
}
/* Camp inspect icon cells need to render the SVG inline rather than
   the older emoji-text layout. */
.inspect-inv-row .inv-icon,
.inspect-equip-chip .chip-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
}

/* ----- Dungeon ----- */
#dungeon-layout {
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto;
  gap: 10px;
  height: 100%;
}
/* Three columns: the wireframe viewport on the left, the d-pad + key
   legend in the middle, the mini-map + map legend on the right. Frees
   vertical space below so the 6-row party roster fits without scrolling. */
#dungeon-top-row {
  display: grid;
  grid-template-columns: 1fr 150px 170px;
  gap: 12px;
  align-items: start;
}
#dungeon-mid, #dungeon-right {
  display: flex;
  flex-direction: column;
}
#dungeon-right {
  align-items: center;
}
#dungeon-canvas {
  background: #000;
  border: 1px solid var(--green);
  /* Cap the canvas height so the 6-row party roster underneath always
     fits inside the screen frame without scrolling. The 320x200 drawing
     surface scales down via the aspect ratio. */
  width: auto;
  height: 200px;
  max-width: 100%;
  aspect-ratio: 320 / 200;
  image-rendering: pixelated;
  display: block;
}
#dungeon-status {
  margin-top: 6px;
  min-height: 36px;
  color: var(--green);
  font-size: 13px;
}
/* Mini-map sits in the rightmost dungeon column. Square panel, breathing
   room around it. */
#minimap-canvas {
  display: block;
  margin: 4px auto 6px;
  background: #000;
  border: 1px solid #444;
  box-shadow: 0 0 0 2px #222;
  image-rendering: pixelated;
}
#minimap-legend {
  text-align: center;
  font-size: 10px;
  color: var(--fg-dim);
  margin: 0;
  padding: 0 2px;
  letter-spacing: 0.04em;
  line-height: 1.4;
}
/* Slim 3-mode minimap toggle: ON / FOG / OFF. Sits just below the
   minimap canvas, no wider than the canvas itself. */
.minimap-toggle {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 2px;
  width: 160px;
  margin: 2px auto 4px;
}
.minimap-toggle button {
  padding: 1px 0;
  font-size: 10px;
  letter-spacing: 0.08em;
  background: #000;
  color: #888;
  border: 1px solid #333;
  cursor: pointer;
  line-height: 1.2;
}
.minimap-toggle button:hover { color: var(--orange); border-color: var(--orange); }
.minimap-toggle button.active {
  background: rgba(255, 122, 0, 0.18);
  border-color: var(--orange);
  color: var(--orange);
  font-weight: bold;
}
/* Non-leader visual hint (the button stays clickable; the server is the
   gate). A disabled attribute would swallow the click entirely, so we
   use a class-based dim state instead. */
.minimap-toggle button.locked {
  opacity: 0.55;
}
.minimap-toggle button.locked:hover { color: #888; border-color: #333; }
#dungeon-side {
  display: flex; flex-direction: column; gap: 6px;
}
#dungeon-compass {
  text-align: center;
  font-size: 13px;
  color: var(--orange);
  border: 1px solid #333;
  padding: 4px;
}
#dungeon-party { font-size: 12px; }
#dungeon-party .char-line { padding: 2px 0; }
/* Keyboard-style arrow cluster. Three square cells per row, two rows:
       [ ↑ ]
   [ ← ][ ↓ ][ → ]
   Buttons are explicitly square so the LEFT and RIGHT cells match and
   the arrow glyphs never wrap. */
.dpad {
  display: grid;
  grid-template-columns: 38px 38px 38px;
  grid-auto-rows: 38px;
  gap: 4px;
  margin-top: 6px;
  width: max-content; /* keep the cluster from stretching across the column */
}
.dpad button {
  padding: 0;
  width: 38px;
  height: 38px;
  font-size: 20px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Courier New', Courier, monospace;
}
.dpad #btn-fwd   { grid-column: 2; grid-row: 1; }
.dpad #btn-left  { grid-column: 1; grid-row: 2; }
.dpad #btn-back  { grid-column: 2; grid-row: 2; }
.dpad #btn-right { grid-column: 3; grid-row: 2; }
.hint { font-size: 11px; color: var(--fg-dim); margin: 4px 0; }

/* The wireframe viewport and the torch share a row at the top of the
   viewport pane. The torch hugs the right edge of the canvas so it
   feels mounted on the dungeon wall right next to the viewport. */
.dungeon-view-row {
  display: flex;
  align-items: flex-start; /* torch sits flush with the top of the canvas */
  gap: 8px;
}

/* ---- Torch + dungeon-level indicator ----
   Compact widget that sits right next to the wireframe viewport. A
   large stylized numeral fills the back of the frame; a small torch
   with a flickering flame burns in front of it. The number's color
   pulses with the flame to suggest the flame is the only light source
   illuminating it. */
#dungeon-torch {
  width: 64px;
  /* BUILD 314: torch + optional LEAD badge are stacked. Auto-height
     so a leader sees the badge below the level number, while a
     follower keeps the original torch-only height. */
  height: auto;
  min-height: 96px;
  flex: 0 0 64px;
  position: relative;
  user-select: none;
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* The torch itself stays at its original size; the badge below sits
   in its own SVG slot. Using fixed pixel sizing here (rather than
   100%) means the leader badge can append without rescaling the
   torch art above. */
#dungeon-torch > svg:first-child { display: block; width: 64px; height: 96px; }
#dungeon-torch .torch-leader-badge {
  display: block;
  width: 64px;
  height: 32px;
  margin-top: 2px;
  filter: drop-shadow(0 0 4px rgba(255, 212, 0, 0.55));
}
.torch-flame {
  transform-origin: 32px 38px;
  animation: torchFlicker 540ms infinite;
  filter: drop-shadow(0 0 4px #ff7a00);
}
@keyframes torchFlicker {
  0%, 100% { transform: scale(1.00, 1.00) translateY(0);    opacity: 1.00; }
  20%      { transform: scale(0.94, 1.10) translateY(-1px); opacity: 0.85; }
  40%      { transform: scale(1.06, 0.94) translateY(0);    opacity: 0.95; }
  60%      { transform: scale(0.97, 1.08) translateY(-0.5px); opacity: 0.92; }
  80%      { transform: scale(1.03, 0.97) translateY(0);    opacity: 0.98; }
}
/* Small 'LV' tag above the floor number. Static grey so the eye lands
   on the bright number first. */
.torch-level-tag {
  font-family: 'Courier New', Courier, monospace;
  font-weight: bold;
  font-size: 10px;
  fill: #888;
  letter-spacing: 0.1em;
}
/* The floor number sits in the label slot below the torch. It's now
   bright amber + bold so it reads cleanly against the dark UI, with a
   subtle warm flicker synced to the flame to keep the torch motif. */
.torch-number {
  font-family: 'Courier New', Courier, monospace;
  font-weight: 900;
  font-size: 18px;
  letter-spacing: 0;
  animation: torchNumberPulse 540ms infinite;
}
@keyframes torchNumberPulse {
  /* Bright amber palette that stays legible the whole cycle. The flame
     keyframes still pull the value up and down a touch so it feels lit
     by the torch rather than statically colored. */
  0%, 100% { fill: #ffb04a; }
  20%      { fill: #ff8a20; }
  40%      { fill: #ffd470; }
  60%      { fill: #ff9430; }
  80%      { fill: #ffc060; }
}

/* ----- Combat ----- */
#combat-layout {
  display: grid;
  /* BUILD 194: top row collapses to a flexible arena that holds the
     log + monster panels side by side. Bottom rows are unchanged:
     character info boxes + the command bar. */
  grid-template-rows: 1fr 110px auto;
  height: 100%;
  gap: 6px;
}
/* Arena: ~40% messages | ~60% monsters. Both columns fill the row
   height so each panel stretches down to the top of the character row.
   BUILD 197: use fr units with minmax(0,) so the gap is subtracted
   cleanly from the row width -- the 40%/60% literal split was pushing
   the right column's border past the visible right edge. */
#combat-arena {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: 10px;
  min-height: 0;
}
#combat-arena-left {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.combat-arena-title {
  color: var(--orange);
  font-size: 28px;
  letter-spacing: 0.16em;
  margin: 0 0 6px;
  text-align: center;
  text-shadow: 0 0 6px rgba(255,154,58,0.35);
}
#combat-arena-right {
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* BUILD 197: container border is OFF by default. Only multi-group
     encounters get a faint outer frame; single-group encounters drop
     it entirely so the lone monster reads as the focal art. */
  border: 0;
  border-radius: 4px;
  /* BUILD 317: padding gives the absolute-positioned groups room to
     translate-X without clipping. overflow:visible -- previous fix
     used overflow:hidden which hard-cut bats off the edge when they
     happened to be wider than the slot. The new 2-row layout
     respects the container width via percentage anchors so nothing
     should overflow; visible mode lets us SEE any overflow that does
     happen instead of silently chopping it off. */
  padding: 8px 14px;
  background: transparent;
  box-sizing: border-box;
  overflow: visible;
}
#combat-arena-right.multi-group {
  border: 1px solid #555;
  background: rgba(10,10,10,0.4);
}
#combat-monsters {
  display: flex;
  gap: 14px;
  align-items: flex-end;
  justify-content: center;
  border-bottom: 0;
  padding-bottom: 0;
  /* Fill the right arena column so the panel stretches down to the
     character row no matter how few monster groups are present. */
  flex: 1;
  min-height: 0;
}
/* Multi-group encounters use a stacked pyramid layout. Switch the
   container into a positioning root so each group can absolute-anchor
   to the bottom-center. */
#combat-monsters.multi-group {
  position: relative;
  display: block;
}
.monster-group { text-align: center; }
.monster-group .label { color: var(--orange); font-size: 13px; }
.monster-group .count { color: var(--green); font-size: 12px; }
.monster-group .avg-hp { color: var(--fg-dim); font-size: 11px; letter-spacing: 0.04em; }
/* BUILD 319: monster art preserves the ORIGINAL aspect ratio no
   matter what shape the slot box happens to be. Old rules
   (height:100% + width:auto + max-width:100%) could squish or
   stretch in some browsers when the box aspect didn't match the
   image aspect (max-width clipping override). object-fit:contain
   is the ONLY rule that GUARANTEES the image is scaled down to fit
   the box bounds while keeping its native aspect -- empty space is
   left where the box and image aspects don't match, never distortion.
   object-position:center bottom anchors the monster to the floor of
   its slot so the creature reads as "standing on the ground" rather
   than floating mid-box. */
.monster-art {
  display: block;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  object-position: center bottom;
}
.monster-art-box {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 100%;
}
/* Selection halo wraps the WHOLE group (art + labels) so the focal art
   stays clean and the target marker is obvious. Border-only for the
   currently-picked group; everyone else stays unframed. */
.monster-group.targeted {
  outline: 2px solid var(--selected);
  outline-offset: 2px;
  border-radius: 4px;
}
/* BUILD 318: row + anchor aware monster-group widths and label
   alignment. Front row claims 30% each (3 across). Back row claims
   26% each but anchored to the outside edges of the stage so the
   label sits in the outer corner above the monster image. The
   .monster-art-box and inner <img> both shrink to fit, so wide
   PNGs (bats / dragons / centipedes) scale down without
   overflowing. box-sizing:border-box so the .targeted outline
   doesn't expand the footprint. */
.monster-group {
  box-sizing: border-box;
}
/* BUILD 329: monsters are noticeably larger now. Front row caps
   bumped to 38% / 320px so the center monster reads as the focal
   art and the side groups still have room to read at their 1.00
   scale. Back row bumped to 28% / 220px so the receding-layer
   monsters don't look like postage stamps. The object-fit:contain
   rule on .monster-art guarantees aspect ratio regardless of the
   slot shape -- bigger slots just mean bigger natural-aspect
   monsters, never stretching. */
.monster-group.row-front {
  width: 38%;
  max-width: 320px;
}
.monster-group.row-back {
  width: 28%;
  max-width: 220px;
}
/* Anchor-aware text alignment. The horizontal anchor (where the
   group is positioned within the stage) determines how the label
   stack reads. Center-anchored = centered text; outside-anchored
   back-row slots = text pinned to the outer corner so it stays out
   of the monster image's way. */
.monster-group.anchor-center    { text-align: center; }
.monster-group.anchor-leftEdge  { text-align: left;   }
.monster-group.anchor-rightEdge { text-align: right;  }
/* When the label sits in an outside corner, the .monster-art-box
   below still centers the image within the group's width so the
   art doesn't drift toward the corner along with the label. */
.monster-group.anchor-leftEdge  .monster-art-box,
.monster-group.anchor-rightEdge .monster-art-box {
  justify-content: center;
}
/* The image scales to its parent box; the box height is set
   inline per-group. width:auto + max-width:100% means portrait
   aspect stays portrait and landscape stays landscape without
   overflowing the group's bounded width. */
/* BUILD 319: superseded -- the canonical .monster-art rules above
   already cover max-width/max-height via the object-fit:contain
   block. Keeping this stub commented so the diff against BUILD 318
   is explicit about the cleanup. */
.monster-group.targeted .monster-art { outline: 0; }
/* BUILD 199: when there's only ONE group in the encounter, suppress
   the selection halo entirely -- there's nothing to choose between,
   and the orange outline just adds visual noise around the focal art. */
#combat-monsters.single-group .monster-group.targeted {
  outline: 0;
}
.monster-group.dead-group { opacity: 0.55; }
.monster-group.dead-group .label { color: #888; text-decoration: line-through; }
.monster-group.dead-group .count { color: #cc4444; font-weight: bold; letter-spacing: 0.08em; }
.skull-art { filter: drop-shadow(0 0 4px #aa3333); }

/* BUILD 317: text under (or above, for back-row) monsters is always
   centered horizontally within the group's bounded width. The old
   side-left/right overrides pulled labels to the OUTSIDE edge of
   each group, which read awkwardly. Centered text reads as "this
   label belongs to THIS monster" regardless of stage position. */
.monster-group.side-left,
.monster-group.side-right,
.monster-group.side-center { text-align: center; }

/* Boss group: ornate double-line gold border with an animated halo
   so the encounter's main threat reads instantly. The inner ring is
   crimson, the outer ring is gold, and a slow pulsing glow under it
   keeps the tile alive even between actions. The label color also
   shifts to gold to reinforce the read. */
.monster-group.boss-group {
  position: relative;
  padding: 8px 6px;
  margin: 0 4px;
  border: 2px solid #ffd400;
  outline: 1px solid #c8a000;
  outline-offset: 3px;
  background:
    linear-gradient(180deg, rgba(255,212,0,0.06), rgba(255,80,80,0.06));
  box-shadow:
    0 0 0 2px #5a1a1a inset,
    0 0 6px rgba(255,212,0,0.35),
    0 0 14px rgba(255,80,80,0.22);
  animation: boss-group-pulse 1.4s ease-in-out infinite alternate;
}
@keyframes boss-group-pulse {
  from {
    box-shadow:
      0 0 0 2px #5a1a1a inset,
      0 0 4px rgba(255,212,0,0.30),
      0 0 10px rgba(255,80,80,0.18);
  }
  to {
    box-shadow:
      0 0 0 2px #5a1a1a inset,
      0 0 14px rgba(255,212,0,0.85),
      0 0 22px rgba(255,80,80,0.55);
  }
}
.monster-group.boss-group .label {
  color: #ffd400;
  font-weight: bold;
  letter-spacing: 0.08em;
}
.monster-group.boss-group.targeted .monster-art {
  outline: 2px solid #ffd400;
}
/* Once defeated, the boss tombstone keeps its decorative chrome but
   fades the gold to a memorial bronze tone. */
.monster-group.boss-group.dead-group {
  border-color: #886020;
  outline-color: #5a3a10;
  animation: none;
  box-shadow: 0 0 0 2px #2a1a08 inset;
}
.monster-group.boss-group.dead-group .label { color: #886020; }

#combat-log {
  border: 1px solid #333;
  padding: 6px 8px;
  font-size: 13px;
  overflow-y: auto;
  /* BUILD 194: stretch to fill the left arena column below the
     ENCOUNTER header, all the way down to the character row. */
  flex: 1;
  min-height: 0;
  max-height: 100%;
}
/* Leader-only MESSAGE-DELAY foldout tab. Sticky at the top of the
   combat log. Default state is .closed -- only a small ▶ pull-tab
   is visible. Clicking the tab swaps in the .open state which shows
   the full [-] 3.0s [+] "message delay" ◀ strip. The collapsed
   state lives in localStorage so the leader's choice persists across
   rounds and renders. */
.combat-speed-dock {
  position: sticky;
  top: 0;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px;
  background: #000;
  border: 1px solid var(--fg-dim);
  border-radius: 0 6px 6px 0;
  margin: 0 0 6px 0;
  z-index: 5;
  pointer-events: auto;
  transition: padding 0.15s ease, border-color 0.15s ease;
  width: fit-content;
}
.combat-speed-dock.closed {
  padding: 1px 4px;
  border-color: #333;
}
.combat-speed-dock.open {
  border-color: var(--orange);
  box-shadow: 0 0 6px rgba(255, 122, 0, 0.4);
}
.combat-speed-btn {
  width: 26px;
  height: 22px;
  line-height: 1;
  padding: 0;
  border: 1px solid var(--fg-dim);
  background: #111;
  color: var(--fg);
  font-family: inherit;
  font-size: 14px;
  cursor: pointer;
  pointer-events: auto;
}
.combat-speed-btn:hover { border-color: var(--orange); color: var(--orange); }
.combat-speed-value {
  color: var(--orange);
  font-weight: bold;
  font-size: 12px;
  min-width: 36px;
  text-align: center;
}
.combat-speed-label {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 0.05em;
  margin: 0 6px 0 2px;
}
/* The triangle pull-tab. In the closed state it's the ONLY thing in
   the dock; in the open state it sits at the far right of the strip
   as a collapse handle. Tiny, square, no border so it reads as a tab
   rather than another button. */
.combat-speed-handle {
  width: 20px;
  height: 22px;
  line-height: 1;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--orange);
  font-family: inherit;
  font-size: 12px;
  cursor: pointer;
  pointer-events: auto;
}
.combat-speed-handle:hover { color: #ffd400; }
#combat-log .line.attack { color: var(--green); }
#combat-log .line.miss { color: var(--fg-dim); }
#combat-log .line.kill { color: var(--orange); }
#combat-log .line.spell { color: var(--violet); }
#combat-log .line.partyDeath { color: #ff4040; }
#combat-log .line.victory { color: var(--green); font-weight: bold; }
/* BUILD 323: friendly-encounter A/P prompt card. Renders INSIDE the
   left combat-log box so the bottom commands strip doesn't grow and
   squeeze the monster stage above. Compact: title, two big buttons
   side-by-side, red warning beneath. */
.friendly-offer-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 18px 14px;
  margin: 12px 4px;
  border: 2px solid var(--orange);
  border-radius: 6px;
  background: rgba(40, 20, 0, 0.4);
  box-shadow: 0 0 12px rgba(255, 154, 58, 0.35);
}
.friendly-offer-title {
  color: var(--orange);
  font-size: 18px;
  font-weight: bold;
  letter-spacing: 0.18em;
  text-align: center;
}
.friendly-offer-buttons {
  display: flex;
  gap: 18px;
  justify-content: center;
}
.friendly-offer-btn-attack,
.friendly-offer-btn-pass {
  font-family: 'Courier New', monospace;
  font-size: 14px;
  font-weight: bold;
  letter-spacing: 0.12em;
  padding: 8px 22px;
  cursor: pointer;
}
.friendly-offer-btn-attack {
  background: #3a0808;
  color: #ff7070;
  border: 1px solid #ff5050;
}
.friendly-offer-btn-attack:hover { background: #5a1010; }
.friendly-offer-btn-pass {
  background: #082a08;
  color: #70ff70;
  border: 1px solid #41ff00;
}
.friendly-offer-btn-pass:hover { background: #103a10; }
.friendly-offer-warning {
  color: #ff5050;
  font-size: 11px;
  font-style: italic;
  text-align: center;
  letter-spacing: 0.04em;
  max-width: 280px;
}
#combat-log .line.round-banner { color: var(--orange); font-weight: bold; letter-spacing: 0.1em; padding: 4px 0; }
#combat-log .line.hero-banner {
  color: #ffd400;
  font-weight: bold;
  letter-spacing: 0.16em;
  padding: 8px 4px;
  text-align: center;
  border: 2px solid #ffd400;
  background: rgba(255, 212, 0, 0.06);
  margin: 4px 0;
  animation: hero-banner-glow 1.2s ease-in-out infinite alternate;
}
@keyframes hero-banner-glow {
  from { box-shadow: 0 0 4px rgba(255, 212, 0, 0.3); }
  to   { box-shadow: 0 0 14px rgba(255, 212, 0, 0.8); }
}
#combat-log .line.surprise-banner {
  color: #ff5050;
  font-weight: bold;
  letter-spacing: 0.18em;
  padding: 8px 4px;
  text-align: center;
  border: 2px solid #ff5050;
  background: rgba(255, 80, 80, 0.10);
  margin: 4px 0;
  animation: surprise-banner-pulse 0.6s ease-in-out infinite alternate;
}
/* Party-surprise wears the same layout but a gold palette so the
   player reads it instantly as a GOOD outcome (vs the red monster
   ambush). */
#combat-log .line.surprise-banner.party-surprise {
  color: #ffd400;
  border-color: #ffd400;
  background: rgba(255, 212, 0, 0.10);
  animation: party-surprise-pulse 0.45s ease-in-out infinite alternate;
}
@keyframes party-surprise-pulse {
  from { box-shadow: 0 0 4px rgba(255,212,0,0.30); }
  to   { box-shadow: 0 0 16px rgba(255,212,0,0.80); }
}
@keyframes surprise-banner-pulse {
  from { box-shadow: 0 0 4px rgba(255, 80, 80, 0.3); }
  to   { box-shadow: 0 0 14px rgba(255, 80, 80, 0.85); }
}
/* Level-up banner: gold trophy + congratulatory text. Slides in with
   a soft glow on the trophy. Lives in the combat log so the pacing
   matches every other event (3 seconds per beat). */
#combat-log .line.levelup-banner {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 10px 14px;
  border: 2px solid #ffd400;
  background: rgba(255, 212, 0, 0.10);
  margin: 6px 0;
  animation: levelup-glow 1.0s ease-in-out infinite alternate;
}
#combat-log .line.levelup-banner .trophy-art {
  flex-shrink: 0;
  filter: drop-shadow(0 0 4px rgba(255, 212, 0, 0.6));
}
#combat-log .line.levelup-banner .levelup-body { flex: 1; }
#combat-log .line.levelup-banner .levelup-headline {
  color: #ffd400;
  font-weight: bold;
  letter-spacing: 0.12em;
  margin-bottom: 4px;
}
#combat-log .line.levelup-banner .levelup-detail {
  color: var(--green);
  font-size: 12px;
  letter-spacing: 0.04em;
}
@keyframes levelup-glow {
  from { box-shadow: 0 0 4px rgba(255, 212, 0, 0.3); }
  to   { box-shadow: 0 0 14px rgba(255, 212, 0, 0.85); }
}
/* Canvas wrapper provides the positioning context for every overlay
   banner so they center over the wireframe viewport itself rather than
   the canvas + torch combined width. */
.dungeon-canvas-wrap {
  position: relative;
  flex: 0 0 auto;
  display: inline-block;
}

/* Unified viewport banner. A single rule does the layout; per-variant
   classes paint the color. Centered absolutely over the canvas wrap,
   pointer-events disabled so the banner never eats clicks. */
.viewport-banner {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 18px;
  text-align: center;
  letter-spacing: 0.12em;
  font-weight: bold;
  font-size: 14px;
  z-index: 25;
  pointer-events: none;
  /* Wrap long messages onto multiple lines so they stay inside the
     wireframe canvas instead of bleeding past the right edge. The
     width was tuned so "YOU FOUND THE SILVER KEY" -- a typical
     single-line message -- fits without wrapping, but longer text
     wraps gracefully. */
  white-space: normal;
  word-break: break-word;
  overflow-wrap: break-word;
  max-width: 280px;
  line-height: 1.25;
  border: 2px solid currentColor;
  background: rgba(0, 0, 0, 0.55);
}
/* Color variants -- the border + glow inherit currentColor. */
.viewport-banner.v-white   { color: #ffffff; }
.viewport-banner.v-red     { color: #ff7070; }
.viewport-banner.v-blue    { color: #80c8ff; }
.viewport-banner.v-green   { color: #41ff00; }
.viewport-banner.v-orange  { color: #ff9a3a; }
.viewport-banner.v-violet  { color: #c750ff; animation: vb-pulse-violet 0.30s ease-in-out infinite alternate; }
.viewport-banner.v-cyan    { color: #80f0ff; }
.viewport-banner.v-yellow  { color: #ffd400; }
.viewport-banner.v-red     { animation: vb-pulse-red    0.45s ease-in-out infinite alternate; }
.viewport-banner.v-blue    { animation: vb-pulse-blue   0.45s ease-in-out infinite alternate; }
.viewport-banner.v-green   { animation: vb-pulse-green  0.45s ease-in-out infinite alternate; }
.viewport-banner.v-orange  { animation: vb-pulse-orange 0.45s ease-in-out infinite alternate; }

/* ----- Elevator panel (in-viewport, BUILD 180) -----
   The whole panel sits absolutely positioned over the wireframe
   .dungeon-canvas-wrap so it eats the entire viewport (320x200ish).
   When traveling we add .elevator-traveling which hides the buttons
   and unveils the doors/LED/beam stage on top of everything. */
.elevator-panel {
  position: absolute;
  inset: 0;
  background: #0c0c0e;
  border: 2px solid #555;
  border-radius: 4px;
  z-index: 30;
  color: #d7d7d7;
  font-size: 11px;
  letter-spacing: 0.06em;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 6px 8px;
  overflow: hidden;
  /* Brushed-metal feel: subtle vertical streaks. */
  background-image:
    linear-gradient(180deg, rgba(255,255,255,0.04) 0%, rgba(0,0,0,0) 40%),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.025) 0 1px, rgba(0,0,0,0) 1px 3px);
}
.elevator-title {
  color: #ff9a3a;
  font-weight: bold;
  letter-spacing: 0.16em;
  font-size: 13px;
  margin-bottom: 2px;
}
.elevator-sub {
  color: #80c8ff;
  font-size: 10px;
  margin-bottom: 4px;
}
.elevator-empty {
  color: #ff9a3a;
  text-align: center;
  font-size: 10px;
  margin: 8px 4px;
  line-height: 1.3;
}
.elevator-hint {
  color: #888;
  font-size: 10px;
  margin: 4px 0;
  text-align: center;
}
.elevator-buttons {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
  max-height: 140px;
  overflow-y: auto;
  align-items: stretch;
  padding: 0 6px;
  margin: 2px 0 4px;
}
.elevator-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.elevator-floor-btn {
  width: 28px;
  height: 22px;
  flex: 0 0 28px;
  background: radial-gradient(circle at 30% 30%, #6a4419 0%, #1a0e04 70%);
  border: 1px solid #ffb060;
  border-radius: 50%;
  color: #ffd28a;
  font-weight: bold;
  font-size: 11px;
  cursor: pointer;
  padding: 0;
  text-align: center;
  box-shadow: 0 0 6px rgba(255, 176, 96, 0.30);
}
.elevator-floor-btn:hover:not(:disabled) {
  background: radial-gradient(circle at 30% 30%, #c78a3a 0%, #2a1a08 70%);
  box-shadow: 0 0 10px rgba(255, 176, 96, 0.65);
}
.elevator-floor-btn:active:not(:disabled) {
  background: #1a0e04;
  box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.8);
}
.elevator-floor-btn.current {
  background: radial-gradient(circle at 30% 30%, #ffd28a 0%, #c78a3a 70%);
  color: #1a0e04;
  box-shadow: 0 0 10px rgba(255, 210, 138, 0.75);
}
.elevator-floor-btn:disabled {
  cursor: default;
}
/* "?" hint button: visually dimmer than a real floor button so it
   reads as "this floor exists but you haven't found it yet". */
.elevator-floor-btn-hint {
  background: radial-gradient(circle at 30% 30%, #2a2a2a 0%, #0a0a0a 70%);
  border-color: #555;
  color: #888;
  box-shadow: none;
  opacity: 0.65;
}
.elevator-floor-btn-hint:hover:not(:disabled),
.elevator-floor-btn-hint:disabled { /* stay dim on hover/disabled */
  background: radial-gradient(circle at 30% 30%, #2a2a2a 0%, #0a0a0a 70%);
  box-shadow: none;
}
.elevator-floor-label {
  color: #d7d7d7;
  font-size: 10px;
  letter-spacing: 0.10em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
}
.elevator-leave-wrap { margin-top: auto; padding-top: 4px; }
.elevator-leave-btn {
  background: #1a1a1a;
  color: #ccc;
  border: 1px solid #555;
  padding: 3px 10px;
  font-size: 10px;
  letter-spacing: 0.08em;
  cursor: pointer;
}
.elevator-leave-btn:hover { background: #2a2a2a; color: #fff; }

/* ----- Travel stage (doors + LED + beam) -----
   Painted on top of the elevator panel when a floor is selected. */
.elevator-panel.elevator-traveling .elevator-buttons,
.elevator-panel.elevator-traveling .elevator-hint,
.elevator-panel.elevator-traveling .elevator-leave-wrap,
.elevator-panel.elevator-traveling .elevator-sub,
.elevator-panel.elevator-traveling .elevator-title {
  visibility: hidden;
}
.elevator-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 5;
}
/* Two metal door panels that slide together from the sides. Initial
   state (no .closed): each parked at its outer edge. With .closed
   they meet at the center seam. */
.elevator-door {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 50%;
  background:
    linear-gradient(90deg, rgba(0,0,0,0.4) 0%, rgba(255,255,255,0.06) 50%, rgba(0,0,0,0.4) 100%),
    repeating-linear-gradient(180deg, #4a4a52 0 4px, #3a3a42 4px 8px);
  border: 1px solid #2a2a32;
  box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.7);
  transition: transform 1s cubic-bezier(0.25, 0.1, 0.25, 1);
}
.elevator-door-left {
  left: 0;
  transform: translateX(-100%);
  border-right: 2px solid #1a1a1a;
}
.elevator-door-right {
  right: 0;
  transform: translateX(100%);
  border-left: 2px solid #1a1a1a;
}
.elevator-door-left.closed  { transform: translateX(0); }
.elevator-door-right.closed { transform: translateX(0); }

/* LED dot-matrix floor display in the upper-right of the stage --
   visible above the closed doors as if mounted on the elevator
   ceiling. Window is masked to one digit height; the .elevator-led-roll
   inside contains two stacked digits and translates vertically. */
.elevator-led {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 32px;
  height: 50px;
  background: #0a0a0a;
  border: 2px solid #5a3a14;
  border-radius: 3px;
  overflow: hidden;
  z-index: 7;
  box-shadow: 0 0 6px rgba(0,0,0,0.7), inset 0 0 4px rgba(0,0,0,0.9);
}
.elevator-led-roll {
  display: flex;
  flex-direction: column;
}
.elevator-led-digit {
  width: 32px;
  height: 46px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 2px 0;
}
.elevator-led-row {
  display: flex;
  gap: 1px;
  margin-bottom: 1px;
}
.elevator-led-dot {
  width: 4px;
  height: 4px;
  background: #1a0a02;
  border-radius: 50%;
}
.elevator-led-dot.on {
  background: #ff9a3a;
  box-shadow: 0 0 3px #ff9a3a, 0 0 6px #ff6a00;
}

/* Light beam at the crack between the two closed doors. The beam is a
   horizontal slit (full panel width, ~6px tall) that translates
   vertically across the stage to simulate the elevator's motion. */
.elevator-beam {
  position: absolute;
  left: 0;
  right: 0;
  height: 6px;
  background: linear-gradient(180deg, rgba(255,255,255,0) 0%, #ffe18a 40%, #fff8d6 50%, #ffe18a 60%, rgba(255,255,255,0) 100%);
  box-shadow: 0 0 14px #ffe18a, 0 0 28px rgba(255,225,138,0.6);
  z-index: 6;
  pointer-events: none;
  opacity: 0;
}
.elevator-beam.beam-running { opacity: 1; }
/* Going DOWN: beam slides from the floor (bottom) to the ceiling (top)
   of the visible stage to suggest the car moving through floors. */
.elevator-beam.beam-running.beam-down {
  animation: elev-beam-down 0.85s linear 1;
}
.elevator-beam.beam-running.beam-up {
  animation: elev-beam-up 0.85s linear 1;
}
@keyframes elev-beam-down {
  from { top: calc(100% - 6px); opacity: 0.0; }
  10%  { opacity: 1.0; }
  90%  { opacity: 1.0; }
  to   { top: 0; opacity: 0.0; }
}
@keyframes elev-beam-up {
  from { top: 0; opacity: 0.0; }
  10%  { opacity: 1.0; }
  90%  { opacity: 1.0; }
  to   { top: calc(100% - 6px); opacity: 0.0; }
}
.viewport-banner.v-cyan    { animation: vb-pulse-cyan   0.30s ease-in-out infinite alternate; }
.viewport-banner.v-yellow  { animation: vb-pulse-yellow 0.45s ease-in-out infinite alternate; }
@keyframes vb-pulse-red    { from { box-shadow: 0 0 4px rgba(255,80,80,0.30);   } to { box-shadow: 0 0 14px rgba(255,80,80,0.85);   } }
@keyframes vb-pulse-blue   { from { box-shadow: 0 0 4px rgba(128,200,255,0.30); } to { box-shadow: 0 0 14px rgba(128,200,255,0.85); } }
@keyframes vb-pulse-green  { from { box-shadow: 0 0 4px rgba(65,255,0,0.25);    } to { box-shadow: 0 0 14px rgba(65,255,0,0.80);    } }
@keyframes vb-pulse-orange { from { box-shadow: 0 0 4px rgba(255,154,58,0.30);  } to { box-shadow: 0 0 14px rgba(255,154,58,0.85);  } }
@keyframes vb-pulse-violet { from { box-shadow: 0 0 4px rgba(199,80,255,0.30);  } to { box-shadow: 0 0 14px rgba(199,80,255,0.90);  } }
@keyframes vb-pulse-cyan   { from { box-shadow: 0 0 4px rgba(128,240,255,0.30); } to { box-shadow: 0 0 14px rgba(128,240,255,0.90); } }
@keyframes vb-pulse-yellow { from { box-shadow: 0 0 4px rgba(255,212,0,0.30);   } to { box-shadow: 0 0 14px rgba(255,212,0,0.85);   } }

/* Persistent water countdown is a smaller cyan banner ANCHORED to the
   top of the viewport (not centered) so it doesn't fight with other
   overlays. Different visual layer from the splash "GLUB GLUB" entry
   banner above. */
.water-banner {
  position: absolute;
  top: 8px; left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.60);
  color: #80c8ff;
  border: 2px solid #80c8ff;
  padding: 6px 10px;
  text-align: center;
  letter-spacing: 0.10em;
  font-weight: bold;
  font-size: 12px;
  z-index: 24;
  pointer-events: none;
  animation: vb-pulse-blue 0.45s ease-in-out infinite alternate;
}
.victory-banner {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 14px 12px;
  border: 2px solid var(--orange);
  background: rgba(255, 122, 0, 0.08);
  text-align: center;
}
.victory-banner h2 {
  color: var(--orange);
  letter-spacing: 0.18em;
  margin: 0;
  font-size: 24px;
  animation: victory-pulse 1.2s ease-in-out infinite alternate;
}
.victory-banner p { margin: 0; color: var(--green); }
@keyframes victory-pulse {
  from { text-shadow: 0 0 4px rgba(255, 220, 0, 0.4); }
  to   { text-shadow: 0 0 16px rgba(255, 220, 0, 0.9); }
}

#combat-party {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
  border-top: 1px dotted #444;
  padding-top: 4px;
}
.combat-char {
  border: 1px solid #333;
  padding: 4px;
  font-size: 11px;
  cursor: pointer;
}
.combat-char.awaiting { border-color: var(--selected); }
.combat-char.ready { border-color: var(--green); }
.combat-char.dead {
  border-color: #ff3030;
  background: rgba(255, 0, 0, 0.10);
  box-shadow: 0 0 0 1px #ff3030 inset;
  color: #ff8080;
}
.combat-char.dead .name { color: #ff5050; }
.combat-char.not-yours { opacity: 0.65; cursor: not-allowed; }
.combat-char .name { color: var(--violet); font-weight: bold; }
.combat-char .class-race {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.combat-char .owner-line {
  color: #ffd400;
  font-size: 11px;
  font-style: italic;
  margin-bottom: 2px;
}

#combat-commands {
  border-top: 1px dotted #444;
  padding-top: 4px;
  min-height: 60px;
  font-size: 13px;
}

/* ----- Persistent command bar (outside the CRT screen) ----- */
#screen-commands {
  width: 100%;
  max-width: 880px;
  margin-top: 10px;
  padding: 8px 12px;
  background: #161616;
  border: 2px solid #444;
  border-radius: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  min-height: 38px;
  box-shadow: 0 0 0 4px #222;
  font-size: 13px;
}
#screen-commands:empty::before {
  content: ' ';
  color: var(--fg-dim);
  font-style: italic;
  letter-spacing: 0.06em;
}
#screen-commands .cmd-label {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.1em;
  margin-right: 6px;
}
#screen-commands button {
  padding: 4px 12px;
  margin: 0;
  font-size: 13px;
}

/* ----- Footer ----- */
#footer-bar {
  width: 100%;
  max-width: 880px;
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--fg-dim);
  padding: 6px 8px;
}
#footer-bar { flex-wrap: wrap; align-items: center; }
#footer-bar span { padding: 0 6px; }
/* BUILD 266: the redundant "YOU: <name>" tag was retired. Collapse
   the now-always-empty #player-tag so it doesn't reserve padding. */
#player-tag { display: none; }
#leader-tag.is-leader { color: var(--orange); }
/* BUILD 266: a player's OWN name in the players strip carries
   a >name< bracket pair to make it obvious which name is them.
   Slightly brighter background tint so it pops over the others. */
#leader-tag .party-name.is-me {
  background: rgba(255, 255, 255, 0.06);
}
#leader-tag {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  flex: 1 1 auto;
}
#leader-tag .party-name {
  padding: 0 4px;
  letter-spacing: 0.04em;
  border-radius: 2px;
  transition: background-color 0.15s ease;
}
#leader-tag .party-name.is-leader {
  text-decoration: underline;
  text-underline-offset: 3px;
  font-weight: bold;
}
#leader-tag .party-name.clickable {
  cursor: pointer;
}
#leader-tag .party-name.clickable:hover {
  background: rgba(255, 255, 255, 0.12);
  outline: 1px dotted currentColor;
}
#leader-tag .party-name.flash {
  background: rgba(255, 255, 255, 0.85);
  color: #000 !important;
  text-shadow: none;
  animation: name-flash 0.8s ease-out;
}
@keyframes name-flash {
  0%   { background: rgba(255, 255, 255, 0.9); color: #000; }
  60%  { background: rgba(255, 255, 255, 0.5); }
  100% { background: rgba(255, 255, 255, 0); color: inherit; }
}
#leader-tag .loc-badge {
  font-size: 10px;
  padding: 0 2px;
  letter-spacing: 0.06em;
}

/* ----- Save / load controls ----- */
.save-load {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dotted #333;
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}

/* ----- Spell help popup ----- */
.spell-help-btn {
  background: #fff7b8 !important;
  color: #2a2a00 !important;
  border-color: #d4b800 !important;
  font-weight: bold;
  letter-spacing: 0.06em;
}
.spell-help-btn:hover {
  background: #fff080 !important;
  color: #000 !important;
}
.spell-help-overlay {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.85);
  display: flex; align-items: center; justify-content: center;
  z-index: 1100;
}
.spell-help-card {
  background: #0a0a0a;
  border: 3px solid #d4b800;
  box-shadow: 0 0 0 4px #222;
  width: min(720px, 92vw);
  max-height: 80vh;
  overflow-y: auto;
  padding: 16px 18px;
}
.spell-help-title {
  color: #fff080;
  font-weight: bold;
  letter-spacing: 0.1em;
  text-align: center;
  border-bottom: 1px solid #d4b800;
  padding-bottom: 8px;
  margin-bottom: 10px;
}
.spell-help-table {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.spell-help-row {
  display: grid;
  grid-template-columns: 140px 1fr 140px 200px;
  gap: 8px;
  padding: 6px 4px;
  border-bottom: 1px dotted #333;
  font-size: 12px;
  align-items: start;
}
.spell-help-head {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.08em;
  border-bottom: 1px solid #444;
}
.spell-help-name { color: var(--violet); font-weight: bold; }
.spell-help-desc { color: var(--fg); }
.spell-help-cost { color: var(--blue); }
.spell-help-dmg  { color: var(--green); }

/* BUILD 240: the spell-name column is now a click-to-cast button with
   a small subtitle line under it for school + level + slots-remaining.
   Hover gives the orange-border treatment used elsewhere; disabled
   (out-of-slots) dims and removes the pointer cursor. */
.spell-help-name-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: stretch;
}
.spell-help-name-btn {
  background: transparent;
  border: 1px solid var(--violet);
  color: var(--violet);
  font-weight: bold;
  letter-spacing: 0.08em;
  padding: 6px 8px;
  cursor: pointer;
  border-radius: 2px;
  text-align: center;
  font-family: inherit;
}
.spell-help-name-btn:hover:not(:disabled) {
  background: rgba(199, 80, 255, 0.14);
  border-color: var(--orange);
  color: var(--orange);
}
.spell-help-name-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  border-color: #555;
  color: #888;
}
.spell-help-name-sub {
  color: var(--fg-dim);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-align: center;
}
.spell-help-close {
  display: block;
  margin: 12px auto 0;
  padding: 6px 18px;
  background: #fff080;
  color: #000;
  border: 2px solid #d4b800;
  letter-spacing: 0.1em;
}
@media (max-width: 720px) {
  .spell-help-row {
    grid-template-columns: 1fr;
    gap: 2px;
  }
}

/* ----- Disconnect overlay -- blocks all interaction when offline ----- */
#disconnect-overlay {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.93);
  display: flex; align-items: center; justify-content: center;
  z-index: 2000;
  cursor: not-allowed;
}
#disconnect-overlay.hidden { display: none; }
#disconnect-overlay .dc-card {
  border: 3px solid #ff3030;
  background: #1a0a0a;
  padding: 20px 28px;
  max-width: 480px;
  text-align: center;
  box-shadow: 0 0 0 4px #222;
}
#disconnect-overlay h2 {
  color: #ff5050;
  letter-spacing: 0.18em;
  margin: 0 0 12px;
  animation: shake 0.6s linear infinite alternate;
}
#disconnect-overlay p { color: var(--fg); margin: 4px 0; }
#disconnect-overlay p.note { color: var(--fg-dim); }

/* =================================================================
   BUILD 381: TOS + account manager UI.
   ================================================================= */

/* TOS acceptance row on the signup panel. */
.auth-tos-row {
  display: flex; align-items: flex-start; gap: 6px;
  margin: 8px 0 10px;
  font-size: 12px;
  color: var(--fg);
}
.auth-tos-row input[type=checkbox] { margin-top: 3px; }
.auth-tos-row input[type=checkbox]:disabled { cursor: not-allowed; }
.auth-tos-label { line-height: 1.4; }
.auth-tos-link {
  background: none; border: none; padding: 0;
  color: var(--orange); text-decoration: underline;
  cursor: pointer; font: inherit;
}
.auth-tos-link:hover { color: #ffd97a; }

/* Post-OAuth TOS gate above the play-mode picker. */
.auth-tos-gate {
  border: 2px solid #ffcc33;
  background: rgba(255, 204, 51, 0.08);
  padding: 10px 12px;
  margin: 6px 0 12px;
  text-align: center;
}
.auth-tos-gate-title {
  color: #ffd97a; font-weight: bold; letter-spacing: 0.08em;
  margin: 0 0 6px;
}
.auth-tos-gate-body { color: var(--fg); font-size: 12px; margin: 0 0 10px; }
.play-modes-gated { opacity: 0.35; pointer-events: none; }
.play-modes-gated .play-mode-btn { cursor: not-allowed; }

/* TOS modal (overlay id="tos-overlay"). */
.tos-modal {
  border: 3px solid var(--orange);
  background: #1a1608;
  padding: 16px 20px;
  max-width: 640px; width: 92vw;
  max-height: 88vh;
  display: flex; flex-direction: column;
  box-shadow: 0 0 0 4px #222, 0 0 22px rgba(255, 140, 0, 0.35);
}
.tos-title {
  color: var(--orange);
  letter-spacing: 0.14em;
  margin: 0 0 10px;
  text-align: center;
}
.tos-scroll {
  flex: 1 1 auto;
  overflow-y: auto;
  border: 1px solid #444;
  background: #0a0a0a;
  padding: 10px 14px;
  min-height: 260px;
  max-height: 55vh;
}
.tos-text {
  white-space: pre-wrap;
  color: var(--fg);
  font-family: "Courier New", monospace;
  font-size: 12px;
  line-height: 1.5;
  margin: 0;
}
.tos-scroll-hint {
  text-align: center; margin: 8px 0 4px;
  color: #ffcc33;
  font-size: 12px;
  letter-spacing: 0.06em;
}
.tos-scroll-hint-done { color: var(--green); }
.tos-controls { display: flex; flex-direction: column; gap: 8px; }
.tos-check-label {
  display: flex; align-items: center; gap: 8px;
  color: var(--fg); font-size: 12px;
}
.tos-check-label input:disabled { cursor: not-allowed; }
.tos-buttons { display: flex; justify-content: flex-end; gap: 8px; }
.tos-buttons button { padding: 6px 14px; }

/* Account panel overlay. */
.account-modal {
  border: 3px solid var(--orange);
  background: #12100a;
  padding: 16px 20px 20px;
  max-width: 720px; width: 92vw;
  max-height: 88vh;
  overflow-y: auto;
  position: relative;
  box-shadow: 0 0 0 4px #222, 0 0 22px rgba(255, 140, 0, 0.35);
}

/* BUILD 386: profile-setup interstitial (shown once after email
   verification). Two-column body: avatar dropzone on the left,
   display name + notes on the right. Bottom row: SKIP | msg | SAVE. */
.psu-modal {
  border: 3px solid var(--orange);
  background: #12100a;
  padding: 18px 22px 22px;
  max-width: 640px; width: 92vw;
  max-height: 92vh;
  overflow-y: auto;
  position: relative;
  box-shadow: 0 0 0 4px #222, 0 0 22px rgba(255, 140, 0, 0.40);
  color: #d8ffc0;
  font-family: 'Courier New', monospace;
}
.psu-title {
  color: var(--orange);
  letter-spacing: 0.14em;
  margin: 0 0 6px;
  text-align: center;
  font-size: 20px;
  text-shadow: 0 0 8px rgba(255, 140, 40, 0.45);
}
.psu-sub {
  text-align: center;
  color: #b9ffa0;
  font-size: 12px;
  margin: 0 0 16px;
  letter-spacing: 0.02em;
}
.psu-body {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 20px;
  align-items: start;
  margin-bottom: 14px;
}
.psu-label {
  color: var(--orange);
  font-size: 11px;
  letter-spacing: 0.12em;
  margin-bottom: 6px;
  display: block;
}
.psu-preview {
  width: 194px; height: 234px;   /* 388x468 scaled down */
  border: 1px dashed #3a6a22;
  background: #060906;
  position: relative;
  cursor: pointer;
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  transition: border-color 0.12s linear, box-shadow 0.12s linear,
              background 0.12s linear;
}
.psu-preview:hover {
  border-color: var(--orange);
  box-shadow: 0 0 12px rgba(255, 154, 60, 0.45);
}
/* BUILD 388: drag-over state. Solid orange border + brighter glow +
   the "DROP TO UPLOAD" overlay fades in. Any child element the mouse
   passes over during drag would fire dragleave without this rule;
   pointer-events:none on the overlay solves that. */
.psu-preview.drag-over {
  border-style: solid;
  border-color: var(--orange);
  background: rgba(255, 154, 60, 0.08);
  box-shadow: 0 0 22px rgba(255, 154, 60, 0.75);
}
.psu-preview.drag-over .psu-preview-drop-hint { opacity: 1; }
.psu-preview-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.psu-preview-img.hidden, .psu-preview-ph.hidden { display: none; }
.psu-preview-ph {
  color: #6aa84a;
  text-align: center;
  font-size: 11px;
  letter-spacing: 0.10em;
  line-height: 1.6;
  padding: 8px;
}
.psu-preview-drop-hint {
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: var(--orange);
  background: rgba(0, 0, 0, 0.55);
  font-size: 13px;
  letter-spacing: 0.14em;
  text-shadow: 0 0 8px rgba(255, 154, 60, 0.9);
  opacity: 0;
  pointer-events: none;   /* never intercept drag/drop events */
  transition: opacity 0.10s linear;
}
.psu-note {
  color: #6aa84a;
  font-size: 10px;
  line-height: 1.4;
  margin: 8px 0 0;
  letter-spacing: 0.02em;
}
.psu-name-col input[type="text"] {
  width: 100%;
  padding: 8px 10px;
  background: #0a1408;
  border: 1px solid #3a6a22;
  color: #d8ffc0;
  font: 14px 'Courier New', monospace;
  letter-spacing: 0.06em;
  outline: none;
}
.psu-name-col input[type="text"]:focus {
  border-color: var(--orange);
  box-shadow: 0 0 10px rgba(255, 154, 60, 0.35);
}
/* BUILD 388: Richardsry animated logo slot in the right column of the
   profile-setup panel. Fills whatever height remains under the display
   name + note, centered horizontally, and object-fit:contain keeps
   the wordmark from being cropped or stretched at any aspect ratio.
   min-height gives it a floor when the modal is short; max-height
   guards against it stretching the modal on wide screens. */
.psu-logo-slot {
  margin-top: 14px;
  flex: 1 1 auto;
  min-height: 120px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.psu-logo-video {
  width: 100%;
  max-width: 320px;
  max-height: 180px;
  object-fit: contain;
  display: block;
  /* BUILD 388.1: NO glow / drop-shadow / border on the logo. The
     video ships with a black background on purpose so it blends
     seamlessly into the page background. See CLAUDE.md
     uiPatterns.richardsryLogo -- this rule applies EVERYWHERE the
     logo appears. */
}
/* Right column must be flex-column so the logo slot's flex:1 can
   actually consume the remaining vertical space. */
.psu-name-col {
  display: flex;
  flex-direction: column;
}
.psu-btn-row {
  display: flex; align-items: center; gap: 12px;
  border-top: 1px dotted #3a6a22;
  padding-top: 12px;
}
.psu-btn-row .btn {
  padding: 8px 18px;
  font: 12px 'Courier New', monospace;
  letter-spacing: 0.10em;
  cursor: pointer;
  border: 1px solid #666;
  background: #1a1a1a;
  color: #b9ffa0;
  transition: background 0.10s linear, color 0.10s linear,
              border-color 0.10s linear, font-weight 0.10s linear;
}
.psu-btn-row .btn.secondary:hover {
  background: #333; color: #fff; border-color: #999;
}
.psu-btn-row .btn.primary {
  background: var(--orange); color: #000; border-color: var(--orange);
  font-weight: 700;
  margin-left: auto;
}
.psu-btn-row .btn.primary:hover {
  background: #34d039; color: #fff; border-color: #34d039;
  box-shadow: 0 0 14px rgba(52, 208, 57, 0.55);
}
.psu-btn-row .btn:disabled {
  opacity: 0.4; cursor: not-allowed;
}
.psu-msg { color: #6aa84a; font-size: 11px; letter-spacing: 0.04em;
           flex: 1; text-align: center; }
.psu-msg.ok   { color: #d8ffc0; }
.psu-msg.err  { color: #ff6060; }
.psu-msg.busy { color: var(--orange); }
.psu-msg.info { color: #ffd97a; }
.account-title {
  color: var(--orange);
  letter-spacing: 0.14em;
  margin: 0 0 12px;
  text-align: center;
}
.account-close {
  position: absolute; top: 6px; right: 10px;
  background: none; border: none; color: var(--fg);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.account-close:hover { color: var(--orange); }
.account-section {
  border-top: 1px solid #333;
  padding: 10px 0;
}
.account-section:first-of-type { border-top: none; padding-top: 4px; }
.account-section h3 {
  color: var(--orange);
  letter-spacing: 0.10em;
  font-size: 13px;
  margin: 0 0 8px;
}
.account-row {
  display: grid;
  grid-template-columns: 200px 1fr auto auto;
  gap: 8px;
  align-items: center;
  margin-bottom: 6px;
}
.account-row label {
  color: #c8c8a8; font-size: 12px; letter-spacing: 0.06em;
}
.account-row input {
  padding: 4px 6px; font-family: inherit; font-size: 13px;
  background: #0a0a0a; color: var(--fg);
  border: 1px solid #444;
}
.account-row input:disabled { color: #888; background: #14120c; }
.acct-btn {
  padding: 4px 12px; font-size: 12px;
  background: #241a08; color: var(--orange);
  border: 1px solid var(--orange);
  cursor: pointer;
}
.acct-btn:hover { background: var(--orange); color: #000; }
.acct-btn-quiet {
  background: transparent; color: #c8c8a8; border-color: #666;
}
.acct-btn-quiet:hover { background: #333; color: var(--fg); }
.account-playtime-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}
.account-playtime-grid > div {
  border: 1px solid #333;
  padding: 6px 10px;
  background: #0a0a0a;
}
.pt-lbl { color: #c8c8a8; font-size: 11px; margin-right: 4px; }
.pt-val { color: var(--orange); font-weight: bold; }
.account-message {
  border-left: 3px solid var(--orange);
  padding: 6px 10px;
  margin-bottom: 10px;
  font-size: 12px;
}
.account-message-ok    { border-color: var(--green); color: var(--green); }
.account-message-error { border-color: #ff5050;      color: #ff8080; }
.account-message-info  { border-color: #ffcc33;      color: #ffd97a; }

/* BUILD 380: idle-timeout variant. Informational tone -- yellow
   border, calmer background, no shaking heading, no red accents.
   Distinguishes an intentional server-side release from a real
   "connection lost" event. */
#disconnect-overlay.idle-timeout-variant .dc-card {
  border: 3px solid #ffcc33;
  background: #1a1608;
  box-shadow: 0 0 0 4px #222, 0 0 18px rgba(255, 204, 51, 0.35);
}
#disconnect-overlay.idle-timeout-variant h2 {
  color: #ffd97a;
  animation: none;
  letter-spacing: 0.14em;
  font-size: 18px;
}
#disconnect-overlay.idle-timeout-variant .dc-msg {
  color: var(--fg);
  font-weight: bold;
}

/* ----- Party-wipe overlay ----- */
.overlay {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.92);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  z-index: 1000;
}
.overlay.hidden { display: none; }
.overlay h1 {
  color: #ff3030;
  font-size: 48px;
  letter-spacing: 0.2em;
  margin: 0 0 18px;
  animation: shake 0.6s linear infinite alternate;
}
@keyframes shake {
  from { transform: translateX(-3px); }
  to   { transform: translateX(3px); }
}

/* ----- Leave-game confirm modal ----- */
/* Shown by the back-button / backspace guards. Solid black overlay
   with a small bordered modal in the middle. Y leaves, N stays
   (default). Both keyboard and click drive it. */
#leave-game-overlay .leave-game-modal {
  background: #000;
  border: 2px solid var(--orange);
  border-radius: 6px;
  padding: 24px 32px;
  max-width: 460px;
  box-shadow: 0 0 24px rgba(255, 122, 0, 0.4);
}
#leave-game-overlay h2 {
  color: var(--orange);
  font-size: 28px;
  letter-spacing: 0.18em;
  margin: 0 0 12px;
  /* override .overlay h1's red shake by being our own scope */
  animation: none;
}
#leave-game-overlay p {
  margin: 8px 0;
  letter-spacing: 0.04em;
}
#leave-game-overlay .note { color: var(--fg-dim); font-size: 12px; }
.leave-game-buttons {
  display: flex;
  gap: 12px;
  justify-content: center;
  margin-top: 16px;
}
.leave-game-buttons button {
  padding: 8px 18px;
  font-size: 14px;
  letter-spacing: 0.12em;
  font-weight: bold;
  background: #1f1100;
  color: var(--orange);
  border: 2px solid var(--orange);
  border-radius: 4px;
  cursor: pointer;
}
.leave-game-buttons button:hover { background: var(--orange); color: #000; }
.leave-game-buttons button.primary {
  background: var(--orange);
  color: #000;
}
.leave-game-buttons button.primary:hover { background: #ffa040; }

/* ----- Cemetery ----- */
#cemetery-controls {
  display: flex; flex-direction: column; gap: 6px;
  margin: 8px 0;
}
.cemetery-search { display: flex; align-items: center; gap: 6px; }
.cemetery-search input {
  background: #000; color: var(--fg); border: 1px solid #444;
  font-family: inherit; padding: 3px 6px; flex: 1;
  letter-spacing: 0.04em; text-transform: uppercase;
}
.cemetery-nav { display: flex; align-items: center; gap: 8px; }
.cemetery-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;
  margin-top: 6px;
}
.tombstone {
  background: #0a0a0a;
  border: 2px solid #444;
  border-radius: 4px 4px 0 0;
  padding: 8px;
}
.tombstone-top { display: flex; gap: 10px; align-items: center; }
.tombstone-top .char-avatar { background: #000; border: 1px solid #333; }
.tombstone-id .name { color: var(--violet); font-weight: bold; }
.tombstone-id .sub { color: var(--fg-dim); font-size: 11px; }
.tombstone .epitaph { color: #c0c0c0; font-style: italic; margin: 6px 0 2px; }
.tombstone .date { color: var(--fg-dim); font-size: 11px; margin: 0; }

/* ----- Inspect screen ----- */
/* Tab strip at the very top -- one tab per active party member. The
   selected tab gets an orange underline + bold text so it reads as
   "you are looking at this character". Arrow keys cycle between
   tabs; ESC closes the inspect screen. */
.inspect-selector {
  display: flex;
  flex-wrap: wrap;
  gap: 0;
  margin-bottom: 12px;
  border-bottom: 2px solid #444;
}
.inspect-selector button {
  background: transparent;
  border: 1px solid transparent;
  border-bottom: none;
  color: var(--fg-dim);
  padding: 6px 12px;
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
  margin-bottom: -2px;
  letter-spacing: 0.06em;
}
.inspect-selector button:hover { color: var(--orange); }
.inspect-selector button.selected {
  background: #1a0e02;
  color: var(--orange);
  border-color: #444 #444 transparent #444;
  border-bottom: 2px solid var(--orange);
  font-weight: bold;
}
/* Identity line right under the avatar: (OWNER)  23 Yrs  Status: OK
   in one row. Age is blue + bold so the eye lands on it. Status text
   inherits color from its HP-graded class (status-ok-full, etc). */
.inspect-id-line {
  margin: 4px 0 6px;
  letter-spacing: 0.06em;
}
.inspect-owner { color: var(--fg-dim); }
.inspect-age   { color: #80c8ff; font-weight: bold; }
.inspect-status-label { color: var(--fg-dim); }
.inspect-status { font-weight: bold; }
.inspect-grid {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 16px;
}
.inspect-portrait { display: flex; justify-content: center; margin-bottom: 8px; }
.inspect-portrait .char-avatar { background: #000; border: 2px solid #444; }
.inspect-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; margin: 6px 0; }
.inspect-stats span { color: var(--green); }
.inspect-item-row { display: flex; gap: 6px; align-items: center; padding: 3px 0; border-bottom: 1px dotted #222; }
.inspect-item-row .equipped { color: var(--orange); }
/* The first column in each item row holds the SVG icon (matches the
   same column class used by Boltac's and the camp inspect screen). */
.inspect-item-row .inv-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; flex: 0 0 22px;
}
.inspect-item-row .inv-icon svg { display: block; }
.inspect-gold-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 4px 0; }
.inspect-gold-row input {
  background: #000; color: var(--green); border: 1px solid var(--green);
  font-family: inherit; padding: 2px 6px; width: 80px;
}
.inspect-trade-prompt {
  margin-top: 8px; padding: 6px 8px; border: 1px solid var(--orange);
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}

/* ===== Refactored dungeon inspect layout (BUILD 163) ===== */

/* Large character-name banner above the portrait. Color is set
   inline to the controlling player's chat color. */
.inspect-name-banner {
  font-size: 28px;
  letter-spacing: 0.12em;
  margin: 0 0 6px;
  text-align: center;
  text-shadow: 0 0 8px rgba(255, 122, 0, 0.18);
}

/* Expanded stat sheet: two-column grid of label/value pairs. The
   labels right-align so the values form a tidy column, and the
   whole sheet sits inside a thin dotted card. */
.inspect-statsheet {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 14px;
  margin: 6px 0;
  padding: 8px 10px;
  border: 1px solid #2a2a2a;
}
.inspect-stat-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px;
}
.inspect-stat-label {
  color: var(--fg-dim);
  font-size: 12px;
  letter-spacing: 0.04em;
}
.inspect-stat-value {
  color: var(--green);
  font-weight: bold;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* GOLD card under the stats. A thin orange border separates it as
   its own section so the eye reads it as "this is the gold panel". */
.inspect-gold-card {
  margin: 10px 0;
  padding: 8px 10px;
  border: 1px solid #a08020;
  border-radius: 4px;
  background: rgba(120, 80, 0, 0.06);
}
.inspect-gold-header {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}
.inspect-gold-icon {
  display: inline-flex;
  width: 22px;
  height: 22px;
  flex: 0 0 22px;
}
.inspect-gold-icon svg { width: 100%; height: 100%; display: block; }
.inspect-gold-title {
  color: #ffd400;
  font-weight: bold;
  letter-spacing: 0.14em;
  font-size: 14px;
  flex: 1;
}
.inspect-gold-amt-label {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.06em;
}

/* Tabbed inventory card on the right side. Thin border, internal
   header strip with two tab buttons, then a vertical list of item
   rows. Each row has a thin top-border separator and a uniform
   4-column grid: icon | name+desc | equip | trade. */
.inspect-inv-card {
  border: 1px solid #444;
  border-radius: 4px;
  background: rgba(30, 30, 30, 0.3);
}
.inspect-inv-tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid #444;
}
.inspect-inv-tab {
  flex: 1;
  background: transparent;
  border: 0;
  color: var(--fg-dim);
  padding: 8px 10px;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.08em;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.inspect-inv-tab:hover { color: var(--orange); }
.inspect-inv-tab.selected {
  color: var(--orange);
  border-bottom-color: var(--orange);
  background: rgba(255, 122, 0, 0.05);
  font-weight: bold;
}
.inspect-inv-list { padding: 0 10px 8px; }
.inspect-inv-empty {
  padding: 16px 0;
  text-align: center;
  color: var(--fg-dim);
}
/* Inventory rows render as a real HTML <table>. Each item occupies
   TWO <tr> rows; the icon, equip, and trade <td> cells use rowspan=2
   so they span both rows while the name + description sit one above
   the other in the middle column. Browser-native column alignment
   keeps the layout strict -- long names can't bleed back over the
   icon column the way they did under CSS grid.
   The iconScale modifier on the wrapper (.inv-icon-scale-1 vs -3)
   controls the icon column width via a CSS variable. */
.inspect-inv-card.inv-icon-scale-1 { --inv-icon-w: 28px; }
.inspect-inv-card.inv-icon-scale-3 { --inv-icon-w: 72px; }
/* BUILD 203: 4x of the original "small" 28px tile -- used by the
   inspect view so painted PNG item art reads at a useful size. */
.inspect-inv-card.inv-icon-scale-4 { --inv-icon-w: 112px; }

/* BUILD 203: hover callout for inspect-view inventory icons. Floats
   next to the icon with a 200px version of the painted art + the
   item's name/description/stats. Position is set inline from JS so
   the anchor logic can clamp to the viewport. */
.inv-icon-callout {
  position: fixed;
  z-index: 900;
  width: 260px;
  padding: 10px 12px;
  background: #0c0c0c;
  border: 2px solid var(--orange);
  border-radius: 4px;
  box-shadow: 0 0 12px rgba(255,154,58,0.5);
  pointer-events: none; /* don't trap mouse -- closes when icon mouseleave fires */
  font-size: 12px;
  letter-spacing: 0.02em;
}
.inv-icon-callout-art {
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto 8px;
}
.inv-icon-callout-art img,
.inv-icon-callout-art svg {
  width: auto;
  height: 200px;
  max-width: 100%;
  object-fit: contain;
  image-rendering: auto;
  display: block;
}
.inv-icon-callout-name {
  margin: 0 0 4px;
  color: var(--orange);
  text-align: center;
  letter-spacing: 0.08em;
  font-size: 14px;
}
.inv-icon-callout-desc {
  margin: 0 0 6px;
  color: #ccc;
  line-height: 1.3;
}
.inv-icon-callout-stats {
  border-top: 1px dotted #444;
  padding-top: 6px;
  color: var(--green);
  font-size: 11px;
  letter-spacing: 0.04em;
  display: flex; flex-direction: column; gap: 2px;
}

.inspect-inv-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  margin: 0;
}
.inspect-inv-table .inv-col-icon   { width: var(--inv-icon-w, 72px); }
.inspect-inv-table .inv-col-text   { width: auto; }
.inspect-inv-table .inv-col-action { width: 56px; }

.inspect-inv-table td {
  padding: 4px 8px;
  vertical-align: middle;
  word-wrap: break-word;
  overflow-wrap: break-word;
}
/* Top of each item row pair gets a thin separator. The FIRST item
   (no .divider class on tr) is flush against the card border. */
.inspect-inv-table tr.divider > td { border-top: 1px solid #2a2a2a; }

/* ICON CELL -- centered, sized via the column width. */
.inspect-inv-table .inv-cell-icon {
  width: var(--inv-icon-w, 72px);
  height: var(--inv-icon-w, 72px);
  padding: 8px 4px;
  text-align: center;
}
.inspect-inv-table .inv-cell-icon svg,
.inspect-inv-table .inv-cell-icon img {
  width: var(--inv-icon-w, 72px);
  height: var(--inv-icon-w, 72px);
  max-width: 100%;
  max-height: 100%;
  display: inline-block;
  object-fit: contain;
}

/* NAME row -- top of the middle column. */
.inspect-inv-table .inv-cell-name {
  color: var(--fg);
  font-size: 13px;
  font-weight: bold;
  letter-spacing: 0.06em;
  padding-top: 6px;
  padding-bottom: 0;
}
.inspect-inv-table .inv-cell-name.equipped { color: var(--orange); }

/* DESC row -- bottom of the middle column. */
.inspect-inv-table .inv-cell-desc {
  color: var(--fg-dim);
  font-size: 11px;
  line-height: 1.3;
  padding-top: 0;
  padding-bottom: 6px;
}

/* ACTION CELLS (equip + trade) -- icon button stacked over its
   callout label. Each cell is only as wide as the column. */
.inspect-inv-table .inv-cell-action {
  text-align: center;
  padding: 4px 6px;
}
.inspect-inv-icon-btn {
  width: 36px;
  height: 36px;
  padding: 4px;
  background: #111;
  border: 1px solid var(--fg-dim);
  border-radius: 4px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.inspect-inv-icon-btn {
  position: relative;  /* anchor for the CSS tooltip below */
}
.inspect-inv-icon-btn:hover {
  border-color: var(--orange);
  background: #1a1100;
  box-shadow: 0 0 4px rgba(255, 122, 0, 0.4);
}
.inspect-inv-icon-btn svg { width: 100%; height: 100%; display: block; }
/* CSS-only tooltip for the inventory action buttons. Reads the text
   from data-tip and renders a small orange-bordered callout above
   the button on hover (and on keyboard focus for accessibility).
   The native browser title= attribute still works as a backup but
   can fail to fire on some platforms / setups -- this rule guarantees
   the hint shows up. */
.inspect-inv-icon-btn[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: #000;
  color: var(--orange);
  border: 1px solid var(--orange);
  padding: 4px 8px;
  font-size: 11px;
  letter-spacing: 0.04em;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 120ms ease-in-out 200ms;
  z-index: 100;
  box-shadow: 0 0 6px rgba(0, 0, 0, 0.8);
}
.inspect-inv-icon-btn[data-tip]:hover::after,
.inspect-inv-icon-btn[data-tip]:focus-visible::after {
  opacity: 1;
}

/* ----- Camp screen ----- */
.camp-top {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 12px;
  align-items: start;
}
.campfire-art {
  background: radial-gradient(ellipse at 50% 80%, #2a1208 0%, #000 80%);
  border: 1px solid #444;
}
/* BUILD 205: campfire is now a looping MP4 (with SVG fallback). The
   host <div> needs to clip the video to its border box and not pick up
   any inline-flow whitespace. */
.campfire-art-video {
  overflow: hidden;
  line-height: 0;
  box-sizing: border-box;
}
/* ----- Camp info pane (upper-right of camp screen) -----
   FIXED height (matching the campfire) + internal scroll so the panel
   never grows tall enough to overlap the controls or roster below it.
   Anything beyond the visible window is reachable via the scrollbar. */
.camp-info-pane {
  min-width: 320px;
  /* BUILD 193: shorter default so all six party rows below stay above
     the fold. The pane expands to a tall popover when activePane is
     'inspect' (see #screen-camp.inspect-mode .camp-info-pane below). */
  height: 230px;
  box-sizing: border-box;
  border: 1px dotted #444;
  padding: 8px 12px;
  font-size: 12px;
  overflow-y: auto;
  overflow-x: hidden;
}
/* BUILD 193: the "USE THE COMMAND BAR..." hint pinned just under the
   campfire image. Tight margins so it doesn't add a tall row. */
.camp-left { display: flex; flex-direction: column; gap: 4px; }
.camp-left .camp-hint {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.04em;
  line-height: 1.3;
  color: var(--violet);
  max-width: 320px;
}
/* INSPECT MODE: the info pane balloons to fill the rest of the screen
   above the command bar, and the bottom party-roster (#camp-log) is
   hidden until ESC dismisses inspect. */
#screen-camp.inspect-mode .camp-info-pane {
  height: auto;
  min-height: 360px;
  /* Subtract some chrome (campfire/hint/command bar) from viewport. */
  max-height: calc(100vh - 260px);
}
#screen-camp.inspect-mode #camp-log { display: none; }
/* Make sure long lines wrap rather than blow out the width. */
.camp-info-pane p, .camp-info-pane div { word-wrap: break-word; }
.camp-pane-title { color: var(--orange); margin: 0 0 6px; letter-spacing: 0.10em; }
.camp-inspect-title {
  color: var(--orange);
  letter-spacing: 0.10em;
  font-size: 15px;
  margin: 0 0 6px;
  border-bottom: 1px solid #444;
  padding-bottom: 4px;
}
.camp-inspect-selector { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 6px; }
.camp-inspect-selector button { padding: 2px 6px; font-size: 11px; }
.camp-inspect-selector button.selected { background: var(--orange); color: #000; }
.camp-inspect-close { margin-left: auto; }
.camp-inspect-vitals { margin: 4px 0 6px; color: var(--green); }
.camp-inspect-vitals .gold-label { color: var(--orange); }
.camp-inspect-vitals .gold-amt { color: var(--orange); font-weight: bold; }
.camp-inspect-section { color: var(--orange); margin: 6px 0 3px; font-size: 12px; letter-spacing: 0.08em; }
.camp-equipped-list { display: flex; flex-wrap: wrap; gap: 6px; }
.camp-equipped-list .equipped-item { color: var(--green); }
.camp-inspect-item-row {
  display: flex; gap: 6px; align-items: center; padding: 2px 0;
  border-bottom: 1px dotted #1d1d1d;
}
.camp-inspect-item-row span { flex: 1; }
.camp-inspect-item-row .equipped { color: var(--green); }
.camp-inspect-item-row button { padding: 2px 6px; font-size: 11px; }
.camp-gold-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.camp-gold-row input { width: 70px; padding: 2px; font-size: 12px; }
.camp-gold-row button { padding: 2px 6px; font-size: 11px; }

/* ----------------------------------------------------------------------
   Camp INSPECT redesign (BUILD 126):
   Card-style sections inside the camp info pane. The pane keeps its
   fixed height + scroll, so even a heavily-loaded character page can
   grow vertically without disturbing the camp layout around it.
   ---------------------------------------------------------------------- */
.inspect-card {
  border: 1px solid #2d2d2d;
  border-left: 3px solid var(--orange);
  padding: 6px 8px;
  margin: 6px 0;
  background: rgba(255,255,255,0.015);
}
.inspect-card-title {
  color: var(--orange);
  font-size: 11px;
  letter-spacing: 0.14em;
  margin: 0 0 5px;
  text-transform: uppercase;
}
.inspect-identity {
  display: flex; gap: 10px; align-items: center;
}
.inspect-identity .who {
  flex: 1; line-height: 1.4;
}
.inspect-identity .who .name {
  color: var(--green); font-size: 15px; letter-spacing: 0.08em;
}
.inspect-identity .who .sub { color: #c9c9c9; font-size: 12px; }
.inspect-identity .who .sub.note { color: #888; }

/* BUILD 374/375: large identity card rendered in the camp LEFT column
   (under the campfire) while inspect mode is active. Naked layout --
   no orange left bar, no card border, no portrait outline -- so the
   player's eye lands on the artwork, not the frame. A thin decorative
   rule with a centered diamond separates the camp-hint text above
   from the identity block. */
.big-identity-card {
  margin-top: 6px;
  padding: 0;
  background: transparent;
  border: none;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
}
.big-identity-separator {
  position: relative;
  width: 100%;
  height: 14px;
  margin: 4px 0 2px;
  display: flex; align-items: center; justify-content: center;
}
.big-identity-separator::before,
.big-identity-separator::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--orange) 40%, var(--orange) 60%, transparent);
  opacity: 0.55;
}
.big-identity-separator-mark {
  color: var(--orange);
  font-size: 10px;
  padding: 0 8px;
  opacity: 0.85;
}
.big-identity-portrait {
  width: 260px; height: 320px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden;
  background: transparent;
  border: none;
}
.big-identity-portrait img,
.big-identity-portrait svg {
  width: 100%; height: 100%; object-fit: contain;
}
.big-identity-who {
  width: 100%; line-height: 1.5; text-align: center;
}
.big-identity-who .name {
  color: var(--orange); font-size: 18px; letter-spacing: 0.10em;
  margin-bottom: 4px;
}
.big-identity-who .arc-line {
  /* Alignment / Race / Class -- a couple of points larger than the
     default so the build reads at a glance. Slightly muted color so
     the name still dominates. */
  color: #d8d8b8; font-size: 15px; letter-spacing: 0.06em;
  margin-bottom: 2px;
  text-transform: uppercase;
}
.big-identity-who .owner-line {
  color: #888; font-size: 13px; font-style: italic;
  margin-bottom: 4px;
}
.big-identity-who .status-line {
  margin-top: 4px; font-size: 13px;
}
.big-identity-who .status-line .status-label { color: #c9c9c9; }
.big-identity-who .status-line .status-value { font-weight: bold; }

/* BUILD 374: spell-target picker banner (camp info-pane top) and combat
   command-panel title. The banner is a thin orange-bordered card. */
.spell-pick-banner {
  border: 1px solid var(--orange);
  background: rgba(255,140,0,0.08);
  padding: 8px 10px;
  margin-bottom: 8px;
}
.spell-pick-banner button { margin-top: 6px; }
.spell-pick-title {
  color: var(--orange);
  letter-spacing: 0.10em;
  margin: 0 0 4px;
}

/* BUILD 376: target-pick feedback chrome.
   - .picker-active on the roster wrapper enables a strong hover
     highlight on every row while a spell/potion target is pending.
   - .row-flash is a one-shot animation applied to the row that was
     just clicked, so the user sees an unmistakable confirmation.
   - .spell-pick-action-row lays out CANCEL + hovered-name preview.
   - .pick-feedback-box is the transient (3s) result panel that
     flashes the target name + describes what happened. */
.roster-table.picker-active .roster-row {
  cursor: pointer;
  transition: background-color 0.08s linear, box-shadow 0.08s linear;
}
.roster-table.picker-active .roster-row:hover {
  background-color: rgba(255, 140, 0, 0.18) !important;
  box-shadow: inset 0 0 0 2px var(--orange);
}
@keyframes camp-row-flash {
  0%, 100% { background-color: rgba(255, 140, 0, 0.05); }
  20%, 60% { background-color: rgba(255, 140, 0, 0.65); }
  40%, 80% { background-color: rgba(255, 140, 0, 0.15); }
}
.roster-table .roster-row.row-flash {
  animation: camp-row-flash 1.2s ease-in-out;
}
/* BUILD 376: same flash treatment for combat party tiles. The
   combat tile sits on a darker background; the box-shadow ring gives
   the user a strong "you clicked HERE" beat. */
@keyframes combat-tile-flash {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255,140,0,0); }
  25%      { box-shadow: 0 0 24px 4px rgba(255,217,122,0.95); }
  50%      { box-shadow: 0 0 12px 2px rgba(255,140,0,0.65); }
  75%      { box-shadow: 0 0 24px 4px rgba(255,217,122,0.95); }
}
.combat-char.row-flash {
  animation: combat-tile-flash 1.2s ease-in-out;
}
.spell-pick-action-row {
  margin-top: 6px;
  display: flex; align-items: center; gap: 10px;
}
.spell-pick-action-row button { padding: 4px 10px; }
.spell-pick-hovered-name {
  color: var(--orange);
  font-weight: bold;
  font-size: 13px;
  letter-spacing: 0.06em;
  padding: 2px 8px;
  border: 1px dashed rgba(255,140,0,0.55);
  min-width: 140px;
  text-align: center;
}
.spell-pick-hovered-name.empty {
  color: #777;
  font-style: italic;
  font-weight: normal;
  border-style: dotted;
  border-color: rgba(200,200,200,0.25);
}
@keyframes pick-feedback-name-flash {
  0%, 100% { color: var(--orange); text-shadow: 0 0 6px rgba(255,140,0,0.0); transform: scale(1); }
  25%      { color: #ffe7a3; text-shadow: 0 0 14px rgba(255,217,122,0.95); transform: scale(1.06); }
  50%      { color: var(--orange); text-shadow: 0 0 6px rgba(255,140,0,0.4); transform: scale(1); }
  75%      { color: #ffe7a3; text-shadow: 0 0 14px rgba(255,217,122,0.95); transform: scale(1.06); }
}
.pick-feedback-box {
  border: 2px solid var(--orange);
  background: rgba(255,140,0,0.10);
  box-shadow: 0 0 14px rgba(255,140,0,0.30);
  padding: 8px 12px;
  margin-bottom: 8px;
  text-align: center;
}
.pick-feedback-name {
  font-size: 18px;
  font-weight: bold;
  letter-spacing: 0.10em;
  animation: pick-feedback-name-flash 0.5s ease-in-out 4;
  display: inline-block;
}
.pick-feedback-line {
  margin-top: 4px;
  color: var(--green);
  font-size: 13px;
  letter-spacing: 0.06em;
}

/* BUILD 375: Boltac avatar hover callout. Floats next to the cursor
   after a 1.5s linger; clicking opens the full-portrait popout. The
   wrapper is pointer-events:none so mouse-move on the underlying
   portrait still fires and dismisses the callout. The inner button
   re-enables pointer events for itself. */
.boltac-avatar-callout {
  position: fixed; z-index: 9999;
  pointer-events: none;
}
.boltac-avatar-callout-btn {
  pointer-events: auto;
  font-family: 'Courier New', monospace;
  font-size: 11px;
  letter-spacing: 0.10em;
  background: #0a0a0a;
  color: var(--orange);
  border: 1px solid var(--orange);
  padding: 4px 10px;
  cursor: pointer;
  box-shadow: 0 0 10px rgba(255,140,0,0.35);
}
.boltac-avatar-callout-btn:hover {
  background: var(--orange);
  color: #000;
}

/* BUILD 374: party-row visual hint that the row is the click target
   for an in-progress spell pick. Works for both the combat .combat-char
   tile and the camp UI.partyRoster row. Soft orange glow + pointer
   cursor so the player knows they should click someone. */
.spell-pick-eligible {
  outline: 2px solid var(--orange) !important;
  outline-offset: -2px;
  box-shadow: 0 0 12px rgba(255,140,0,0.45) !important;
  cursor: pointer;
}
.spell-pick-ineligible {
  opacity: 0.45;
  filter: grayscale(40%);
}

/* Vitals & attribute pill grids. */
.inspect-pill-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(80px, 1fr));
  gap: 4px 8px;
}
.inspect-pill {
  display: flex; align-items: baseline; gap: 5px;
  padding: 3px 6px;
  border: 1px dotted #333;
  background: rgba(255,255,255,0.02);
  font-size: 12px;
  white-space: nowrap;
}
.inspect-pill .pill-icon { font-size: 13px; line-height: 1; }
.inspect-pill .pill-label {
  color: var(--orange); font-size: 10px; letter-spacing: 0.06em;
  min-width: 28px;
}
.inspect-pill .pill-val {
  color: var(--green); font-weight: bold;
  margin-left: auto; /* push value to the right of each pill */
}
.inspect-pill .pill-val.subtle { color: #c9c9c9; font-weight: normal; }
.inspect-pill .pill-val.bad { color: #d44; }
/* Gold (GP) pill matches Boltac's gold-yellow styling. */
.inspect-pill .pill-val.gold { color: #ffd400; }

/* Spell slot rows -- one line per school */
.inspect-spell-row {
  display: flex; gap: 6px; align-items: center; font-size: 12px; padding: 2px 0;
}
.inspect-spell-row .school {
  color: var(--orange); width: 56px; letter-spacing: 0.08em;
}
.inspect-spell-row .slots { color: var(--green); font-family: monospace; }
.inspect-spell-row .slots .empty { color: #555; }
.inspect-spell-row .slots .full  { color: var(--green); }
.inspect-spell-row .slots .partial { color: #ffd400; }

/* Equipped chips. */
.inspect-equip-chips {
  display: flex; flex-wrap: wrap; gap: 5px;
}
.inspect-equip-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 6px;
  border: 1px solid #2d2d2d;
  background: rgba(0,0,0,0.25);
  font-size: 12px;
}
.inspect-equip-chip .chip-icon { font-size: 13px; }
.inspect-equip-chip .chip-name { color: var(--green); }
.inspect-equip-chip.empty { color: #555; border-style: dashed; }

/* Inventory rows -- icon + name (+ E badge) + desc + buttons. */
.inspect-inv-row {
  display: grid;
  grid-template-columns: 18px 1fr 1fr auto auto;
  gap: 6px;
  align-items: center;
  padding: 3px 0;
  border-bottom: 1px dotted #1d1d1d;
  font-size: 12px;
}
.inspect-inv-row .inv-icon { text-align: center; }
.inspect-inv-row .inv-name { color: #c9c9c9; }
.inspect-inv-row .inv-name.equipped { color: var(--green); font-weight: bold; }
.inspect-inv-row .inv-name .eq-badge {
  margin-left: 4px; font-size: 10px; color: var(--orange);
  letter-spacing: 0.08em;
}
.inspect-inv-row .inv-desc { color: #888; font-size: 11px; }
.inspect-inv-row button { padding: 2px 6px; font-size: 11px; }
.inspect-inv-empty { color: #555; font-style: italic; }

/* Monster intel callout (BUILD 128) -- leader-only right-click on a
   monster group in combat. Floats above the combat screen near the
   pointer; dismisses on any click outside it. */
.monster-callout {
  position: fixed;
  z-index: 95;
  background: #050505;
  border: 2px solid var(--orange);
  padding: 10px 12px;
  min-width: 280px;
  max-width: 380px;
  max-height: 50vh;
  overflow-y: auto;
  box-shadow: 0 0 14px rgba(0,0,0,0.8);
  font-size: 12px;
  color: var(--green);
}
.monster-callout .callout-title {
  color: var(--orange);
  font-size: 13px;
  letter-spacing: 0.14em;
  margin: 0 0 6px;
  text-transform: uppercase;
  border-bottom: 1px solid #444;
  padding-bottom: 4px;
  display: flex; justify-content: space-between; align-items: center;
}
.monster-callout .callout-close {
  background: none;
  border: 1px solid #444;
  color: #888;
  font-size: 11px;
  padding: 1px 6px;
  cursor: pointer;
}
.monster-callout .callout-close:hover { color: var(--orange); border-color: var(--orange); }
.monster-callout .callout-summary {
  color: #c9c9c9; font-size: 11px; margin-bottom: 6px;
}
/* CSS-grid table for the monster callout (BUILD 130).
   Rationale: the previous monospace padded-string layout drifted right
   because inherited letter-spacing scaled every data-row char by a hair.
   With a real grid the browser owns column placement, so alignment is
   independent of font metrics, letter-spacing, or any cascade quirks.
   Each .callout-row uses `display: contents` so its child spans land
   directly in the parent grid -- which means every row's cells line up
   with the header's cells perfectly. The ability sub-line spans all
   columns via grid-column: 1 / -1. */
.monster-callout .callout-table {
  display: grid;
  /* Columns: # | NAME | LV | HP/MHP | AC | T */
  grid-template-columns: 2ch 18ch 4ch 11ch 6ch auto;
  column-gap: 2ch;
  row-gap: 0;
  font-family: 'Courier New', Courier, monospace;
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: 0;
}
.monster-callout .callout-row { display: contents; }
.monster-callout .callout-row > span {
  /* Tabular nums keeps digit widths uniform even on systems where
     Courier substitutes. White-space:pre preserves any embedded space
     (we shouldn't have any, but defence-in-depth). */
  font-variant-numeric: tabular-nums;
  white-space: pre;
  letter-spacing: 0;
}
.monster-callout .callout-row > .c-num,
.monster-callout .callout-row > .c-lv,
.monster-callout .callout-row > .c-hp,
.monster-callout .callout-row > .c-ac { text-align: right; }
.monster-callout .callout-row > .c-name,
.monster-callout .callout-row > .c-t { text-align: left; }

/* Header row sits at the top -- orange + a subtle underline. */
.monster-callout .callout-row.header > span { color: var(--orange); }
.monster-callout .callout-row.header { border-bottom: 1px solid #2d2d2d; }

/* Per-row state coloring works through descendant selectors so the
   contents wrapper doesn't need its own paint surface. */
.monster-callout .callout-row.alive > span { color: var(--green); }
.monster-callout .callout-row.hurt  > span { color: #ffd400; }
.monster-callout .callout-row.low   > span { color: #d44; }
.monster-callout .callout-row.asleep > span { color: #8af; }
.monster-callout .callout-row.dead  > span {
  color: #555; text-decoration: line-through;
}

/* The ability sub-line stretches across all columns. */
.monster-callout .callout-special {
  grid-column: 1 / -1;
  padding-left: 4ch;
  color: #ffd400;
  font-size: 11px;
  letter-spacing: 0;
}
.monster-callout .callout-special.none { color: #888; }
.monster-callout .callout-special.boss { color: #ff66ff; }

/* Status badge for non-OK characters. */
.inspect-status-badge {
  display: inline-block; padding: 1px 6px; margin-left: 6px;
  font-size: 11px; letter-spacing: 0.08em;
  border: 1px solid #d44; color: #d44;
}
.inspect-status-badge.ok { border-color: var(--green); color: var(--green); }
.inspect-status-badge.poisoned { border-color: #4dd44d; color: #4dd44d; }
.inspect-status-badge.stoned { border-color: #888; color: #888; }
/* BUILD 186: badge inherits border color from the status-* class so
   all afflictions read with their proper colors. */
.inspect-status-badge.status-ok-full,
.inspect-status-badge.status-ok-warn,
.inspect-status-badge.status-alive,
.inspect-status-badge.status-notgood,
.inspect-status-badge.status-critical { border-color: currentColor; }
.inspect-status-badge.status-dead      { border-color: #8a0000; }
.inspect-status-badge.status-ashes     { border-color: #666; }
.inspect-status-badge.status-bad,
.inspect-status-badge.status-poisoned,
.inspect-status-badge.status-stoned,
.inspect-status-badge.status-paralyzed,
.inspect-status-badge.status-asleep,
.inspect-status-badge.status-afraid,
.inspect-status-badge.status-silenced,
.inspect-status-badge.status-confused,
.inspect-status-badge.status-blinded   { border-color: currentColor; }
.trade-banner {
  text-align: center; padding: 6px;
  border: 1px solid var(--orange);
  margin: 4px 0;
  letter-spacing: 0.10em;
}
.trade-banner.success { color: var(--green); border-color: var(--green); }
.trade-banner.error   { color: #ff5050;       border-color: #ff5050; }
.camp-heal-section { margin-top: 8px; }
.camp-potion-card {
  border: 1px dotted #444;
  padding: 4px 8px;
  margin: 4px 0;
}
.camp-potion-card .caster-name {
  color: var(--violet);
  font-weight: bold;
  margin-bottom: 2px;
}
/* When augmented (camp), roster gets two extra columns: XP + TO-NEXT. */
.roster-table.augmented .roster-row {
  grid-template-columns: 32px 26px 150px 80px 32px 32px 56px 120px 80px 1fr;
}
.roster-row .col-xp   { color: var(--blue);  text-align: right; }
.roster-row .col-need { color: var(--green); }
.roster-row .col-need.ready { color: var(--orange); font-weight: bold; }
.camp-caster-row {
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  padding: 2px 0;
}
.camp-caster-row .caster-name { color: var(--violet); min-width: 120px; }
/* #camp-log is now repurposed as the bottom-of-screen party roster
   container, so it no longer needs the log-style scrolling box. */
#camp-log {
  margin-top: 10px;
  padding: 4px 0 0;
  border-top: 1px dotted #444;
  font-size: 12px;
}

/* ----- Chest art ----- */
.chest-art-box {
  display: flex;
  justify-content: center;
  margin: 8px 0 6px;
}
.chest-art {
  background: #000;
  border: 1px solid #444;
  box-shadow: 0 0 0 3px #222;
}
/* Painted chest PNG. Capped at a sensible max height so the giant
   source artwork doesn't dominate the screen, but lets the image
   scale down on narrow viewports. */
.chest-art-image {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 320px;
  object-fit: contain;
  border-radius: 4px;
  filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.6));
}
.chest-distribution {
  margin-top: 8px;
  padding: 6px 8px;
  border-left: 3px solid var(--orange);
}
.chest-distribution p {
  margin: 4px 0;
  color: var(--green);
}
/* Per-item loot rows shown beneath each recipient's headline. */
.chest-loot-list {
  margin: 2px 0 8px 18px;
  padding: 4px 8px;
  border-left: 1px dotted #444;
}
.chest-loot-row {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  align-items: center;
  gap: 6px;
  padding: 2px 0;
  font-size: 12px;
}
.chest-loot-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
}
.chest-loot-icon svg { display: block; }
.chest-loot-name { color: var(--green); }
.chest-loot-desc { color: #888; font-size: 11px; }
/* BUILD 207: loot-reveal Boltac-style mini-cards. A flex-wrap container
   lays each character's loot out horizontally so multi-piece drops
   form a tidy row of cards. Each card has a 50px icon on the left and
   name/desc/stat lines stacked on the right -- same shape as the shop
   cards in Boltac's. */
.chest-loot-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 4px 0 10px 18px;
}
.chest-loot-card {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border: 1px solid #654221;
  border-radius: 3px;
  background: rgba(60, 40, 18, 0.32);
  width: 260px;
  box-sizing: border-box;
}
.chest-loot-card-icon {
  width: 56px;
  height: 56px;
  flex: 0 0 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.35);
  border: 1px solid #333;
  border-radius: 3px;
}
.chest-loot-card-icon svg,
.chest-loot-card-icon img {
  display: block;
  max-width: 50px;
  max-height: 50px;
  height: 50px;
  width: auto;
}
.chest-loot-card-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
  flex: 1 1 auto;
}
.chest-loot-card-name {
  color: var(--green);
  font-size: 13px;
  letter-spacing: 0.04em;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chest-loot-card-desc {
  color: #c8b48c;
  font-size: 11px;
  line-height: 1.25;
}
.chest-loot-card-stat {
  color: var(--orange);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.chest-trap-log {
  margin-top: 8px;
  padding: 6px 10px;
  border: 1px solid #5a1a1a;
  background: rgba(255, 0, 0, 0.06);
}
.chest-trap-log h3 { color: #ff8080; margin: 0 0 4px; font-size: 13px; letter-spacing: 0.08em; }
.chest-trap-log p { margin: 2px 0; font-size: 12px; }

/* ----- Chest ACTION-phase two-column layout -----
   Left column carries the character + action buttons (the things the
   designated player actually has to interact with), right column shows
   the chest art with the TRAP STATUS card directly underneath. Goal:
   everything visible without scrolling. */
.chest-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 18px;
  align-items: start;
  margin: 6px 0 4px;
}
.chest-grid-left { display: flex; flex-direction: column; gap: 6px; }
.chest-grid-left .option-row { flex-wrap: wrap; }
.chest-grid-left h3 { margin: 6px 0 2px; }
.chest-grid-left p { margin: 2px 0; }
.chest-grid-right {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.chest-grid-right .chest-art-box { margin: 0; }
.chest-grid-right .chest-art-image { max-height: 240px; }
.chest-trap-status-card {
  width: 100%;
  border: 2px solid var(--orange);
  background: rgba(255, 140, 0, 0.08);
  border-radius: 6px;
  padding: 10px 12px;
  text-align: center;
  box-shadow: 0 0 12px rgba(255, 140, 0, 0.18);
}
.chest-trap-status-label {
  font-size: 13px;
  letter-spacing: 0.14em;
  color: var(--green);
  margin-bottom: 6px;
}
.chest-trap-status-value {
  font-size: 26px;
  font-weight: bold;
  letter-spacing: 0.08em;
  color: var(--orange);
  text-shadow: 0 0 6px rgba(255, 140, 0, 0.5);
  line-height: 1.1;
  word-break: break-word;
}
.chest-trap-status-value.chest-trap-status-known {
  color: #ffb84d;
}
.chest-trap-status-value.chest-trap-status-failed {
  color: #ff7a3a;
  font-size: 22px;
}
@media (max-width: 720px) {
  .chest-grid { grid-template-columns: minmax(0, 1fr); }
  .chest-grid-right { order: -1; }
}

/* ----- Chest volunteer phase -----
   BUILD 201: VOLUNTEER phase rides the same .chest-grid 2-column
   layout that ACTION uses. ME/PASS + text input + PICK NOW button
   stack on the left; countdown + tally + volunteer roster sit on
   the right alongside the chest art. Keeps every actionable widget
   above the scroll line. */
.chest-volunteer-row {
  margin-top: 8px;
}
.chest-pick-now-btn {
  margin-top: 6px;
  background: #1a1a1a;
  color: var(--orange);
  border: 1px solid var(--orange);
  padding: 4px 12px;
  letter-spacing: 0.08em;
  font-weight: bold;
  cursor: pointer;
}
.chest-pick-now-btn:hover { background: var(--orange); color: #000; }
.chest-volunteer-status {
  margin-top: 10px;
  padding: 6px 10px;
  border: 1px dotted #444;
  border-radius: 4px;
  background: rgba(20,20,20,0.45);
}
.chest-volunteer-time { margin: 0 0 6px; }
.chest-volunteer-tally {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12px;
  margin-bottom: 4px;
}
.chest-tally-label { color: var(--violet); letter-spacing: 0.06em; }
.chest-tally-num   { color: var(--green);  font-weight: bold; }
.chest-volunteer-names { margin: 4px 0 0; }
.chest-me-btn {
  font-size: 22px;
  padding: 10px 24px;
  letter-spacing: 0.18em;
  color: var(--orange);
  border-color: var(--orange);
}
.chest-me-btn:hover {
  background: var(--orange);
  color: #000;
}
.countdown {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.1em;
}
#chest-me-input {
  background: #000;
  border: 1px solid var(--orange);
  color: var(--orange);
  font-family: inherit;
  font-size: inherit;
  padding: 4px 8px;
  width: 220px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-top: 6px;
}
#chest-me-input:focus { outline: 2px solid var(--green); }

/* ----- Character creation ----- */
#training-controls .stat-table {
  display: grid;
  grid-template-columns: 80px 60px 80px;
  gap: 4px 12px;
  margin: 8px 0;
}
#training-controls .stat-table .stat-name { color: var(--violet); }
#training-controls .stat-table .stat-val  { color: var(--green); text-align: right; }

/* ----- Roll stage: stats left, qualifying-class portraits right ----- */
.roll-grid {
  display: grid;
  grid-template-columns: minmax(0, 280px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
  margin-top: 6px;
}
.roll-stats { display: flex; flex-direction: column; gap: 6px; }
.roll-stats button { align-self: flex-start; }
.roll-qualifies {
  border: 1px solid #444;
  border-radius: 6px;
  background: rgba(20, 20, 20, 0.45);
  padding: 10px 12px;
  min-height: 220px;
}
.roll-qualify-title {
  margin: 0 0 8px;
  color: var(--orange);
  letter-spacing: 0.12em;
  font-size: 14px;
}
.roll-qualify-empty {
  color: #888;
  font-style: italic;
}
.roll-qualify-hint {
  margin-top: 10px;
  color: var(--green);
  font-size: 12px;
  letter-spacing: 0.06em;
}

/* Grid of qualifying-class cards. --card-size is set inline from JS based
   on how many classes the candidate qualifies for, so 1-class shows a
   big portrait and 8-class shrinks to fit. */
.class-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--card-size, 100px), 1fr));
  gap: 10px;
  justify-items: center;
}
.class-card {
  background: rgba(10, 10, 10, 0.5);
  border: 1px solid #555;
  border-radius: 6px;
  padding: 6px 6px 8px;
  cursor: pointer;
  color: var(--green);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 100%;
  max-width: calc(var(--card-size, 100px) + 24px);
  transition: border-color 0.12s, transform 0.12s, box-shadow 0.12s;
}
.class-card:hover {
  border-color: var(--orange);
  transform: translateY(-1px);
  box-shadow: 0 0 8px rgba(255, 140, 0, 0.35);
}
.class-card:active { transform: translateY(0); }
.class-card-img {
  width: var(--card-size, 100px);
  height: var(--card-size, 100px);
  max-width: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  border-radius: 4px;
  background: #000;
}
.class-card-name {
  color: var(--orange);
  font-weight: bold;
  letter-spacing: 0.08em;
  font-size: 12px;
  text-align: center;
}

/* Bonus-points flash cue. Toggled by JS when the player tries to commit
   a class with points unspent. Pulses red->white->orange so the eye
   catches both the number and the SPEND-ALL message below it. */
.roll-bonus-header { transition: color 0.15s; }
.roll-bonus-number {
  font-weight: bold;
  color: var(--orange);
  font-size: 16px;
}
@keyframes flashBonusKf {
  0%   { background: rgba(255, 60, 60, 0.0); color: var(--green); transform: scale(1); }
  20%  { background: rgba(255, 60, 60, 0.45); color: #fff; transform: scale(1.06); }
  40%  { background: rgba(255, 140, 0, 0.45); color: var(--orange); transform: scale(1.02); }
  60%  { background: rgba(255, 60, 60, 0.45); color: #fff; transform: scale(1.06); }
  100% { background: rgba(255, 60, 60, 0.0); color: var(--green); transform: scale(1); }
}
.flash-bonus {
  animation: flashBonusKf 0.9s ease-in-out 1;
  border-radius: 4px;
  padding-left: 6px;
  padding-right: 6px;
}

@media (max-width: 760px) {
  .roll-grid { grid-template-columns: minmax(0, 1fr); }
}
.option-row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.option-row button.selected { background: var(--green); color: #000; }

/* ====================================================================
   BUILD 211: account login / signup / verify / forgot / reset panels.
   Layout sits inside #screen-connect, replacing the old single-input
   "ENTER YOUR NAME" prompt. Keeps the amber/green terminal aesthetic.
   ==================================================================== */
.auth-root {
  margin: 18px auto 10px;
  max-width: 380px;
  text-align: left;
  position: relative;
  z-index: 2;
}
.auth-panel {
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid var(--orange);
  border-radius: 4px;
  padding: 18px 22px 14px;
  box-shadow: 0 0 14px rgba(255, 160, 60, 0.18);
}
.auth-panel[hidden] { display: none; }
.auth-title {
  margin: 0 0 14px;
  color: var(--orange);
  letter-spacing: 0.12em;
  text-align: center;
  font-size: 16px;
}
.auth-panel label {
  display: block;
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.08em;
  margin: 8px 0 3px;
}
.auth-panel input[type="text"],
.auth-panel input[type="email"],
.auth-panel input[type="password"] {
  width: 100%;
  background: #0a0a0a;
  border: 1px solid var(--fg-dim);
  color: var(--green);
  font-family: 'Courier New', monospace;
  font-size: 14px;
  letter-spacing: 0.06em;
  padding: 6px 8px;
  box-sizing: border-box;
}
.auth-panel input:focus {
  outline: none;
  border-color: var(--orange);
  box-shadow: 0 0 4px rgba(255, 160, 60, 0.4);
}

/* BUILD 337: password wrap + show/hide eye button.
   The wrap is relative so the eye can sit absolutely at the right
   edge of the input. Padding-right on the input keeps typed text
   from sliding under the eye. The button is full input height so
   the click target is generous. */
.auth-panel .pw-wrap {
  position: relative;
  width: 100%;
}
.auth-panel .pw-wrap input[type="password"],
.auth-panel .pw-wrap input[type="text"] {
  padding-right: 34px;   /* room for the eye */
}
.auth-panel .pw-eye {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 32px;
  background: transparent;
  border: none;
  color: var(--green);
  cursor: pointer;
  font-size: 16px;
  line-height: 1;
  padding: 0;
  display: flex; align-items: center; justify-content: center;
  opacity: 0.7;
  transition: opacity 100ms ease-out;
}
.auth-panel .pw-eye:hover { opacity: 1; }
.auth-panel .pw-eye:focus { outline: none; opacity: 1; }
.auth-primary {
  display: block;
  width: 100%;
  margin: 14px 0 8px;
  background: #0a1a08;
  border: 1px solid var(--green);
  color: var(--green);
  padding: 8px;
  cursor: pointer;
  font-family: 'Courier New', monospace;
  font-size: 13px;
  letter-spacing: 0.12em;
  font-weight: 700;
}
.auth-primary:hover {
  background: #14301a;
  box-shadow: 0 0 6px rgba(80, 220, 80, 0.5);
}
.auth-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 6px 14px;
  margin-top: 8px;
  font-size: 11px;
}
.auth-links a {
  color: var(--fg-dim);
  text-decoration: none;
  letter-spacing: 0.04em;
}
.auth-links a:hover { color: var(--orange); text-decoration: underline; }
/* BUILD 288: real buttons instead of clickable text for FORGOT PASSWORD,
   CREATE ACCOUNT, SIGN OUT, etc. Same dim look as the old <a> links but
   with an obvious bordered hit target so nobody has to guess what's
   clickable. */
.auth-link-btn {
  background: transparent;
  color: var(--fg-dim);
  border: 1px solid var(--fg-dim);
  border-radius: 2px;
  padding: 4px 10px;
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.04em;
  cursor: pointer;
  text-transform: none;
  line-height: 1.1;
}
.auth-link-btn:hover {
  color: var(--orange);
  border-color: var(--orange);
  background: rgba(255, 132, 0, 0.06);
}
.auth-link-btn:focus-visible {
  outline: 1px solid var(--orange);
  outline-offset: 1px;
}
.auth-note {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.05em;
  margin: 0 0 10px;
  text-align: center;
}
.auth-message {
  margin: 10px 0 0;
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.04em;
  min-height: 16px;
}
.auth-message-ok    { color: var(--green); }
.auth-message-error { color: #ff7070; }
.auth-message-info  { color: var(--orange); }
.auth-lobby-greeting {
  text-align: center;
  margin: 4px 0 12px;
  line-height: 1.4;
}
.auth-lobby-name {
  color: var(--green);
  font-size: 16px;
  letter-spacing: 0.08em;
  font-weight: 700;
}
.auth-lobby-meta {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.06em;
}

/* BUILD 212: OR divider + Google sign-in slot inside the login/signup
   auth panels. The Google button itself renders as an iframe inside
   .auth-google-btn-host -- no CSS needed for the button; we just give
   it a centered slot. */
.auth-oauth-row {
  margin: 10px 0 6px;
}
.auth-oauth-divider {
  position: relative;
  text-align: center;
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 0.18em;
  margin: 10px 0 12px;
}
.auth-oauth-divider::before,
.auth-oauth-divider::after {
  content: '';
  position: absolute;
  top: 50%;
  width: 38%;
  height: 1px;
  background: #444;
}
.auth-oauth-divider::before { left: 0; }
.auth-oauth-divider::after  { right: 0; }
.auth-oauth-divider span {
  background: rgba(0,0,0,0.92);
  padding: 0 8px;
  position: relative;
  z-index: 1;
}
.auth-google-btn-host {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 44px;
}

/* BUILD 216: tavern roster row -- six small numbered slot buttons that
   assign the row's character to that party slot. Replaces the older
   single "+ ADD TO PARTY (SLOT N)" button. Buttons render in shades:
   - empty slot   -> neutral grey/green
   - occupied     -> amber outline (a click will REPLACE the occupant)
   - already in   -> dimmed/disabled (this character already lives there) */
.roster-slot-row {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 4px 0 6px;
}
.roster-slot-label {
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--fg-dim);
  margin-right: 4px;
}
.tavern-slot-btn {
  min-width: 32px;
  padding: 4px 0;
  background: #0a1a08;
  border: 1px solid var(--green);
  color: var(--green);
  cursor: pointer;
  font-family: 'Courier New', monospace;
  font-weight: 700;
  letter-spacing: 0.06em;
  border-radius: 3px;
}
.tavern-slot-btn:hover { background: #14301a; box-shadow: 0 0 5px rgba(80,220,80,0.5); }
.tavern-slot-btn.has-occupant {
  border-color: var(--orange);
  color: var(--orange);
  background: #1a1108;
}
.tavern-slot-btn.has-occupant:hover { background: #2a1f0a; box-shadow: 0 0 5px rgba(255,160,60,0.5); }
.tavern-slot-btn.is-self,
.tavern-slot-btn:disabled {
  opacity: 0.4;
  cursor: default;
  box-shadow: none;
  background: #0a0a0a;
}

/* BUILD 217 (Phase L2): play-mode picker. Three big buttons in the
   AuthView lobby panel -- solo (always live), private + public
   ("COMING SOON" until L3/L4). */
.play-mode-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 6px;
}
.play-mode-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
  padding: 10px 12px;
  background: #0a1a08;
  border: 1px solid var(--green);
  color: var(--green);
  cursor: pointer;
  font-family: 'Courier New', monospace;
  border-radius: 3px;
  gap: 4px;
}
.play-mode-btn:hover:not(:disabled) {
  background: #14301a;
  box-shadow: 0 0 6px rgba(80, 220, 80, 0.45);
}
.play-mode-btn .play-mode-title {
  font-size: 14px;
  letter-spacing: 0.1em;
  font-weight: 700;
}
.play-mode-btn .play-mode-sub {
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.03em;
  text-transform: none;
  line-height: 1.3;
}
.play-mode-btn.coming-soon,
.play-mode-btn:disabled {
  border-color: var(--fg-dim);
  color: var(--fg-dim);
  cursor: default;
  opacity: 0.65;
}
.play-mode-btn.coming-soon:hover { box-shadow: none; background: #0a1a08; }

/* BUILD 218 (Phase L3): private-room sub-panel. CREATE / JOIN tabs at
   the top, a tabpane for each, and a prominent room-code reveal box
   that appears after a successful CREATE. */
.priv-tabs {
  display: flex;
  gap: 0;
  margin: 0 0 10px;
  border-bottom: 1px dotted #444;
}
.priv-tab {
  flex: 1;
  padding: 6px 8px;
  background: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--fg-dim);
  font-family: 'Courier New', monospace;
  font-size: 12px;
  letter-spacing: 0.1em;
  cursor: pointer;
}
.priv-tab.selected {
  color: var(--orange);
  border-bottom-color: var(--orange);
}
.priv-tabpane { display: block; }
.priv-tabpane[hidden] { display: none; }
.priv-room-code {
  margin: 14px 0 10px;
  padding: 10px;
  text-align: center;
  border: 1px solid var(--orange);
  background: rgba(60, 40, 18, 0.32);
  border-radius: 3px;
}
.priv-room-code-label {
  display: block;
  font-size: 10px;
  letter-spacing: 0.15em;
  color: var(--fg-dim);
}
.priv-room-code-value {
  display: block;
  margin: 4px 0;
  font-size: 28px;
  letter-spacing: 0.30em;
  color: #ffd97a;
  font-weight: 700;
  font-family: 'Courier New', monospace;
}
.priv-room-code-hint {
  display: block;
  font-size: 10px;
  letter-spacing: 0.10em;
  color: var(--fg-dim);
}

/* BUILD 219 (Phase L4): roster row for a character that's currently
   checked out to a DIFFERENT room. Visible in the tavern roster so the
   user knows the character is alive and playing elsewhere, but the
   slot buttons are replaced with this banner. */
/* BUILD 259: elsewhere banner is now a solid red block with bold
   white text -- impossible to miss. The whole row dims heavily so
   the character feels truly off-limits. */
.roster-elsewhere-row {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px 10px;
  margin-top: 2px;
  background: #b40000;
  border: 1px solid #ff5050;
  border-radius: 3px;
  box-shadow: 0 0 6px rgba(255, 80, 80, 0.4);
}
.roster-elsewhere-label {
  color: #ffffff;
  font-size: 12px;
  letter-spacing: 0.14em;
  font-weight: 700;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.7);
}

/* BUILD 239: roster row dimming for non-clickable states. The roster
   now lists every character the user owns; party members and chars
   checked out to other rooms appear here too, dimmed, with a status
   banner in place of the 1-6 slot buttons. */
.roster-entry.is-in-party {
  opacity: 0.55;
  filter: saturate(0.6);
}
.roster-entry.is-in-party:hover {
  opacity: 0.75;
}
/* BUILD 259: elsewhere rows are MUCH dimmer + desaturated to make
   the unavailability immediate. The red banner provides the actual
   call-out; this just pushes the whole row to the background. */
.roster-entry.is-elsewhere {
  opacity: 0.40;
  filter: saturate(0.25) grayscale(0.3);
}
.roster-entry.is-elsewhere:hover {
  opacity: 0.60;
  filter: saturate(0.4) grayscale(0.2);
}
/* The banner ITSELF stays bright -- it should pop even when the rest
   of the row is muted. */
.roster-entry.is-elsewhere .roster-elsewhere-row {
  opacity: 1;
  filter: none;
}
.roster-inparty-row {
  display: flex;
  align-items: center;
  padding: 6px 8px;
  margin-top: 2px;
  background: rgba(40, 80, 130, 0.18);
  border: 1px dashed #4f7ba6;
  border-radius: 3px;
}
.roster-inparty-label {
  color: #80c8ff;
  font-size: 11px;
  letter-spacing: 0.08em;
  font-weight: 600;
}

/* BUILD 220 (Phase L5): matchmaking panel. Shown while the user is
   sitting in the public-game queue. The status block updates 1Hz
   via authView.js polling /lobby/queue-status. */
.match-status {
  margin: 8px 0 14px;
  padding: 10px;
  background: rgba(40, 30, 10, 0.5);
  border: 1px solid #4a3a18;
  border-radius: 3px;
}
.match-line {
  margin: 4px 0;
  font-size: 12px;
  letter-spacing: 0.04em;
  display: flex;
  justify-content: space-between;
}
.match-line-label {
  color: var(--fg-dim);
  letter-spacing: 0.08em;
}
.match-line-value {
  color: #ffd97a;
  font-weight: 600;
}
.match-status .auth-note {
  margin-top: 8px;
  text-align: center;
}

/* BUILD 335: ambient dragon-fire loop behind the connect screen.
   Anchored to the bottom-center, behind the connect grid (the grid is
   z-index:2 inside #screen-connect; the video sits at z-index:0).
   - max-height capped at 55% of the viewport so it never crowds the
     login form on short laptop screens.
   - object-fit:contain + object-position bottom keeps the dragon's
     feet flush with the screen bottom regardless of aspect ratio.
   - opacity:0.55 + soft top fade-out so the dragon eases into the
     dark background instead of cutting off as a hard edge.
   - pointer-events:none so any UI that overlaps still gets clicks.
   - body.is-joined hides it once you're past the login screen (the
     game UI doesn't need atmospheric background). */
#screen-connect { position: relative; }
.connect-dragon-bg {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  width: 100%;
  max-height: 55vh;
  object-fit: contain;
  object-position: center bottom;
  opacity: 0.55;
  pointer-events: none;
  z-index: 0;
  -webkit-mask-image: linear-gradient(to top, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%);
          mask-image: linear-gradient(to top, rgba(0,0,0,1) 60%, rgba(0,0,0,0) 100%);
}
body.is-joined .connect-dragon-bg { display: none; }

/* BUILD 221: two-column connect screen.
   - Left column: branding (h1/h2/copy/build-tag) on top, status +
     player-lobby below the auth box.
   - Right column: the auth panel stack (login / signup / verify /
     forgot / reset / lobby / matchmaking / private). Sticky enough
     to stay above the fold.
   Narrow viewports collapse to a single column. */
.connect-grid {
  display: grid;
  grid-template-columns: 1fr 420px;
  grid-template-areas:
    "left   right"
    "bottom right";
  gap: 24px;
  max-width: 1100px;
  margin: 0 auto;
  padding: 18px 22px;
  align-items: start;
  position: relative;
  z-index: 2;
}
.connect-col-left        { grid-area: left;   text-align: center; }
.connect-col-right       { grid-area: right;  text-align: left; }
.connect-col-left-bottom { grid-area: bottom; text-align: center; }
.connect-grid .auth-root { margin: 0; max-width: none; }
.connect-grid #connect-lobby { margin-top: 14px; }
@media (max-width: 760px) {
  .connect-grid {
    grid-template-columns: 1fr;
    grid-template-areas: "left" "right" "bottom";
    padding: 12px;
  }
  .connect-col-right { text-align: center; }
}

/* BUILD 221: play-mode hover -- orange highlight (was dark green
   which was too low contrast against the dim body text). */
.play-mode-btn:hover:not(:disabled) {
  background: rgba(255, 160, 60, 0.16);
  border-color: var(--orange);
  color: var(--orange);
  box-shadow: 0 0 7px rgba(255, 160, 60, 0.45);
}
.play-mode-btn:hover:not(:disabled) .play-mode-sub {
  color: #ffd97a;
}

/* BUILD 225: full-resolution avatar popout. Clicking any character
   portrait opens this modal showing the same PNG at 80% of its
   intrinsic disk size, centered, with a dimmed backdrop. */
.avatar-popout-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: avatar-popout-fade 0.18s ease-out;
}
@keyframes avatar-popout-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.avatar-popout-panel {
  position: relative;
  background: #000;
  border: 2px solid var(--orange);
  border-radius: 4px;
  box-shadow: 0 0 24px rgba(255, 160, 60, 0.55);
  padding: 10px;
  max-width: 95vw;
  max-height: 95vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.avatar-popout-img {
  display: block;
  object-fit: contain;
  /* The JS sizes width/height inline at 80% of natural size; here we
     just clamp to viewport so very large portraits stay on-screen. */
  max-width: 90vw;
  max-height: 85vh;
  background: #000;
}
.avatar-popout-caption {
  color: var(--orange);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-align: center;
  padding: 4px 8px;
}
.avatar-popout-close {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 28px;
  height: 28px;
  background: #000;
  border: 1px solid var(--orange);
  color: var(--orange);
  font-size: 18px;
  line-height: 24px;
  cursor: pointer;
  border-radius: 14px;
}
.avatar-popout-close:hover {
  background: var(--orange);
  color: #000;
}
/* The portrait box on the inspect screen needs a bit more room now
   that the avatar is 154px instead of 128px. */
.inspect-portrait .char-avatar {
  background: #000;
  border: 2px solid #444;
  /* contain: image is now letterboxed inside the box, never cropped */
}

/* BUILD 233: tiny BACK button shown at the top of the race / align /
   roll character-creation steps. Lets the player rewind their choices
   without losing prior state. */
.training-back-btn {
  background: transparent;
  border: 1px solid var(--fg-dim);
  color: var(--fg-dim);
  padding: 2px 10px;
  font-size: 11px;
  letter-spacing: 0.08em;
  margin-bottom: 8px;
  cursor: pointer;
  border-radius: 2px;
}
.training-back-btn:hover {
  color: var(--orange);
  border-color: var(--orange);
  background: rgba(255, 160, 60, 0.08);
}

/* ======================================================================
 * BUILD 242: dungeon HUD spell-timer rows shown beside the compass.
 * Live countdown for PHOSPHIA (the next-tile light spell) and CARTOMA
 * (the mini-map reveal spell). Each is a small chip in its own color
 * so they're easy to recognize at a glance. */
#dungeon-spell-timers {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-top: 4px;
}
.spell-timer-row {
  font-size: 11px;
  letter-spacing: 0.08em;
  padding: 3px 8px;
  border: 1px solid currentColor;
  border-radius: 2px;
  text-align: center;
  font-weight: 600;
}
.spell-timer-phosphia { color: #ffd680; background: rgba(255, 200, 100, 0.08); }
.spell-timer-cartoma  { color: #80c8ff; background: rgba(80,  160, 255, 0.08); }

/* BUILD 250: short transport flash drawn inside the wireframe
 * canvas wrap. Quick + non-blocking so it doesn't impede play. */
.transport-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 5;
  background: rgba(0, 0, 0, 0.35);
  animation: transport-flash-fade 0.65s linear forwards;
}
.transport-flash svg {
  width: 70%;
  height: 70%;
  animation: transport-flash-swirl 0.65s ease-out forwards;
  filter: drop-shadow(0 0 4px rgba(65, 255, 0, 0.7));
}
@keyframes transport-flash-fade {
  0%   { background: rgba(0, 0, 0, 0.0); }
  20%  { background: rgba(255, 255, 255, 0.20); }
  50%  { background: rgba(65, 255, 0, 0.18); }
  100% { background: rgba(0, 0, 0, 0.0); }
}
@keyframes transport-flash-swirl {
  0%   { opacity: 0; transform: scale(0.2) rotate(0deg); }
  35%  { opacity: 1; transform: scale(1.0) rotate(180deg); }
  100% { opacity: 0; transform: scale(2.0) rotate(540deg); }
}

/* BUILD 243: Town Portal / EVASIO teleport cinematic.
 * Full-screen overlay played when the rift actually succeeds. The
 * pieces animate via keyframes; the JS just appends and removes the
 * overlay element. Pointer events captured for the whole 3.4s so the
 * player can't click stale UI behind the transition. */
.teleport-cinematic {
  position: fixed;
  inset: 0;
  z-index: 5000;
  background: rgba(0, 0, 0, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: auto;
  cursor: wait;
  overflow: hidden;
}
.teleport-banner {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #41ff00;
  font-size: 32px;
  letter-spacing: 0.32em;
  font-weight: 700;
  text-shadow: 0 0 12px rgba(65, 255, 0, 0.7);
  opacity: 0;
  animation: tp-banner 1.4s ease-out forwards;
}
@keyframes tp-banner {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.6); }
  20%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05); }
  60%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.0); }
}
.teleport-swirl {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 380px;
  height: 380px;
  margin-left: -190px;
  margin-top: -190px;
  opacity: 0;
  animation: tp-swirl 3.0s cubic-bezier(0.55, 0.0, 0.55, 1) 0.2s forwards;
  transform-origin: 50% 50%;
}
.teleport-swirl svg {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 0 8px rgba(65, 255, 0, 0.6));
}
@keyframes tp-swirl {
  0%   { opacity: 0; transform: scale(0) rotate(0deg); }
  20%  { opacity: 1; transform: scale(0.6) rotate(180deg); }
  60%  { opacity: 1; transform: scale(1.5) rotate(900deg); }
  85%  { opacity: 1; transform: scale(2.4) rotate(1620deg); }
  100% { opacity: 0; transform: scale(0.05) rotate(2160deg); }
}
.teleport-blackout {
  position: absolute;
  inset: 0;
  background: #000;
  opacity: 0;
  animation: tp-blackout 3.4s linear forwards;
}
@keyframes tp-blackout {
  0%, 75%   { opacity: 0; }
  76%, 82%  { opacity: 1; }
  100%      { opacity: 0; }
}
.teleport-flash {
  position: absolute;
  inset: 0;
  background: #ffffff;
  opacity: 0;
  animation: tp-flash 3.4s linear forwards;
}
@keyframes tp-flash {
  0%, 84%   { opacity: 0; }
  85%       { opacity: 1; }
  100%      { opacity: 0; }
}

/* BUILD 237: in-maze spell-cast modal. Opened from the CAST button on
 * the leader's dungeon command bar. Lists casters in the active party
 * and their dungeon-eligible spells with remaining slot counts. The
 * orange border + dark backdrop matches the avatar-popout look so the
 * UI feels of a piece with the rest of the dungeon overlays.
 * ====================================================================== */
.dungeon-cast-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.82);
  z-index: 2100;
  display: flex;
  align-items: center;
  justify-content: center;
  animation: avatar-popout-fade 0.16s ease-out;
}
.dungeon-cast-panel {
  position: relative;
  background: #000;
  border: 2px solid var(--orange);
  border-radius: 4px;
  box-shadow: 0 0 24px rgba(255, 160, 60, 0.55);
  padding: 18px 22px 14px;
  min-width: 360px;
  max-width: 560px;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.dungeon-cast-header {
  color: var(--orange);
  font-size: 14px;
  letter-spacing: 0.14em;
  text-align: center;
  border-bottom: 1px solid #4a2a08;
  padding-bottom: 8px;
}
.dungeon-cast-close {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 28px;
  height: 28px;
  background: #000;
  border: 1px solid var(--orange);
  color: var(--orange);
  font-size: 18px;
  line-height: 24px;
  cursor: pointer;
  border-radius: 14px;
}
.dungeon-cast-close:hover {
  background: var(--orange);
  color: #000;
}
.dungeon-cast-body {
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.dungeon-cast-empty {
  color: var(--fg-dim);
  font-style: italic;
  text-align: center;
  padding: 16px 8px;
}
.dungeon-cast-caster {
  border: 1px solid #2c2c2c;
  border-radius: 3px;
  padding: 8px 10px;
  background: #0a0a0a;
}
.dungeon-cast-caster-name {
  color: var(--fg);
  font-size: 12px;
  letter-spacing: 0.08em;
  margin-bottom: 6px;
}
.dungeon-cast-spell-btn {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 1px solid #555;
  color: var(--fg);
  padding: 6px 10px;
  font-size: 12px;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
  cursor: pointer;
  border-radius: 2px;
}
.dungeon-cast-spell-btn:hover:not(:disabled) {
  color: var(--orange);
  border-color: var(--orange);
  background: rgba(255, 160, 60, 0.08);
}
.dungeon-cast-spell-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.dungeon-cast-footer {
  text-align: right;
  border-top: 1px solid #4a2a08;
  padding-top: 8px;
}
.dungeon-cast-cancel {
  background: transparent;
  border: 1px solid var(--fg-dim);
  color: var(--fg-dim);
  padding: 4px 14px;
  font-size: 11px;
  letter-spacing: 0.10em;
  cursor: pointer;
  border-radius: 2px;
}
.dungeon-cast-cancel:hover {
  color: var(--orange);
  border-color: var(--orange);
}

/* BUILD 234: prominent instruction banner at the top of the
   qualifying-classes panel during character creation. Replaces the
   easy-to-miss tiny note at the bottom. */
.roll-qualify-instruction {
  margin: 4px 0 10px;
  padding: 8px 10px;
  border: 1px solid var(--orange);
  background: rgba(255, 160, 60, 0.10);
  color: var(--orange);
  font-size: 12px;
  letter-spacing: 0.06em;
  line-height: 1.4;
  text-align: center;
  border-radius: 3px;
}

/* BUILD 234: class-card portraits use Avatar.create now, which
   returns an <img> tagged with .char-avatar. Make sure that scope
   doesn't override the size driven by the card-grid CSS variable. */
.class-card .class-card-img.char-avatar {
  width: var(--card-size);
  height: var(--card-size);
}

/* BUILD 234: class-card portraits should commit the class on click,
   not open the avatar-popout modal. Avatar.create wires its own click
   handler that stopPropagation's; disabling pointer-events on the
   image lets the click fall through to the parent .class-card button. */
.class-card .class-card-img {
  pointer-events: none;
}
