/* One monospace on every device, rather than whichever of a list the phone
   happens to own. Menlo itself cannot be redistributed, but it is Apple's fork
   of Bitstream Vera Sans Mono, and DejaVu Sans Mono is that same design carried
   on freely - the same skeleton at the same 0.602em advance. No local() entry:
   a face that varies by platform is the thing this replaces. */
@font-face {
  font-family: "Brogue Mono";
  src: url("./fonts/dejavu-sans-mono.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  color-scheme: dark;
  --void: #030409;
  --panel: rgba(8, 10, 22, 0.96);
  --button: rgba(20, 28, 52, 0.94);
  --button-active: #253f62;
  --line: rgba(144, 184, 222, 0.28);
  --pale: #c8e8ff;
  --ember: #ffc56e;
  --toolbar-height: 62px;
  font-family: "Brogue Mono", ui-monospace, monospace;
  background: var(--void);
  color: #e7f4ff;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body, #app { width: 100%; height: 100%; margin: 0; overflow: hidden; }
/* On the root as well as the body: Android reads the root element's value when
   it decides whether a downward drag is a pull-to-refresh. */
html { overscroll-behavior: none; }
body { background: #000; overscroll-behavior: none; touch-action: none; }
button, input { font: inherit; color: inherit; }
button { touch-action: manipulation; }

#app {
  position: relative;
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
    env(safe-area-inset-bottom) env(safe-area-inset-left);
  background: radial-gradient(circle at 50% 35%, #111427 0, #030409 64%);
}

#game-surface { position: absolute; inset: 0; overflow: hidden; }
#game {
  width: 100%; height: 100%; display: block;
  image-rendering: auto;
  cursor: crosshair;
  touch-action: none;
}

#magnifier {
  position: absolute; z-index: 7; pointer-events: none;
  border: 1px solid rgba(200, 232, 255, .5); border-radius: 4px;
  background: #000; box-shadow: 0 10px 28px rgba(0, 0, 0, .85);
}
#magnifier[hidden] { display: none; }

#boot {
  position: absolute; inset: 0; z-index: 20;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; background: #020308; letter-spacing: .18em;
  transition: opacity 550ms ease;
}
/* The cover comes off the finished title screen rather than being replaced by
   it, and stops answering entirely once it has gone. */
#boot.ready {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 550ms ease, visibility 0s linear 550ms;
}
#boot img { image-rendering: pixelated; filter: drop-shadow(0 0 28px rgba(255, 127, 45, .32)); }
/* The word is Brogue's own lettering, drawn from the font half of its tile
   sheet rather than set in the page's face. It arrives with the sheet, which
   is why the canvas holds its place from the first paint. */
#boot-title { display: block; width: min(62vw, 252px); height: auto; }
#boot span { color: #7793ab; font-size: 11px; }

/* The title framebuffer is the original Brogue terminal screen. These are
   larger hit targets around one-row copies of Brogue's own menu buttons. */
#title-screen { position: absolute; inset: 0; z-index: 8; overflow: hidden; }
#title-screen[hidden] { display: none; }
.title-actions {
  position: absolute; top: var(--title-menu-top, 62%); right: var(--title-menu-right, 4vw);
  display: grid; grid-template-columns: repeat(2, max-content);
  gap: var(--title-cell-height, 7px);
}
.title-actions button {
  display: grid; place-items: center; min-width: 48px; min-height: 48px; padding: 0;
  border: 0; background: transparent; color: #fff; font-weight: 400; white-space: nowrap;
}
.brogue-menu-row { display: flex; }
.brogue-menu-cell, #title-version span { display: grid; place-items: center; line-height: 1; }
.brogue-menu-cell {
  width: var(--title-button-cell-width, 20px);
  height: var(--title-button-cell-height, 35px);
  font-size: var(--title-button-font-size, 29px);
}
#title-version span {
  width: var(--title-cell-width, 4px); height: var(--title-cell-height, 7px);
  font-size: calc(var(--title-cell-height, 7px) * .82);
}
.title-actions button:active:not(:disabled) .brogue-menu-cell { filter: brightness(.48); color: #b3a9b8 !important; }
.title-actions button:disabled { opacity: .45; }
.title-actions button[hidden] { display: none; }
/* Six entries pair off into three even rows, so the two that are their own
   row - the run you were already playing, and the settings - span it. */
#continue-game { grid-column: 1 / -1; justify-self: center; }
#title-options { grid-column: 1 / -1; justify-self: center; }
#title-version {
  position: absolute; right: max(2px, env(safe-area-inset-right));
  bottom: max(1px, env(safe-area-inset-bottom)); display: flex; color: #1a1a1a;
}
.title-mode #game-surface, .loading-mode #game-surface { top: 0; bottom: 0; border: 0; }
.title-mode #toolbar, .title-mode #more-menu, .title-mode #text-entry,
.loading-mode #toolbar, .loading-mode #more-menu, .loading-mode #text-entry,
.ending #toolbar, .ending #more-menu, .ending #text-entry { display: none; }

