:root {
  --bg: #1c1a17;
  --panel: #2b2722;
  --panel-border: #4a4238;
  --text: #d8d0c0;
  --muted: #8f8677;
  --accent: #c8a45a;
}
* { box-sizing: border-box; }
html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--text);
  font: 13px/1.4 "Segoe UI", system-ui, sans-serif;
  overflow: hidden;
}
#app {
  display: grid;
  grid-template-columns: 1fr 240px;
  height: 100vh;
  /* Full screen on a phone means the notch and the gesture bar are the
     page's problem: the viewport meta says cover the lot, and these give
     the borrowed edges back so no panel hides under hardware. They are
     zero on everything that has no insets, which is every desktop. */
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* Small screens are played by tapping, and a tap that has to wait to see
   whether it was a double tap is a tap that feels broken. Nothing here
   zooms — the world is drawn to fit — so the wait is pure cost. */
#game, #buttonsbody button, .paneltoggle {
  touch-action: manipulation;
}
/* The viewport unit follows the browser's own chrome as it hides and
   shows, where the browser supports it; 100vh is the fallback above. */
@supports (height: 100dvh) {
  #app { height: 100dvh; }
}
#gamewrap {
  display: flex; flex-direction: column;
  min-width: 0; min-height: 0;
  background: #000;
}
/* The canvas and anything laid over it. It is its own positioned box so an
   overlay can cover the view alone: the console below it and the bag and
   stats beside it stay where they are, and stay readable. */
#view {
  position: relative;
  flex: 1 1 0; min-height: 0;
  display: flex;
}
#game {
  /* flex-basis 0 so the backing store's own size never feeds back into layout: the
     renderer sizes it from this element, in device pixels, and letterboxes itself. */
  flex: 1 1 0;
  display: block;
  width: 100%; min-height: 0;
  cursor: crosshair;
  outline: none;
  background: #000;
}
#console {
  height: 150px; flex: 0 0 auto;
  display: flex; flex-direction: column;
  background: var(--panel); border-top: 2px solid var(--panel-border);
}
/* What the console is showing, one toggle per kind of message. A strip
   rather than a menu: which kinds are off is the sort of thing you want to
   see without asking, since a filter you have forgotten about looks exactly
   like a game that has stopped telling you things. */
#logfilters {
  flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 3px;
  padding: 3px 6px; border-bottom: 1px solid var(--panel-border);
}
#logfilters button {
  font: inherit; font-size: 10px; line-height: 1.4;
  padding: 0 5px; border-radius: 3px; cursor: pointer;
  border: 1px solid #3a352c; background: #221f18; color: var(--text);
  /* Each switch is named by the kind it hides, and is given that kind's
     own string to display rather than a label written out beside it, so
     the two can never drift. Capitalising is this stylesheet's business:
     the button reads as a word on a strip, and the same string at the head
     of a line reads as what the line is. */
  text-transform: capitalize;
}
#logfilters button[aria-pressed="true"] { border-color: #6a6250; background: #332f24; }
/* An off filter is legible but plainly off: dimmed and struck through, so
   a glance along the strip says what is missing from the log. */
#logfilters button[aria-pressed="false"] { color: #6d675c; text-decoration: line-through; }
#log {
  flex: 1; overflow-y: auto; padding: 4px 8px;
  font-family: ui-monospace, Consolas, monospace; font-size: 12px;
  white-space: pre-wrap; word-break: break-word;
}
/* What kind each line is, shown only while the Type switch on the strip is
   on. Every line carries its label whether or not it is being shown, so
   switching it on names the log you already have rather than starting a
   new one. Each kind gets a colour of its own, unlike the lines
   themselves — a blow and a shopkeeper are both plain white to read, and
   the whole point of the label is to tell apart what looks alike. It is
   padded to the longest kind so the words after it line up in a column. */
#log .kind { display: none; }
#log.types .kind {
  display: inline-block; width: 8ch; font-size: 11px;
}
#log .k-say { color: #f2d24a; }
#log .k-noise { color: #ff9a3c; }
#log .k-combat { color: #ff8a8a; }
#log .k-loot { color: #6ede6e; }
#log .k-level { color: #c9a2ff; }
#log .k-event { color: #6fb8e8; }
#log .k-server { color: #cfcfcf; }
#log .k-blocked { color: #d9863c; }
#log .k-debug { color: #8fa3b5; }
#log .k-error { color: #ff2d55; }
/* The arrival time on every line: quiet, so the words stay the loudest
   thing on the line. */
