:root {
  color-scheme: dark;
  --bg: #0f1115;
  --panel: #161922;
  --border: #262b38;
  --text: #d8dce6;
  --muted: #6b7280;
  --accent: #5b9dff;
  --ok: #3ecf8e;
  --warn: #f2b84b;
  --bad: #ef5a5a;
  --sidebar-width: 280px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}

.hidden {
  display: none !important;
}

input {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 8px;
  border-radius: 6px;
  font: inherit;
}

button {
  background: #1e2330;
  border: 1px solid var(--border);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
}

button:hover {
  border-color: var(--accent);
}

/* --- login screen --- */

.login {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.login-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 260px;
  padding: 24px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
}

.login-card h1 {
  font-size: 16px;
  margin: 0 0 6px;
  color: var(--accent);
  text-align: center;
  letter-spacing: 0.02em;
}

.login-card input {
  width: 100%;
}

/* Secondary action on the auth card (register beside log in): same shape,
   quieter, so the primary action still reads as the one to press. */
button.secondary {
  background: none;
  color: var(--muted);
}

button.secondary:hover {
  color: var(--text);
}

/* --- password rules --- */

/* The requirements sit next to the fields and tick over as you type, so the
   rule you're failing is visible before you submit rather than after. */
.input-rules {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 12px;
}

.input-rules li {
  display: flex;
  align-items: baseline;
  gap: 7px;
  color: var(--muted);
}

/* A dot that becomes a tick — same width either way, so the text doesn't
   shift sideways as rules are met. */
.input-rules li::before {
  content: "·";
  width: 9px;
  text-align: center;
}

.input-rules li.met {
  color: var(--ok);
}

.input-rules li.met::before {
  content: "✓";
}

/* --- character select --- */

.player-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* One row per character, tall enough to be a comfortable tap target on the
   phone this screen is mostly used from. */
.player-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 9px 11px;
  text-align: left;
  -webkit-tap-highlight-color: transparent;
}

.player-row-name {
  font-weight: 600;
}

.player-row-meta {
  color: var(--muted);
  font-size: 12px;
}

.player-empty {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  text-align: center;
}

/* Name field and Create button share a line — the form is one field, so a
   stacked layout would waste the card's width. */
.new-player {
  display: flex;
  gap: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}

.new-player input {
  flex: 1;
  min-width: 0;
}

.login-status {
  min-height: 16px;
  margin: 0;
  font-size: 12px;
  color: var(--warn);
  text-align: center;
}

/* --- game screen --- */

.game {
  height: 100%;
  display: flex;
  flex-direction: column;
}

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
}

header h1 {
  font-size: 15px;
  margin: 0;
  color: var(--accent);
  letter-spacing: 0.02em;
}

.conn {
  display: flex;
  align-items: center;
  gap: 10px;
}


/* Log on the left, panel sidebar on the right. The sidebar is a real grid
   column rather than an overlay, so panels never cover the log. */
main {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--sidebar-width);
}

/* --- player HUD strip --- */

/* Name, level and the two meters worth watching mid-fight, pinned to the top
   of the stage on every screen size. It wraps to a second line rather than
   squeezing the meters when the name and level leave too little room. */
.player-bar {
  display: flex;
  flex: none;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 7px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
}

