:root {
  --bg: #1c1a17;
  --panel: #2b2722;
  --panel-border: #4a4238;
  --text: #d8d0c0;
  --muted: #8f8677;
  --accent: #c8a45a;
}
* { box-sizing: border-box; }
/* Departure Mono, a pixel font, served with the game under the SIL Open
   Font License (fonts/DepartureMono-OFL.txt), for the switch in the ⋯ menu
   (2026-10-01: "Add a setting in the menu that uses departure mono as the
   font"). The page's fonts are three tokens, so the switch is one class on
   the root that redefines them: the UI's, the console's monospace and the
   area title's serif. The font is drawn on an eleven-pixel grid, so the
   base size goes to 11px with it, and it has no bold of its own, so none
   is faked: a smeared pixel font is worse than one weight. */
@font-face { font-family: "Departure Mono"; src: url("fonts/DepartureMono-Regular.woff2") format("woff2"); font-display: swap; }
:root {
  --gripw: 8px;
  /* The smallest thing anybody is asked to press, mouse or finger
     (2026-10-10: "The hit region for buttons must be 44x44 so the icons
     can be smaller if necessary or better"). Every button, square and
     mark is at least this big in both directions; what is drawn in it can
     be smaller, and mostly is. It is Apple's 44 points and WCAG's
     enhanced target size. */
  --hit: 44px;
  /* What is drawn in a square of --hit: the thirty-two pixel pictures, and
     less once the squares are too small to hold them inside their border. */
  --hitart: min(32px, calc(var(--hit) - 4px));
  /* The chat line's presses, and the way into it on a phone: the one
     place under --hit (2026-10-10: "make the chat buttons 38px"). The
     line is typed in far more than it is pressed, and every pixel of it
     is a pixel of log. */
  --chatpress: 38px;
  /* The other place under --hit: the marks over the world that are not a
     panel's icon (2026-10-10: "make the non panel icons in the game view
     region 38px"), the info button and the statuses in the top left, the
     floor chip, full screen and the ⋯ in the top right. They are pressed
     far less than they are looked past, and every pixel they take is
     world hidden. The panels' icons and the things lying in reach keep
     --hit. */
  --viewhit: 38px;
  --uifont: "Segoe UI", system-ui, sans-serif;
  --monofont: ui-monospace, Consolas, monospace;
  --titlefont: Georgia, "Times New Roman", serif;
}
/* Smaller presses, chosen from the menu's Button size (2026-10-12: "In the
   menu, add an option to set panel button size. 32, 38, 44"). 44 above is
   where everybody starts; these are a player's own trade of finger room for
   room on the screen. */
html.hit38 { --hit: 38px; }
html.hit32 { --hit: 32px; }
html.pixelfont {
  --uifont: "Departure Mono", ui-monospace, monospace;
  --monofont: "Departure Mono", ui-monospace, monospace;
  --titlefont: "Departure Mono", ui-monospace, monospace;
}
html.pixelfont body { font-size: 11px; }
html.pixelfont * { font-synthesis: none; }
html, body {
  margin: 0; height: 100%;
  background: var(--bg); color: var(--text);
  font: 13px/1.4 var(--uifont);
  overflow: hidden;
}
#app {
  display: grid;
  /* One column: the world's. There were panel areas either side of it,
     with a grip on each inside edge, until 2026-10-01, when the panels
     became icons along the bottom of the world ("Show each of the panels
     as an icon along the bottom (or top)"); see #panelbar. */
  grid-template-columns: 1fr;
  /* One row: the health and mana bars were a row of their own across the
     whole page until 2026-09-30, when they went into the world's column,
     directly over it ("Always show the health and mana bars directly above
     the main window"). */
  grid-template-rows: 1fr;
  /* Less the phone's keyboard while the chat line is open over it
     (fitAboveKeyboard in main.ts), so the line stands on the keyboard
     rather than under it; nothing anywhere else. */
  height: calc(100vh - var(--kbd, 0px));
  /* 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);
}
/* The page does not zoom (2026-10-15: "Lock the screen scale so players
   can't zoom the page"): pan-x pan-y lets a finger scroll what scrolls and
   takes the pinch and the double-tap zoom from the browser everywhere,
   since an element's touch-action can only narrow what its page allows.
   The viewport meta and zoomlock.ts say it to the browsers that do not
   listen here. */
html { touch-action: pan-x pan-y; }
/* 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, so the wait is
   called off wherever a tap is the gesture. */
#buttonsbody button, #spellsbody button, .paneltoggle, #viewtools button, #viewtoolsleft button, #grounddock button {
  touch-action: manipulation;
}
/* The world takes every gesture rather than only the double tap, because
   two fingers on it are a pinch that zooms the window (see main.ts) and
   the browser may not have them for a page zoom. The minimap used to own
   its drag the same way; the window owns the pinch now that it is the
   thing being zoomed. */
#game { touch-action: none; }
/* 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: calc(100dvh - var(--kbd, 0px)); }
}
/* The two numbers a fight is read off, directly over the world and as wide
   as it (they spanned the whole page, panel areas and all, until
   2026-09-30). They were the first two rows of a panel that
   folds, drags and scrolls, which is three ways to lose sight of your own
   health while something is biting you; here nothing can move them. The
   bars keep the classes and the ids they had, so what draws them did not
   change at all. */
#topbars {
  flex: none;
  display: flex; gap: 4px;
  padding: 3px 6px;
  background: var(--panel); border-bottom: 2px solid var(--panel-border);
}
#topbars .bar { flex: 1 1 0; margin: 0; height: 16px; }
#topbars .bar span { line-height: 16px; font-size: 12px; }
/* The page: the info panel down its left from top to bottom, and
   everything else in a column to its right (2026-10-06: "Always show the
   info panel tiled on the left at the full screen height"). It is the box
   the panel's width is a share of, and the one its grip stands in. On a
   narrow screen it is a column instead: see the narrow rules. */
#gamewrap {
  position: relative;
  display: flex; flex-direction: row;
  min-width: 0; min-height: 0;
  background: #000;
  /* How wide the info panel is: never more than half the page, however
     narrow the page becomes after it was dragged. main.ts sets it. */
  --drawerw: min(300px, 50%);
}
/* The bars, the world, the panels beside it and the log under them, to the
   right of the info panel. */
#gamecol {
  flex: 1 1 0; min-width: 0; min-height: 0;
  display: flex; flex-direction: column;
}
/* 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;
  /* Sized by the page and never by what is in it, so what is over the
     world can ask which way round the world is: see the floor chip. */
  container: view / size;
}
/* The stick under a held finger on the world (2026-10-12: "Allow long
   pressing then dragging to move like a joystick"): a ring where the finger
   came down, and a knob that follows it once it drags. Fixed to the page and
   placed by main.ts at the finger, so it is a child of the body and not of
   the view, whose size containment would otherwise be what it is fixed to. */
#stickring {
  position: fixed;
  width: 88px; height: 88px;
  margin: -44px 0 0 -44px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .55);
  background: rgba(0, 0, 0, .25);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
  pointer-events: none;
  z-index: 40;
}
#stickring[hidden] { display: none; }
#stickknob {
  position: absolute;
  left: 50%; top: 50%;
  width: 36px; height: 36px;
  margin: -18px 0 0 -18px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .45);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .4);
}
#stickring.walking #stickknob { background: var(--accent); }
#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;
}
/* The furniture over the world: the strip in the top right that is about
   the window — full screen, and the pair that looks a floor up and a
   floor down — and the strip in the bottom right that is about what you
   are carrying and what you are standing on. Asked for on 2026-09-22:
   "show the fullscreen/windowed button in the top right of the main view,
   with an appropriate glyph and tooltip", "move the up and down level map
   controls into the main window top right like the fullscreen/windowed
   control", "remove the inventory panel and make it a bag icon in the
   bottom right corner. clicking it pops up the inventory", and "when
   there's an item under the player, have an icon pop up next to the
   inventory icon in the bottom right with the item under the player
   displayed".

   Everything here is in service of one rule: this is furniture over
   somebody's game, so it takes as little of the world as it can and none
   of the clicks that were not meant for it. Each button is the size of
   its own drawing and no bigger, which is the whole of what stops it
   eating a click on the ground: the world's listeners are on the canvas,
   a click inside these twenty-six pixels never reaches it, and a click
   one pixel outside them is a click on the canvas like any other.

   The strips themselves are the one piece of wrapper on the page and
   they are wrapper for a reason: a control that comes and goes — the
   ground icon appears when you walk onto something and vanishes when you
   walk off — must not move the one beside it, and a row packed against
   an edge is what keeps the bag in the same pixels all session. The
   price of a row is the gaps between its buttons, which would be a
   transparent lid over a piece of ground somebody meant to click on, and
   that is paid for outright: the strip passes presses through and only
   the buttons catch them.

   They are dim at rest and light up when pointed at. The corner of a
   game is not a toolbar and a row of bright chips sitting there all
   session would be read as something happening; dim, they are plainly
   controls and plainly not part of the scene. They keep a solid-ish
   backing rather than being drawn straight onto the world so that they
   survive whatever is underneath them — the ground is sand in one place
   and stone in another, and a bare line drawing has no contrast it can
   rely on.

   z-index 4 puts them under the things that cover the whole view: the
   trade window and the death screen, each of which
   is over the world on purpose. A button poking up through the wash over
   a dead character is the one thing on that screen that had not noticed.

   What else the client draws near these corners was checked rather than
   assumed. The line written across the top of the view while you are
   looking at another floor used to be centred in the whole of it and used
   to run under the one button that was up there; with three marks at one
   end and the spell rail at the other it would lose both ends of its
   sentence at a phone's width, so it is centred in the band between them
   now, measuring their widths off these elements — see peekLabel in
   render.ts, which is why the strips are the size of what is in them and
   nothing more. The bar that asks
   whether you want to trade and the reconnect bar are both pinned to the
   top middle and both stand a layer higher, so they cross the strip and
   cover it for the few seconds they are up, which is the right way round:
   a question you have to answer outranks a control you might press. The
   bottom right had nothing in it at all — the three full-view overlays
   are each centred inside the view — which is half the reason the bag is
   there and the other half is that it is the corner a right hand rests
   in. */
#viewtools, #grounddock {
  position: absolute; z-index: 4;
  display: flex; align-items: flex-start; gap: 4px;
  /* The strip is scenery; only what is in it is a target. */
  pointer-events: none;
}
#viewtools { top: 6px; right: 6px; }
#viewtoolsleft {
  position: absolute; z-index: 6; top: 6px; left: 6px;
  display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: start; justify-items: start;
  gap: 4px; pointer-events: none;
  max-width: calc(100% - 160px);
}
/* The info button in the corner, the statuses in a row to its right, and
   the ride button under the info button (2026-10-13: "show the mount
   button below the info drawer button"). The statuses (their rule is below) span both rows, so
   a second row of them wraps in beside the horse rather than under it. */
#statsbtn { grid-column: 1; grid-row: 1; }
#ridebtn { grid-column: 1; grid-row: 2; }
/* The buttons rode the drawer's edge over the world while it was out
   (2026-09-29: "When the stats panel is shown, still have the stats button
   float in the main view, pressing it again closes the stats"). Since
   2026-10-06 the info panel is never over the world, so the corner stays
   in the corner. */
/* The statuses, as icons in a row to the right of the info button
   (2026-10-01: "show statuses as icons to the right of the info icon.
   remove the statuses field from the stats panel"), wrapping onto a second
   row rather than running under the floor chip. Each is the size of the
   corner's buttons and wears its status's colour on its border and its
   drawing, as the chips did; a status with a clock carries the clock in
   small figures along its foot. A press on one says what it is in the
   console, as its tooltip does to a mouse that waits. */
#statusicons { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
.statusicon {
  pointer-events: auto; position: relative;
  width: 26px; height: 26px; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 20, 15, .72); color: #b9c2cc;
  border: 1px solid #3a4048; border-radius: 3px; cursor: pointer;
  padding: 0; font: inherit;
}
.statusicon svg { width: 16px; height: 16px; display: block; }
.statusicon.good { border-color: #4a6a8c; color: #9cc4ee; }
.statusicon.warn { border-color: #8c7a3a; color: #e0c46a; }
.statusicon.bad { border-color: #8c4a4a; color: #e88; }
.statusicon .clock, #ridebtn .clock {
  position: absolute; right: 1px; bottom: 0; font-size: 9px; font-weight: bold; line-height: 1;
  color: #fff; text-shadow: 0 0 2px #000, 0 0 2px #000; pointer-events: none;
}
.statusicon:has(.clock) svg { margin-top: -5px; }
/* Statuses are facts about you, not things to press (2026-10-05: "Status
   icons should be differentiated somehow, they currently look like regular
   buttons"). The corner's button rules reached them as well, so each wore
   the same dark square as the info button beside it. Now each is a disc
   tinted with its own colour, ringed in it, with its clock on a pill
   under its foot, and does not light up under a pointer the way a button
   does. The id in the selector is what lets these win over the corner's
   rules for buttons. */
/* The press is the whole 38 (--viewhit) and the disc is drawn in the middle
   of it, at 26 and 30 on a touch screen as it was, so the discs stand
   apart by what their hit regions take rather than by a gap. */
#statusicons { gap: 0; }
#statusicons .statusicon {
  width: var(--viewhit); height: var(--viewhit); --disc: 26px;
  border: 0; background: none; box-shadow: none;
}
#statusicons .statusicon::before {
  content: ""; position: absolute; pointer-events: none;
  inset: calc((var(--viewhit) - var(--disc)) / 2); border-radius: 50%; box-sizing: border-box;
  border: 1.5px solid currentColor;
  background: color-mix(in srgb, currentColor 22%, rgba(10, 10, 12, .78));
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .55);
}
#statusicons .statusicon svg { position: relative; }
#statusicons .statusicon:hover, #statusicons .statusicon:focus-visible { background: none; }
#statusicons .statusicon:hover::before, #statusicons .statusicon:focus-visible::before {
  background: color-mix(in srgb, currentColor 32%, rgba(10, 10, 12, .82));
}
#statusicons .statusicon.good, #statusicons .statusicon.good:hover { color: #9cc4ee; }
#statusicons .statusicon.warn, #statusicons .statusicon.warn:hover { color: #e0c46a; }
#statusicons .statusicon.bad, #statusicons .statusicon.bad:hover { color: #e88; }
#statusicons .statusicon svg { width: 14px; height: 14px; }
#statusicons .statusicon:has(.clock) svg { margin-top: 0; }
#statusicons .statusicon .clock, #ridebtn .clock {
  left: 50%; right: auto; bottom: calc((var(--viewhit) - var(--disc)) / 2 - 7px); transform: translateX(-50%);
  padding: 0 3px; border-radius: 6px; background: rgba(0, 0, 0, .85);
  text-shadow: none;
}
/* The square's own foot, where a status has its disc's. */
#ridebtn .clock { bottom: -7px; }
/* The info panel: Stats, Skills and Players, stacked (one panel at a time
   at first, all three since 2026-09-29: "Skills, stats and players should
   all pop out together and stack vertically"). It slid out over the left
   of the world from a button in its corner until 2026-10-06: "Always show
   the info panel tiled on the left at the full screen height". Since then,
   wider than the narrow breakpoint, it is a column down the left of the
   page from top to bottom with everything else to its right, covering
   nothing. It was always out there for a few hours, and then went back to
   the button showing and hiding it ("Don't show it always in horizontal
   mode"). On a narrow screen it is a drawer the button brings out above
   the world: see the narrow rules. */
#drawer {
  flex: none;
  width: var(--drawerw); box-sizing: border-box; min-height: 0;
  padding: 6px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 6px;
  background: var(--panel); border-right: 2px solid var(--panel-border);
}
/* Shut, it takes no room at all, at every width, and nor does its grip
   (below): the info button shows and hides it (2026-10-06). */
#drawer:not(.open) { display: none; }
/* Stats, Skills and Players share the drawer's height and each scrolls
   its own rows inside its share (2026-10-17: "Make each panel
   scrollable"), its title staying put above them; the drawer scrolled the
   three as one before. A panel gives up room in proportion to its height,
   so a long list of players gives way before a short one. */
#drawer section { flex: 0 1 auto; min-height: 0; display: flex; flex-direction: column; }
#drawer section > .title { flex: none; }
#drawer section > .panelbody { flex: 0 1 auto; min-height: 0; }
#drawer section[hidden] { display: none; }
/* Skills and Players are boxed together only when held upright on a phone
   (see the narrow rules); everywhere else the box is not drawn and the
   three stack in the drawer as they did. */
#drawerside { display: contents; }
/* The panel's right edge, dragged for its width (2026-09-29: "It should
   be a resizable panel"): the same strip with a line in it as the
   columns' own grips, standing on the edge from the top of the page to
   the bottom. */
#drawergrip {
  position: absolute; z-index: 5; top: 0; bottom: 0; left: var(--drawerw);
  width: 10px; margin-left: -5px;
  display: flex; align-items: center; justify-content: center;
  cursor: col-resize; touch-action: none;
}
#drawergrip::after {
  content: ""; height: 34px; width: 3px; border-radius: 2px;
  background: var(--panel-border);
}
#drawergrip:hover::after, #drawergrip.dragging::after { background: var(--accent); }
#drawergrip:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
#drawer:not(.open) + #drawergrip { display: none; }
#statsbtn.on, #ridebtn.on { color: var(--accent); border-color: var(--accent); }
/* The Ride button while a fight keeps the horse from standing for you
   (2026-10-13: "show a timer for how long until a mount can be used, in
   the button"): the seconds left in small figures on a pill under its
   foot, the clock the statuses wear ("The countdown should use the same
   subscript thing like the light status"); see .statusicon .clock below.
   ui.ts ticks the number down. */