#log .ts { color: #7a746a; font-size: 11px; }
#log .say { color: #f0d060; }
#log .server { color: #dcdcdc; }
/* Blows land in the same plain white as anything else said to you: they
   are their own kind so the filters can turn them off, not because they
   should look different while they are on. */
#log .combat { color: #dcdcdc; }
/* World news — arrivals, departures, deaths, level-ups — reads apart from
   messages addressed to you. */
#log .event { color: #8fb8d8; font-style: italic; }
#log .loot { color: #7ed77e; }
#log .level { color: #ff8; font-weight: bold; }
#log .error { color: #f66; }
/* The damage sums, when they are asked for: quiet and monospaced, since
   they are arithmetic to be read rather than news to be told. */
#log .debug { color: #9aa8b4; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 11px; }
/* The way between levels is blocked: a state of the world rather than a
   mistake of yours, so it gets its own colour between the two. */
#log .blocked { color: #f09a5a; }
/* Noises — eating, and whatever else gets a sound — in Tibia's
   creature-sound orange, apart from the yellow of words. */
#log .noise { color: #ffae42; }
/* The minimap's level peek: the floor being shown, named, and small arrows
   to change it. The name goes to the left of the arrows and the whole lot
   is floated right, so the arrows keep their place while the name beside
   them changes length. */
#maplevelctl { float: right; }
/* The zoom sits to the right of the level arrows and left of nothing, so
   the two pairs read as one row of controls on the panel's title. */
#mapzoomctl { float: right; margin-left: 6px; }
#maplevelctl button, #mapzoomctl button {
  background: #201c17; color: var(--text); border: 1px solid var(--panel-border);
  padding: 0 6px; margin-left: 2px; cursor: pointer; font-size: 10px; line-height: 16px;
}
#maplevelctl button:hover, #mapzoomctl button:hover { border-color: var(--accent); }
/* At either end of the range the button is plainly spent rather than
   gone: a control that disappears takes the pair's shape with it. */
#mapzoomctl button:disabled { color: var(--muted); border-color: #2a251e; cursor: default; }
#mapzoomctl button:disabled:hover { border-color: #2a251e; }
#maplevel { color: var(--muted); margin-right: 6px; font-size: 11px; }
/* A floor that is not the one you are standing on wears the accent, since
   that is the thing worth noticing about it. */
#maplevel.peek { color: var(--accent); }
#log .own { color: #ffe9a0; }
#chat {
  border: 0; border-top: 1px solid var(--panel-border);
  background: #16140f; color: var(--text); padding: 6px 8px; font: inherit; outline: none;
}
#chat:focus { background: #221f18; }

#side {
  background: var(--panel); border-left: 2px solid var(--panel-border);
  overflow-y: auto; padding: 6px;
  display: flex; flex-direction: column; gap: 6px;
}
.panel {
  background: #201d19; border: 1px solid var(--panel-border);
  padding: 6px; border-radius: 3px;
}
.panel .title { font-weight: bold; color: var(--accent); margin-bottom: 4px; }
.panel .title small { color: var(--muted); font-weight: normal; }
/* The title is also the switch that folds the panel away. It is a real
   <button> — reached by Tab, announced as a button, pressed by Space —
   dressed back down to the title it replaced, so the panel's name is still
   the plainest thing on the row and nothing about it reads as a form
   control. What says which way the panel is, is the caret: a shape rather
   than a colour, so it survives a phone in sunlight and anybody who does
   not see the accent as a colour at all. Both carets take the same width,
   so folding a panel does not shuffle its name sideways.

   Everything here hangs off the button's own aria-expanded rather than off
   a class put on the panel beside it. That is one fact kept in one place:
   a panel that tells a screen reader it is collapsed cannot be sitting
   there open, because the attribute the reader is quoting is the same one
   that hides the body. */