@media (orientation: portrait) {
  .title-actions {
    right: 0; left: 0; width: 100%; padding-inline: 12px;
    grid-template-columns: max-content;
    justify-content: center; justify-items: center; transform: none;
  }
}

#portrait-hud, #portrait-messages { display: none; }

/* While the history is open it is the only thing on screen that answers: a tap
   anywhere else puts it away and does nothing more. Above the toolbar, so that
   goes quiet too. */
#message-catcher { position: absolute; inset: 0; z-index: 30; }
#message-catcher[hidden] { display: none; }

#modal-layer {
  position: absolute; inset: 0; z-index: 9;
  display: flex; align-items: center; justify-content: center;
  padding: 8px; box-sizing: border-box;
}
#modal-layer[hidden] { display: none; }
#modal-dim { position: absolute; inset: 0; background: rgba(0, 0, 0, .72); }
/* A description is not a dialog: it sits out of the way at the foot of the play
   area, over the status bars if it must, and leaves the map undimmed. */
#modal-layer.pass-through {
  pointer-events: none; align-items: flex-end;
  padding-bottom: calc(var(--toolbar-height) + 12px);
}
#modal-layer.pass-through #modal-dim { display: none; }
#modal-layer.pass-through #modal-scroll {
  /* A strip along the top for the close button, for the descriptions whose
     first line runs far enough right to sit under it. */
  padding: 26px 8px 5px;
  border-color: rgba(144, 184, 222, .22);
  box-shadow: 0 -4px 18px rgba(0, 0, 0, .75);
}
/* The description covers the status bars, and reading those is often the reason
   it was opened in the first place, so it takes a tap anywhere on itself to
   close - and carries a target for it that does not need aiming. */
#modal-layer.pass-through #modal-scroll,
#modal-layer.pass-through #modal-close { pointer-events: auto; }
#modal-frame { position: relative; display: flex; max-width: 100%; max-height: 100%; }
#modal-close {
  position: absolute; top: 1px; right: 1px; z-index: 1;
  display: none; place-items: center;
  width: 32px; height: 25px; padding: 0;
  border: 0; border-radius: 0; color: var(--pale);
  /* The box's own background, not a shape: nothing of the button shows except
     the character, but text scrolled up under it stays out of its way. */
  background: #000;
  font-size: 17px; line-height: 1;
}
#modal-layer.pass-through #modal-close { display: grid; }
/* The corner was empty: the button goes straight on it and the box keeps its
   ordinary padding. */
#modal-layer.pass-through.close-inline #modal-scroll { padding-top: 5px; }
/* Brogue surrounds a dialog with black padding that fades into the screen
   behind it; the glow stands in for that fade. */
#modal-scroll {
  /* The frame carries the size limit; a flex item has to be allowed to shrink
     below its content before it will scroll inside one. */
  position: relative; min-width: 0; min-height: 0; max-width: 100%; max-height: 100%;
  padding: 10px; overflow: auto; overscroll-behavior: contain;
  border: 1px solid rgba(144, 184, 222, .34); border-radius: 4px;
  background: #000;
  box-shadow: 0 0 26px 14px rgba(0, 0, 0, .82), 0 22px 60px rgba(0, 0, 0, .9);
  scrollbar-width: none; touch-action: pan-x pan-y;
}
/* A scroll container's own touch-action is what the browser consults, so this
   box opts back into panning that the page as a whole refuses. When it has
   nothing to scroll, the drag is handed to whatever is behind it - and behind
   it is the page. `contain` is not enough: it only applies while the element
   really scrolls, and a description usually fits. */