#ridebtn:not(.waiting) .clock { display: none; }
/* The info button stays at every width: on a wide screen it shows and
   hides the panel's column, as it does the drawer on a phone (2026-10-06:
   "Don't show it always in horizontal mode ... The button to hide it
   doesn't appear anymore"). The world is pushed over by the column rather
   than covered (2026-10-01: "the info drawer should not hover over the
   map it should push it in"), and the renderer, which sizes itself from
   the canvas, letterboxes the world into what is left. */
/* The floor chip: the floor being looked at, as one rounded piece
   (2026-09-29: "implement it, fading area title, sound in the menu"). The
   floor is lit in the accent while you are looking at one that is not your
   own, and a press on it then brings you back; see paintFloor. Look up and
   look down were its two ends until 2026-10-13 ("Only show the level up
   down buttons when the current level isn't the one the player is on.
   Otherwise just show the current level and expand it out to protrude into
   the main view with up/down when clicked"): now they hang under the
   floor, or out to its left down the right edge (below), over the world,
   while you are looking at another floor (.peek) or after a press on the
   floor (.open). The floor itself stays
   where it is either way, so nothing in the strip moves when they come and
   go. */
#floorchip {
  position: relative;
  pointer-events: auto;
  display: flex; align-items: center; height: calc(var(--viewhit) + 2px); box-sizing: border-box;
  background: rgba(22, 20, 15, .72);
  border: 1px solid var(--panel-border); border-radius: 19px;
}
#viewtools #floorchip button { width: var(--viewhit); height: 100%; border: 0; border-radius: 19px; background: transparent; }
#viewtools #floorchip button:hover, #viewtools #floorchip button:focus-visible { background: rgba(255, 255, 255, .06); }
#viewtools #floorchip svg { width: 12px; height: 12px; }
#viewtools #floorlabel {
  width: auto; min-width: var(--viewhit); padding: 0 2px;
  color: var(--text); font: 600 12px/1 var(--uifont); letter-spacing: .02em;
}
#floorsteps {
  position: absolute; top: calc(100% + 4px); right: -1px;
  display: none; flex-direction: column; width: calc(var(--viewhit) + 2px); box-sizing: border-box;
  background: rgba(22, 20, 15, .72);
  border: 1px solid var(--panel-border); border-radius: 19px;
}
#floorchip.open #floorsteps, #floorchip.peek #floorsteps { display: flex; }
#viewtools #floorsteps button { width: 100%; height: var(--viewhit); }
#floorchip.peek, #floorchip.peek #floorsteps { border-color: var(--accent); }
#viewtools #floorchip.peek #floorlabel { color: var(--accent); }
/* Down the right edge under the ⋯ while the world is wider than it is tall
   (2026-10-10: "when the game view is wider than it is tall, show the
   floorchip aligned vertically below the morebtn"), and the strip across
   the top a chip shorter. The ⋯ is the strip's last button, so its right
   edge is the strip's. Only where the column clears what stands in the
   bottom right corner: the chip comes down to 2 presses and 8px (it was 4
   and 12 while look up and look down were under it), so 250px leaves room for the right panels' one row of
   icons wider than the narrow breakpoint, and 350px for the three that go
   up the right edge on a phone. Shorter than that the chip stays in the
   strip, where it was. Down the edge, look up and look down come out to
   the left of the floor rather than under it, up then down, at the
   floor's own height (2026-10-13: "When the floor chip is on the right,
   clicking it should show the floor change buttons popping out to the
   left of it"). */
@container view (orientation: landscape) and (min-height: 250px) {
  @media (min-width: 721px) {
    #floorchip { position: absolute; top: calc(100% + 4px); right: 0; width: calc(var(--viewhit) + 2px); }
    #viewtools #floorlabel { width: 100%; min-width: 0; padding: 0; }
    #floorsteps { top: -1px; right: calc(100% + 4px); flex-direction: row; width: auto; height: calc(var(--viewhit) + 2px); }
    #viewtools #floorsteps button { width: var(--viewhit); height: 100%; }
  }
}
@container view (orientation: landscape) and (min-height: 350px) {
  #floorchip { position: absolute; top: calc(100% + 4px); right: 0; width: calc(var(--viewhit) + 2px); }
  #viewtools #floorlabel { width: 100%; min-width: 0; padding: 0; }
  #floorsteps { top: -1px; right: calc(100% + 4px); flex-direction: row; width: auto; height: calc(var(--viewhit) + 2px); }
  #viewtools #floorsteps button { width: var(--viewhit); height: 100%; }
}
/* The ⋯ and what it opens: full screen and sound, the marks set once and
   left, as a small list under the strip with a word beside each drawing. */
#moremenu {
  position: absolute; z-index: 7; top: calc(var(--viewhit) + 10px); right: 6px;
  display: flex; flex-direction: column; gap: 2px; padding: 4px;
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
  /* Never taller than the window: main.ts sets max-height from where the
     menu starts, and the rest scrolls under a finger or the wheel without
     dragging the page or the log behind it. */
  overflow-y: auto; overscroll-behavior: contain;
}
#moremenu[hidden] { display: none; }
/* A row keeps its size in a menu too long for the window and the menu
   scrolls instead: a flex column would otherwise squeeze the rule between
   the window's choices and the game's to nothing first. */
#moremenu > *, #popmenu > * { flex: none; }
#moremenu button {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px 6px 8px;
  background: transparent; color: var(--text); border: 0; border-radius: 4px;
  font: 13px var(--uifont); white-space: nowrap; cursor: pointer; text-align: left;
}
#moremenu button[hidden] { display: none; }
#moremenu button:hover, #moremenu button:focus-visible { background: rgba(200, 164, 90, .15); color: var(--accent); }
#moremenu svg { width: 16px; height: 16px; display: block; }
#moremenu button, #popmenu button { min-height: var(--hit); box-sizing: border-box; }
/* Button size: its name, then the three sizes in a row, the one in use
   underlined in the accent. */
#hitsize { display: flex; align-items: center; gap: 2px; }
#hitsize .hitlabel { display: flex; align-items: center; gap: 8px; flex: 1; padding: 0 8px; white-space: nowrap; }
#moremenu #hitsize button { min-width: var(--hit); padding: 6px 8px; justify-content: center; }
#hitsize button[aria-checked="true"] { color: var(--accent); box-shadow: inset 0 -2px 0 var(--accent); }
/* The game's buttons in the menu since 2026-09-30 ("Move debug toggle, help,
   logout, changes, highscores buttons all into the ... Button"): a rule
   between them and the window's two, the debug switch red while it is on,
   Changes in the accent while anything is waiting, and Log out greyed
   while you may not leave (a skull, or a fight under ten seconds old). The ⋯ itself is lit while changes wait,
   since the count on Changes is out of sight until it is opened. */
#moremenu .menusep { height: 1px; margin: 3px 4px; background: var(--panel-border); }
#moremenu button.on { color: #e88; }
#moremenu button.unread { color: var(--accent); }
#moremenu button:disabled { color: var(--muted); cursor: not-allowed; opacity: .55; background: transparent; }
#morebtn.unread { color: var(--accent); border-color: var(--accent); }
#morebtn.on { color: var(--accent); border-color: var(--accent); }
/* The small menu that pops up beside what was pressed (2026-10-08: "use a
   context menu that pops up over them, like we're supposed to have for
   message log interactions"): one of the world's people, a line of the log,
   the bag's icon. No backdrop, so the world shows and plays round it; it is
   placed by popmenu.ts, which keeps it inside the window. The ⋯ menu's
   look, since it is the same kind of thing. */
#popmenu {
  position: fixed; z-index: 35; left: 0; top: 0;
  display: flex; flex-direction: column; gap: 1px; padding: 4px;
  min-width: 140px; max-width: min(300px, calc(100vw - 12px));
  max-height: calc(100vh - 12px); max-height: calc(100dvh - 12px);
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
  outline: none;
}
#popmenu[hidden] { display: none; }
#popmenu .pophead {
  padding: 4px 8px 5px; margin-bottom: 2px; border-bottom: 1px solid var(--panel-border);
  color: var(--muted); font: 12px/1.35 var(--uifont); cursor: default;
}
#popmenu button {
  display: flex; align-items: center; gap: 6px; padding: 6px 12px 6px 8px;
  background: transparent; color: var(--text); border: 0; border-radius: 4px;
  font: 13px var(--uifont); text-align: left; cursor: pointer;
}
#popmenu button:hover:not(:disabled), #popmenu button:focus-visible, #popmenu button:focus { background: rgba(200, 164, 90, .15); color: var(--accent); outline: none; }
/* A quest on an NPC's menu (2026-10-05: "Make clicking an NPC provide
   options like starting quests, vendor, etc"): first, with its "!", and in
   the accent, so the errand stands out from the small talk under it. */
#popmenu button.quest { color: var(--accent); }
#popmenu button:disabled { color: var(--muted); cursor: not-allowed; opacity: .55; }
#popmenu .poptick { display: inline-block; width: 1em; text-align: center; color: var(--accent); }
/* A choice that is a thing (the Equipment panel's places): its picture,
   the size it is in the bag with how many in the corner, then its name.
   Its numbers wait under the name for a long press or a right-click. */
#popmenu { -webkit-touch-callout: none; user-select: none; }
#popmenu .popicon { position: relative; flex: none; width: 32px; height: 32px; }
#popmenu .popicon canvas { display: block; width: 32px; height: 32px; image-rendering: pixelated; }
#popmenu .popicon .count {
  position: absolute; right: -2px; bottom: -3px; font-size: 10px; color: #fff; text-shadow: 0 0 2px #000;
}
#popmenu .poplabel { display: flex; flex-direction: column; min-width: 0; }
#popmenu .popdetail { margin-top: 2px; color: var(--muted); font-size: 11px; line-height: 1.3; white-space: normal; }
#popmenu .popdetail[hidden] { display: none; }
#popmenu .popstats { color: var(--muted); font-size: 11px; line-height: 1.25; white-space: nowrap; }
/* What a thing is, when the menu is there only to say it: a long press on
   the Equipment panel's square. Words, not choices. */
#popmenu .popabout { padding: 3px 8px; color: var(--text); font: 12px/1.4 var(--uifont); cursor: default; }
#popmenu .popabout + .popabout { color: var(--muted); }
/* An area's name on arriving in it: large, over the top of the world, for
   a few seconds, then faded out. Scenery, never a target. */
#areatitle {
  position: absolute; z-index: 3; top: 16%; left: 50%; transform: translateX(-50%);
  max-width: 80%; text-align: center; pointer-events: none;
  color: #f2e6c8; text-shadow: 0 2px 6px #000, 0 0 2px #000;
  opacity: 0; transition: opacity .8s ease-in;
}
#areatitle.show { opacity: 1; transition: opacity .25s ease-out; }
#areatitle b { display: block; font: 700 26px/1.15 var(--titlefont); letter-spacing: .04em; }
#areatitle i { display: block; font: italic 13px var(--titlefont); opacity: .85; margin-top: 2px; }
/* The zoom's note: smaller than the area's name, and below the middle of
   the world, near where the eye already is while the ground grows or
   shrinks but clear of your own character, who is in the middle. */
#zoomtag {
  position: absolute; z-index: 3; top: 68%; left: 50%; transform: translate(-50%, -50%);
  pointer-events: none; white-space: nowrap;
  padding: 4px 10px; border-radius: 3px;
  background: rgba(22, 20, 15, .72); border: 1px solid var(--panel-border);
  color: #f2e6c8; font: 600 15px var(--titlefont); font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity .6s ease-in;
}
#zoomtag.show { opacity: 1; transition: opacity .15s ease-out; }
#grounddock { bottom: 6px; right: 6px; }
/* The ground at your feet shares the bottom right corner with the panels'
   icons since they came over the world (2026-10-01: "float the panelbar
   icons over the map"): it steps up over the right side's row of them when
   there are any, and on a narrow screen, where the shut panels' icons go up
   the right edge, it steps in beside them. */
#view:has(#panelbarright > .panelicon:not([hidden])) #grounddock { bottom: calc(var(--hit) + 12px); }
#viewtools button, #viewtoolsleft button, #grounddock button {
  pointer-events: auto;
  position: relative;
  width: var(--viewhit); height: var(--viewhit); padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 20, 15, .72); color: var(--muted);
  border: 1px solid var(--panel-border); border-radius: 3px;
  cursor: pointer;
  /* A button that is also held down must not select anything or raise the
     browser's own menu over itself: the hold is the tooltip, the same as
     on a spell. */
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
/* display: flex above beats the hidden attribute, and hidden is how three
   of these keep themselves off the screen: the full screen button where the
   browser has no full screen to give, the ground icon whenever there is
   nothing under your feet, and the ride button for somebody with no horse.
   Without this line the first of those gets a button that cannot work,
   which is worse than no button at all, and the others get an empty square
   in the corner for ever. */
#viewtools button[hidden], #viewtoolsleft button[hidden], #grounddock button[hidden] { display: none; }
#viewtools button:hover, #viewtools button:focus-visible,
#viewtoolsleft button:hover, #viewtoolsleft button:focus-visible,
#grounddock button:hover, #grounddock button:focus-visible {
  color: var(--accent); border-color: var(--accent); background: rgba(22, 20, 15, .92);
}
/* The drawings take the button's colour and nothing else decides their
   hue; all that is settled here is how big they are. See viewicons.ts. */
#viewtools svg, #viewtoolsleft svg, #grounddock svg { width: 16px; height: 16px; display: block; }
/* What is lying in reach, one icon a tile (2026-10-20, see showGround in
   ui.ts): the size of the other icons over the world, drawn from the same
   sprite the bag and the ground itself use, so the square in the corner
   and the thing on the floor are recognisably one object. They were one
   icon twice the size of the rest until then (2026-09-24), for the tile
   under you alone. The row runs along the foot of the world from the
   right edge when the world is taller than it is wide, and up its right
   edge from the bottom when it is wider, and wraps when it runs out of
   room rather than running into the strip at the top. Each is lit the
   way the depot's icon is beside a box (.panelicon.urgent below), since
   each is a thing beside you to be had for a press, and is the size of the
   bag's squares and the panels' icons, --hit, which follows the menu's
   Button size (2026-10-20: "They should be sized to the button size the
   user selected in the menu like everything else"). */
#grounddock button { width: var(--hit); height: var(--hit); }
#grounddock { flex-direction: row-reverse; flex-wrap: wrap-reverse; align-items: flex-end; max-width: calc(100% - 12px); }
@container view (orientation: landscape) {
  #grounddock { flex-direction: column-reverse; max-height: calc(100% - 2 * var(--viewhit) - 32px); max-width: none; }
}
#grounddock canvas { width: var(--hitart); height: var(--hitart); image-rendering: pixelated; }
/* How many of the thing lying there there are: a small number in the
   corner of the button, out of the way of the drawing under it. */
#grounddock .count {
  position: absolute; right: 2px; bottom: 0;
  font-size: 13px; line-height: 1; font-weight: bold;
  color: #fff; text-shadow: 0 0 2px #000, 0 0 2px #000;
}
/* The two readings at the top of the Inventory panel: how many different
   things the bag holds, and what it weighs against what this character
   can lift. They sat in a box in the corner of the world for a few hours
   on 2026-09-22 and are back on the panel's own line with the squares.

   A row of their own above the grid rather than beside the panel's name,
   because every other panel's title is one line of one thing and at a
   phone's width the two of them were measured spilling past the panel's
   edge over the stats beside it. The weight takes no colour however high
   it climbs: it is the limit nobody meets, a reading and not a warning. */
/* The spells, down the left edge of the world (asked for on 2026-09-22:
   "put the spells as icons along the left side of the screen").

   The rail is the full height of the view so that the icons in it have a
   height to wrap against, and it is scenery for the same reason the
   corner strips are: a full-height box down the left of the world would
   otherwise be a transparent wall you could not click through. Only the
   icons and the question mark take presses.

   It wraps into a second column rather than scrolling or clipping. A
   short view — somebody who has dragged the log up, or a telephone held
   upright — cannot show twelve icons in one column, and of the three
   things that can happen then, two are unacceptable: clipping loses
   spells with no sign that they are gone, and a scrollbar thirty pixels
   wide over the world is a control nobody will find. Wrapping costs
   another strip of the world's left edge in exactly the case where there
   was no room, and costs nothing at all the rest of the time, which is
   most of the time: at a desk the column never wraps. The character is in
   the middle of the view either way, so what a second column covers is
   the far left of what is around you and never you.

   z-index 4, with the corner strips, and under the three overlays that
   take the whole view for the same reason. */
/* The spells, laid out to the width the panel has, exactly as the squares
   are: auto-fill with a floor, so a narrow column puts four icons across
   and a wide one seven, and the rows follow. It was a rail down the left
   edge of the world for a few hours on 2026-09-22, with the icons in a
   wrapping column; back in a panel it is the ordinary rule, since a panel
   is a thing with a width and that is all this ever needed.

   The bag squares' floor, since 2026-10-09 ("reduce the size
   of the spell icons to match the inventory tile size"): a spell button is
   a square as a bag square is, its drawing and its cost fitted inside. It
   was 38, and 44 on a touch screen, with the button as tall as its
   contents, until then. */
/* The grid hangs on a class rather than on the body's own id, which is
   the trap the row of buttons in the Controls panel fell into first: the
   fold is a rule about .panelbody, one id outranks any number of classes,
   and a layout rule hung on a body's id goes on laying it out under a
   title that says the panel is shut. It looked for all the world like a
   click that had not registered. A test says so. */