.paneltoggle {
  display: inline-block; padding: 1px 4px 1px 0; margin: 0;
  background: none; border: 0; border-radius: 2px;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.paneltoggle::before { content: "\25be"; display: inline-block; width: 1em; color: var(--muted); }
.paneltoggle[aria-expanded="false"]::before { content: "\25b8"; }
.paneltoggle:hover::before, .paneltoggle:focus-visible::before { color: var(--accent); }
.panel:has(> .title > .paneltoggle[aria-expanded="false"]) > .panelbody { display: none; }
/* Folded, a panel is its name and nothing else. The other things that live
   on a title — the map's zoom and its level arrows, a panel's question
   mark, what the bag weighs — are all about the body underneath it, so
   left hanging over a folded panel they would be offering the zoom on a
   map that is not on the screen. */
.panel:has(> .title > .paneltoggle[aria-expanded="false"]) > .title > :not(.paneltoggle) { display: none; }
/* What the bag weighs, beside the panel's own name: a fact about the
   squares below rather than a stat of its own, so it is quiet. */
#invcn { color: var(--muted); font-weight: normal; font-size: 11px; }
.row { display: flex; justify-content: space-between; margin-top: 3px; }
.row b.hungry { color: #e66; }
/* The ping reads as what it costs you rather than as a number to admire:
   green while nothing notices, amber where a step starts to lag, red where
   a swing lands late. A dash — nothing to report — keeps the plain colour. */
.row b.good { color: #8ec98e; }
.row b.fair { color: #d8bd6a; }
.row b.bad { color: #e66; }
.bar {
  position: relative; height: 14px; background: #111; border: 1px solid #000;
  margin: 3px 0; overflow: hidden;
}
.bar > div { height: 100%; width: 0; transition: width .15s; }
.bar.hp > div { background: linear-gradient(#e33, #900); }
.bar.food > div { background: linear-gradient(#c98a3a, #7a4d18); }
.bar.food.empty > div { background: linear-gradient(#6a3030, #3a1a1a); }
/* Violet, and nothing else on the panel is: health is red, mana blue and
   food brown, and experience used to be a blue near enough to mana's that
   the two read as one fact at a glance. It is also the one bar that only
   ever climbs, which is worth seeing without reading the numbers. */
.bar.xp > div { background: linear-gradient(#b07ae8, #4c2a8c); }
/* Mana is Tibia's blue, and deliberately not the experience bar's: one is
   a pool that empties and fills all day, the other only ever climbs. */
.bar.mana > div { background: linear-gradient(#3aa0e8, #1a4a86); }
/* The healing a potion still owes you, drawn as the stretch of the health
   bar between where you are now and where the window will leave you. It is
   the same red washed out towards white rather than a colour of its own,
   because what it has to say is "this part of the bar is coming" — one
   fact about the health bar, not a second thing on the panel. Paler and
   never darker: a darker red is what the food bar wears when it is empty,
   and darkness on a health bar reads as something already lost. Being a
   wrung-out red it cannot be taken for the mana bar's blue or the food
   bar's orange-brown either; it is the only pale thing in the column.
   It is positioned rather than in flow so it can start where the solid
   fill ends, which is why the two never overlap and why a window that is
   not running — left and width meeting at the same point — draws nothing
   at all rather than being hidden by a rule that checks for it. */
#hpproj {
  position: absolute; top: 0; left: 0; height: 100%;
  background: linear-gradient(#f4a6a6, #b06060);
  transition: left .15s, width .15s;
}
.bar span {
  position: absolute; inset: 0; text-align: center; font-size: 11px; line-height: 14px;
  color: #fff; text-shadow: 0 0 2px #000, 0 0 2px #000;
}
/* Shown only while the player stands on safe ground. */
/* Statuses share one line. The row hands the whole remaining width to the
   value so it can be measured for overflow: the labels are shortened until
   they fit rather than wrapping or being cut off. */
.row.statuses { align-items: baseline; gap: 6px; }
#statuses {
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  display: flex; gap: 4px; justify-content: flex-end; white-space: nowrap;
}
#statuses .status {
  /* Never shrink. The line picks between writing the statuses out in full,
     as initials, or as icons by measuring whether they overflow, and a chip
     that squashes instead of overflowing hides that from the measurement:
     the row then reads as fitting while the words inside it are cut off. */
  flex: 0 0 auto;
  padding: 0 3px; border: 1px solid #3a4048; border-radius: 2px;
  font-size: 10px; font-weight: bold; letter-spacing: .5px; color: #b9c2cc;
}
#statuses .status.good { border-color: #4a6a8c; color: #9cc4ee; }
#statuses .status.warn { border-color: #8c7a3a; color: #e0c46a; }
#statuses .status.bad { border-color: #8c4a4a; color: #e88; }
#statuses .status.icon { border: 0; padding: 0; font-size: 12px; letter-spacing: 0; }
#statuses .none { color: #666; font-size: 11px; }

/* Skills: a name, a thin bar of progress to the next level, and the level. */
#skills .skill {
  display: grid; grid-template-columns: 5.2em 1fr 2em; align-items: center; gap: 6px;
  margin-top: 2px; font-size: 12px;
}
#skills .skill span { color: var(--muted); text-transform: capitalize; }
#skills .skill b { text-align: right; }

#skills .skillbar { height: 6px; background: #111; border: 1px solid #000; overflow: hidden; }
#skills .skillbar > div { height: 100%; width: 0; background: linear-gradient(#9c7, #463); transition: width .15s; }

#inv, #depot {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px;
}
#inv .slot, #depot .slot, #trade .slot {
  aspect-ratio: 1; background: #111; border: 1px solid #3a342c;
  display: flex; align-items: center; justify-content: center; position: relative;
  cursor: pointer;
}
/* A touch long-press is the right-click, so the browser's own answers to a
   held finger — iOS's magnifier and callout, text selection, double-tap
   zoom — must stand aside on everything that takes one. */
#game, #inv .slot, #depot .slot, #trade .slot {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}
/* What is on the table, marked on the bag: the outline in the trade's own
   blue, which nothing else on the panel wears, and the number offered in
   the corner the count leaves free, with an arrow to say it is going. */
#inv .slot.offered { border-color: #6fb8e8; box-shadow: inset 0 0 0 1px rgba(111, 184, 232, .5); }
#inv .slot .offer {
  position: absolute; left: 2px; top: 0; font-size: 10px; color: #9cd0f4; text-shadow: 0 0 2px #000;
}
/* The minimap wants the same hands off, and a touch-action of its own:
   dragging it pans the map, so the browser may not have the gesture. */
#minimap {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
#inv .slot:hover, #depot .slot:hover { border-color: var(--accent); }
/* The weapon and armor the server is actually fighting with. */
#inv .slot.equipped { border-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(200, 164, 90, .5); }
#inv .slot canvas, #trade .slot canvas { width: 32px; height: 32px; image-rendering: pixelated; }
#inv .slot .count, #depot .slot .count, #trade .slot .count {
  position: absolute; right: 2px; bottom: 0; font-size: 10px; color: #fff; text-shadow: 0 0 2px #000;
}
/* Dragged to look around, so the browser's own idea of what a drag on a
   canvas means — scrolling the panel, zooming the page — is called off.
   The pointer stays a pointer, because the commonest thing done here is
   still a click that walks you somewhere; the hand appears only once a
   drag is actually under way, which the script says by hanging .panning
   on the canvas. An open hand sitting there all the time advertised the
   rarer of the two gestures and hid the other one. */
#minimap {
  width: 100%; image-rendering: pixelated; border: 1px solid #000;
  cursor: pointer; touch-action: none;
}
#minimap.panning { cursor: grabbing; }

/* The row of buttons is laid out by a class rather than by the body's id,
   and that is not a matter of taste: the fold is a rule about .panelbody,
   and one id in a selector outranks any number of classes. A layout rule
   naming the body by its id therefore goes on laying the buttons out in a
   row under a title that says the panel is shut — which looks exactly
   like a click that did not register. Nothing that lays out a panel's
   body may name it by its id; a test says so. */
.buttonrow { display: flex; flex-wrap: wrap; gap: 4px; }
#buttonsbody button {
  flex: 1 1 auto;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
  padding: 5px 10px; font: inherit; font-weight: bold; border-radius: 2px; cursor: pointer;
}
#buttonsbody button:hover, #buttonsbody button:focus-visible { border-color: var(--accent); background: #221f18; }
/* PvP on is the state worth noticing, so the button goes red for it. */
#buttonsbody button.on { border-color: #8c4a4a; color: #e88; }
#buttonsbody button.on:hover, #buttonsbody button.on:focus-visible { border-color: #e88; }
/* Logging out is refused while something is after you; say so by feel. */
#buttonsbody button:disabled { color: var(--muted); cursor: not-allowed; opacity: .55; }
#buttonsbody button:disabled:hover { border-color: var(--panel-border); background: #16140f; }

#help, #invhelp, #maphelp, #scores, #changes, #slotmenu, #playermenu, #who, #login {
  position: fixed; inset: 0; background: rgba(0,0,0,.85);
  display: flex; align-items: center; justify-content: center;
}
#help[hidden], #invhelp[hidden], #maphelp[hidden], #scores[hidden], #changes[hidden], #slotmenu[hidden], #playermenu[hidden], #who[hidden], #login[hidden] { display: none; }
#counter[hidden], #trade[hidden], #tradeask[hidden] { display: none; }
#help, #invhelp, #maphelp, #scores, #changes, #slotmenu, #playermenu, #who { z-index: 10; }
/* A panel's own help: a question mark beside its title, quiet until it is
   wanted. It replaced a caption that had room for two clicks out of six
   and was in the way of the panel the rest of the time. */
.panelhelp {
  float: right; width: 16px; height: 16px; padding: 0; line-height: 14px;
  background: #201c17; color: var(--muted); border: 1px solid var(--panel-border);
  border-radius: 8px; font: inherit; font-size: 10px; cursor: pointer;
}
.panelhelp:hover { color: var(--accent); border-color: var(--accent); }
/* The boards from the login screen: a link under the form rather than a
   button competing with Enter Game, since it is a thing to read on the way
   in rather than a way in. */

/* What to do with one thing in the bag: the touchscreen's answer to the
   right-click, and the only way a phone can reach the whole pile. Its
   buttons are finger-sized rather than pointer-sized — a mis-tap here puts
   forty rocks on the floor — and the whole box sits in the middle of the
   screen rather than beside the slot, since a menu anchored to a square in
   the corner of a telephone has nowhere to open. */
/* The menu on a player is the same box: the same gesture opens it, on a
   person instead of a thing, and it should feel like the same menu. */
#slotmenubox, #playermenubox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 16px 18px; width: min(320px, calc(100vw - 32px)); max-height: 85vh; overflow-y: auto;
}
#slotmenubox h2, #playermenubox h2 { margin: 0 0 6px; color: var(--accent); font-size: 17px; letter-spacing: 1px; }
/* What the thing is. On a mouse this is the slot's tooltip; a finger
   cannot hover, so the one place a phone can read it is here. */
#slotmenubox p, #playermenubox p { margin: 0 0 12px; color: var(--muted); font-size: 12px; line-height: 1.4; }
#slotmenuacts, #playermenuacts { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
#slotmenubox button, #playermenubox button {
  font: inherit; font-size: 14px; padding: 10px 12px; width: 100%;
  border: 1px solid var(--panel-border); border-radius: 3px;
  background: #201c17; color: var(--text); cursor: pointer; text-align: left;
}
#slotmenubox button:hover, #slotmenubox button:focus-visible,
#playermenubox button:hover:not(:disabled), #playermenubox button:focus-visible:not(:disabled) { border-color: var(--accent); }
/* Letting go of a whole pile is the one action here worth a second's
   thought, so it is coloured like one rather than sitting in the row
   looking exactly like the button above it. Attacking a person is the
   same kind of button. */
#slotmenuacts button.all, #playermenuacts button.all { color: #e8a; border-color: #6a4a58; }
#slotmenuacts button.all:hover, #slotmenuacts button.all:focus-visible,
#playermenuacts button.all:hover:not(:disabled), #playermenuacts button.all:focus-visible:not(:disabled) { border-color: #e8a; }
#slotmenuclose, #playermenuclose { color: var(--muted); text-align: center; }
/* An action the rules do not allow just now is greyed rather than gone —
   Attack with PvP off — so the tooltip on it can say why, and the menu
   keeps its shape whoever it is opened on. */
#playermenuacts button:disabled { color: var(--muted); border-color: #2a251e; cursor: not-allowed; opacity: .6; }

#helpbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; max-width: 460px; max-height: 85vh; overflow-y: auto;
}
#helpbox h2 { margin: 0 0 10px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
/* Each section of the help has its own heading and its own key/value list,
   so the controls can be found without reading how the game works. */
#helpbox h3 {
  margin: 12px 0 6px; padding-bottom: 3px; border-bottom: 1px solid var(--panel-border);
  color: var(--accent); font-size: 14px; letter-spacing: 1px; text-transform: uppercase;
}
#helpbox h2 + h3 { margin-top: 0; }
#helpbox dl {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 14px;
  margin: 0; font-size: 12px;
}
#helpbox dt { color: var(--accent); white-space: nowrap; }
#helpbox dd { margin: 0; color: var(--muted); }
#helpclose, #scoresclose, #counterclose {
  display: block; width: 100%; margin-top: 14px;
  background: var(--accent); color: #221; border: 0; padding: 7px;
  font: inherit; font-weight: bold; cursor: pointer;
}
/* The highscores: one small table per board — level, then each skill —
   flowing side by side as the width allows. */