#modal-layer.pass-through #modal-scroll { overscroll-behavior: none; }
#modal-scroll::-webkit-scrollbar { display: none; }
#modal { display: block; }

#toolbar {
  position: absolute; z-index: 10;
  right: max(4px, env(safe-area-inset-right)); bottom: max(3px, env(safe-area-inset-bottom));
  display: flex; align-items: center; padding: 4px;
  border: 1px solid var(--line); border-radius: 6px;
  background: linear-gradient(90deg, rgba(0,0,0,.1), rgba(2,4,10,.94) 15%);
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
#toolbar button {
  display: grid; place-items: center; min-width: 48px; height: 48px; padding: 3px 6px;
  border: 0; border-right: 1px solid rgba(145,185,222,.12); background: transparent;
  color: var(--pale); border-radius: 3px;
}
#toolbar button:active, #toolbar button[aria-pressed="true"] { background: var(--button-active); color: var(--ember); }
/* Drawn rather than typeset: emoji and geometric glyphs come from different
   fonts and will not agree on size or baseline. */
#toolbar svg {
  display: block; width: 22px; height: 22px;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
#toolbar small { font-family: inherit; font-size: 8px; letter-spacing: .04em; color: #8faabe; }

.panel {
  position: absolute; z-index: 14; right: max(6px, env(safe-area-inset-right));
  bottom: calc(var(--toolbar-height) + max(5px, env(safe-area-inset-bottom)));
  width: min(290px, calc(100vw - 12px)); max-height: calc(100vh - 80px); overflow: auto;
  padding: 6px; border: 1px solid var(--line); border-radius: 5px;
  background: var(--panel); box-shadow: 0 12px 36px #000;
}
.panel[hidden] { display: none; }
#more-menu button {
  display: flex; gap: 8px; justify-content: space-between; align-items: center;
  width: 100%; min-height: 42px; padding: 9px 12px;
  border: 0; border-bottom: 1px solid rgba(145,185,222,.1); background: transparent;
  /* Every row is one line: the columns are sized for the longest label. */
  font-size: 13px; white-space: nowrap; text-align: left;
}
#more-menu button:active { background: var(--button-active); }
/* The More menu is a sheet across the foot of the screen rather than a box
   hanging off the burger, which is nowhere near it. Its rows fill the width in
   as many columns as fit, so the whole menu is usually there without a scroll,
   and the catcher behind it means a tap anywhere else puts it away. */
#more-menu {
  left: max(6px, env(safe-area-inset-left)); right: max(6px, env(safe-area-inset-right));
  display: grid; grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  width: auto; max-height: min(58vh, calc(100vh - var(--toolbar-height) - 24px));
  padding: 0; border-bottom: 0; border-radius: 8px 8px 0 0;
}
/* The sheet's own display value has to lose to hidden, which .panel[hidden]
   alone cannot do against an id. */
#more-menu[hidden] { display: none; }
#more-menu button { border-right: 1px solid rgba(145, 185, 222, .1); }
/* The two stair commands read as a pair, so they stay one beside the other
   however many columns the sheet is wide. */
.menu-pair { display: grid; grid-template-columns: 1fr 1fr; }
.menu-pair button { padding-inline: 9px; }
/* Below the toolbar, so the burger still closes the sheet it opened and the
   other actions stay live; above everything else, which the sheet takes out of
   play while it is up. */
#menu-catcher { position: absolute; inset: 0; z-index: 9; }
#menu-catcher[hidden] { display: none; }
/* font: 11px inherit is invalid - inherit cannot appear inside a shorthand -
   so this used to fall back to the browser's own monospace at whatever size
   the row happened to be, next to text set in Brogue Mono. */