/* The rows are --hit tall too (2026-10-17: "spell icons should not shrink
   to try to fit the panel"): a row left to size itself from a square
   button counted the button as only as tall as its drawing and cost, so
   down a side column too short for them all the rows squeezed to about
   31 and the squares overlapped one another, rather than the panel
   scrolling. */
.spellgrid {
  display: grid; grid-template-columns: repeat(auto-fill, var(--hit)); grid-auto-rows: var(--hit); gap: 3px;
}
#console {
  height: 150px; flex: 0 0 auto;
  display: flex; flex-direction: column;
  background: var(--panel); border-top: 2px solid var(--panel-border);
}
/* The chat line: the box you type in, and at the end of it the button
   that opens what the console is showing. The button is last in the row
   and takes no more room than its word, so the box has everything else —
   a filter list is opened a few times a session and typed beside
   constantly, and the one that is used more gets the space. */
#chatrow {
  flex: 0 0 auto; display: flex; align-items: stretch;
  border-top: 1px solid var(--panel-border);
  /* Kept out of a phone's rounded corners in full screen: see --cornerx
     under the touch-screen rules. Nought everywhere else. */
  padding-left: var(--cornerx, 0px); padding-right: var(--cornerx, 0px); padding-bottom: var(--cornery, 0px);
  background: var(--panel);
}
/* --chatpress tall, 38, its border aside. */
#chatrow { min-height: calc(var(--chatpress) + 1px); }
/* Folded away on a phone until asked for (foldChat in main.ts): shrunk to
   nothing and clipped rather than display: none, so the chat box can
   still take the focus, and the focus arriving is what opens it. The
   log's foot keeps clear of a phone's corners in its place (under
   #logwrap). */
#chatrow.folded {
  position: absolute; width: 1px; height: 1px; min-height: 0; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
#chatrow #chat { flex: 1; min-width: 0; border-top: 0; }
#filtersbtn {
  flex: 0 0 auto; font: inherit; font-size: 11px; cursor: pointer;
  padding: 0 10px; border: 0; border-left: 1px solid var(--panel-border);
  background: #1c1a14; color: var(--muted);
}
#filtersbtn:hover, #filtersbtn:focus-visible { background: #2a261d; color: var(--text); }
/* A filter you have forgotten about looks exactly like a game that has
   stopped telling you things, which is why the strip these came from was
   always on show. A menu cannot do that, so the button says the count
   instead — "Filters (2)" — and wears the accent while any kind is off:
   the fact that something is being hidden stays outside the menu, and
   only which kinds it is has to be asked for. */
#filtersbtn.muting { color: var(--accent); }
/* The two toggles on the chat line, Yell at its head and Types beside
   Filters: an icon each, on a button that stands out of the row while it is
   off and is pressed into it while it is on (2026-10-08: "it should look
   like a button that can be pressed in (toggled)"). Out is a light top
   edge and a dark bottom one; in is the same edges swapped, a shadow
   inside, and the accent on the icon. */
#chatrow .toggle {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: var(--chatpress); padding: 0; margin: 0; cursor: pointer;
  border: 1px solid; border-color: #5a5444 #14120d #14120d #5a5444; border-radius: 3px;
  background: linear-gradient(#2e2a20, #1f1c15); color: var(--muted);
}
#chatrow .toggle:hover, #chatrow .toggle:focus-visible { color: var(--text); }
#chatrow .toggle[aria-pressed="true"] {
  border-color: #0e0d09 #4a4538 #4a4538 #0e0d09;
  background: #15130e; color: var(--accent);
  box-shadow: inset 1px 2px 4px rgba(0, 0, 0, 0.7);
}
#chatrow .toggle[aria-pressed="true"] svg { transform: translateY(1px); }
/* While yelling, the line is typed as it will be heard. The placeholder
   is left as it is written. */
#chat.yelling { text-transform: uppercase; }
#chat.yelling::placeholder { text-transform: none; }
#filtersmenuacts button {
  /* 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 menu, and the same string at the head
     of a line reads as what the line is. */
  text-transform: capitalize;
}
#filtersmenuacts button[aria-pressed="true"] { border-color: #6a6250; background: #332f24; }
/* An off filter is legible but plainly off: dimmed and struck through, so
   a glance down the menu says what is missing from the log. */
#filtersmenuacts button[aria-pressed="false"] { color: #6d675c; text-decoration: line-through; }
/* How many lines of the kind the log holds, hidden ones included
   (2026-10-07: "in the chat filters menu, show how many messages exist in
   each category, included those that were filtered"): at the far end of
   the switch, quiet beside the name, with figures of one width so a count
   ticking up while the menu is open does not shuffle. Floated rather than
   laid out by flex because a float is not struck through with the name:
   an off kind's count is still a plain fact, and the one reason to look. */
#filtersmenuacts button .count {
  float: right; margin-left: 12px;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
#filtersmenuacts button[aria-pressed="false"] .count { color: #6d675c; }
/* The log's box, which it shares with the button back to its newest line
   (2026-10-07: "add a button to scroll the chat log to the very newest
   messages"). It takes the log's place in the console and the log fills
   it; it is a positioned box only so the button can stand in its corner. */
#logwrap {
  flex: 1; min-height: 0; position: relative;
  display: flex; flex-direction: column;
}
/* With the chat line folded away (above), the log's foot is the console's
   and keeps clear of a phone's corners in full screen. */
#console:has(#chatrow.folded) #logwrap { padding-bottom: var(--cornery, 0px); }
/* overflow-anchor: none because the log keeps the line you are reading in
   place itself (holdLogView in ui.ts), and does it in every browser. Left
   on, Chromium's own scroll anchoring would move the view for the oldest
   lines going off the top as well, and the two together move it twice. */
#log {
  flex: 1; min-height: 0; overflow-y: scroll; padding: 4px 8px;
  font-family: var(--monofont); font-size: 12px;
  white-space: pre-wrap; word-break: break-word;
  overflow-anchor: none;
  -webkit-touch-callout: none;
}
/* A held finger on a line of the log opens its menu, Copy message
   (2026-10-07, buildLogCopy in ui.ts), so on a touchscreen the log is not
   selectable: a hold would otherwise pick out a word and put the phone's
   own Copy bar over the menu. A mouse still selects lines as it always
   has, for Cmd+C. The line copied lights for a moment with a word in its
   corner, laid over the line so nothing in the log moves. */
@media (hover: none), (pointer: coarse) {
  #log { user-select: none; -webkit-user-select: none; }
}
#log > div { position: relative; }
#log > div.copied { background: rgba(126, 215, 126, .16); }
#log > div.copyfail { background: rgba(255, 102, 102, .16); }
/* The line whose menu is up (see popmenu.ts), so it is plain which one the
   menu beside it is about. */
#log > div.menued { background: rgba(200, 164, 90, .16); }
#log > div.copied::after, #log > div.copyfail::after {
  content: "Copied"; position: absolute; top: 0; right: 0;
  padding: 0 4px; border-radius: 2px;
  font-size: 11px; font-style: normal; font-weight: bold;
  background: #7ed77e; color: #1c1a14;
  pointer-events: none;
}
#log > div.copyfail::after { content: "Could not copy"; background: #f66; }
/* Back to the newest line: a small square in the bottom right corner of
   the log, the size and colours of the marks over the world, shown only
   while the log is scrolled back. It stands clear of the scrollbar, whose
   width ui.ts puts in --logbar, since a scrollbar is as wide as the
   system says and on a phone it is no width at all. The count in its
   corner is how many lines have come in since you scrolled back, on its
   left so a button at the edge of a phone does not push it off the glass. */
#logend {
  position: absolute; right: calc(var(--logbar, 0px) + 6px); bottom: 6px; z-index: 1;
  width: var(--hit); height: var(--hit); padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 20, 15, .85); color: var(--muted);
  border: 1px solid var(--panel-border); border-radius: 3px;
  cursor: pointer;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
#logend[hidden] { display: none; }
/* The way into the chat line while it is folded away on a phone
   (2026-10-10: "Implement the visible chat button"): the same small
   square as the way back to the newest line, --chatpress like the
   buttons on the line it opens, in the log's top right corner
   (2026-10-17: "The button to start a chat should be on the top right of
   the message log"), clear of the way back down in the bottom one. Gold while a
   filter is off, as Filters is: Filters is folded away with the line,
   and a forgotten filter looks like a game that has stopped talking. */
#chatopen {
  position: absolute; right: calc(var(--logbar, 0px) + 6px); top: 6px; z-index: 1;
  width: var(--chatpress); height: var(--chatpress); padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 20, 15, .85); color: var(--text);
  border: 1px solid var(--panel-border); border-radius: 3px;
  cursor: pointer;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
#chatopen[hidden] { display: none; }
#chatopen svg { display: block; }
#console:has(#filtersbtn.muting) #chatopen { color: var(--accent); border-color: var(--accent); }
#console:has(#chatrow.folded) #logend { bottom: calc(6px + var(--cornery, 0px)); }
#logend:hover, #logend:focus-visible { color: var(--accent); border-color: var(--accent); background: rgba(22, 20, 15, .95); }
#logend svg { width: 16px; height: 16px; display: block; }
#logend .count {
  position: absolute; top: -7px; left: -7px;
  min-width: 14px; height: 14px; padding: 0 3px; box-sizing: border-box; border-radius: 7px;
  font-size: 11px; line-height: 14px; font-weight: bold; text-align: center;
  background: var(--accent); color: #1c1a14;
}
#logend .count[hidden] { display: none; }
/* What kind each line is, shown only while the Types switch on the chat
   line is on. Every line carries its label whether or not it is being
   shown, so switching Types 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-private { color: #6cdcff; }
#log .k-noise { color: #ff8c00; }
#log .k-dealt { color: #ffb070; }
#log .k-received { color: #ff8a8a; }
#log .k-loot { color: #6ede6e; }
#log .k-level { color: #c9a2ff; }
#log .k-event { color: #6fb8e8; }
#log .k-answer { color: #ffffff; }
#log .k-server { color: #cfcfcf; }
#log .k-blocked { color: #d9863c; }
#log .k-spells { color: #b58cff; }
#log .k-debug { color: #8fa3b5; }
#log .k-error { color: #ff2d55; }
#log .k-critical { color: #ff3b30; }
/* 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; }
/* An answer to something you asked — a look, a bearing — is white in the
   log because it is white over your head: the same sentence arrives in two
   places at once and should read as one thing arriving, not two. */
#log .answer { color: #ffffff; }
/* Blows land in the same plain white as anything else said to you: they
   are their own kinds so the filters can turn them off — what you deal and
   what you take, apart, since 2026-09-27 — not because they should look
   different while they are on. */
#log .dealt, #log .received { 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: var(--monofont); 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, a rat in the dark, whatever else gets a sound — in the
   creature-sound orange, apart from the yellow of words.

   Dark orange, asked for on 2026-09-21: "show the noises in dark orange".
   It was #ffae42, which is a light amber: beside the say yellow at #f0d060
   it read as a second shade of the same thing rather than as a colour of
   its own, which is the whole job the colour has to do now that creatures
   sound off on their own. #ff8c00 is CSS `darkorange` exactly, and the
   value was picked by measuring rather than by eye. Against the console's
   own background — --panel, #2b2722 — the amber stood at 8.0:1 and this
   stands at 6.4:1; two darker oranges were held against it and both were
   worse buys, #e07000 at 4.6:1, which is on the 4.5:1 floor for text
   somebody is expected to read, and #cc6600 at 3.9:1, which is under it.
   So: plainly darker and plainly more orange than what it replaces, with
   half again the contrast the standard asks for still in hand.

   The kind label above wears the same value rather than a shade near it.
   The label and the line are the one channel named twice, and two oranges
   a hair apart is a difference a reader has to work out is not meaningful. */
#log .noise { color: #ff8c00; }
/* The server telling everybody it is going down: red, bold, and never
   filtered (see UNFILTERED in ui.ts). The same red is drawn over the
   player's own head, so the line reads as one thing arriving twice. */
#log .critical { color: #ff3b30; font-weight: bold; }
#log .own { color: #ffe9a0; }
/* Private messages (/tell) in light blue, as Tibia has them, and your own
   half paler, as your own says are. */
#log .private { color: #6cdcff; }
#log .private.own { color: #b8ecff; }
/* The words of a spell going off, anybody's: SPELL_COLOR in game.ts, the
   colour they are drawn in over the caster's head too. */
#log .spells.spell { color: #c9a7ff; }
#chat {
  border: 0;
  background: #16140f; color: var(--text); padding: 6px 8px; font: inherit; outline: none;
}
#chat:focus { background: #221f18; }

/* The console's other edge: the strip you drag to say how much of the
   screen is log and how much is world. It is the top of the console rather
   than the bottom for the plain reason that the bottom is the bottom of the
   window — the boundary worth moving is the one between the view and the
   log, and that is this one. CSS `resize: vertical` was the cheap answer
   and it grips the wrong corner.

   It has to look like something that can be taken hold of, so it wears a
   short bar across the middle of it and an ns-resize cursor, and the bar
   takes the accent under the pointer and while it is being dragged — the
   same gold the panel titles' hand answers to. A keyboard gets the focus
   ring, drawn inside the strip since the strip is only a few pixels tall
   and an outline hung outside it would be drawn over the world.

   touch-action: none is what makes a finger work here, and it is why the
   drag needs no long press the way a panel's does. The column of panels
   scrolls under a thumb, so a touch there is ambiguous from its first pixel
   and the hold is how the ambiguity is settled; this strip scrolls nothing
   and nothing else wants a vertical drag on it, so there is nothing to
   settle and the press can mean the drag at once. What the browser would
   otherwise do with the gesture — pan the page, or bounce it — is what this
   line refuses.

   And a finger is wider than a hairline, so a coarse pointer gets twice the
   strip to aim at. The bar drawn across it stays the same size: what grew
   is the target, not the furniture. */
#loggrip {
  flex: 0 0 auto; height: 8px;
  display: flex; align-items: center; justify-content: center;
  background: #221f18;
  cursor: ns-resize;
  touch-action: none;
}
#loggrip::after {
  content: ""; width: 34px; height: 2px; border-radius: 1px;
  background: var(--panel-border);
}
#loggrip:hover::after, #loggrip.dragging::after { background: var(--accent); }
#loggrip:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* What a screen with no pointer gets. A finger is wider than a hairline
   and there is no hover on it to find anything with, so a target it has to
   hit is bigger and a control it has to notice is brighter — and both cost
   a piece of the world, which is why they are here and not at every width.
   The marks in the corners of the view sit where a thumb rests while the
   other hand walks, and a pointer-sized square there is a mis-tap on the
   ground behind it; the spells down the left edge are the same argument
   with twelve targets instead of five.

   There is one of these blocks in this stylesheet and everything about a
   touchscreen belongs in it. It is kept whole rather than split per
   control because these are all the same decision about the same kind of
   screen, and each one of them is a piece of somebody's world given up:
   they are easier to weigh against each other in one place than in six,
   and a second block written above this one would be read as the whole of
   what a phone gets. */
@media (pointer: coarse) {
  #loggrip { height: 16px; }
  /* And the info panel's right edge, a column's grip on a phone held
     sideways, gets the same: at 10px a thumb missed it (2026-10-13: "Make
     the drawer resizable"). The narrow rules lay it across instead. */
  #drawergrip { width: 16px; margin-left: -8px; }
  /* The way back to the newest line, finger-sized, like the marks over
     the world. */
  #logend { color: var(--text); }
  #logend svg { width: 20px; height: 20px; }
  /* The rounded corners of a phone's glass (2026-10-04: "In full screen
     portrait mode on mobile, the bottom of the interface is being pressed
     into the rounded corners of the screen, in this case the chat input
     field and filters button"). The safe-area insets the body pads by are
     the notch and the system bars; no phone reports its corners as one,
     and in full screen the gesture bar goes, so the bottom inset falls to
     nothing and the chat line's two ends sat in the curve. In full screen
     on a touch screen the chat line comes in from both sides and up from
     the bottom by what a phone's corner takes at that height: about
     eighteen pixels in from the side a dozen up, for corners of the
     forty-odd pixel radius phones have. What the body already pads at the
     bottom counts towards the dozen. */
  html:fullscreen { --cornerx: 18px; --cornery: max(0px, calc(12px - env(safe-area-inset-bottom, 0px))); }
  #viewtools button, #viewtoolsleft button, #grounddock button { color: var(--text); }
  #statusicons .statusicon { --disc: 30px; }
  #statusicons .statusicon svg { width: 16px; height: 16px; }
  #viewtools svg, #grounddock svg { width: 20px; height: 20px; }
  #viewtools #floorchip svg { width: 16px; height: 16px; }
  #viewtools #floorlabel { font-size: 14px; }
  #moremenu button { padding: 10px 14px 10px 10px; font-size: 15px; }
  #popmenu button { padding: 10px 14px 10px 10px; font-size: 15px; }
  #grounddock .count { font-size: 14px; }
  /* A name on the Players panel is a press for the player menu, and a
     twelve-pixel line of text is a mis-tap on the name above it. */
  #players .prow { padding: 5px 3px; }
}

/* The log keeps its scrollbar gutter whether or not there is anything to
   scroll to, so its width is one width (Ben, 2026-09-19: "always show the
   scrollbar on the panels, to avoid having it appear and disappear when
   things resize"). The panel areas it was also written for went on
   2026-10-01. */