/* A stat whose value is a way in rather than just a number: the Online
   count opens the list of who is behind it. It has to read as the value it
   replaced — the panel is a column of facts and one of them suddenly
   looking like a button would be louder than it deserves — so it keeps the
   bold of every other value and says it is clickable by the underline and
   the hand, which only show when the pointer is on it. */
.rowbtn {
  font: inherit; font-weight: bold; color: var(--text);
  background: none; border: 0; padding: 0; cursor: pointer;
}
.rowbtn:hover, .rowbtn:focus-visible { color: var(--accent); text-decoration: underline; }

/* Who is in the world. It borrows the highscores' box and its table, being
   the same thing in miniature — a list of names with a number each — but
   it is not a ranking, so there is no rank column to put in front. */
#whobox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; min-width: 220px; max-width: 420px; max-height: 85vh; overflow-y: auto;
}
#whobox h2 { margin: 0 0 10px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#whobody table { border-collapse: collapse; font-size: 12px; width: 100%; }
#whobody td { padding: 2px 10px 2px 0; }
#whobody td.val { text-align: right; color: var(--muted); padding-right: 0; }
/* Your own row, picked out the way the highscores pick it out. */
#whobody tr.me td { color: var(--accent); }
#whobody p { color: var(--muted); font-size: 12px; margin: 0; }

#scoresbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; max-width: 640px; max-height: 85vh; overflow-y: auto;
}
#scoresbox h2 { margin: 0 0 10px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#scoresbody { display: flex; flex-wrap: wrap; gap: 14px; }
#scoresbody table { border-collapse: collapse; font-size: 12px; min-width: 150px; }
#scoresbody caption {
  color: var(--accent); font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
  text-align: left; padding-bottom: 4px; border-bottom: 1px solid var(--panel-border);
}
#scoresbody td { padding: 2px 6px; color: var(--muted); }
/* The changelog reads as a column of dated entries. Anything that landed
   while the player was away wears the accent down its edge, so what is new
   is found by scanning rather than by reading dates. */
#changesbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; max-width: 520px; max-height: 85vh; overflow-y: auto;
}
#changesbox h2 { margin: 0 0 10px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#changesbody .entry { padding: 0 0 10px 10px; border-left: 3px solid transparent; margin-bottom: 10px; }
#changesbody .entry.new { border-left-color: var(--accent); }
#changesbody .when { color: var(--muted); font-size: 11px; letter-spacing: 1px; }
#changesbody .entry.new .when::after {
  content: "NEW"; margin-left: 8px; color: var(--accent); font-weight: 700;
}
#changesbody h3 { margin: 2px 0 4px; color: var(--text); font-size: 14px; }
#changesbody p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
#changesbody .none { color: var(--muted); font-size: 12px; }
/* The button says how many are waiting, and stops saying so once they have
   been looked at. */