kbd { color: #7996ad; font-family: inherit; font-size: 11px; }

#text-entry { width: min(420px, calc(100vw - 12px)); }
#text-entry header { display: flex; align-items: center; justify-content: space-between; margin: 0 0 8px 4px; }
#text-entry label { color: var(--ember); font-size: 12px; text-transform: uppercase; }
#text-entry #text-close { width: 36px; height: 36px; padding: 0; border: 0; background: transparent; color: var(--pale); font-size: 24px; }
#text-entry div { display: flex; gap: 5px; }
#text-entry input { min-width: 0; flex: 1; height: 44px; border: 1px solid var(--line); border-radius: 3px; background: #02040a; padding: 8px; }
#text-entry button { min-width: 70px; border: 1px solid var(--line); border-radius: 3px; background: var(--button-active); }

dialog {
  width: min(520px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow: auto;
  border: 1px solid var(--line); border-radius: 6px; padding: 20px;
  background: #080b17; color: #dcecff; box-shadow: 0 20px 60px #000;
}
dialog::backdrop { background: rgba(0,0,0,.78); backdrop-filter: blur(2px); }
dialog h2 { margin-top: 0; color: var(--ember); font-size: 18px; }
dialog p, dialog dd, dialog dt { font-size: 12px; line-height: 1.55; }
dialog button { min-height: 42px; padding: 8px 18px; border: 1px solid var(--line); border-radius: 3px; background: var(--button); }
dialog a { color: #9dd5ff; }
dialog dl > div { display: grid; grid-template-columns: 150px 1fr; border-bottom: 1px solid rgba(145,185,222,.1); }
dialog dt { color: var(--pale); }
dialog dd { margin: 0; }
.legal { color: #8499aa; }

#run-summary-screen {
  position: absolute; inset: 0; z-index: 31;
  display: grid; grid-template-rows: 1fr auto 1fr; place-items: center;
  padding: max(28px, env(safe-area-inset-top)) max(18px, env(safe-area-inset-right))
    max(28px, env(safe-area-inset-bottom)) max(18px, env(safe-area-inset-left));
  background: #000; color: #ddd; cursor: pointer;
  font-size: clamp(13px, 3.5vw, 18px); line-height: 1.6; text-align: center;
  touch-action: manipulation;
}
#run-summary-screen[hidden] { display: none; }
#run-summary-screen div { grid-row: 2; }
#run-summary-screen p { margin: 0; }
#run-summary-score { color: #ffff80; }
#run-summary-screen > p:last-child { grid-row: 3; align-self: end; color: #9980ff; }

.archive-screen {
  position: absolute; inset: 0; z-index: 30; overflow: auto;
  padding: max(28px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right))
    max(58px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
  background: #000; color: #ddd;
  font-size: clamp(11px, 3vw, 16px); touch-action: pan-y;
}
.archive-screen[hidden] { display: none; }
.archive-screen h2 {
  height: 2.2em; margin: 0; color: #ffff80;
  font: inherit; text-align: center;
}
#high-scores-screen { cursor: pointer; }
.score-list { width: min(100%, 900px); margin: 0 auto; padding: 0; list-style: none; counter-reset: score-rank; }
.score-list li {
  display: grid; grid-template-columns: 3ch 8ch 11ch minmax(0, 1fr); gap: 1ch;
  min-height: 1.65em; counter-increment: score-rank; color: #ddd; white-space: nowrap;
}
.score-list li::before { content: counter(score-rank) ")"; text-align: right; }
.score-value { font-variant-numeric: tabular-nums; }
.score-date { font-size: inherit; }
.score-description {
  min-width: 0; overflow: visible; white-space: normal;
  overflow-wrap: anywhere; line-height: 1.35;
}
#high-score-empty, #high-score-error,
#saved-game-empty, #saved-game-error { margin: 20vh 0 0; text-align: center; }
.score-continue, .archive-continue {
  position: fixed; right: 0; bottom: max(20px, env(safe-area-inset-bottom)); left: 0;
  width: max-content; margin: 0 auto; padding: 5px 14px;
  border: 0; background: #000; color: #9980ff; text-align: center;
}
.saved-game-list {
  width: min(100%, 900px); margin: 0 auto; padding: 0;
  list-style: none; counter-reset: save-rank;
}
.saved-game-list li {
  display: flex; align-items: baseline; min-height: 1.65em;
  counter-increment: save-rank; white-space: nowrap;
}
.saved-game-list li::before {
  content: counter(save-rank) ")"; flex: 0 0 3ch; color: #ddd; text-align: right;
  margin-right: 1ch;
}
.saved-game-list button {
  appearance: none; min-height: 0; padding: 0;
  border: 0; border-radius: 0; background: transparent;
  color: inherit; font: inherit; line-height: inherit;
}
.saved-game-list button:active { color: #777; }
.load-saved-game {
  display: flex; min-width: 0; flex: 1 1 auto; align-items: baseline;
  gap: 1ch; text-align: left;
}
.saved-game-list .delete-saved-game {
  flex: 0 0 auto; margin-left: 2ch; color: #9980ff;
}
.save-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: #ddd; }

/* The discoveries list keeps Brogue's own colours: identified kinds in white
   behind the item's glyph, undiscovered ones dark with the good/bad magic hint
   the game has revealed, and the share of what is left to find in brackets. */
.discovery-sections { width: min(100%, 52ch); margin: 0 auto; }
.discovery-sections h3 {
  margin: 1.4em 0 0.4em; color: #8066e6; font: inherit; font-weight: normal;
}
.discovery-sections section:first-child h3 { margin-top: 0; }
.discovery-list { margin: 0; padding: 0; list-style: none; }
.discovery-list li {
  display: grid; grid-template-columns: 2ch minmax(0, 1fr) max-content; gap: 1ch;
  min-height: 1.65em; text-transform: uppercase;
}
.discovery-mark { text-align: center; }
.discovery-name { min-width: 0; overflow-wrap: anywhere; }
.discovery-chance { font-variant-numeric: tabular-nums; }
.discovery-known { color: #fff; }
.discovery-known .discovery-mark { color: #fff200; }
.discovery-good, .discovery-bad, .discovery-unknown { color: #4d4d4d; }
.discovery-good .discovery-mark { color: #4d4080; }
.discovery-bad .discovery-mark { color: #80404d; }
#discovery-empty, #discovery-error { margin: 20vh 0 0; text-align: center; }

/* The tutorial is the Scores screen with a picture on it: the same black page
   and the same tap-anywhere-to-continue, over one room drawn by the standalone
   preview renderer. The rows below the heading are the illustration and the
   text, so a screen with no illustration can centre its words instead. */
#tutorial-screen {
  display: grid; grid-template-rows: auto minmax(0, 1fr); cursor: pointer;
  padding-bottom: max(80px, env(safe-area-inset-bottom));
}
/* An id outranks .archive-screen[hidden], so the shared rule cannot put this
   screen away on its own: naming the display here means naming it hidden too. */
#tutorial-screen[hidden] { display: none; }
/* The room stands clear of the heading, and further clear of the words below
   it, so the illustration reads as its own thing rather than as a header for
   the paragraph. */
#tutorial-stage {
  display: block; width: min(100%, 38ch); height: auto; margin: 2.6em auto 3.6em;
  background: #000; image-rendering: auto;
}
#tutorial-stage[hidden] { display: none; }
.tutorial-body { width: min(100%, 46ch); margin: 0 auto; align-self: start; }
#tutorial-screen.tutorial-plain .tutorial-body { align-self: center; }
#tutorial-text { color: #ddd; line-height: 1.5; text-align: center; }
#tutorial-text p { margin: 0 0 1em; }
#tutorial-text p:last-child { margin-bottom: 0; }
/* The colour a toolbar button takes when it is pressed, so the actions the
   screen lists read as the buttons they name. */
.tutorial-action { color: var(--ember); }
.tutorial-footer {
  position: fixed; right: 0; bottom: max(20px, env(safe-area-inset-bottom)); left: 0;
  text-align: center;
}
.tutorial-footer p { margin: 0; color: #9980ff; }
#tutorial-progress { color: #4d4d4d; letter-spacing: .3em; }

.options-list {
  width: min(100%, 70ch); margin: 2em auto 0; padding: 0;
}
.options-list > div {
  display: flex; align-items: baseline; margin-bottom: 1em; white-space: nowrap;
}
.options-list dt { flex: 0 0 12ch; color: #ddd; }
.options-list dd { margin: 0; color: #555; line-height: 1.8; }
.options-list button {
  appearance: none; min-height: 0; padding: 0;
  border: 0; border-radius: 0; background: transparent;
  color: #777; font: inherit; line-height: inherit;
}
.options-list button[aria-pressed="true"] { color: #ffff80; }
.options-list button:active { color: #fff; }
#tile-preview {
  display: block; width: min(100%, 34ch); height: auto; margin: 1.5em auto 0;
  background: #000; image-rendering: auto;
}

@media (orientation: portrait) and (max-width: 700px) {
  :root {
    --portrait-toolbar: 62px; --portrait-message: 22px; --portrait-hud: 208px;
    /* A breath between the last message and the dungeon, in the bar rather
       than in the map, so the text does not end hard against it. The history
       panel keeps the same gap under its last line. */
    --portrait-message-gap: 6px;
  }

  #game-surface {
    bottom: calc(var(--portrait-toolbar) + env(safe-area-inset-bottom));
    border-bottom: 1px solid var(--line);
  }
  .portrait-game #game-surface {
    top: calc(var(--portrait-message) + var(--portrait-message-gap));
    bottom: calc(var(--portrait-toolbar) + var(--portrait-hud) + env(safe-area-inset-bottom));
    border-bottom: 0;
  }

  /* Three map rows tall. The lines sit on the bottom edge so a long current
     message pushes older ones off the top rather than resizing anything. */
  .portrait-game #portrait-messages {
    position: absolute; z-index: 8; display: block;
    left: env(safe-area-inset-left); right: env(safe-area-inset-right); top: 0;
    height: calc(var(--portrait-message) + var(--portrait-message-gap));
    overflow: hidden;
    background: #000; color: #dcecff;
    transition: height 220ms ease;
  }
  #portrait-message-lines {
    position: absolute; bottom: var(--portrait-message-gap); left: 8px; right: 8px;
  }

  /* The history is the same rows, grown down over the map: same type, same
     colours, no frame around it. Its height is set on the element as it opens,
     which is what the transition runs on. */
  .portrait-game #portrait-messages.expanded {
    z-index: 31;
    display: flex; flex-direction: column;
    overflow-y: auto; overscroll-behavior: none; touch-action: pan-y;
    box-shadow: 0 12px 24px rgba(0, 0, 0, .8);
  }
  /* Held to the bottom by a margin rather than by justify-content, which clips
     the top of a scrolled flex column. The newest message stays at the foot of
     the panel, where it already was.

     The side padding repeats the 8px the collapsed rows are inset by: leaving
     it out let the text box widen as the panel opened, which rewrapped every
     line the moment it appeared. */
  .portrait-game #portrait-messages.expanded #portrait-message-lines {
    position: static; margin-top: auto;
    padding: 4px 8px var(--portrait-message-gap);
  }
  /* A scrollbar would take its width out of the text as well. */
  .portrait-game #portrait-messages.expanded { scrollbar-width: none; }
  .portrait-game #portrait-messages.expanded::-webkit-scrollbar { display: none; }
  @media (prefers-reduced-motion: reduce) {
    .portrait-game #portrait-messages { transition: none; }
  }

  #portrait-hud {
    position: absolute; z-index: 8;
    left: env(safe-area-inset-left); right: env(safe-area-inset-right);
    bottom: calc(var(--portrait-toolbar) + env(safe-area-inset-bottom));
    height: var(--portrait-hud);
    flex-direction: column;
    background: #000;
  }
  .portrait-game #portrait-hud:not([hidden]) { display: flex; }

  #modal-layer { bottom: calc(var(--portrait-toolbar) + env(safe-area-inset-bottom)); }
  /* The layer already stops above the toolbar here; just clear it. */
  #modal-layer.pass-through { padding-bottom: 12px; }

  #portrait-sidebar-scroll {
    flex: 1 1 auto; min-height: 0; overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; touch-action: pan-x;
  }
  #portrait-sidebar-scroll::-webkit-scrollbar { display: none; }
  #portrait-sidebar { display: block; image-rendering: auto; }

  /* A crowded floor gives the bar more columns than fit, and nothing on screen
     says so: there is no scrollbar, and a column edge reads as the end of the
     list. So the bar's last line becomes the sign - the row it costs is one the
     columns would only half fill - and doubles as a way to page it that does
     not need a swipe. It is there only while there is somewhere to go, and each
     arrow keeps its space when its end is reached so the line does not shuffle.
     Hidden entirely when everything fits, which gives the row back. */
  #portrait-more {
    flex: 0 0 auto; display: flex; height: 18px;
    font-size: 11px; line-height: 18px; letter-spacing: .06em;
  }
  #portrait-more[hidden] { display: none; }
  #portrait-more button {
    display: flex; align-items: center; gap: 3px;
    flex: 1 1 0; min-width: 0; padding: 0 6px;
    border: 0; background: transparent; color: var(--pale); opacity: .68;
    font: inherit; letter-spacing: inherit;
  }
  /* Drawn on the toolbar's grid rather than typeset: an arrow glyph comes from
     whichever font has one, and agrees with neither the size nor the baseline
     of the word beside it. Heavier than the toolbar's stroke only because it is
     drawn half the size. */
  #portrait-more svg {
    display: block; width: 12px; height: 12px;
    fill: none; stroke: currentColor; stroke-width: 2.6;
    stroke-linecap: round; stroke-linejoin: round;
  }
  #sidebar-prev { justify-content: flex-start; }
  #sidebar-next { justify-content: flex-end; }
  #portrait-more button:disabled { visibility: hidden; }
  #portrait-flavor {
    flex: 0 0 auto; padding: var(--portrait-message-gap) 8px 0; overflow: hidden;
    /* Keep the full width so the text still wraps to two lines. The measured
       two-line height is the content box; the top gap is added outside it. */
    box-sizing: content-box;
  }

  #toolbar {
    left: env(safe-area-inset-left); right: env(safe-area-inset-right);
    bottom: env(safe-area-inset-bottom); height: var(--portrait-toolbar);
    justify-content: stretch; padding: 5px 4px;
    border-width: 1px 0 0; border-radius: 0;
    background: #04060d;
  }
  #toolbar button { flex: 1 1 0; min-width: 0; height: 50px; padding-inline: 2px; }

  .panel {
    left: max(6px, env(safe-area-inset-left)); right: max(6px, env(safe-area-inset-right));
    bottom: calc(var(--portrait-toolbar) + max(5px, env(safe-area-inset-bottom)));
    width: auto; max-height: calc(100vh - var(--portrait-toolbar) - 18px);
  }
  .portrait-game .panel {
    bottom: calc(var(--portrait-toolbar) + var(--portrait-hud) + max(5px, env(safe-area-inset-bottom)));
  }
  /* The menu keeps the foot of the screen in play too, over the status panel
     rather than above it. */
  #more-menu, .portrait-game #more-menu {
    left: env(safe-area-inset-left); right: env(safe-area-inset-right);
    bottom: calc(var(--portrait-toolbar) + env(safe-area-inset-bottom));
    max-height: min(58vh, calc(100vh - var(--portrait-toolbar) - 24px));
  }
  .ending.portrait-game #game-surface {
    bottom: calc(var(--portrait-hud) + env(safe-area-inset-bottom));
  }
  .ending.portrait-game #portrait-hud { bottom: env(safe-area-inset-bottom); }

}
@media (max-height: 440px) {
  #toolbar button { min-width: 46px; height: 46px; }
  #toolbar button:nth-child(2), #toolbar button:nth-child(4) { display: none; }
}
/* A long press on the play surface raises the loupe, so the browser must not
   also read it as the start of a text selection. The message and status rows
   are real text and a browser tab suppresses nothing by itself - only an
   installed app gets the blanket rule below. The archive screens and dialogs
   are left selectable, so a score or a line of the About panel can be copied. */
#game-surface, #portrait-messages, #portrait-hud, #toolbar {
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}

@media (display-mode: standalone), (display-mode: fullscreen) {
  #app { user-select: none; }
}

/* Debug info, switched on from Options. Deliberately plain and out of the way:
   it is a measuring tool, not part of the game. */
#perf {
  position: fixed;
  top: max(4px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  padding: 3px 8px;
  border-radius: 6px;
  background: rgba(3, 4, 9, 0.82);
  color: var(--ember);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
}