/* The panels' icons. Wider than the narrow breakpoint they float over the
   bottom of the world, the left panels' icons in its bottom left corner and
   the right panels' in its bottom right (2026-10-01: "on wider screens, the
   panels stack horizontally from the left or right, with the icons along
   the bottom (justified left or right depending on whether panels will
   show on that side)", and then "float the panelbar icons over the map":
   they were a strip between the world and the log until then). The bar
   takes no presses, only its icons do, so the world between the two groups
   is still the world. On a narrow screen there is no bar: an open panel's
   icon stands beside the panel, and the shut ones go up the right edge of
   the world from its bottom (#panelfloat). An icon is lit while its panel
   is open. */
#panelbar {
  position: absolute; z-index: 4; left: 6px; right: 6px; bottom: 6px;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  pointer-events: none;
}
#panelbarleft, #panelbarright { display: flex; align-items: center; gap: 4px; min-height: var(--hit); }
/* The right side's icons read from the right edge, as its panels do. */
#panelbarright { flex-direction: row-reverse; }
.panelicon { pointer-events: auto; }
/* Each icon is an inventory square (2026-10-07: "Fix the panel icons to
   be 32x32 like the inventory tiles"): a box of --hit, border and all,
   as the bag's and the equipment's squares are, with a thirty-two pixel
   drawing in it, the size of the pictures in theirs. The box was
   thirty-eight until 2026-10-10, when every press went to 44. It
   was a thirty-two pixel box round a twenty pixel drawing ("make panel
   icons 32x32 like other buttons", the same day), which read as smaller
   than the squares it stands beside. It is that size at every width and
   under a finger as under a mouse. The drawing is the sixteen-pixel
   box drawn at twice its size, so it stays sharp. What stands beside or over the icons allows for the size: the
   ground at your feet steps up and in by it (#grounddock above and in the
   narrow rules), a panel under the world keeps a gutter of it at its right
   for its icon, and a panel beside the world leaves room for it under its
   contents. */
.panelicon {
  flex: none; width: var(--hit); height: var(--hit); padding: 0; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  background: rgba(22, 20, 15, .72); color: var(--muted);
  border: 1px solid var(--panel-border); border-radius: 3px;
  cursor: pointer; touch-action: none;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  position: relative;
}
.panelicon[hidden] { display: none; }
.panelicon:hover, .panelicon:focus-visible { color: var(--text); border-color: var(--accent); }
.panelicon.on { color: var(--accent); border-color: var(--accent); background: #221f18; box-shadow: inset 0 -2px 0 var(--accent); }
.panelicon.dragging { opacity: .6; outline: 1px dashed var(--accent); cursor: grabbing; }
.panelicon svg { width: var(--hitart); height: var(--hitart); display: block; }
/* An icon calling for attention (2026-10-08: "don't automatically open
   the depot when standing next to it. show the panelicon for it with an
   urgent appearance of some kind so it's obvious"): the Depot's, from
   arriving beside a box until its panel is opened or you step away (see
   urgenticon.ts). The drawing and border go to the accent, a ring of it
   pulses out round the square, and a dot sits in its top corner, so it
   reads at thirty-eight pixels over the world, in the bar and up the edge
   of a phone's. The ring is a shadow and the dot is inside the square's
   own corner, so neither takes room or is cut off at the edge of the
   world. Whoever asks for less motion gets the ring held still instead.
   The things within reach (#grounddock) glow and pulse the same but have
   no dot (2026-10-20: "Remove it for the ground icons"): they come
   several in a row, where a dot on each adds nothing. */
.panelicon.urgent, #grounddock button.urgent {
  color: var(--accent); border-color: var(--accent); background: rgba(58, 46, 22, .9);
  animation: panelurgent 1.2s ease-in-out infinite;
}
.panelicon.urgent::after {
  content: ""; position: absolute; top: 2px; right: 2px; padding: 4px;
  border-radius: 50%; background: #f0c060; box-shadow: 0 0 0 1px #16140f;
  pointer-events: none;
}
@keyframes panelurgent {
  0%, 100% { box-shadow: 0 0 0 1px var(--accent), 0 0 4px 1px rgba(200, 164, 90, .5); }
  50% { box-shadow: 0 0 0 3px var(--accent), 0 0 12px 4px rgba(240, 192, 96, .75); }
}
@media (prefers-reduced-motion: reduce) {
  .panelicon.urgent, #grounddock button.urgent { animation: none; box-shadow: 0 0 0 2px var(--accent), 0 0 8px 2px rgba(240, 192, 96, .6); }
}
/* How heavy the bag is, on the bag's icon (2026-10-07: "Can we show the
   inventory encumbrance "fill" on the icon and show more detail by
   hovering?", and "don't show the numbers in the panel anymore"). A wash
   rising from the icon's foot as far up it as the bag is full, --fill
   from 0 to 1, set by main.ts (paintBagLoad) with data-load. It is behind
   the drawing — the drawing is positioned too and comes after it, with a
   dark rim so it stands off an amber wash in the amber of a lit icon — and
   translucent, so the bag reads through it lit or not, and its top edge is
   a brighter line so the level reads at a glance. Muted while there is
   room; amber from four fifths of the way (bagweight.ts, HEAVY_AT); and
   past the line red, hatched, full to the top and the border red with it,
   so over is told by more than a colour. On a lit icon it stands on the
   accent's underline rather than over it. Placed by its edges, with no
   size of its own, so nothing here sizes the icon. */
.bagfill {
  position: absolute; left: 0; right: 0; bottom: 0; top: calc((1 - var(--fill, 0)) * 100%);
  border-radius: 0 0 2px 2px; pointer-events: none;
  background: rgba(143, 134, 119, .28); box-shadow: inset 0 1px 0 rgba(216, 208, 192, .45);
}
.bagfill + svg { position: relative; filter: drop-shadow(0 0 1px rgba(0, 0, 0, .85)); }
.panelicon.on > .bagfill { bottom: 2px; top: calc((1 - var(--fill, 0)) * (100% - 2px)); }
.panelicon[data-load="heavy"] > .bagfill { background: rgba(224, 160, 48, .34); box-shadow: inset 0 1px 0 #e0a030; }
.panelicon[data-load="over"] > .bagfill {
  background: repeating-linear-gradient(135deg, rgba(210, 60, 44, .55) 0 4px, rgba(210, 60, 44, .3) 4px 8px);
  box-shadow: none;
}
.panelicon[data-load="over"], .panelicon[data-load="over"]:hover { border-color: #d23c2c; }
#panelfloat { display: none; }
/* The row the world stands in, with the panels that push it in beside it
   or under it (2026-10-01): see #worldrow in index.html.

   Wider than the narrow breakpoint it is a grid of three columns and two
   rows: the left panels' row down its whole height, the world with its
   bars over the log in the middle, and the right panels' row down its
   whole height, so the log is tiled under the world and as wide as it, and
   the panels run beside both (2026-10-07: "the message log tile under the
   main window always", and of the two ways shown, "tile the chat log under
   the main window only per option A"). It ran across the bottom of the
   world and both panel rows until then. main.ts (placeLog) puts the log in
   this row; on a narrow screen it is under the shelf, outside the row.

   Each side's column is as wide as the panels open in it (2026-10-17:
   "the size of the panels should be fixed since they always scroll. so we
   should be able to automatically fit the panelcol/panelshelf"): each
   panel is one column that scrolls down, so it is the same width however
   full it is, and only a panel opening or shutting moves the world's edge.
   From 2026-10-08 ("make a fixed space for panels and scroll within it to
   prevent the map jarring when you move away from for example a vendor or
   depot") until then it was a width the player dragged, and what did not
   fit scrolled sideways: back then a full bag grew a second column. A side
   is never more than 45% of the row, 45cqw since the row is the container
   its units are a share of, and scrolls sideways past that. A side with
   nothing open in it has no box (.panelcol:not(.hasopen)), so its column
   takes nothing. */
#worldrow {
  flex: 1 1 0; min-height: 0;
  display: grid; position: relative; container-type: inline-size;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: minmax(0, 1fr) auto;
}
#panelcolleft { grid-column: 1; grid-row: 1 / 3; }
#worldmid { grid-column: 2; grid-row: 1; }
#panelcolright { grid-column: 3; grid-row: 1 / 3; }
#console { grid-column: 2; grid-row: 2; }
#worldmid { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
/* The shelf: the open panels stacked under the world on a narrow screen
   (below); wider than that they stand in the side rows instead and it is
   empty. */
#panelshelf { display: none; }
/* No padding (2026-10-09: "remove the padding for every panel, maybe
   combine the separation"): the contents run to the panel's border, and
   what stands between two panels is the one gap of the column, shelf or
   drawer they are in. It was 6px inside every panel as well as the 6px
   between them, so two panels' contents were three gaps apart. */
.panel {
  background: #201d19; border: 1px solid var(--panel-border);
  padding: 0; border-radius: 3px;
  /* Never wider than the share it is given. A flex item's default
     min-width is auto, which is its content's minimum — so on a phone,
     where the panels are a wrapping row of 45% each, one long line
     inside a panel used to push it out to the full column and drop the
     panel beside it onto the next row. That is what three statuses did
     to the stats panel (Ben, 2026-09-19: "The stats panel seems to
     taking the whole column while the extra statuses are present ...
     force the status row to never increase the width of the panel").
     With this, the width is the layout's to decide and the content's to
     wrap into. */
  min-width: 0;
}
/* The side rows the open panels stand in, wider than the narrow
   breakpoint: the left panels side by side from the left edge, the right
   ones from the right edge, each pushing the world in (2026-10-01: "on
   wider screens, the panels stack horizontally from the left or right").
   A row with nothing open in it takes no room. A row is the full height of
   the world and the log under it together, and as wide as its panels up
   to 45% of the row (see #worldrow): past that the panels that do not fit
   across it are scrolled to, sideways, by its scrollbar or the mouse wheel
   (main.ts). */
.panelcol {
  flex: none; display: flex; flex-direction: row; align-items: flex-start; gap: 6px;
  padding: 6px; overflow-x: auto; overflow-y: hidden; box-sizing: border-box;
  scrollbar-width: thin;
  background: var(--panel);
}
#panelcolleft { order: -1; border-right: 2px solid var(--panel-border); width: max-content; max-width: 45cqw; }
#panelcolright { order: 1; flex-direction: row-reverse; border-left: 2px solid var(--panel-border); width: max-content; max-width: 45cqw; }
.panelcol:not(.hasopen) { display: none; }
/* Each panel in a side row lays its contents down one column and is only
   as wide as that column (2026-10-01: "When the panels are on the left or
   right, align the contents vertically to minimise horizontal size"): a
   side row takes its width from the world. The squares go one under
   another, as do the spells' palette and the Controls' buttons, each
   button its drawing then its word. An empty bag or an empty spell list
   takes the width of its note rather than setting it one word to a line.

   What does not fit down the row's height scrolls inside the panel, each
   panel on its own (2026-10-17: "Make each panel scrollable"). Until then
   a panel never scrolled (2026-10-01: "instead of scrolling panels, wrap
   onto a new column or row, depending on the orientation"): what did not
   fit went on in a new column beside it, which made a full bag on a short
   window as wide as several panels. The gutter for the scrollbar is kept
   whether or not it is needed, so a panel does not widen, and push its
   neighbours along, the moment one more thing makes it scroll. */
.panelcol > .panel { flex: none; width: max-content; max-height: 100%; overflow: hidden; box-sizing: border-box; }
.panelcol #inv, .panelcol #depot, .panelcol #vendor, .panelcol #equip, .panelcol #spellsbody, .panelcol #buttonsbody {
  display: grid; grid-auto-flow: row; grid-template-columns: max-content;
}
/* The spells by their body's id, as the bag's are, since 2026-10-05 ("Raise
   the minimum width for spell buttons"): the rule above names #spellsbody,
   and an id outranks a class, so a .spellgrid here was overruled and the
   spells stood as narrow as their drawings, 26px beside the bag's 38. */
.panelcol #inv, .panelcol #depot, .panelcol #vendor, .panelcol #equip, .panelcol #spellsbody { grid-template-columns: var(--hit); }
.panelcol #inv:has(> .none) { grid-template-columns: max-content; }
/* Beside the world the empty spell list's one line runs down the panel
   rather than across it, so the panel is a square wide as it is with
   spells in it (2026-10-17: "my comment about 1 line for the spells was
   assuming the panel was horizontal. for vertical panel (landscape
   screen), something else should be done"): across, it made the panel
   three hundred pixels wide; before that it was a box four lines tall. */
.panelcol #spellsbody .none { writing-mode: vertical-rl; justify-self: center; padding: 6px 4px; }
/* Its row is as tall as the line, not the --hit of a row of spells. */
.panelcol #spellsbody:has(> .none) { grid-auto-rows: auto; }
.panelcol #buttonsbody button { flex: none; justify-content: flex-start; }
.panelcol > .panel:not(.open) { display: none; }
/* An open panel in a side row wears its icon, as one under the world wears
   it beside it (2026-10-01: "the icons should be inline with their contents
   in the wide view just like for the narrow view"), at its foot: the panel
   stands the full height of its row, its contents run down from its top,
   and the icon is at its foot (2026-10-07: "When I said shown
   them at the bottom in horizontal screen, I meant at the bottom of the
   panel, vertically aligning the contents of the panel from top to
   bottom"). It was floated into the top left corner, over the contents,
   until then. So every open panel's icon is along the bottom of the
   screen, at the foot of its own panel. The gap keeps the icon at least
   four pixels under the contents when they reach down that far, and the
   contents scroll above it rather than pushing it off the bottom. */
.panelcol > .panel.open:not([hidden]) { align-self: stretch; display: flex; flex-direction: column; gap: 4px; }
.panelcol > .panel > .panelbody { flex: 0 1 auto; min-height: 0; }
/* Every panel scrolls what it cannot show (2026-10-17: "Make each panel
   scrollable"): down the side, along its row in the shelf under the
   world on a narrow screen (the narrow rules), and in the info drawer.
   The body scrolls and nothing else, so a
   panel's title and its icon stay where they are, and the wheel or a
   finger at the end of one panel's list does not run on into the shelf,
   the drawer or the page behind it. */
.panelcol > .panel > .panelbody, #panelshelf > .panel > .panelbody, #drawer .panel > .panelbody {
  overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-gutter: stable;
}
.panelcol > .panel > .panelicon { margin-top: auto; }
/* The icon stands in the middle of its panel's width (2026-10-17: "center
   the panel icon in the panel when there's a scrollbar"). The body keeps a
   gutter for its scrollbar at its right, so an icon at the panel's left
   edge sat off to one side of a bag or equipment column no wider than it,
   by the width of the scrollbar. */
.panelcol > .panel > .panelicon { align-self: center; }
/* The title is a row: the toggle at the left, and whatever else a panel
   keeps on its title — the map's arrows and its zoom — packed against the
   right. The toggle's auto margin is the rule that does the packing: it
   takes all the free space, so the rest sit at the far end in the order
   they are written. Nothing on the row shrinks: the map's controls on a
   phone-width panel used to fold their own buttons over each other to
   fit, so an item that does not fit goes whole onto a second line, and
   the row's flex-end puts that line at the right too. Nothing on a title
   is a caption any more; what a panel has to explain is in its help
   dialog, opened from the main help since the question mark in the
   title's corner went on 2026-10-10.

   The row is also what a panel is dragged by (main.ts), so it shows a
   hand. `touch-action: pan-y` rather than `none`: a finger that lands
   here is far more often scrolling the column than moving a panel, so
   the browser keeps the vertical pan and a drag is only taken after a
   long press, which main.ts then holds by cancelling the touch's moves.
   No text is selectable on it, since a mouse drag across it would
   otherwise be selecting the name as it went. */
.panel .title {
  display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 3px;
  font-weight: bold; color: var(--accent); margin-bottom: 4px;
  user-select: none; -webkit-user-select: none;
}
.panel .title > * { flex: none; }
.panel .title > .paneltoggle { margin-right: auto; }
/* 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: inherit;
}
.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, what the bag weighs — are controls over 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.

   Dragging needs nothing kept
   on the row at all, since the title itself and the panel's edge are
   what a panel is taken hold of by, and a folded panel has both. */
.panel:has(> .title > .paneltoggle[aria-expanded="false"]) > .title > :not(.paneltoggle) { display: none; }
/* What the bag weighs, at the right of the row: a
   fact about the squares below rather than a stat of its own, so it is
   quiet. */
/* A stat row: the name on the left, the value on the right, and always a
   gap between them (2026-10-21: "add a forced spacing to prevent this kind
   of thing happening", Sight's "3 dark / 10 light / wraith" having run into
   its name). The name never shrinks or wraps; a value too long for what is
   left wraps inside its own column, right-aligned, under itself. */