#changesbtn.unread { border-color: var(--accent); color: var(--accent); }
#scoresbody td.rank { color: var(--accent); text-align: right; }
#scoresbody td.val { text-align: right; color: var(--text); }
#scoresbody tr.me td { color: #ffe9a0; }
/* The collage of web/static/bg/ images behind the login form, darkened so
   the form stays readable over anything. */
#loginbg {
  position: absolute; inset: 0; z-index: -1; overflow: hidden;
  display: flex; flex-direction: column; gap: 2px;
  filter: brightness(0.4) saturate(0.9);
}
/* Rows are measured in script: each one fills the width exactly and every
   image in it keeps its own shape, so the cover here only swallows the odd
   rounding pixel. */
#loginbg .bgrow { display: flex; gap: 2px; flex: 0 0 auto; }
#loginbg img { height: 100%; flex: 0 0 auto; object-fit: cover; display: block; }

#loginform {
  background: var(--panel); border: 2px solid var(--panel-border); padding: 24px 28px;
  width: 320px; display: flex; flex-direction: column; gap: 10px; border-radius: 4px;
}
#loginform h1 { margin: 0; color: var(--accent); font-size: 26px; letter-spacing: 1px; }
#loginform p { margin: 0; color: var(--muted); font-size: 12px; }
#loginonline { color: var(--accent); min-height: 1em; }
#loginform label { display: flex; flex-direction: column; gap: 3px; font-size: 12px; color: var(--muted); }
#loginform input {
  background: #16140f; border: 1px solid var(--panel-border); color: var(--text);
  padding: 6px 8px; font: inherit; font-size: 14px;
}
#loginform button {
  background: var(--accent); color: #221; border: 0; padding: 8px; font: inherit; font-weight: bold;
  cursor: pointer; margin-top: 4px;
}
#loginerr { color: #f66; min-height: 1.4em; font-size: 12px; }
/* The boards from the login screen: a link under the form rather than a
   second button competing with Enter Game, since it is something to read
   on the way in rather than a way in. */