.hud-id {
  display: flex;
  flex: none;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.hud-name {
  color: var(--ok);
  font-size: 13px;
  font-weight: 600;
  max-width: 16ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hud-level {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}

/* Below ~240px of free space the meters take a line of their own. */
.hud-meters {
  display: flex;
  flex: 1 1 240px;
  gap: 14px;
  min-width: 0;
}

.hud-meter {
  display: flex;
  flex: 1 1 0;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.hud-meter b {
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
}

/* Same look as the panel's .stat-bar, without its stacked-row margins. */
.hud-track {
  flex: 1;
  min-width: 36px;
  height: 8px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
}

.hud-text {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The left column: the two action bars stacked over the log, which is the
   only part that scrolls. Identical on desktop and mobile — the bars are the
   primary way to fight either way, so they don't move around between them. */
.stage {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.log {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 10px 16px;
}

/* --- action bars (spells, enemies) --- */

/* One horizontal strip of cards each. They scroll sideways rather than
   wrapping, so however many spells you know or enemies you're facing, the
   bars keep exactly the same height and never push the log off screen.
   `touch-action: pan-x` keeps a sideways drag from being read as a page
   scroll on mobile. */
.action-bar {
  display: flex;
  flex: none;
  gap: 8px;
  padding: 8px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
  overflow-x: auto;
  overflow-y: hidden;
  touch-action: pan-x;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* Nothing to show: the strip collapses to a single muted line rather than
   holding a card's worth of empty height. */
.action-bar.empty {
  padding: 6px 16px;
}

.action-bar-empty {
  color: var(--muted);
  font-size: 12px;
  line-height: 1.4;
}

/* Cards are fixed-width so a long name truncates instead of stretching the
   card and making the strip's scroll length unpredictable. */
.card {
  position: relative;
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 4px;
  width: 128px;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #1b1f2a;
  color: var(--text);
  text-align: left;
  overflow: hidden;
  /* Cards are tapped constantly mid-fight; the tap highlight and the
     text-selection that a double tap causes both get in the way. */
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.card-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px;
}

.card-name {
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-sub {
  flex: none;
  color: var(--muted);
  font-size: 11px;
  line-height: 1.2;
}

.card-bar {
  display: block;
  height: 5px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.07);
  overflow: hidden;
}

.card-bar-fill {
  display: block;
  height: 100%;
  width: 100%;
  transition: width 200ms ease-out;
}

.card-bar-fill.hp {
  background: var(--bad);
}

/* --- buff cards --- */

/* The buff row is the only bar that isn't always there — it's added and
   removed as things run out, so it carries no empty state. Cards aren't
   interactive: a buff is a readout, not something you can act on. */
.buff-card {
  cursor: default;
}

.buff-card .card-name {
  color: #9d7bff;
}

.card-bar-fill.buff {
  background: #9d7bff;
  /* Redrawn every frame as the buff drains, so the 200ms ease the health bars
     use would be permanently chasing its own tail here. */
  transition: none;
}

.buff-card .buff-time {
  font-variant-numeric: tabular-nums;
}

/* --- enemy cards --- */

.enemy-card:hover:not(:disabled) {
  border-color: var(--accent);
}

/* A kill greys the card out in place rather than taking it off the strip, so
   the row you're aiming at never moves — the enemy respawns back into the same
   slot. The `button:hover` accent has to be held off too, or a corpse would
   still look tappable. */
.enemy-card.dead,
.enemy-card.dead:hover {
  border-color: var(--border);
  background: #14171f;
  cursor: default;
  opacity: 0.55;
}

.enemy-card.dead .card-name {
  color: var(--muted);
  text-decoration: line-through;
}

/* The one you're fighting. The ring is drawn with a box-shadow rather than a
   thicker border so the card doesn't shift by a pixel as targets change. */
.enemy-card.targeted {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
  background: rgba(91, 157, 255, 0.12);
}

.enemy-card.targeted .card-name {
  color: var(--accent);
}

/* --- spell cards --- */

/* The cooldown sweep: a scrim over the whole card that drains left to right
   as the spell comes back. Scaled rather than resized so the per-frame tick
   never triggers layout. */
.spell-drain {
  position: absolute;
  inset: 0;
  transform: scaleX(0);
  transform-origin: left center;
  background: rgba(15, 17, 21, 0.72);
  pointer-events: none;
}

.spell-card {
  justify-content: space-between;
}

.spell-card:hover:not(:disabled) {
  border-color: var(--accent);
}

/* What the spell is, as a colour: damage reads as the combat red, buffs and
   heals as the spell purple and the heal green used in the log. */
.spell-card.damage .card-name { color: var(--bad); }
.spell-card.buff .card-name { color: #9d7bff; }
.spell-card.heal .card-name { color: var(--ok); }

/* The global `button:hover` accent still lands on a disabled card, which
   reads as "tap me" for a spell that isn't ready — hold the resting border. */
.spell-card:disabled,
.spell-card:disabled:hover {
  border-color: var(--border);
  cursor: not-allowed;
}

.spell-card:disabled .card-name {
  color: var(--muted);
}

/* The countdown out-ranks the drain, so the number stays readable while the
   scrim is over it. */
.spell-card .card-sub {
  position: relative;
  z-index: 1;
}

.spell-card .cooldown-text.counting {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.line {
  padding: 3px 0;
  line-height: 1.5;
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}

.line .ts {
  color: var(--muted);
  margin-right: 8px;
  font-size: 12px;
}

.line .block-title { font-weight: bold; }
.line .block-row { padding-left: 14px; }
.line .block-row b { color: var(--muted); font-weight: 600; margin-right: 6px; }
.line .block-row.item-row { padding-left: 28px; }

/* --- item tables (inventory / equipment) --- */

/* Cells never wrap, so a long inventory scrolls sideways inside the block
   instead of stretching the log or folding item names onto two lines. */
.line .table-wrap {
  margin-left: 14px;
  overflow-x: auto;
}

/* Tables under a sub-heading row (the per-NPC tables in `list`) line up with
   .block-row.item-row one level deeper. */
.line .table-wrap.nested {
  margin-left: 28px;
}

.line .item-table {
  border-collapse: collapse;
  margin: 4px 0 2px;
  font-size: 13px;
}

.line .item-table th,
.line .item-table td {
  padding: 2px 14px 2px 0;
  text-align: left;
  white-space: nowrap;
}

.line .item-table th {
  color: var(--muted);
  font-weight: 600;
  border-bottom: 1px solid var(--border);
}

/* The block title keeps the log-kind color; the rows themselves are plain
   text so a wide table doesn't turn into a wall of amber. */
.line .item-table td {
  color: var(--text);
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}

.line .item-table tbody tr:last-child td { border-bottom: none; }
.line .item-table .num { text-align: right; }
.line .item-table td.price { color: var(--warn); }
.line .item-table .dim { color: var(--muted); }

/* Room names that travel when clicked (the `todo` listing). Underlined rather
   than recoloured so they still read as part of the table row, and given the
   same focus ring treatment as the map's room nodes for keyboard use. */
.line .room-link {
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-style: dotted;
  text-underline-offset: 2px;
}

.line .room-link:hover,
.line .room-link:focus-visible {
  text-decoration-style: solid;
}

/* Item names carry their rarity as colour, the usual way: plain for common,
   blue for rare, gold for legendary. Names show up both in tables and inside
   log sentences, so these have to out-specify the per-line `.line.drop span`
   colours below — hence the `span` in the selector. */
.line span.item-name.common { color: var(--text); }
.line span.item-name.rare { color: var(--accent); }
.line span.item-name.legendary { color: var(--warn); font-weight: 600; }

.line.system span { color: var(--muted); }
.line.chat span { color: var(--text); }
.line.move span { color: var(--accent); }
.line.look { color: #9d7bff; }
.line.npc { color: #9d7bff; }
.line.damage span { color: var(--bad); }
.line.spell span { color: #9d7bff; }
.line.respawn span { color: var(--ok); }
.line.heal span { color: var(--ok); }
.line.drop span { color: var(--warn); }
.line.inventory { color: var(--warn); }
.line.equipment { color: var(--warn); }
.line.stats { color: var(--accent); }
.line.todo { color: var(--accent); }
.line.ack span { color: var(--muted); font-style: italic; }
.line.error span { color: var(--bad); font-weight: bold; }
.line.raw span { color: var(--muted); }
/* Help flips the usual block coloring: the usage (in <b>) is the part worth
   scanning, so it gets the accent and the descriptions stay muted. */
.line.help { color: var(--muted); }
.line.help .block-row b { color: var(--accent); }

footer {
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  border-top: 1px solid var(--border);
  background: var(--panel);
}

footer input {
  flex: 1;
}

/* --- panel toggles --- */

/* Only the mobile sheets are openable/closable; on desktop every panel is
   always on screen, so the toggles have nothing to toggle. */
.panel-toggles {
  display: none;
}

.panel-toggle.active {
  border-color: var(--accent);
  color: var(--accent);
}

/* --- panel windows --- */

#panels {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 10px 16px;
  border-left: 1px solid var(--border);
  overflow-y: auto;
}

.panel {
  display: flex;
  flex: none;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

/* Open/closed state is a mobile-only concern — the sidebar shows everything.
   Beats .hidden's `display: none !important` on specificity. */
.panel.hidden {
  display: flex !important;
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
  background: #1b1f2a;
}

.panel-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
}

/* Hidden on desktop (nothing to close), shown on the mobile sheets below. */
.panel-close {
  display: none;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}

.panel-close:hover {
  color: var(--text);
}

.panel-body {
  padding: 10px 12px;
  overflow-y: auto;
}

.panel-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 3px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
  font-size: 13px;
}

.panel-row b {
  color: var(--muted);
  font-weight: 600;
}

/* --- stat bars --- */

.stat-bar {
  height: 8px;
  margin: 4px 0 6px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.04);
  overflow: hidden;
}

.stat-bar-fill {
  height: 100%;
  width: 100%;
  transition: width 250ms ease-out;
}

.stat-bar-fill.hp {
  background: var(--bad);
}

.stat-bar-fill.xp {
  background: var(--accent);
}

/* --- map panel --- */

.map {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The window the map is panned around inside. `touch-action: none` hands
   every gesture to the pointer handlers in map.ts, so a drag pans the map
   instead of scrolling the panel behind it. */
.map-viewport {
  position: relative; /* anchors .map-tooltip and .map-focus */
  height: 190px;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: #12151d;
  touch-action: none;
  cursor: grab;
  overscroll-behavior: contain;
  /* A drag pans the map, so don't let it also select the room labels (or pop
     the iOS long-press callout over them). The caption below stays selectable. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}

.map-viewport:active {
  cursor: grabbing;
}

/* Moved by a transform set from JS; the svg itself is always drawn at its
   natural size. */
.map-canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

.map-canvas svg {
  display: block;
}

/* Recentre-on-player button, floating over the bottom-right of the map. */
.map-focus {
  position: absolute;
  right: 6px;
  bottom: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border-radius: 6px;
  background: rgba(22, 25, 34, 0.9);
  color: var(--muted);
  z-index: 2;
}

.map-focus:hover {
  color: var(--accent);
}

.map-focus svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
}

.map-link {
  stroke: var(--border);
  stroke-width: 2;
}

/* up/down connections, which the flat grid can't show as a direction */
.map-link.vertical {
  stroke-dasharray: 3 3;
}

.map-node {
  cursor: pointer;
  outline: none;
}

.map-node rect {
  fill: #1b1f2a;
  stroke: var(--border);
  stroke-width: 1;
}

.map-node:hover rect,
.map-node:focus-visible rect {
  fill: #232936;
  stroke: var(--accent);
}

.map-node:hover text,
.map-node:focus-visible text {
  fill: var(--text);
}

.map-node text {
  fill: var(--muted);
  font-family: inherit;
  font-size: 9px;
  text-anchor: middle;
  dominant-baseline: central;
}

.map-node.current rect {
  fill: rgba(91, 157, 255, 0.22);
  stroke: var(--accent);
}

.map-node.current text {
  fill: var(--accent);
}

/* Hover label for a room, positioned over the svg from JS (see map.ts).
   No `display` here so the `hidden` attribute keeps working. */
.map-tooltip {
  position: absolute;
  transform: translate(-50%, calc(-100% - 6px));
  padding: 3px 7px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: #11151d;
  color: var(--text);
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
  z-index: 1;
}

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

.map-caption b {
  color: var(--accent);
  font-weight: 600;
}

.map-empty {
  color: var(--muted);
  font-size: 13px;
}

/* Mobile: no room for a sidebar, so panels go back to being full-screen
   sheets opened one at a time from the header toggles. */
@media (max-width: 640px) {
  main {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Tighter gutters, and cards sized for a thumb rather than a cursor. */
  .action-bar {
    gap: 6px;
    padding: 8px 10px;
  }

  .action-bar.empty {
    padding: 6px 10px;
  }

  .card {
    width: 122px;
    padding: 8px 10px;
  }

  .log {
    padding: 10px 12px;
  }

  header {
    padding: 8px 12px;
  }

  .player-bar {
    padding: 7px 12px;
  }

  footer {
    padding: 8px 12px;
  }

  .panel-toggles {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
  }

  #panels {
    position: fixed;
    inset: 0;
    padding: 0;
    border-left: none;
    overflow: visible;
    pointer-events: none;
    z-index: 50;
  }

  .panel {
    position: fixed;
    inset: 0;
    border: none;
    border-radius: 0;
    pointer-events: auto;
  }

  .panel.hidden {
    display: none !important;
  }

  .panel-close {
    display: block;
  }

  .panel-header {
    padding: 14px 16px;
  }

  .panel-body {
    padding: 16px;
  }

  /* Full-screen sheet: give the map room to be dragged around in, and a
     bigger focus button for a thumb. */
  .map-viewport {
    height: min(60vh, 420px);
  }

  .map-focus {
    width: 38px;
    height: 38px;
  }
}