.row { display: flex; justify-content: space-between; column-gap: 0.75em; margin-top: 3px; }
.row > span:first-child { flex: none; }
.row > b { min-width: 0; text-align: right; }
.row b.hungry { color: #e66; }
/* The Attack row's second range, a spear's against large and huge
   creatures: a line of its own under the first, right-aligned across the
   whole row rather than under the number. */
#atkrow { flex-wrap: wrap; }
#atkbig { flex-basis: 100%; text-align: right; }
#atkbig[hidden] { display: none; }
/* 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; }
/* Speed is behind the debug switch at every width.
   Everything else on this panel is a thing you watch: the bars move, the
   statuses come and go, the ping wanders. Speed does not. It is a number
   you could work out from your level and then never look at again, since
   nothing in the game changes it but levelling up — and a line that never
   moves is a line the eye stops reading, which costs the lines around it
   the attention it is not using. The tooltip has the same number in tiles a
   second for anybody who wants it.
   The pool bar is behind the switch at every width as well (2026-09-19:
   "Don't show the pool, attack, or defence unless debug is enabled"): it
   is the arithmetic of a fight, which is exactly what the switch is for
   showing. Attack and Defence went behind it on the same day and came out
   again on 2026-09-23 ("show attack and defense without debug enabled"):
   they are what your gear and your training come to, the two numbers a
   player changing weapon or shield wants to see move, and they are shown
   at every width with nothing hiding them. Armor is the third of those
   since 2026-10-07 ("show armor in info panel without requiring debug
   mode to be on"): it kept a narrow-screen rule of its own until then,
   and is now on the panel at every width, debug or not.

   Clank was behind this rule from 2026-09-29 ("hide the clank field in
   stats panel unless debug is enabled") and came out again on 2026-10-08
   ("don't require debug on to show clank stat"): like Armor it is what
   your gear comes to, and the number to watch when choosing between a
   loud piece and a quiet one. It is shown at every width.

   The meal bar joined them on 2026-09-24 ("don't show the 'mm:ss of this
   meal' field/bar unless debug is enabled"): how long the stomach has left
   is bookkeeping the bag does for you, since it eats the next food by
   itself, and the Food row below it — how long everything you carry will
   last — is the number worth watching.

   HP regen and Mana regen joined it on 2026-10-07 ("show hp and mana regen
   rate when debug is on"): the rates the bars refill at are arithmetic
   behind the bars rather than something to watch. */
html:not(.debugon) #speedrow, html:not(.debugon) #mealrow, html:not(.debugon) #hpregenrow, html:not(.debugon) #manaregenrow { display: none; }
.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 defence pool, in steel: it is the shield and the parry, and it sits
   under the food bar because it is the last of the things that run down. */
/* 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;
}
/* The mana bar's own, for a mana draught paying out (2026-10-05: "Mana
   potions should restore over time like health potions do"): the same
   pale stretch, in the bar's blue. */
#manaproj {
  position: absolute; top: 0; left: 0; height: 100%;
  background: linear-gradient(#a6d4f4, #5a86b0);
  transition: left .15s, width .15s;
}
/* What a running dose of poison will still take (2026-10-27: "show the
   projected part of health lost to poison as a green component of the
   health bar"), laid over the end of the red fill in the poison's own
   green, the colour its beats land in. Over the fill rather than past it,
   because it is health you still have and are going to lose; with no dose
   it is nothing wide, the way #hpproj is. */
#hppoison {
  position: absolute; top: 0; left: 0; height: 100%;
  background: linear-gradient(#a6ec86, #4f9a2e);
  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. */

/* Skills: a name and the level, over the progress to the next level filling
   the row from the left behind them, the way experience is drawn (asked for
   on 2026-09-27: "do the skill progress bars in the background of the skill
   like how xp is done"). It was a thin bar between the name and the level,
   which needed a column of its own and, for a moment that day, a minimum
   width below which it was left out; filling the whole row, it is as wide
   as the panel however narrow the panel is. */
#skills .skill {
  position: relative; display: grid; grid-template-columns: 1fr 2em; align-items: center;
  margin-top: 2px; padding: 0 4px; font-size: 12px; line-height: 16px;
  background: #111; border: 1px solid #000; overflow: hidden;
  /* Read and hovered for its tooltip, never typed into or selected, so the
     pointer over the name is the arrow rather than the text cursor
     (2026-10-01: "when hovering over skills, don't show the text cursor
     over the name of the skill"). */
  cursor: default; user-select: none; -webkit-user-select: none;
}
#skills .skillbar { position: absolute; inset: 0; }
#skills .skillbar > div { height: 100%; width: 0; background: linear-gradient(#6f9a55, #2f4a22); transition: width .15s; }
#skills .skill span, #skills .skill b { position: relative; color: #fff; text-shadow: 0 0 2px #000, 0 0 2px #000; }
#skills .skill span { text-transform: capitalize; }
#skills .skill b { text-align: right; white-space: nowrap; }
/* What the items in use add, after the level earned (2026-10-08: "when
   skills are modified by items, show the adjustment in the skills panel"):
   a pale blue, upright, and red for a piece that takes levels away. The
   level's column grows on a row that has one. */
#skills .skill.boosted { grid-template-columns: 1fr auto; }
/* The same forced gap as a stat row, so a long skill name never touches
   its level. */
#skills .skill { column-gap: 6px; }
#skills .skill b .boost { font-style: normal; color: #8cc8ff; }
#skills .skill b .boost.less { color: #ff8a80; }

/* Players (2026-09-27: "show a panel with online players and their levels,
   and skull status"): a name, the skull and its clock when there is one,
   and the level, one row each. The name gives way first, cut short with an
   ellipsis, because the clock and the level are what the panel is read for
   and a column this narrow cannot always hold all three whole. Somebody
   else's row is a button, for the menu, and is dressed as a line of text
   that lights up under the pointer rather than as a button; your own is
   picked out in the accent the Who list uses. */
#players { display: flex; flex-direction: column; gap: 1px; font-size: 12px; }
#players .prow {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto auto auto; align-items: center; column-gap: 0;
  width: 100%; padding: 1px 3px; border: 0; border-radius: 2px;
  background: none; color: var(--text); font: inherit; text-align: left;
}
#players button.prow { cursor: pointer; }
/* Somebody else's name is underlined, the way a link is, so it reads as
   something to click or hold (2026-10-15: "Make it obvious that you can
   click or long press the names in the player list", then "Just underline
   the names"). */
#players button.prow .pname { text-decoration: underline; text-underline-offset: 2px; }
#players button.prow:hover, #players button.prow:focus-visible { background: #221f18; color: var(--accent); }
#players .prow.me { color: var(--accent); }
#players .pname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#players .pskull { display: inline-flex; align-items: center; gap: 3px; color: #e88; font-variant-numeric: tabular-nums; }
#players .pskull[hidden] { display: none; }
#players .pskull img { width: 12px; height: 12px; }
#players .plevel { color: var(--muted); text-align: right; }
/* What they are playing on (2026-10-06), a smaller line under the name
   across the row. */
#players .pplat {
  display: inline-flex; align-items: center; gap: 2px;
  color: var(--muted); font-size: 10px; white-space: nowrap;
}
#players .pplat img { width: 12px; height: 12px; display: block; }
/* The space between the cells is each cell's own left margin rather than
   the grid's gap, because a gap is laid between every two columns whether
   or not anything is in them: the shut eye's column, empty on every row
   but yours, would have put a second gap after every other name, and a
   skull's column with no skull in it one more before the level. An empty
   cell takes no margin and so no room (2026-10-07). */
#players .prow > * + * { margin-left: 6px; }
#players .prow > .phide:empty { margin-left: 0; }
/* The shut eye on your own row while you are off everybody else's list
   (2026-10-07: "if a character is hidden from the player list, show an
   icon on the player list so they know they are hidden"), in the row's
   accent. */
#players .phide { display: inline-flex; align-items: center; }
#players .phide svg { width: 12px; height: 12px; display: block; }

/* Five squares across, which is what the bag has always been. In the depot
   panel the columns still share the panel's width; in the bag, which is
   its own box over the world now rather than a panel in a column, they are
   the squares' own size, so the box is as wide as five squares and not as
   wide as whatever it happens to be in. */
/* The squares fit the width they are given rather than a number of them
   being written down here (asked for on 2026-09-22: "with the panels
   wrapping their contents to fit, like the inventory not needing to be
   5x5"). auto-fill with a floor of 34px is the whole of it: a narrow
   column lays four across and a wide one seven, the rows follow, and the
   same grid is right in a panel, in the box in the corner of the world,
   and at whatever width somebody has just dragged their column to.

   A floor and not a fixed size, because a square smaller than about a
   thumb is a square nothing can be dropped out of on a telephone; and
   1fr as the ceiling so the last column is not a ragged gutter down one
   side. What it costs is that the number of columns is no longer
   something the code can assume — nothing does, since the squares have
   always been a flat list the grid arranges. */
#inv, #depot, #vendor {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--hit), 1fr)); gap: 3px;
}
/* The Equipment panel (2026-10-06: "Add a panel for equipped items"): the
   bag's squares, a place to each. An empty place says which it is, in the
   quiet colour; one the player took off on purpose is dashed, and the off
   hand under a two-handed weapon is dimmed. A piece chosen by hand wears the
   accent along its foot, the way the bag outlines what is in use. */
#equip { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--hit), 1fr)); gap: 3px; }
#equip .wornslot { aspect-ratio: 1; background: #111; border: 1px solid #3a342c; padding: 0; color: var(--muted); font: inherit; cursor: pointer;
  display: flex; align-items: center; justify-content: center; position: relative; -webkit-touch-callout: none; user-select: none; }
#equip .wornslot:hover, #equip .wornslot:focus-visible { border-color: var(--accent); }
#equip .wornslot .placename { font-size: 9px; line-height: 1.05; text-align: center; overflow-wrap: anywhere; padding: 0 1px; }
#equip .wornslot.pick-chosen { box-shadow: inset 0 -2px 0 var(--accent); }
#equip .wornslot.pick-off { border-style: dashed; }
#equip .wornslot.busy { opacity: .45; }
/* A bow's arrows or a crossbow's bolts in the off hand, with how many are
   left; greyed with none (2026-10-08). */
#equip .wornslot.busy.ammo { opacity: 1; }
#equip .wornslot.ammo.empty canvas { opacity: .35; filter: grayscale(1); }

#inv .slot, #depot .slot, #vendor .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, #vendor .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. */
/* An empty bag says so across the whole panel rather than as one square's
   worth of text, since there are no empty squares to show it by. */
#inv .none { grid-column: 1 / -1; color: var(--muted); font-size: 12px; }
/* And an empty depot, which draws only what is stored since 2026-10-05. */
#depot .none { grid-column: 1 / -1; color: var(--muted); font-size: 12px; }
#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;
}
#inv .slot:hover, #depot .slot:hover, #vendor .slot:hover { border-color: var(--accent); }
/* What a lot costs, in the corner the count leaves free, in the gold of
   the purse it comes out of; and a lot the purse cannot reach, dimmed. */
#vendor .slot .price {
  position: absolute; left: 2px; top: 0; font-size: 10px; color: #f0c84a; text-shadow: 0 0 2px #000;
}
#vendor .slot.dear { opacity: .45; }
/* 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 { image-rendering: pixelated; }
/* Inside the squares whatever size they are (the menu's Button size). */
#inv .slot canvas, #depot .slot canvas, #vendor .slot canvas, #trade .slot canvas, #equip .wornslot canvas { width: var(--hitart); height: var(--hitart); }
#inv .slot .count, #depot .slot .count, #vendor .slot .count, #trade .slot .count, #equip .wornslot .count {
  position: absolute; right: 2px; bottom: 0; font-size: 10px; color: #fff; text-shadow: 0 0 2px #000;
}
/* 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; }
/* Each control wears a drawing in front of its word (2026-10-01: "Add icons
   to the controls"), in the button's own colour, so it goes red and grey
   with it. */
#buttonsbody button { display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
#buttonsbody button svg { width: 14px; height: 14px; flex: none; }
/* A red stance 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; }
/* The stance's hand in the stance's colour (2026-10-08: "make our pvp
   stances white, yellow or red"): a white hand, a yellow hand, a red fist,
   on the Controls button and on the Stance row of the stats. */
#pvpbtn.stance-white svg, #stance.stance-white { color: #f4f1e8; }
#pvpbtn.stance-yellow svg, #stance.stance-yellow { color: #e8c547; }
#pvpbtn.stance-red svg, #stance.stance-red { color: #e85050; }
#autostance.auto-none { color: #9a9a9a; }
#autostance.auto-aware { color: #e8c547; }
#autostance.auto-all { color: #f4f1e8; }
/* Logging out is refused within ten seconds of a fight; 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; }

/* The spells. Built the same way as the controls above — the same body
   class laying them out, the same dark button in the accent colour — so
   that a row of things to press looks like a row of things to press
   wherever on the side of the page it is.

   Four across, and each button a picture with its cost under it (asked
   for on 2026-09-22: "Use the latest revision of the spell svgs for the
   spell panel. Don't keep the names alongside, put that in the tooltip.
   On mobile make that appear with a long press"). They were one to a
   line before, because "Find person" does not share a line with anything
   at this width; with the name gone into the hover there is nothing left
   to need a line, and twelve names down the side of the page was a
   column a third the height of the window.

   Four is what the width gives. The side column is 240 wide, which loses
   6 of padding either side and the scrollbar gutter it always keeps, and
   then the panel's own border and padding, leaving a little under 200
   for the row; four buttons and three 4-pixel gaps puts each at about 46
   across, which is a comfortable finger and leaves the 16-pixel glyph
   room to breathe. Five would be 36, which is under the size a thumb can
   be trusted to hit; three would waste a third of the row and make the
   twelve spells four rows deep instead of three.

   The basis is a quarter of the row less its share of the gaps, so four
   sit exactly across it, and it neither grows nor shrinks: a character
   who can hold one spell gets one small button in the corner rather than
   one enormous one across the whole panel, and the square a spell lives
   in does not move as the list grows underneath it.

   The layout is hung on a class of its own rather than on the body's id,
   for the reason written over .buttonrow above: one id outranks any number
   of classes, so a display named by the body's id goes on laying the
   buttons out under a title that says the panel is shut. The three state
   colours below may name the id because none of them sets a display.

   No text selection and no callout, because every button here is now
   something to hold down: a long press that highlighted the cost, or
   raised the browser's own menu, would be the gesture being answered by
   the wrong thing. */
.spellbtn {
  flex: 0 0 calc(25% - 3px); min-width: 0; aspect-ratio: 1; box-sizing: border-box;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
  padding: 0 2px; font: inherit; font-weight: bold; border-radius: 2px; cursor: pointer;
  text-align: center;
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
.spellbtn:hover, .spellbtn:focus-visible { border-color: var(--accent); background: #221f18; }
/* A spell that is running: what is left of it, as a share of a whole
   casting, filled in behind the picture in the button's own colour and
   draining from the right (2026-09-29: "when lasting spell effects are
   enabled, show the duration remaining as part of the button for the spell
   (like a background progress gradient)"). ui.ts sets --left from the
   server's whole seconds, and the width eases across each second, so it
   drains smoothly rather than in steps. The fill sits under the picture
   and the cost, and lets clicks through to the button. */
.spellbtn { position: relative; overflow: hidden; }
.spellbtn > * { position: relative; }
.spellbtn.lasting::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: calc(var(--left, 0) * 100%);
  background: linear-gradient(to right, color-mix(in srgb, currentColor 40%, transparent), color-mix(in srgb, currentColor 15%, transparent));
  transition: width 1s linear;
  pointer-events: none;
}
/* A spell whose cooldown group is still cooling (2026-10-07: "spells should
   have a cooldown"): dimmed, with a shade over it that sweeps off from the
   top as the wait runs out. ui.ts sets the whole cooldown's length and a
   negative delay for however much of it has already gone, so the sweep
   ends when the group is ready whenever the state arrived. */
.spellbtn.cooling > * { opacity: .45; }
.spellbtn.cooling::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(0, 0, 0, .5);
  transform-origin: bottom;
  animation: spellcool var(--cool-ms, 2000ms) linear var(--cool-delay, 0ms) forwards;
  pointer-events: none;
}
@keyframes spellcool { from { transform: scaleY(1); } to { transform: scaleY(0); } }
/* The drawing. It has no colour of its own and takes the button's, which
   is the whole reason the glyphs are cut the way they are: the three
   colours below are the panel's actual job, and an icon in its own hue
   would be a second thing saying something about a spell that the server
   never said. currentColor is set on the svg in spellicons.ts; all that is
   decided here is how big it is. */
.spellbtn svg { width: 18px; height: 18px; flex: none; display: block; }
/* A spell whose icon this bundle has no drawing for. The name goes on the
   face of the button instead, small and wrapped, since a button with
   nothing on it is the one outcome worth ruling out. Long words break
   rather than push the button wider, because the width here is the grid's
   and not the content's. */
.spellbtn .spellname { font-size: 9px; font-weight: normal; line-height: 1.1; overflow-wrap: anywhere; }
/* What a spell costs, under its picture, in the quiet colour: it is a fact
   about the spell rather than the name of it, and it is the number that
   decides whether the button is red. It stayed on the button when the name
   left because the list is sorted by it and it is the commonest reason a
   button is red — with it gone, red would be unexplained until somebody
   hovered, and on a telephone there is no hovering. */
.spellbtn .cost { font-size: 10px; line-height: 1; color: var(--muted); font-weight: normal; }
/* A monster ability's wait, counted down in whole seconds where a spell's
   cost goes (2026-10-27: "show the time to cooldown on the monster ability
   spell button"): bright and over the cooling shade, so it reads while the
   rest of the button is dimmed. See paintAbilityClock in ui.ts. */
#spellsbody .spellbtn .cost.clock, #spellsbody .spellbtn.cooling > .cost.clock {
  opacity: 1; z-index: 1; color: #fff; font-weight: bold; text-shadow: 0 0 2px #000, 0 0 2px #000;
}
/* A pinned spell (2026-10-17: "Support pinning spells"): the bag's pin
   head, in the top right corner, which nothing else on the button uses. */