#loginform #loginscoreline { margin: 2px 0 0; text-align: center; }
#loginform #loginscores {
  background: none; border: 0; border-radius: 0; padding: 0; width: auto; cursor: pointer;
  color: var(--accent); font: inherit; font-size: 12px; font-weight: 400; text-decoration: underline;
}
#loginform #loginscores:hover:not(:disabled) { color: var(--text); }
#loginform #loginscores:disabled { color: var(--muted); cursor: default; text-decoration: none; }


@media (max-width: 720px) {
  #app { grid-template-columns: 1fr; grid-template-rows: 1fr auto; }
  #side { flex-direction: row; flex-wrap: wrap; border-left: 0; border-top: 2px solid var(--panel-border); max-height: 40vh; }
  .panel { flex: 1 1 45%; }
  /* Attack, Defense, Speed and Clank are arithmetic about your gear, and a
     phone has no room for them. What a small screen has to carry is what
     is happening to you now — the bars, the level, who is online, the
     ping, the statuses — and four rows of numbers you would only change by
     going to the bag pushed the statuses off the bottom of a panel that is
     allowed 40vh. They are exactly what the damage debug switch is for, so
     that switch brings them back rather than a setting of their own; on a
     wide screen there is room for everything and they never go at all.
     ui.ts hangs the class on the root and says nothing about width: which
     screens this applies to is a question for the stylesheet. */
  html:not(.debugon) #atkrow, html:not(.debugon) #defrow,
  html:not(.debugon) #speedrow, html:not(.debugon) #clankrow { display: none; }
}

/* A counter — the outfitter's stock or the smith's anvil: a row per line,
   each with what it is, what it does and what it costs. Anything out of
   reach of the purse is dimmed rather than hidden, since knowing what to
   save up for is half of what such a window is for. */
/* Over the view rather than over the page: the bag is what you are
   spending out of and the stats are what you are spending on, so both stay
   in sight. The wash is lighter than a full-page dialog's for the same
   reason — what is under it is still meant to be looked at. */