.spellbtn .pin {
  position: absolute; right: 2px; top: 2px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 1px #000; pointer-events: none;
}
/* Three colours, and the server picks which by sending one of two
   sentences or neither: green is "this will go off and do something",
   the accent gold is "this will go off and do nothing worth the mana" —
   homecoming on the temple floor, provoke in an empty room, a light over a
   light — and red is "the server would refuse this right now" — not
   enough mana, a fight you are still in, a skull. The same red the bad
   statuses and the PvP switch wear, because it is the same thing being
   said: this is the state worth noticing. Green is its own, a shade
   between the good ping's and the accepted trade's, and the gold is the
   button's own resting colour, given a class of its own so that a gold
   button is gold on purpose and not by omission.

   It is a colour and not a `disabled`, and that is the whole point.
   Disabling the button would make the client the thing that enforces the
   rule, and the client does not know the rule — it knows only what the
   last state told it, which is a fraction of a second old and was worked
   out before the rat bit you. Worse, a disabled button swallows the
   click, and the click is how the player finds out why: pressing a red
   spell says the words into the world exactly as typing them would, and
   the server answers in the console with the reason. So the colour is a
   hint that saves you the trouble, and the refusal is still the server's.

   The cost goes red with the drawing rather than staying quiet, since on
   the commonest refusal of all — not enough mana — the cost is the thing
   the player is being told to look at. */
#spellsbody button.can { border-color: #4a6a44; color: #6cbf5a; }
#spellsbody button.can .cost { color: #6cbf5a; }
#spellsbody button.can:hover, #spellsbody button.can:focus-visible { border-color: #6cbf5a; }
#spellsbody button.moot { color: var(--accent); }
#spellsbody button.moot .cost { color: var(--accent); }
#spellsbody button.cannot { border-color: #8c4a4a; color: #e88; }
#spellsbody button.cannot .cost { color: #e88; }
#spellsbody button.cannot:hover, #spellsbody button.cannot:focus-visible { border-color: #e88; }
/* The rail with nothing in it: a level one holds no mana, so no spell is
   listed, and a blank strip down the side of the world reads as something
   that failed to load. One quiet line says what it is instead.

   It is the one thing in the rail that is words rather than a picture, and
   it is allowed to be wider than the icons for the plain reason that there
   are no icons when it is showing: an empty rail can afford the width
   because it is not covering anything a full one would have been. It is
   one line, as wide as it is (2026-10-17: "the spell text when you have
   no spells doesn't need to break onto different lines"): it was a box
   118 pixels across until then, four lines tall, and the rail it is
   in would set it one word to a line if left to the rail's width. */
#spellsbody .none {
  flex: none; white-space: nowrap;
  color: var(--muted); padding: 4px 6px; font-size: 11px; font-style: italic;
  background: rgba(22, 20, 15, .72); border: 1px solid var(--panel-border); border-radius: 3px;
}

/* The way to each icon panel's own help, from the main help's Panel icons
   entry: their question marks went with their titles on 2026-10-01, and
   the info drawer's off its titles on 2026-10-10. */
.helplinks { display: inline-flex; flex-wrap: wrap; gap: 4px; vertical-align: baseline; }
.helplink {
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
  border-radius: 2px; padding: 1px 6px; font: inherit; cursor: pointer;
}
.helplink:hover, .helplink:focus-visible { border-color: var(--accent); }
#help, .helpdlg, #scores, #charpage, #changes, #marketdlg, #depotdlg, #vendordlg, #slotmenu, #playermenu, #filtersmenu, #racedlg, #login {
  position: fixed; inset: 0; background: rgba(0,0,0,.85);
  display: flex; align-items: center; justify-content: center;
}
#help[hidden], .helpdlg[hidden], #scores[hidden], #charpage[hidden], #changes[hidden], #marketdlg[hidden], #depotdlg[hidden], #vendordlg[hidden], #slotmenu[hidden], #playermenu[hidden], #filtersmenu[hidden], #racedlg[hidden], #login[hidden] { display: none; }
#selldlg[hidden], #trade[hidden], #tradeask[hidden], #death[hidden] { display: none; }
#help, .helpdlg, #scores, #changes, #slotmenu, #playermenu, #filtersmenu, #racedlg { z-index: 10; }
#marketdlg, #depotdlg, #vendordlg { z-index: 10; }
/* The depot screen covers the world only (2026-10-19: "make the depot
   dialog pop up over the game view, not the whole screen, so we can still
   see the message log"): it is in the view, which is positioned, so the
   log under the world and the panels beside it stay in sight. */
#depotdlg { position: absolute; }
/* So does the shop's window, for the same reason (2026-10-19: "make trade
   a dialog over the game view"). */
#vendordlg { position: absolute; }
/* A character's page goes over the highscores it was opened from: on the
   same layer, and after them in the page. */
#charpage { z-index: 10; }
/* The login screen covers the whole game, the panels on their shelf, the
   drawer and the corner strips included (2026-10-01: "the inventory,
   controls and spells panels don't go away when i'm at the login screen"),
   and stays under the dialogs it opens itself. */
#login { z-index: 9; }
/* Dragging down on the login screen reloads the page (2026-10-01: "Can we
   make it so dragging down on the login screen reloads the page?"): the
   screen takes the vertical drag from the browser, whose own pull to
   refresh has nothing to pull on a page that never scrolls, and brings
   the reload mark down from above the top edge with it (loginpull.ts).
   The mark lights once letting go would reload, and turns while the page
   does. Pinching zoomed it until the page stopped zooming (see html). */
#login { touch-action: none; }
#login.pulling { user-select: none; -webkit-user-select: none; cursor: grabbing; }
#loginpull {
  position: absolute; left: 50%; top: calc(env(safe-area-inset-top) - 44px);
  width: 36px; height: 36px; border-radius: 50%; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--panel-border); color: var(--muted);
  transform: translate(-50%, 0); opacity: 0; pointer-events: none;
  transition: transform .2s ease-out, opacity .2s ease-out, color .1s;
}
#login.pulling #loginpull { transition: color .1s; }
#loginpull.ready { color: var(--accent); border-color: var(--accent); }
#loginpull.reloading { animation: loginpullspin .8s linear infinite; }
@keyframes loginpullspin { from { rotate: 0deg; } to { rotate: 360deg; } }
/* 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. */
/* The item dialog (#sellbox) is the same box as well, with the same
   heading, words and buttons as the spell's menu (2026-10-17: "Unify the
   spell and item dialogs in style"): a long press on a thing and on a spell
   should read as the one menu, the item's with a slider in it. */
#slotmenubox, #playermenubox, #filtersmenubox, #sellbox {
  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, #filtersmenubox h2, #sellbox 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, #filtersmenubox p, #sellbox p { margin: 0 0 12px; color: var(--muted); font-size: 12px; line-height: 1.4; }
#slotmenuacts, #playermenuacts, #filtersmenuacts, #sellbtns { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
#slotmenubox button, #playermenubox button, #filtersmenubox button, #sellbtns 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;
}
#filtersmenubox button:hover, #filtersmenubox button:focus-visible,
#slotmenubox button:hover:not(:disabled), #slotmenubox button:focus-visible:not(:disabled),
/* What a button does for the number asked, after its verb: Sell 3 for 9
   gold (3 each), the part after Sell a little quieter than the verb. */
#sellbtns .selldetail { color: color-mix(in srgb, var(--text) 65%, var(--muted)); }
#sellbtns button:hover:not(:disabled), #sellbtns button:focus-visible:not(:disabled),
#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, #sellbtns button.all { color: #e8a; border-color: #6a4a58; }
#slotmenuacts button.all:hover, #slotmenuacts button.all:focus-visible,
#sellbtns button.all:hover, #sellbtns button.all:focus-visible,
#playermenuacts button.all:hover:not(:disabled), #playermenuacts button.all:focus-visible:not(:disabled) { border-color: #e8a; }
#slotmenubox #slotmenuclose, #playermenubox #playermenuclose, #sellbtns #sellcancel { 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, #slotmenuacts button:disabled, #sellbtns button:disabled { color: var(--muted); border-color: #2a251e; cursor: not-allowed; opacity: .6; }

/* The race chooser: the help's box and heading, with the slot menu's
   finger-sized buttons, one to a people, each with its name in the accent
   and a line under it. It is as wide as a phone allows less 16px a side,
   and never wider than the login card is by much. */
#racebox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; width: min(380px, calc(100vw - 32px)); max-height: 90vh; overflow-y: auto;
  box-sizing: border-box;
}
#racebox h2 { margin: 0 0 10px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#racebox p { margin: 0 0 12px; color: var(--muted); font-size: 12px; line-height: 1.4; }
#raceacts { display: flex; flex-direction: column; gap: 8px; }
#raceacts button {
  display: grid; grid-template-columns: auto 1fr; align-items: center; column-gap: 12px; row-gap: 3px;
  width: 100%; box-sizing: border-box;
  font: inherit; padding: 10px 12px; text-align: left; cursor: pointer;
  border: 1px solid var(--panel-border); border-radius: 3px; background: #201c17; color: var(--text);
}
#raceacts button b { color: var(--accent); font-size: 15px; letter-spacing: 1px; grid-column: 2; align-self: end; }
/* Each race's picture, down the left of its button across both lines of
   text (2026-09-29: "Include a sample picture with each race when you
   choose"): the sprite at twice its size with square pixels. */
.racepic { grid-column: 1; grid-row: 1 / span 2; width: 68px; height: auto; image-rendering: pixelated; }
#raceacts button span { grid-column: 2; align-self: start; color: var(--muted); font-size: 12px; line-height: 1.35; }
#raceacts button:hover:not(:disabled), #raceacts button:focus-visible { border-color: var(--accent); }
#raceacts button:disabled { cursor: progress; opacity: .6; }
/* The race you are now, when the chooser comes up at a login: keeping it
   is pressing it (2026-09-30: "Allow players to choose their race each
   time at login"). */
#raceacts button.current { border-color: var(--accent); box-shadow: inset 0 0 0 1px rgba(200, 164, 90, .5); }
#racebox #racenow { color: var(--text); }
#racebox #raceerr { margin: 10px 0 0; color: #f66; }
#racebox #raceerr:empty { display: none; }

/* The help and the panels' own help read as a page (2026-10-21: "The help
   doesn't fit well on phone. Make it looks a regular page, with headings and
   stuff"): a column no wider than reads comfortably, every entry its name as
   a heading over its paragraph rather than a two-column list whose names,
   kept on one line, pushed the words off the side of a phone. On a phone it
   is the whole screen. */
#helpbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 26px 26px; box-sizing: border-box;
  width: calc(100% - 32px); max-width: 720px; max-height: 90vh; overflow-y: auto;
  overflow-wrap: break-word; overscroll-behavior: contain;
}
#helpbox h2 { margin: 0 0 10px; color: var(--accent); font-size: 22px; letter-spacing: 1px; }
/* The main help's title stays at the top with Close beside it, so it can be
   shut from anywhere down the page. */
#helpbox .helphead {
  position: sticky; top: -18px; z-index: 1;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: -18px -26px 0; padding: 10px 26px;
  background: var(--panel); border-bottom: 1px solid var(--panel-border);
}
#helpbox .helphead h2 { margin: 0; }
.helphead #helpclose {
  display: inline-block; width: auto; margin: 0; padding: 0 18px;
  min-width: var(--hit); min-height: var(--hit);
}
#helpbox .helptoc {
  display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 12px 0 0; font-size: 14px;
}
#helpbox .helptoc::before { content: "Contents:"; color: var(--muted); }
#helpbox .helptoc a { color: var(--accent); }
/* Each section of the help has its own heading and its own entries, so the
   controls can be found without reading how the game works. */
#helpbox h3 {
  margin: 28px 0 4px; padding-bottom: 4px; border-bottom: 1px solid var(--panel-border);
  color: var(--accent); font-size: 18px; letter-spacing: .5px;
  scroll-margin-top: 64px;
}
#helpbox h2 + h3 { margin-top: 0; }
#helpbox dl { margin: 0; font-size: 15px; line-height: 1.6; }
#helpbox dt { margin: 18px 0 2px; color: var(--accent); font-weight: bold; line-height: 1.3; }
#helpbox dd { margin: 0; color: var(--text); }
#helpbox p { font-size: 15px; line-height: 1.6; }
#helpclose, #scoresclose, #charclose {
  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. */
/* Each board is only as tall as its own rows (2026-10-08: "vertically align
   highscore entries, don't spread them out if there aren't a full 10"). A
   flex line stretches its items to the tallest by default, and a table
   made taller than its rows hands the difference out among them, so a
   board of one ring-bearer beside a board of ten had its one row a whole
   board high. Packed from the top, with every caption and cell kept to a
   single line, row N of each board stands at the same height as row N of
   the boards beside it and a short board just ends early. */
#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; align-items: flex-start; }
#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);
  white-space: nowrap;
}
#scoresbody td { padding: 2px 6px; color: var(--muted); white-space: nowrap; }
#scoresbody td.race { opacity: 0.75; }
/* The changelog reads as a column of days, each a list of commit titles.
   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. */
/* The Market's dialog (2026-10-17: "Make the market a full screen dialog
   instead"): as much of the screen as a list of lots can use, the whole
   of it on a phone, with its name and Close across the top and the
   Market panel under them, its lots scrolling inside. The panel's own
   frame goes, since the box is its frame now. */
#marketbox {
  display: flex; flex-direction: column; gap: 8px; box-sizing: border-box;
  width: min(760px, calc(100% - 32px)); height: calc(100% - 32px);
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 12px 16px;
  padding-top: max(12px, env(safe-area-inset-top)); padding-bottom: max(12px, env(safe-area-inset-bottom));
}
#markettop { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
#markettop h2 { margin: 0; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#marketclose {
  min-width: var(--hit); min-height: var(--hit); padding: 0 14px;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border); border-radius: 2px;
  font: inherit; font-weight: bold; cursor: pointer;
}
#marketclose:hover, #marketclose:focus-visible { border-color: var(--accent); }
#marketbox > #marketpanel { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; background: none; border: 0; }
#marketbox #marketbody { flex: 1 1 auto; min-height: 0; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
#marketbox #market { width: auto; }
#marketbox > #marketpanel > .panelicon { display: none; }
/* The depot screen (2026-10-17: "implement the depot screen"; see
   showDepotDlg in main.ts): the bag, the depot and the market in three
   columns across a box as big as the market's, each column scrolling its
   own, with one filter across the top and the gold along the foot. The
   squares are the bag's and the depot's own, at their own size, as many
   to a row as the column holds. The quantity dialogs and menus they open
   come over it, being dialogs of their own. */
#depotbox {
  display: flex; flex-direction: column; gap: 8px; box-sizing: border-box;
  width: min(1100px, calc(100% - 32px)); height: calc(100% - 32px);
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 12px 16px;
  padding-top: max(12px, env(safe-area-inset-top)); padding-bottom: max(12px, env(safe-area-inset-bottom));
}
#depottop { flex: none; display: flex; align-items: center; gap: 10px; }
#depottop h2 { margin: 0; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#depotfilter {
  flex: 1 1 auto; min-width: 0; max-width: 320px; margin-left: auto; min-height: var(--chatpress); box-sizing: border-box;
  background: #16140f; color: var(--text); border: 1px solid var(--panel-border); border-radius: 2px; padding: 0 8px; font: inherit;
}
#depotfilter:focus-visible { outline: none; border-color: var(--accent); }
#depotfull, #depotclose, #depotbox .depotbtn, #depotstoreloot, #depotstoregold, #depotcollect, #depottake {
  min-width: var(--hit); min-height: var(--hit); padding: 0 12px; box-sizing: border-box;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border); border-radius: 2px;
  font: inherit; font-weight: bold; cursor: pointer;
}
#depotfull:hover, #depotfull:focus-visible, #depotclose:hover, #depotclose:focus-visible, #depotstoreloot:hover, #depotstoreloot:focus-visible,
#depotstoregold:hover, #depotstoregold:focus-visible, #depotcollect:hover, #depotcollect:focus-visible,
#depottake:hover, #depottake:focus-visible { border-color: var(--accent); }
/* The full screen twin: a square with the world's own drawing in it. */
#depotfull { padding: 0; display: flex; align-items: center; justify-content: center; flex: none; }
#depotfull[hidden] { display: none; }
#depotfull svg { width: 20px; height: 20px; display: block; }
#depottabs { display: none; }
#depotcols {
  flex: 1 1 auto; min-height: 0;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.3fr); gap: 10px;
}
#depotbox.nomarket #depotcols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
#depotbox.nomarket #depotmarketcol, #depotbox.nomarket #depottabs { display: none; }
.depotcol {
  min-height: 0; min-width: 0; display: flex; flex-direction: column; gap: 6px;
  background: #201d19; border: 1px solid var(--panel-border); border-radius: 3px; padding: 6px;
}
.depothead { flex: none; display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.depothead h3 { margin: 0; font-size: 13px; color: var(--accent); letter-spacing: 1px; text-transform: uppercase; }
.depothead span { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.depotscroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; }
#depotbox #inv, #depotbox #depot { grid-template-columns: repeat(auto-fill, var(--hit)); }
#depotbox #depot .slot.picked { border-color: var(--accent); box-shadow: inset 0 0 0 2px var(--accent); }
#depotbox > #depotpanel { display: none; }
#depotstoreloot { flex: none; align-self: flex-start; }
#depotpick { flex: none; display: flex; flex-direction: column; gap: 6px; border-top: 1px solid var(--panel-border); padding-top: 6px; }
#depotpick[hidden] { display: none; }
#depotpickrow { display: flex; align-items: center; gap: 8px; }
#depotpickwhat { flex: 1 1 auto; min-width: 0; color: var(--text); font-weight: bold; }
/* The market's own goods to sell are the depot's squares here, picked
   above, so its Sell list goes, and its form stands under the pick. */
#depotbox #market .marketsub, #depotbox #market .marketgoods, #depotbox #market .marketfilter, #depotbox #market .markethead { display: none; }
#depotsellslot { font-size: 12px; }
#depotbox #market { width: auto; }
#depotfoot {
  flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums;
}
#depotfoot b { color: var(--accent); }
#depotheldline[hidden] { display: none; }
#depotstoregold { margin-left: auto; }
/* The narrow screen's own rules for it, kept beside the rest of the depot
   screen's rather than in the narrow block further down. */
@media screen and (max-width: 720px) {
  /* On a phone the box is the screen, the depot stands over the bag, and
     the market is the second tab. */
  /* And it covers everything but the log (2026-10-19: "on phone, the
     depot interface show over eveyrthing except the chat log"): fixed,
     which the view does not hold in, from the top of the window to the
     top of the log, --depotfoot over the window's bottom (fitDepotDlg in
     main.ts). */
  #depotdlg { position: fixed; inset: 0 0 var(--depotfoot, 0px) 0; }
  #depotbox { width: 100%; height: 100%; border: 0; border-radius: 0; padding-left: 10px; padding-right: 10px; }
  #depottabs { display: flex; gap: 4px; flex: none; }
  #depottabs button {
    flex: 1 1 0; min-height: var(--chatpress); background: #16140f; color: var(--muted);
    border: 1px solid var(--panel-border); border-radius: 2px; font: inherit; cursor: pointer;
  }
  #depottabs button[aria-selected="true"] { color: var(--accent); border-color: var(--accent); }
  #depotcols { grid-template-columns: minmax(0, 1fr) !important; grid-template-rows: minmax(0, 1fr) minmax(0, 1fr); }
  #depotgoodscol { order: -1; }
  /* A picked square's bar (Take, and the market's form to list it) is
     under the depot's squares, and the depot had half the column, so on a
     short phone the bar took the squares' room and still ran out under
     the bag, its List out of reach (2026-10-22: "Check it's possible to
     list items on the market on phone"). While something is picked the
     depot has the whole column and the bag steps aside until the square
     is pressed again; a row of squares always shows, and what still does
     not fit scrolls in the depot's own box. */
  #depotbox[data-tab="goods"] #depotcols:has(#depotpick:not([hidden])) { grid-template-rows: minmax(0, 1fr); }
  #depotbox[data-tab="goods"] #depotcols:has(#depotpick:not([hidden])) #depotbagcol { display: none; }
  #depotgoodscol { overflow-y: auto; overscroll-behavior: contain; }
  #depotgoodsslot { min-height: calc(var(--hit) + 2px); }
  /* And the form's boxes and List are a finger's size, as every press is. */
  #depotsellslot .marketbtn { min-width: var(--hit); min-height: var(--hit); }
  #depotsellslot input { min-height: var(--chatpress); box-sizing: border-box; }
  #depotbox[data-tab="market"] #depotcols { grid-template-rows: minmax(0, 1fr); }
  #depotbox[data-tab="market"] #depotbagcol, #depotbox[data-tab="market"] #depotgoodscol { display: none; }
  #depotbox[data-tab="goods"] #depotmarketcol { display: none; }
  #depotmarketcol .depothead { display: none; }
  #depotstoregold { margin-left: 0; }
}
/* The shop's window (see showVendorDlg in main.ts): the shopkeeper's goods
   in a column that scrolls, built like the depot screen's, with the bag's
   gold along the foot. The bag stays in its own panel (2026-10-19:
   "selling should be over the game view, with no bag panel"). */
#vendorbox {
  display: flex; flex-direction: column; gap: 8px; box-sizing: border-box;
  width: min(520px, calc(100% - 32px)); height: min(480px, calc(100% - 32px));
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 12px 16px;
  padding-top: max(12px, env(safe-area-inset-top)); padding-bottom: max(12px, env(safe-area-inset-bottom));
}
#vendortop { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 10px; }
#vendortop h2 { margin: 0; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#vendorclose {
  min-width: var(--hit); min-height: var(--hit); padding: 0 12px; box-sizing: border-box;
  background: #16140f; color: var(--accent); border: 1px solid var(--panel-border); border-radius: 2px;
  font: inherit; font-weight: bold; cursor: pointer;
}
#vendorclose:hover, #vendorclose:focus-visible { border-color: var(--accent); }
#vendorcols { flex: 1 1 auto; min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr); }
#vendorbox #vendor { grid-template-columns: repeat(auto-fill, var(--hit)); }
#vendorbox > #vendorpanel { display: none; }
#vendorfoot {
  flex: none; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums;
}
#vendorfoot b { color: var(--accent); }
#vendorhint { font-size: 12px; }
@media screen and (max-width: 720px) {
  /* On a phone the world is the window, and the bag's panel stays on the
     shelf under it, where a long press on a slot sells. */
  #vendorbox { width: 100%; height: 100%; border: 0; border-radius: 0; padding-left: 10px; padding-right: 10px; }
  #vendorhint { display: none; }
}
#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 .day { margin-bottom: 12px; }
#changesbody .when {
  margin: 0 0 4px; color: var(--accent); font-size: 12px; letter-spacing: 1px;
  padding-bottom: 2px; border-bottom: 1px solid var(--panel-border);
}
#changesbody ul { list-style: none; margin: 0; padding: 0; }
#changesbody li {
  padding: 2px 0 2px 10px; border-left: 3px solid transparent;
  color: var(--text); font-size: 13px; line-height: 1.4;
}
/* The time hangs out to the left, so a title that wraps lines up under
   itself and not under its time. */
#changesbody li:has(.at) { padding-left: calc(10px + 5.5em); text-indent: -5.5em; }
#changesbody li .at {
  display: inline-block; width: 5.5em; text-indent: 0;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
#changesbody li.new { border-left-color: var(--accent); }
#changesbody li.new::after {
  content: "NEW"; margin-left: 8px; color: var(--accent); font-weight: 700; letter-spacing: 1px;
}
#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; }
/* Each board pages ten rows at a time (2026-10-18). The buttons are --hit
   square like every other pressable; a short last page is padded with
   blank rows so they stay where they were. */
#scoresbody td.pager { padding: 6px 0 0; border-top: 1px solid var(--panel-border); }
#scoresbody td.pager > div { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
#scoresbody td.pager button {
  min-width: var(--hit); min-height: var(--hit); padding: 0; box-sizing: border-box;
  font: inherit; font-size: 18px; line-height: 1; color: var(--text);
  background: var(--panel); border: 1px solid var(--panel-border); border-radius: 3px; cursor: pointer;
}
#scoresbody td.pager button:disabled { opacity: 0.35; cursor: default; }
#scoresbody td.pager .where { color: var(--muted); font-size: 12px; }
/* A name on a board of characters opens their page: it reads as the name
   it always was, underlined on hover, with the whole cell to press. */
#scoresbody .charlink, #charbody .charlink {
  background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit;
  cursor: pointer; text-align: left; text-decoration: underline dotted; text-underline-offset: 2px;
}
#scoresbody .charlink:hover, #scoresbody .charlink:focus-visible,
#charbody .charlink:hover, #charbody .charlink:focus-visible { color: var(--accent); text-decoration-style: solid; }
#charbox {
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 18px 22px; width: min(520px, calc(100% - 32px)); box-sizing: border-box; max-height: 85vh; overflow-y: auto;
}
#charbox h2 { margin: 0 0 6px; color: var(--accent); font-size: 20px; letter-spacing: 1px; }
#charbody .facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 12px; font-size: 13px; margin: 0 0 12px; }
#charbody .facts dt { color: var(--muted); }
#charbody .facts dd { margin: 0; color: var(--text); }
#charbody h3 {
  margin: 10px 0 4px; color: var(--accent); font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
  padding-bottom: 2px; border-bottom: 1px solid var(--panel-border);
}
#charbody ul { list-style: none; margin: 0; padding: 0; }
#charbody li { padding: 3px 0; font-size: 13px; line-height: 1.4; color: var(--text); }
#charbody li + li { border-top: 1px solid rgba(255,255,255,.05); }
#charbody .who { font-weight: bold; }
#charbody .sub { display: block; color: var(--muted); font-size: 12px; }
#charbody .none { color: var(--muted); font-size: 12px; }
#charbody table { border-collapse: collapse; font-size: 12px; }
#charbody td { padding: 1px 12px 1px 0; color: var(--muted); }
#charbody td.val { text-align: right; color: var(--text); }
/* The collage of web/static/bg/ images behind the login form, darkened so
   the form stays readable over anything. */
/* It is not pressed or dragged itself (2026-10-01): a press on it is the
   login screen's, so a drag down it is a pull to reload and not a picture
   being dragged off the page. */
#loginbg {
  position: absolute; inset: 0; z-index: -1; overflow: hidden; pointer-events: none;
  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; }
/* Whether the server is there, said in a word and a dot.
   A dot AND a word, not a dot alone: a coloured pip is the classic way to
   say this and it is the classic way to say it to nobody who cannot tell
   the two colours apart, which for green against red is a great many
   people. The colour is the glance and the word is the answer.
   Three states rather than two, because "down" before anybody has asked is
   a guess dressed as a fact — the line starts out saying it is finding
   out, which is what it is doing. */
#loginstatus { min-height: 1em; font-size: 12px; color: var(--muted); }
#loginstatus::before {
  content: "\25cf"; display: inline-block; width: 1em; color: currentColor;
}
#loginstatus.up { color: #8ec98e; }
#loginstatus.down { color: #f66; }
/* The ping beside "Server is up" (2026-10-06), coloured as the game's
   Ping row is. */
#loginstatus .ping.good { color: #8ec98e; }
#loginstatus .ping.fair { color: #d8bd6a; }
#loginstatus .ping.bad { color: #e66; }
#loginonline { color: var(--accent); min-height: 1em; }
#loginring { color: #d8bd6a; }
#loginring:empty { display: none; }
#loginrate { color: #8ec98e; }
#loginrate:empty { display: none; }
#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. */
/* The two things worth reading before you type a name: who is winning, and
   what has changed since you were last here. Both are buttons dressed as
   links, so that neither competes with Enter Game — they are things to
   read on the way in rather than ways in — and they share one rule
   because they are one kind of thing. The line wraps rather than
   squeezing them, since on a narrow card two links side by side is two
   links on two lines and that is fine. */
#loginform #loginscoreline {
  margin: 2px 0 0; text-align: center;
  display: flex; flex-wrap: wrap; gap: 4px 12px; justify-content: center;
}
#loginform #loginscores, #loginform #loginchanges {
  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), #loginform #loginchanges:hover:not(:disabled) { color: var(--text); }
#loginform #loginscores:disabled, #loginform #loginchanges:disabled { color: var(--muted); cursor: default; text-decoration: none; }


@media (max-width: 720px) {
  /* The help is the whole screen on a phone, a page of its own. */
  #helpbox {
    width: 100%; max-width: none; height: 100%; max-height: none;
    border: 0; border-radius: 0; padding: 18px 16px 32px;
  }
  #helpbox .helphead { margin: -18px -16px 0; padding: 6px 16px; }
  /* On a narrow screen the open panels stack under the world, each the
     width of the screen less its icon, which stands beside it and shuts
     it; the world gives up the height they take (2026-10-01: "on narrow
     screens, panels should stack vertically and push the map in. the
     icons for the panels should be on the right hand side of the screen
     starting at the bottom. when a panel is shown ... the icon should
     appear next to the panel so it identifies it and acts as a way to hide
     it again. remaining unexpanded icons move further up to stay over the
     map"). The shelf is as tall as the panels open on it, each one row
     that scrolls along, up to half the height, past which it scrolls up
     and down (2026-10-17: "the size of the panels should be fixed since
     they always scroll. so we should be able to automatically fit the
     panelcol/panelshelf"). From 2026-10-08 ("make a fixed space for
     panels and scroll within it") until then it was two fifths of the
     height, later dragged by a strip on its top edge, whatever was open
     in it. The shut panels' icons go up the right
     edge of the world from its bottom, over it, and the strip under the
     world goes. */
  /* A column rather than the wide screen's grid: the world over the
     shelf, and the log under them both, out in #gamecol, where placeLog in
     main.ts keeps it at this width. */
  #worldrow { display: flex; flex-direction: column; }
  .panelcol { display: none !important; }
  #panelshelf {
    display: flex; flex-direction: column; gap: 6px; flex: none;
    max-height: 50%; overflow-y: auto; padding: 6px; box-sizing: border-box;
    background: var(--panel); border-top: 2px solid var(--panel-border);
  }
  #panelshelf:not(.hasopen) { display: none; }
  /* Each open panel is one row here that scrolls sideways (2026-10-17:
     "When the panels are below, the scrolling should be horizontal"), as
     beside the world each is one column that scrolls down: the bag's,
     the depot's and the vendor's squares, the equipment and the Controls'
     buttons each go on along their row at the bag's square size, and the
     panels stack in the shelf, which scrolls up and down past them as it
     did. Until the same day, earlier, they shared the shelf's height and
     scrolled down in their shares; before that their contents wrapped
     onto new rows and only the shelf scrolled. The spells are one row
     already (below), and the Market is a dialog of its own (#marketbox),
     never on the shelf while open. main.ts turns a mouse
     wheel over a row into scrolling it along. The spells go along their
     row the same way since the same day ("No the spells should scroll to
     the side like the inventory does"), and the Controls' buttons are the
     one panel that wraps onto more rows instead ("The only panel that
     shouldn't do this is the controls panel, it should wrap"). */
  /* At least as tall as the icon beside it, border and all: the empty
     spell list's one line is shorter, and its icon hung out of the foot
     of the shelf, which then scrolled for it. */
  #panelshelf > .panel { position: relative; flex: none; margin-right: calc(var(--hit) + 8px); min-height: calc(var(--hit) - 2px); }
  #panelshelf > .panel > .panelbody { overflow-x: auto; overflow-y: hidden; overscroll-behavior: auto; overscroll-behavior-x: contain; }
  #panelshelf #inv, #panelshelf #depot, #panelshelf #vendor, #panelshelf #equip, #panelshelf #spellsbody {
    grid-auto-flow: column; grid-template-columns: none; grid-auto-columns: var(--hit);
  }
  #panelshelf #inv:has(> .none) { grid-auto-columns: max-content; }
  #panelshelf #spellsbody:has(> .none) { grid-auto-columns: max-content; }
  #panelshelf #buttonsbody button { flex: none; }
  #panelshelf > .panel:not(.open) { display: none; }
  /* The icon stands beside its panel in the middle of the panel's height
     (2026-10-20: "The panel icon should be centred vertically too, for
     example the controls panel in portrait orientation"): it was at the
     panel's top, which on the Controls' rows of buttons left it up in a
     corner. */
  #panelshelf > .panel > .panelicon { position: absolute; top: 50%; transform: translateY(-50%); right: calc(-8px - var(--hit)); }
  #panelfloat {
    position: absolute; z-index: 4; right: 6px; bottom: 6px;
    display: flex; flex-direction: column-reverse; gap: 4px;
  }
  #panelbar { display: none; }
  /* The Market's dialog is the whole screen on a phone. */
  #marketbox { width: 100%; height: 100%; border: 0; border-radius: 0; }
  /* The spells are squares of --hit exactly, as the bag's are, one row
     that scrolls along (above; 2026-10-17: "Remove the ellipsis from spell
     panel. ... Work out why spell button size ignores menu button size
     setting"). Until then they shared one row: columns of at least --hit
     that stretched to fill it, with the spells that did not fit behind a
     … button, so a smaller Button size only made room for more spells in
     the row and they stretched back to fill it; at 44 they came out 52
     wide on a phone, at 32 about 35. */
  #panelshelf .spellbtn svg { max-width: 100%; height: auto; }
  /* The info panel is a drawer here, and pushes the world down rather
     than covering it (2026-10-01: "on mobile the info drawer isn't taking
     up space from the map"). Wider than this it is a column the full
     height of the page (2026-10-06: "Always show the info panel tiled on
     the left at the full screen height"), but a full-height
     column beside the world would leave a phone a strip of world a
     hundred pixels wide, or none. So here it is shut until the info
     button in the top left corner of the world brings it out, and then it
     stands above the bars and the world, the screen's width and at most
     a third and a bit of the page's height, about the half of the view it
     was allowed while it stood in the view, scrolling past that, and they
     give up what it takes. It has no width to drag, but its height drags
     since 2026-10-13 (the grip below). */
  #gamewrap { flex-direction: column; }
  #drawer { width: auto; max-height: var(--drawermaxh, 35%); border-right: 0; border-bottom: 2px solid var(--panel-border); }
  /* Held upright, the three stand side by side rather than one above
     another (2026-10-10: "in portrait mode, could we put the drawer panels
     arranged horizontally next to each other?", then "commit this" of
     Stats beside the rest): Stats down the left half, Skills over Players
     down the right, each side scrolling on its own inside the drawer's
     height.
     Stacked, the drawer showed only Stats and a scroll led to the rest;
     three columns cut names short at a third of a phone, and a swipe
     showed one panel at a time. Held sideways the drawer keeps its stack.
     It is as tall as the taller side and no taller, up to the 35%
     (2026-10-10: "When collapsing panels in the drawer in portrait mode,
     resize the drawer to fit the contents"), so folding panels gives the
     world back what they took instead of leaving a blank strip.

     The right side is one box, Skills over Players, that scrolls as one
     (2026-10-13: "The 2 panels on the right hand side in the drawer aren't
     scrollable when the drawer is too small in portrait mode"). They were
     two rows of a grid, Skills as tall as it liked and Players the rest,
     so a drawer dragged short gave Players nothing and Skills nothing to
     scroll by. Now the drawer is one row, and a row is cut to the
     drawer's cap whatever is in it, so each side is the row's height and
     scrolls what it does not show. Since 2026-10-17 ("Make each panel
     scrollable") Skills and Players share their side, each scrolling its
     own rows under its own title, and Stats scrolls its rows under its. */
  @media (orientation: portrait) {
    #drawer {
      flex-direction: row; align-items: stretch; overflow: hidden;
    }
    #drawer > #statpanel, #drawer > #drawerside { flex: 1 1 0; min-width: 0; min-height: 0; overflow-y: auto; }
    #drawerside { display: flex; flex-direction: column; gap: 6px; }
  }
  /* Its foot drags its height (2026-10-13: "Make the drawer resizable"):
     the grip that drags its width wider than this, laid across under it
     and drawn as the shelf's and the log's strips are, in the page's flow
     between the drawer and the world. It drags the 35% above, which
     setDrawerHeight in main.ts sets as --drawermaxh, and goes with the
     drawer. */
  #drawergrip {
    position: static; width: auto; height: 8px; margin: 0;
    background: #221f18;
    cursor: ns-resize;
  }
  #drawergrip::after { width: 34px; height: 2px; border-radius: 1px; }
  @media (pointer: coarse) {
    #drawergrip { height: 16px; }
  }
  /* The ground at your feet beside the shut panels' icons (see #grounddock). */
  #view:has(#panelbarright > .panelicon:not([hidden])) #grounddock { bottom: 6px; }
  #view:has(#panelfloat > .panelicon) #grounddock { right: calc(var(--hit) + 12px); }
  /* No row of the stats panel is hidden here any more. Armor was, behind
     the debug switch, as arithmetic about your gear that a phone had no
     room for, until 2026-10-07: "show armor in info panel without
     requiring debug mode to be on". It is what your gear comes to against
     a blow, the same kind of number as Attack and Defence, which have been
     shown at every width since 2026-09-23, and one row is not what pushes
     the statuses off a drawer that scrolls. Speed, Clank, the pool and the
     meal bar are behind the switch at every width by the rule further up. */
}
/* Held upright, the panels stand one above another, each the whole width
   of the stack (2026-09-29: "In portrait mode, stack panels vertically").
   Two to a row halved every panel on a phone, so the bag's squares, the
   spell grid and the stat rows were all squeezed into half a screen and
   the stack wrapped into a patchwork of whatever heights happened to sit
   side by side. One to a row reads top to bottom and scrolls the same
   way. A narrow screen held sideways keeps two to a row from the block
   above: it has the width for two and not the height for one. */