#counter {
  position: absolute; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.6);
}
#counterbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; max-width: 460px; max-height: calc(100% - 24px); overflow-y: auto;
}
#counterbox h2 { margin: 0 0 4px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#counternote { margin: 0 0 10px; color: var(--muted); font-size: 12px; }
#counterbody { display: flex; flex-direction: column; gap: 6px; }
#counterbody button {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  background: #2a2621; border: 1px solid var(--panel-border); border-radius: 3px;
  color: var(--text); font: inherit; font-size: 12px; padding: 6px 8px; cursor: pointer;
}
#counterbody button:hover:not(:disabled) { border-color: var(--accent); }
#counterbody button:disabled { opacity: .45; cursor: default; }
#counterbody canvas { image-rendering: pixelated; width: 32px; height: 32px; flex: none; }
#counterbody .what { flex: 1; }
#counterbody .what b { color: var(--text); font-weight: 600; }
#counterbody .what span { display: block; color: var(--muted); font-size: 11px; }
#counterbody .cost { color: var(--accent); font-weight: 700; white-space: nowrap; }

/* Mending the lot: an action rather than a line of stock, so it wears the
   accent like the Close button under it rather than the row styling above. */

/* Somebody wants to trade. A bar along the top of the view rather than a
   window in the middle of it: the only question is yes or no, and the
   game behind it is still being played — the asker is standing beside
   you, and looking at them is part of deciding. */
#tradeask {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 6;
  display: flex; align-items: center; gap: 8px; max-width: calc(100% - 20px);
  background: var(--panel); border: 2px solid var(--accent); border-radius: 4px;
  padding: 8px 12px; font-size: 13px; color: var(--text); white-space: nowrap;
}
#tradeask button {
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
  padding: 4px 10px; font: inherit; font-weight: bold; border-radius: 2px; cursor: pointer;
}
#tradeask button:hover, #tradeask button:focus-visible { border-color: var(--accent); }
#tradeaskno { color: var(--muted); font-weight: normal; }

/* The trade window: over the view alone, like the counter, and for a
   stronger reason — the bag beside the view is how things are put on the
   table, so it has to stay in reach the whole time. The wash is the
   counter's, since what is under it is still being looked at: the other
   party is standing there. */
#trade {
  position: absolute; inset: 0; z-index: 5;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.6);
}
#tradebox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; max-width: 460px; max-height: calc(100% - 24px); overflow-y: auto;
}
#tradebox h2 { margin: 0 0 4px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#tradenote { margin: 0 0 10px; color: var(--muted); font-size: 12px; }
/* Two columns, yours on the left where the bag's own habit of reading
   left to right puts what is yours first. Each is a grid of squares the
   bag's size, four across and as many rows as it takes. */
#tradebody { display: flex; gap: 16px; justify-content: center; }
#tradebody .offer { display: flex; flex-direction: column; gap: 4px; }
#tradebody .who { font-size: 12px; font-weight: bold; color: var(--text); white-space: nowrap; }
/* A side that has accepted is named in green, with the tick in the
   text: the tick is the fact and the colour is so it reads from across
   the room. */
#tradebody .who.accepted { color: #8ec98e; }
#tradebody .grid { display: grid; grid-template-columns: repeat(4, 38px); gap: 3px; }
#trade .slot { width: 38px; height: 38px; }
#trade .slot:hover { border-color: var(--accent); }
/* Offered, but the bag no longer holds that many: the square is called
   out in the moment before the offer is cut down to what is there. */
#trade .slot.short { border-color: #e66; border-style: dashed; }
/* The line that must not be missed: an accept that was given is gone. It
   is the one thing in the window drawn in red, and it stays until the
   player accepts again. */
#tradewarn { margin: 10px 0 0; color: #f66; font-size: 12px; font-weight: bold; }
#tradewarn[hidden] { display: none; }
#tradeacts { display: flex; gap: 8px; margin-top: 14px; }
#tradeacts button {
  flex: 1 1 auto; font: inherit; font-weight: bold; padding: 7px; border-radius: 2px; cursor: pointer;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
}
#tradeacts button:hover, #tradeacts button:focus-visible { border-color: var(--accent); }
/* Accepted reads as a button held down: filled, in the green the names
   over the columns use for the same fact. */
#tradeaccept[aria-pressed="true"] { background: #2e4a2e; border-color: #8ec98e; color: #c8f0c8; }
#tradecancel { color: var(--muted); }