/* Held upright or sideways, a narrow screen stacks its panels one above
   another since 2026-10-01 (see the shelf above); there was a rule here
   for two to a row held sideways, from when the shelf wrapped. */


/* 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. */
/* The reconnect bar, in the same place and the same shape as the trade
   question below, because it is the same kind of thing: one line over the
   world, saying what is going on, taking nothing away. Muted rather than
   alarming — three seconds of a wifi handover is not a disaster and should
   not be dressed as one — and it does not dim what is behind it, since the
   frozen world under it is the thing the player is waiting to have back. */
#reconnecting {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 6;
  max-width: calc(100% - 20px);
  background: var(--panel); border: 2px solid var(--panel-border); border-radius: 4px;
  padding: 8px 12px; font-size: 13px; color: var(--muted); white-space: nowrap;
}

#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, and for a
   stronger reason — the squares are how things are put on the table, so
   they have to stay in reach the whole time. They are a panel beside the
   view again, so they are never covered by this and a trade no longer has
   to open anything to make them reachable. The wash is a light one,
   since what is under it is still being looked at: the other party is
   standing there. */
/* The death screen, over the view and nothing else. The wash is red-black
   rather than the plain black the other dialogs use, because it is not a
   window that happens to be open over the world — it is the state the
   world is in for whoever is looking at it, and it stays until they press
   the button. Dimmer than those, too: what is behind it is the point, so
   it has to be read through rather than merely sensed. */
#death {
  position: absolute; inset: 0; z-index: 6;
  display: flex; align-items: center; justify-content: center;
  background: rgba(30, 0, 0, .55);
}
#deathbox { text-align: center; padding: 0 24px; }
#deathbox h2 {
  margin: 0 0 6px; font-size: 30px; letter-spacing: 1px; color: #f55;
  text-shadow: 0 2px 0 #000, 0 0 14px rgba(0, 0, 0, .9);
}
/* Sized and shadowed to be read off a dimmed world rather than off a
   panel: there is no box behind this text, on purpose, so that the tile
   the character died on stays visible under it. */
#deathbox p {
  margin: 0 0 14px; max-width: 320px; font-size: 12px; color: #e9c9c9;
  text-shadow: 0 1px 0 #000, 0 0 10px rgba(0, 0, 0, .9);
}
#respawnbtn {
  padding: 8px 18px; font: inherit; font-weight: bold; cursor: pointer;
  color: #1a1410; background: var(--accent); border: 1px solid #000;
  border-radius: 3px;
}
#respawnbtn:hover, #respawnbtn:focus-visible { filter: brightness(1.12); }

#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, var(--hit)); gap: 3px; }
#trade .slot { width: var(--hit); height: var(--hit); }
#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); }

/* Selling from the bag beside Grubb (Wenna's counter until 2026-09-24). What she pays sits
   in the bag slot's top corner in the Vendor panel's own price colour, and
   a pile dragged onto the panel is sold. */
/* A pinned kind (2026-10-17): a small round pin head in the top right
   corner, the one the count and the vendor's price leave free. */
#inv .slot .pin {
  position: absolute; right: 2px; top: 2px; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 0 1px #000; pointer-events: none;
}
#inv .slot .sellprice {
  position: absolute; left: 2px; top: 0; font-size: 10px; color: #f0c84a; text-shadow: 0 0 2px #000;
}
.dragghost {
  position: fixed; width: 32px; height: 32px; z-index: 20; pointer-events: none;
  image-rendering: pixelated; opacity: .85;
}
#vendorgoodscol.droptarget { outline: 2px dashed #f0c84a; outline-offset: -2px; }
/* The Market panel (2026-10-07: "add a market feature Grubb"): a list of
   lots rather than a grid of squares, so it is a column of its own width
   and the list scrolls inside it rather than wrapping into columns the way
   the bag does. Prices in the purse's gold, as the Vendor panel's are.
   Beside the world it is the one panel that gives way rather than the
   column scrolling sideways (2026-10-08: "opening the market seems to
   trigger a horizontal scrollbar"): it was 268 pixels whatever the room,
   and beside the bag on a short window, whose squares wrap into more
   columns, the two were wider than the 45% a side is allowed, so the side
   scrolled sideways and the panel stood past its edge over the world. Now
   it narrows to what the side has left, down to 160 pixels, and a lot
   whose words, count and Buy do not fit on one line puts the count and
   Buy on a line of their own under its words. Since 2026-10-17 ("Make the
   market a full screen dialog instead") it is never beside the world while
   it is open: it stands in its own dialog (#marketbox), as wide as that. */
#market { display: flex; flex-direction: column; gap: 5px; width: 268px; max-width: 100%; min-width: 0; font-size: 12px; }
#market .markethead { display: flex; align-items: center; gap: 6px; color: #f0c84a; }
#market .markethead[hidden], :is(#market, #depotsellslot) .marketform[hidden] { display: none; }
#market .marketheld { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
#market .markettools { display: flex; gap: 4px; min-width: 0; }
#market .marketfilter { flex: 1 1 4em; min-width: 0; }
#market .marketsort { flex: none; max-width: 50%; }
#market input, #market select, #depotsellslot input {
  background: #111; color: inherit; border: 1px solid #3a342c; font: inherit; padding: 2px 4px; border-radius: 2px;
}
:is(#market, #depotsellslot) .marketcount, :is(#market, #depotsellslot) .marketprice { width: 4.5em; }
:is(#market, #depotsellslot) .marketbtn {
  flex: none; background: #16140f; color: var(--accent); border: 1px solid var(--panel-border);
  padding: 2px 8px; font: inherit; font-weight: bold; border-radius: 2px; cursor: pointer;
}
:is(#market, #depotsellslot) .marketbtn:hover, :is(#market, #depotsellslot) .marketbtn:focus-visible { border-color: var(--accent); background: #221f18; }
#market .marketlist { display: flex; flex-direction: column; gap: 3px; }
#market .marketrow { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 5px; background: #111; border: 1px solid #3a342c; padding: 2px 4px 2px 2px; }
#market .marketrow.mine { border-color: #6b5a34; }
#market .marketrow canvas { width: 32px; height: 32px; flex: none; image-rendering: pixelated; }
#market .marketwhat { flex: 1 1 7em; min-width: 0; overflow-wrap: anywhere; }
/* The count and Buy (or Take back) at the right, on the words' line or,
   wrapped, on one of their own. */
#market .marketwhat + * { margin-left: auto; }
#market .marketrow .marketprice { color: #f0c84a; width: auto; }
#market .marketrow .marketasked { color: var(--muted); }
#market .marketseller { color: var(--muted); }
#market .marketrow .marketcount { width: 3.5em; }
#market .marketsub { color: var(--accent); font-weight: bold; margin-top: 4px; }
#market .marketmine, #market .none { color: var(--muted); }
#market .marketgoods { display: grid; grid-template-columns: repeat(auto-fill, var(--hit)); gap: 3px; }
#market .marketgoods .slot {
  width: var(--hit); height: var(--hit); padding: 0; background: #111; border: 1px solid #3a342c; position: relative;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
#market .marketgoods .slot:hover, #market .marketgoods .slot:focus-visible { border-color: var(--accent); }
#market .marketgoods .slot canvas { width: var(--hitart); height: var(--hitart); image-rendering: pixelated; }
#market .marketgoods .slot .count { position: absolute; right: 2px; bottom: 0; font-size: 10px; color: #fff; text-shadow: 0 0 2px #000; }
:is(#market, #depotsellslot) .marketform { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
:is(#market, #depotsellslot) .marketformwhat { flex: 1 1 100%; min-width: 0; overflow-wrap: anywhere; }
/* How many to sell: a small box over the world. */
#selldlg {
  position: fixed; inset: 0; z-index: 10; background: rgba(0,0,0,.85);
  display: flex; align-items: center; justify-content: center;
}
/* What a look at the thing says: words about it rather than numbers, so on
   a line of their own under the facts, in italics and the parchment colour
   of something said rather than measured (2026-10-17: "Show the flavour
   text on a different line and in a different style in the item dialog"). */
#sellbox #sellflavour { font-style: italic; color: #c9b48a; }
#selllook + #sellflavour { margin-top: -8px; }
#sellpick { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; }
/* The slot menu keeps its Cancel ten pixels under the actions; so does this. */
#sellbtns #sellcancel { margin-top: 4px; }
#sellbox #sellbtns { margin-bottom: 0; }
#sellrange { flex: 1; }
#sellcount { width: 72px; background: #16140f; color: var(--text); border: 1px solid var(--panel-border); padding: 4px 6px; font: inherit; }
#sellbox #sellpay { margin: -2px 0 12px; color: #f0c84a; font-size: inherit; font-weight: 700; }
/* The quick amounts: a row of smaller buttons over the slider, quieter
   than the one that trades since all they do is move it. */
#sellpresets { display: flex; gap: 6px; margin: 0 0 8px; }
#sellpresets button {
  flex: 1; background: #201c17; border: 1px solid var(--panel-border); border-radius: 3px;
  color: var(--text); font: inherit; font-size: 12px; padding: 6px; cursor: pointer;
}
#sellpresets button:hover, #sellpresets button:focus-visible { border-color: var(--accent); }
/* Greyed when all it could do is say what the slider says already: Invert
   and Half with a single lot (see howManyLayout). */
#sellpresets button:disabled, #sellpresets button:disabled:hover {
  color: var(--muted); border-color: var(--panel-border); cursor: not-allowed; opacity: .5;
}

/* The pixel font on the grid it is drawn on (2026-10-01: "Snap to 11px where
   it makes sense"): every size set in pixels above, set again at the nearest
   multiple of eleven while the pixel font is on — the small print and the
   body to 11, the headings to 22, the death screen's title to 33. The one
   left alone is the clock on a status icon, which is nine pixels in the
   corner of a 26-pixel square and has no room for eleven. Last in the
   sheet, and a class deeper than the rules it answers, so it wins them
   wherever they are, media queries included. */
html.pixelfont #topbars .bar span,
html.pixelfont #viewtools #floorlabel,
html.pixelfont #moremenu button,
html.pixelfont #popmenu button,
html.pixelfont #popmenu .pophead,
html.pixelfont #areatitle i,
html.pixelfont #zoomtag,
html.pixelfont #grounddock .count,
html.pixelfont #filtersbtn,
html.pixelfont #logend .count,
html.pixelfont #log,
html.pixelfont #log.types .kind,
html.pixelfont #log .ts,
html.pixelfont #log .debug,
html.pixelfont #log > div.copied::after,
html.pixelfont #log > div.copyfail::after,
html.pixelfont .bar span,
html.pixelfont #skills .skill,
html.pixelfont #players,
html.pixelfont #players .pplat,
html.pixelfont #inv .none,
html.pixelfont #depot .none,
html.pixelfont #equip .wornslot .placename,
html.pixelfont #inv .slot .offer,
html.pixelfont #vendor .slot .price,
html.pixelfont #inv .slot .count,
html.pixelfont #depot .slot .count,
html.pixelfont #vendor .slot .count,
html.pixelfont #trade .slot .count,
html.pixelfont #equip .wornslot .count,
html.pixelfont #market,
html.pixelfont #market .marketgoods .slot .count,
html.pixelfont .spellbtn .spellname,
html.pixelfont .spellbtn .cost,
html.pixelfont #spellsbody .none,
html.pixelfont #depottop h2,
html.pixelfont .depothead h3,
html.pixelfont .depothead span,
html.pixelfont #depotsellslot,
html.pixelfont #depotfoot,
html.pixelfont #vendortop h2,
html.pixelfont #vendorfoot,
html.pixelfont #vendorhint,
html.pixelfont #slotmenubox p,
html.pixelfont #playermenubox p,
html.pixelfont #filtersmenubox p,
html.pixelfont #sellbox p,
html.pixelfont #slotmenubox button,
html.pixelfont #playermenubox button,
html.pixelfont #filtersmenubox button,
html.pixelfont #sellbtns button,
html.pixelfont #popmenu .popdetail,
html.pixelfont #popmenu .popstats,
html.pixelfont #popmenu .popabout,
html.pixelfont #popmenu .popicon .count,
html.pixelfont #racebox p,
html.pixelfont #raceacts button b,
html.pixelfont #raceacts button span,
html.pixelfont #helpbox h3,
html.pixelfont #helpbox dl,
html.pixelfont #helpbox p,
html.pixelfont #helpbox .helptoc,
html.pixelfont #scoresbody table,
html.pixelfont #scoresbody caption,
html.pixelfont #scoresbody td.pager .where,
html.pixelfont #charbody li,
html.pixelfont #charbody .sub,
html.pixelfont #charbody .facts,
html.pixelfont #charbody table,
html.pixelfont #charbody h3,
html.pixelfont #charbody .none,
html.pixelfont #changesbody .when,
html.pixelfont #changesbody li,
html.pixelfont #changesbody .none,
html.pixelfont #loginform p,
html.pixelfont #loginstatus,
html.pixelfont #loginform label,
html.pixelfont #loginform input,
html.pixelfont #loginerr,
html.pixelfont #loginform #loginscores,
html.pixelfont #loginform #loginchanges,
html.pixelfont #reconnecting,
html.pixelfont #tradeask,
html.pixelfont #deathbox p,
html.pixelfont #tradenote,
html.pixelfont #tradebody .who,
html.pixelfont #tradewarn,
html.pixelfont #inv .slot .sellprice,
html.pixelfont #sellpresets button { font-size: 11px; }
html.pixelfont #areatitle b,
html.pixelfont #slotmenubox h2,
html.pixelfont #playermenubox h2,
html.pixelfont #filtersmenubox h2,
html.pixelfont #racebox h2,
html.pixelfont #helpbox h2,
html.pixelfont #scoresbox h2,
html.pixelfont #scoresbody td.pager button,
html.pixelfont #charbox h2,
html.pixelfont #changesbox h2,
html.pixelfont #markettop h2,
html.pixelfont #loginform h1,
html.pixelfont #tradebox h2,
html.pixelfont #sellbox h2 { font-size: 22px; }
html.pixelfont #deathbox h2 { font-size: 33px; }

/* The game launched from a phone's home screen has no browser frame
   either, so it clears the corners the way full screen does (see
   --cornerx under the touch-screen rules). */
@media (pointer: coarse) and (display-mode: fullscreen), (pointer: coarse) and (display-mode: standalone) {
  :root { --cornerx: 18px; --cornery: max(0px, calc(12px - env(safe-area-inset-bottom, 0px))); }
}
/* A list a command answered with (logList in ui.ts): under the line that
   heads it, in columns as wide as its longest item, as many as the log's
   width has room for, read down each column and then the next, so a list
   in order reads in order; or one column of items each wrapped under
   itself. */
#log .loglist {
  columns: var(--col, 20ch); column-gap: 2ch;
  margin-left: 2ch;
}
#log .loglist.one { columns: auto; }
#log .loglist > span {
  display: block; break-inside: avoid;
  padding-left: 2ch; text-indent: -2ch;
}
