/*!
 * KRYON — the game shell.
 *
 * The canvas is the page: everything else floats over it as a thin HUD and one
 * glass dialogue panel.  The room list and the agent chips are rendered for
 * assistive technology rather than for the eye, which is why they live here as
 * visually-hidden lists instead of a sidebar the player has to look past.
 */

/* ------------------------------------------------------------ pixel chrome */

/* The chrome face is drawn on a fixed grid.  A synthesised weight would smear
   that drawing instead of emboldening it, so weight is never faked here.  Font
   smoothing is left to the browser: switching it off makes small text thinner
   and harder to read, which is the opposite of what the chrome needs. */
.game {
  font-synthesis: none;
}

/* ------------------------------------------------------------------ page */

/* `hidden` is how the whole page shows and hides things: the menu's three panes,
   the "no rooms yet" line, the fold button, the pending-actions block.  The
   browser hides a `hidden` element through its own stylesheet, and *any* author
   rule that sets `display` on that element outranks it — author styles beat
   user-agent styles regardless of specificity.

   `.menu__pane { display: flex }` did exactly that.  The attribute was set
   correctly and the panes were genuinely `hidden` in the DOM; they were simply
   still laid out, so the menu showed rooms, account and settings stacked on top
   of each other while the tab highlight moved between them.  The tabs worked.
   The content under them did not belong to the tab.

   `!important` is the point of this rule, not an accident: without it a later
   `.menu__pane { display: flex }` wins again on source order, which is how the
   bug got in twice over.  Two narrower rules already did this job for the panel
   and the pending block; this makes the whole file obey it. */
[hidden] {
  display: none !important;
}

.page-game {
  /* 100vh first so a browser without dynamic viewport units still fills the
     screen; 100dvh then keeps the page out from under the collapsing address
     bar.  The var carries the height the canvas was actually given, which is
     the visual viewport once a keyboard is up. */
  height: 100vh;
  height: 100dvh;
  height: var(--game-height, 100dvh);
  /* Anchored to the viewport rather than laid out in the flow.  The map fills
     the screen and the dialogue panel hangs off it, so a document that could
     scroll is a map the player can push off the top of the window - and a
     finger dragging the map would scroll the page instead of moving the city.
     Fixed positioning also keeps the stage anchored to the top when a keyboard
     shrinks the visual viewport, which is what the measured height is for. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  overflow: hidden;
  margin: 0;
  background: var(--bg);
}

.page-game main {
  height: 100%;
}

/* ----------------------------------------------------------------- stage */

.game {
  position: relative;
  height: 100%;
  width: 100%;
}

.game__canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The map never scrolls or zooms, so the browser must not try to: a doubled
     tap would otherwise zoom the page instead of reaching the game. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* The room you walked into.  It sits over the map, filling the stage, and is
   drawn at pixel resolution: `pixelated` is what keeps every chunky square
   square when the browser blows the buffer up to the size of the screen. */
.game__room {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  background: var(--bg);
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* What is left when the world cannot be drawn at all.  It lives here, not in a
   `style` attribute, so the page has one place to change. */
.game__noscript {
  padding: 24px;
  color: var(--text);
  font-family: var(--font-pixel);
  font-size: 14px;
  line-height: 1.5;
}

/* The top band: where you are, and what the world says about it.
   It used to paint itself `--bg` across the whole window and rule a border under
   it, and in a room that stripe lay over the place it described: 190 pixels of
   solid black along the top of the art, more once a board or a workflow filled
   in.  The band now paints nothing.  The words own a small card in one corner
   and the room keeps everything else. */
.game__hud {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: 12px 16px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  pointer-events: none;
}

/* The card.  Its fill is translucent on purpose, so the room reads through it,
   and it stops well short of the window's height, so no board and no workflow
   can grow it into a curtain again.  The width leaves a lane on the right for
   the "Карта" and "Меню" controls. */
.game__place {
  min-width: 0;
  max-width: min(420px, calc(100% - 170px));
  max-height: min(58vh, 420px);
  overflow: hidden;
  padding: 12px 14px;
  border: var(--border-width) solid var(--border);
  border-radius: 0;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
}

/* The lockup: a boxed initial, then the name.  The name is set in the display
   face at a whole multiple of its 8-pixel grid, so each letter lands on a pixel
   instead of being smoothed into mush.  The tracking is gone because this face
   is already one em wide per glyph — spacing it out only broke the word apart. */
.game__brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-bottom: 10px;
  font: 400 16px/1 var(--font-display);
  letter-spacing: 0;
  text-transform: uppercase;
  color: var(--accent);
}

.game__brand-mark {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 0;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  font-size: 12px;
  letter-spacing: 0;
}

.game__room-title {
  margin: 0 0 4px;
  font: 600 22px/1.2 var(--font-pixel);
  color: var(--text);
  text-shadow: 2px 2px 0 0 #000000;
}

/* Three lines, and no more.  A long description used to push the whole band down
   over the room; clamped, it can only fill the card it was given. */
.game__room-desc {
  margin: 0;
  font: 400 13px/1.5 var(--font-pixel);
  color: var(--text-muted);
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.game__board {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 8px;
  padding: 8px 10px;
  border: 1px solid var(--accent-border);
  border-radius: 0;
  background: color-mix(in srgb, var(--accent) 9%, var(--bg));
  max-height: 72px;
  overflow: hidden;
}

.game__board[hidden] {
  display: none;
}

.game__board-line {
  font: 400 12px/1.5 var(--font-pixel);
  color: var(--text-muted);
}

.game__workflow {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 6px;
  padding: 6px 10px;
  border: 1px solid var(--warn-border);
  border-radius: 0;
  background: var(--warn-soft);
  max-height: 72px;
  overflow: hidden;
}

.game__workflow[hidden] {
  display: none;
}

.game__workflow-line {
  font: 400 12px/1.5 var(--font-pixel);
  color: var(--warn);
}

.game__status {
  min-height: 18px;
  margin-top: 10px;
  font: 400 12px/1.5 var(--font-pixel);
  color: var(--accent);
}

/* The corner controls.  They used to sit on the bar's own black paint; with the
   bar gone they carry a plate of their own, or "Меню" would be grey text over
   whatever the room happens to draw behind it. */
.game__links {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 8px;
  border: var(--border-width) solid var(--border);
  border-radius: 0;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  font: 400 12px/1 var(--font-pixel);
  color: var(--text-subtle);
  pointer-events: auto;
}

.game__links a,
.game__links button {
  color: var(--text-muted);
  text-decoration: none;
  border-bottom: 1px dotted var(--border-strong);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  font: inherit;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease), border-bottom-color var(--dur-fast) var(--ease);
}

.game__links a:hover,
.game__links button:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.game__links a:focus-visible,
.game__links button:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
  outline-offset: 3px;
}

/* The way back out of a room.  It is the one link that is a real place to go,
   so it reads as a pill rather than as part of the quiet text row. */
.game__links .game__map-back {
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 0;
  padding: 5px 12px;
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.game__links .game__map-back:hover {
  border-color: var(--accent);
  border-bottom-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 20%, transparent);
}

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

.game__dock {
  position: absolute;
  inset: 0;
  padding: 0 16px 16px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
  gap: 10px;
  pointer-events: none;
}

/* The plate under the panel, on its own layer so the dock can cover the whole
   window without tinting the sky.  A pixel UI has no fades: the dock is a solid
   plate with one hard edge, not a gradient the eye has to soften. */
.game__dock::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 160px;
  background: var(--bg);
  border-top: var(--border-width) solid var(--border);
  pointer-events: none;
}

/* The dock's own content keeps to the flow at the bottom of the window.  The
   panel is measured against it, so it can rest above the hint and the pending
   proposals instead of swallowing them. */
.game__dock-content {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.game__hint {
  font: 400 12px/1.4 var(--font-pixel);
  color: var(--text-subtle);
}

/* The panel floats: it can be dragged anywhere over the map, and folded down to
   its title bar so the room it is talking about is not hidden behind it. */
.game__panel {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  pointer-events: auto;
  transform: translate(var(--panel-x, 0px), var(--panel-y, 0px));
  width: min(760px, calc(100% - 32px));
  /* However long the conversation runs, the panel stops short of taking the
     stage: the city keeps at least this much room no matter what is written in
     the log, which is the whole point of framing it in what is left over. */
  max-height: calc(100% - 104px);
  display: flex;
  flex-direction: column;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  border-radius: 0;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  box-shadow: var(--shadow-lg);
  backdrop-filter: none;
  overflow: hidden;
}

.game__panel.is-dragging {
  box-shadow: var(--shadow-lg), 0 0 0 1px var(--accent-border);
}

.game__panel[hidden] {
  display: none;
}

.game__panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  /* The head is six things wide - a grip, the agent's name, two switch boxes
     and two buttons - and the panel clips what it holds.  A wrapped second row
     is ugly; a clipped control is a control nobody can press.  The narrow-screen
     rules below lay the wrap out properly; this one only refuses to clip. */
  flex-wrap: wrap;
  row-gap: 10px;
  padding: 12px 16px;
  border-bottom: var(--border-width) solid var(--border-subtle);
  cursor: grab;
  touch-action: none;
  user-select: none;
}

.game__panel.is-dragging .game__panel-head {
  cursor: grabbing;
}

/* Six dots: the only part of the panel a player is meant to grab.  A checkered
   tile, so the dots are whole pixels instead of a smoothed circle. */
.game__grip {
  flex: 0 0 auto;
  width: 12px;
  height: 16px;
  border-radius: 0;
  background-image: repeating-conic-gradient(var(--text-subtle) 0 25%, transparent 0 50%);
  background-size: 4px 4px;
  background-position: 0 2px;
}

.game__panel-tools {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}

.game__fold {
  font: 400 12px/1 var(--font-pixel);
  color: var(--text-muted);
  background: transparent;
  borderNone: var(--border-width) solid var(--border-subtle);
  border-radius: 0;
  padding: 7px 13px;
  cursor: pointer;
}

.game__fold:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.game__settings {
  font: 400 12px/1 var(--font-pixel);
  color: var(--text-muted);
  background: transparent;
  borderNone: var(--border-width) solid var(--border-subtle);
  border-radius: 0;
  padding: 7px 13px;
  cursor: pointer;
}

.game__settings:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}

.game__panel-body {
  display: flex;
  flex-direction: column;
  /* The body gives way first when the panel runs out of height, so the log is
     what shrinks and scrolls and the field the player types into stays put. */
  flex: 1 1 auto;
  min-height: 0;
}

.game__panel.is-folded .game__panel-body {
  display: none;
}

.game__panel.is-folded .game__panel-head {
  border-bottom: 0;
}

.game__agent-name {
  font: 600 15px/1.2 var(--font-pixel);
  color: var(--text);
}

.game__agent-name::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 8px;
  border-radius: 0;
  background: var(--accent);
  box-shadow: 3px 3px 0 0 var(--accent);
  vertical-align: middle;
}

.game__share,
.game__voice {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 400 12px/1 var(--font-pixel);
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
}

.game__share input,
.game__voice input {
  accent-color: var(--accent);
  width: 15px;
  height: 15px;
}

.game__log {
  list-style: none;
  margin: 0;
  padding: 14px 16px;
  /* A third of the window, but never more than 260px: on a tall screen a log
     that grew with the window would leave the city a strip at the top. */
  max-height: min(34vh, 260px);
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--border) transparent;
}

.game__log:empty::after {
  content: "Здесь начинается разговор.";
  font: 400 13px/1.5 var(--font-pixel);
  color: var(--text-subtle);
}

.game__msg {
  margin: 0 0 9px;
  padding: 9px 13px;
  border-radius: 0;
  font: 400 14px/1.55 var(--font-pixel);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.game__msg--user {
  margin-left: auto;
  max-width: 82%;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid var(--accent-border);
  color: var(--accent-text);
}

.game__msg--assistant {
  max-width: 92%;
  background: transparent;
  borderNone: var(--border-width) solid var(--border-subtle);
  color: var(--text);
}

/* Offered only when the browser refused to start the reply's audio on its
   own; the tap is the gesture it was waiting for. */
.game__msg-play {
  display: block;
  margin-top: 8px;
  padding: 5px 10px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  border-radius: 0;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent-text);
  font: 600 12px/1 var(--font-pixel);
  cursor: pointer;
}

.game__msg--system {
  max-width: 92%;
  background: none;
  border: 1px dashed var(--danger-border);
  color: var(--warn);
  font-size: 13px;
}

.game__form {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: 10px;
  padding: 12px 14px 14px;
  border-top: var(--border-width) solid var(--border-subtle);
}

.game__input {
  flex: 1;
  min-height: 42px;
  max-height: 140px;
  padding: 11px 13px;
  borderNone: var(--border-width) solid var(--border-subtle);
  border-radius: 0;
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  color: var(--text);
  font: 400 14px/1.45 var(--font-pixel);
  resize: none;
  outline: none;
}

.game__input:focus {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.game__input:disabled {
  opacity: 0.55;
}

.game__send {
  flex: none;
  padding: 12px 20px;
  border: 1px solid color-mix(in srgb, var(--accent) 55%, transparent);
  border-radius: 0;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-text);
  font: 600 14px/1 var(--font-pixel);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}

.game__send:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  box-shadow: 4px 4px 0 0 var(--accent-border);
}

.game__send:disabled {
  opacity: 0.5;
  cursor: default;
}

.game__mic {
  flex: none;
  padding: 12px 14px;
  borderNone: var(--border-width) solid var(--border);
  border-radius: 0;
  background: var(--surface-hover);
  color: var(--text);
  font: 600 14px/1 var(--font-pixel);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.game__mic:hover:not(:disabled) {
  background: var(--surface-hover);
}

.game__mic:disabled {
  opacity: 0.5;
  cursor: default;
}

/* Recording is a state the player must read at a glance, on a phone in
   daylight: the label flips to "Стоп", and the pulse repeats the same
   fact for anyone who reads motion better than words. */
.game__mic--recording {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 22%, transparent);
  color: var(--danger);
  animation: game__mic-pulse 1.2s ease-in-out infinite;
}

@keyframes game__mic-pulse {
  50% {
    box-shadow: 3px 3px 0 0 var(--danger-border);
  }
}

@media (prefers-reduced-motion: reduce) {
  .game__mic--recording {
    animation: none;
  }
}

/* ---------------------------------------------------------------- pending */

.game__pending {
  pointer-events: auto;
  width: min(760px, 100%);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.game__pending[hidden] {
  display: none;
}

.game__card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 12px 14px;
  border: 1px solid var(--danger-border);
  border-radius: 0;
  background: var(--danger-soft);
  box-shadow: var(--shadow-lg);
}

.game__card-summary {
  font: 400 14px/1.45 var(--font-pixel);
  color: var(--text);
}

.game__card-actions {
  display: flex;
  gap: 8px;
  flex: none;
}

.game__card-button {
  padding: 8px 13px;
  borderNone: var(--border-width) solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font: 400 13px/1 var(--font-pixel);
  cursor: pointer;
}

.game__card-button--confirm {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-text);
  font-weight: 600;
}

.game__card-button:disabled {
  opacity: 0.45;
  cursor: default;
}

/* ------------------------------------------------------- keyboard access */

/* The map is drawn; these lists are how the same moves are made without a
   mouse.  Hidden from sight, never from assistive technology. */
.game__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.game__sr:focus-within {
  position: absolute;
  top: 96px;
  left: 22px;
  width: min(320px, calc(100vw - 44px));
  height: auto;
  max-height: 44vh;
  overflow-y: auto;
  clip: auto;
  clip-path: none;
  margin: 0;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  border-radius: 0;
  background: var(--surface);
  z-index: 5;
}

.game__sr h2 {
  margin: 0 0 8px;
  font: 600 12px/1 var(--font-pixel);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

.game__rooms,
.game__agents {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.game__agents {
  margin-top: 10px;
}

.game__room,
.game__agent {
  padding: 7px 11px;
  borderNone: var(--border-width) solid var(--border);
  border-radius: 0;
  background: transparent;
  color: var(--text-muted);
  font: 400 13px/1 var(--font-pixel);
  cursor: pointer;
}

.game__room.is-active,
.game__agent.is-active {
  border-color: color-mix(in srgb, var(--accent) 60%, transparent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent-text);
}

.game__agents-empty {
  font: 400 12px/1.4 var(--font-pixel);
  color: var(--text-subtle);
}

/* -------------------------------------------------------------- narrow */

@media (max-width: 720px) {
  .game__hud {
    padding: 10px 12px;
  }

  .game__room-title {
    font-size: 18px;
  }

  .game__room-desc {
    display: none;
  }

  .game__links {
    gap: 10px;
  }

  .game__dock {
    padding: 0 10px 10px;
  }

  .game__log {
    max-height: 26vh;
  }

  .game__card {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }

  .game__card-actions {
    justify-content: flex-end;
  }

  /* The head holds six things - the grip, the agent's name, two switch boxes and
     two buttons - and one row of them is wider than a phone.  It ran past the
     panel's own edge, and the panel clips what it holds, so the last control was
     not merely narrow: it was gone.  That control is "Развернуть", and a stage
     under 720 pixels always opens the panel folded, so the one button that
     brings the conversation back was the one a phone could not reach.  The name
     gives way first; the tools take a row of their own. */
  .game__panel-head {
    flex-wrap: wrap;
    row-gap: 10px;
  }

  .game__agent-name {
    flex: 1 1 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .game__panel-tools {
    flex: 1 1 100%;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
  }

  /* Trimmed, the two buttons keep the tools' row as short as it can be.  The row
     carries four controls now - two switches and two buttons - so on a phone it
     wraps; the padding only decides how many lines that costs. */
  .game__settings,
  .game__fold {
    padding: 7px 12px;
  }
}

/* A short window - a laptop in a half-height frame, a tablet in a split screen -
   cannot afford a header that spends 210 pixels of itself on padding.  The room's
   name and the line that says what is happening stay; the air around them goes
   back to the map. */
@media (max-height: 780px) {
  .game__hud {
    padding: 10px 14px;
  }

  .game__brand {
    margin-bottom: 6px;
  }

  .game__room-title {
    font-size: 19px;
  }

  .game__status {
    margin-top: 6px;
  }
}

/* Shorter still and the room's own description is the least of it: the name and
   the status line say enough, and the space goes to the room they describe. */
@media (max-height: 640px) {
  .game__room-desc {
    display: none;
  }
}

/* A phone held sideways has almost no height, and the dialogue panel would eat
   the whole map.  It stops being a panel and becomes a sheet across the bottom,
   with the log given whatever is left above the keyboard. */
@media (max-height: 520px) {
  .game__hud {
    padding: 8px 10px;
  }

  .game__brand {
    margin-bottom: 6px;
  }

  .game__room-desc,
  .game__hint {
    display: none;
  }

  .game__dock {
    padding: 0 10px 8px;
    gap: 6px;
  }

  .game__log {
    max-height: 22dvh;
  }

  .game__panel-head {
    padding: 8px 12px;
  }

  .game__form {
    padding: 8px 10px 10px;
  }

  .game__input {
    min-height: 38px;
  }
}

/* ------------------------------------------------------------- safe areas */

/* Notches, rounded corners and the home indicator live inside the screen the
   page is given, so the HUD and the panel step aside for them.  Zero on a
   desktop, which is exactly right. */
@supports (padding: env(safe-area-inset-top)) {
  .game__hud {
    padding-top: calc(12px + env(safe-area-inset-top));
    padding-left: calc(16px + env(safe-area-inset-left));
    padding-right: calc(16px + env(safe-area-inset-right));
  }

  .game__dock {
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }

  @media (max-width: 720px) {
    .game__hud {
      padding-top: calc(10px + env(safe-area-inset-top));
      padding-left: calc(12px + env(safe-area-inset-left));
      padding-right: calc(12px + env(safe-area-inset-right));
    }

    .game__dock {
      padding-bottom: calc(10px + env(safe-area-inset-bottom));
    }
  }
}

/* --------------------------------------------------------- touch devices */

/* Everything a finger has to hit grows; a mouse keeps the compact spacing. */
@media (pointer: coarse) {
  .game__send {
    padding: 13px 22px;
  }

  .game__mic {
    padding: 13px 16px;
  }

  .game__card-button {
    padding: 11px 15px;
  }

  /* "Меню" was a twelve-pixel strip in the corner - a third of a fingertip, and
     the only way off the map.  Two classes, because the plain `.game__links
     button` rule above sets its padding to zero. */
  .game__links .game__menu-toggle {
    padding: 12px;
  }

  .game__room,
  .game__agent {
    padding: 9px 13px;
  }

  /* 16px keeps iOS from zooming the page the moment the field is focused. */
  .game__input {
    font-size: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .game__send {
    transition: none;
  }

  .game__mic {
    transition: none;
  }
}

/*
 * The settings pane can ask for calm without the player touching their OS, so the
 * same declaration is offered to a class on the root.  A player who has not asked
 * for quiet still gets exactly the media query above and nothing else.
 */
:root.is-quiet .game__send {
  transition: none;
}


.game__menu {
  border: 0;
  padding: 0;
  margin: auto;
  background: transparent;
}

.game__menu::backdrop {
  background: color-mix(in srgb, var(--bg) 72%, transparent);
}

.menu {
  width: min(920px, 94vw);
  max-height: min(92vh, 900px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.menu__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 18px 0 14px;
  gap: 16px;
}

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

.menu__tab {
  padding: 6px 10px;
  border-radius: 0;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
  font: 600 12px/1 var(--font-pixel);
  cursor: pointer;
  letter-spacing: 0.01em;
}

.menu__tab--active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--text);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

.menu__close {
  padding: 6px 10px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: var(--surface-hover);
  color: var(--text);
  font: 400 12px/1 var(--font-pixel);
  cursor: pointer;
}

.menu__body {
  flex: 1 1 auto;
  overflow: auto;
  padding: 14px 18px 18px;
  display: flex;
  flex-direction: column;
}

.menu__pane {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.menu__create-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

.menu__input {
  flex: 1 1 160px;
  padding: 8px 10px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--bg) 40%, transparent);
  color: var(--text);
  font: 400 13px/1.4 var(--font-pixel);
}

.menu__input--slug {
  flex: 0 1 140px;
}

.menu__create,
.menu__action {
  padding: 8px 12px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: var(--surface-hover);
  color: var(--text);
  font: 600 12px/1 var(--font-pixel);
  cursor: pointer;
  white-space: nowrap;
}

.menu__action--primary {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}

.menu__action--danger {
  background: color-mix(in srgb, var(--danger) 24%, transparent);
  border-color: var(--danger-border);
}

/* An `<a>` wearing `.menu__action`.  The class hands it the button's colours,
   padding and border, but an anchor is inline by default — where vertical padding
   paints across the text instead of around it, and `border-radius` draws nothing
   worth rounding.  It has to become a box before the rest of the class can show.
   `text-decoration` is dropped because `.menu__action` never carried one and an
   underlined blue-on-dark link reads as a different thing from its neighbours. */
.menu__link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.menu__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* The ready-made locations sit below the player's own rooms behind a rule, so the
   list of templates is not mistaken for a list of the player's rooms. */
.menu__presets {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.menu__item {
  padding: 10px 12px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 30%, transparent);
}

.menu__row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  flex-wrap: wrap;
}

.menu__title {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.menu__name {
  font: 600 15px/1.2 var(--font-pixel);
  color: var(--text);
}

.menu__slug {
  font: 400 11px/1 var(--font-pixel);
  color: var(--text-muted);
  opacity: 0.9;
}

.menu__actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.menu__badge {
  padding: 4px 8px;
  border-radius: 0;
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: var(--text);
  font: 600 11px/1 var(--font-pixel);
}

.menu__note {
  color: var(--text-muted);
  font: 400 12px/1 var(--font-pixel);
}

.menu__warning {
  color: var(--danger);
  font: 400 12px/1.4 var(--font-pixel);
}

.menu__empty {
  margin: 0;
  color: var(--text-muted);
  text-align: center;
  padding: 12px;
}

.menu__card {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 14px 16px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 30%, transparent);
}

.menu__card-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.menu__card-title {
  margin: 0 0 4px;
  font: 600 18px/1.2 var(--font-pixel);
  color: var(--text);
}

.menu__card-sub {
  margin: 0;
  color: var(--text-muted);
  font: 400 13px/1.5 var(--font-pixel);
}

.menu__card-stats {
  margin: 0;
  color: var(--accent);
  font: 600 13px/1.4 var(--font-pixel);
}

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

/* `.menu__input` carries `flex: 1 1 160px` because the room row wants the name field to
   take the spare *width*.  In a column form the main axis is vertical, so the same
   declaration is read as a 160px *height* — and the agent's one-line name input was
   measured at 160px tall next to a 37px select, because a `<input>` cannot grow its
   own content and has nothing to show in the extra space.

   `>` on purpose: the field editor's rows are `.menu__input` too, and they sit in
   `.menu__field-top`, which is a row and genuinely does want them to grow.  Only a
   direct child of a column form is the wrong place for the base rule. */
.menu__form > .menu__input {
  flex: 0 0 auto;
}

.menu__form-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.menu__label {
  color: var(--text);
  font: 600 12px/1 var(--font-pixel);
}

/* A label and its "generate" button on one line: the label keeps the left, the
   button sits at the right, and the field below spans both.  Without this the
   button would fall under the label and read as a control for the whole form. */
.menu__label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* The generate spark, sized to the button's own text.  It inherits the button
   colour, so a monochrome chrome gains a mark and not a second hue. */
.menu__ai-icon {
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
  fill: currentColor;
}

/* The per-field generation button: quieter than a form action, because pressing it
   is a suggestion the player then edits, not a write. */
.menu__ai {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 8px;
  border-radius: 0;
  border: 1px solid var(--accent-border);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent-text);
  font: 600 11px/1.4 var(--font-pixel);
  cursor: pointer;
}

.menu__ai:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
}

.menu__ai:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The purpose block: the input the "ИИ" buttons read from, set apart from the
   fields it fills so it reads as the question and they as the answers. */
.menu__form--ai {
  padding: 10px;
  border-radius: 0;
  border: 1px dashed var(--accent-border);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.menu__action--ai {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.menu__hint {
  margin: 0;
  color: var(--text-muted);
  font: 400 12px/1.4 var(--font-pixel);
}

/* A failure, said in the same weight as the hints around it.  The colour is the only
   difference on purpose: this line replaces a card, and a card drawn in red would
   read as "your world is gone" when the truth is "we could not read it just now". */
.menu__hint--warn {
  color: var(--danger);
}

.menu__danger {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-radius: 0;
  border: 1px dashed var(--danger-border);
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.menu__danger-title {
  margin: 0;
  color: var(--danger);
  font: 600 13px/1.2 var(--font-pixel);
}

.menu__danger-text {
  margin: 0;
  color: var(--danger);
  font: 400 12px/1.5 var(--font-pixel);
}

.menu__check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  color: var(--text);
  font: 400 13px/1.5 var(--font-pixel);
}

/* A column of `menu__check`s (the probed models a player may tick) and the list
   of models a connected key already serves.  Both are rebuilt on every paint of
   the providers pane, so neither holds state beyond what the card says. */
.menu__checks {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.menu__models {
  margin: 0;
  padding-left: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  list-style: none;
}

.menu__models li {
  padding: 3px 8px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font: 400 12px/1.4 var(--font-pixel);
}

/* ------------------------------------------------------ the agent editor */

/* A textarea takes the same border as `.menu__input` but keeps its own height, and
   must not inherit the input's `flex` — inside a column form that would stretch it
   to fill and push everything below it off the dialog. */
.menu__textarea {
  padding: 8px 10px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--bg) 40%, transparent);
  color: var(--text);
  font: 400 13px/1.5 var(--font-pixel);
  resize: vertical;
  min-height: 60px;
}

.menu__select {
  padding: 8px 10px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: color-mix(in srgb, var(--bg) 40%, transparent);
  color: var(--text);
  font: 400 13px/1.4 var(--font-pixel);
}

/* The type picker is narrow because its widest option is «Выбор из списка»; letting
   it grow would push the "обязательное" checkbox off the row on a narrow phone. */
.menu__select--type {
  flex: 0 0 auto;
}

.menu__fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
}

.menu__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 25%, transparent);
}

.menu__field-top {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.menu__input--field {
  flex: 1 1 160px;
}

.menu__input--options {
  flex: none;
}

.menu__hint--field {
  font-size: 11px;
}

.menu__field-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}

/* Inline beside the "Убрать" button rather than stacked above it: it is a property
   of the row, not a paragraph about it. */
.menu__check--inline {
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-muted);
}

/* Placement sits under its agent, not in a dialog of its own: which rooms an agent
   stands in is a property of that agent, and the answer is one tap to change. */
.menu__places {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  padding: 8px 0 0;
  margin-top: 8px;
  border-top: 1px dashed var(--border);
}

.menu__select--place {
  flex: 1 1 140px;
}

/* A removal affordance inside a badge.  24px is the smallest target a finger can hit
   without aiming; below that a player taps the badge and expects the agent's name. */
.menu__badge-x {
  margin-left: 4px;
  padding: 0;
  min-width: 20px;
  min-height: 20px;
  border: 0;
  border-radius: 0;
  background: var(--surface-hover);
  color: var(--text);
  font: 600 12px/1 var(--font-pixel);
  cursor: pointer;
}

.menu__badge-x:disabled {
  opacity: 0.5;
  cursor: default;
}

/* ---- operator console: the removed admin pages, now inside the world ---- */

.menu__operator {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

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

.menu__subtab {
  padding: 6px 11px;
  border-radius: 0;
  border: 1px solid var(--border-strong);
  background: transparent;
  color: var(--text-muted);
  font: 600 12px/1 var(--font-pixel);
  cursor: pointer;
}

.menu__subtab--active {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 44%, transparent);
  color: var(--text);
}

.menu__card--operator {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.menu__stat {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}

.menu__stat-cell {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}

.menu__stat-value {
  font: 700 20px/1.1 var(--font-pixel);
  color: var(--text);
}

.menu__kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 14px;
  margin: 8px 0;
}

.menu__kv dt {
  color: var(--text-muted);
  font: 600 12px/1.4 var(--font-pixel);
}

.menu__kv dd {
  margin: 0;
  color: var(--text);
  font: 400 13px/1.4 var(--font-pixel);
  overflow-wrap: anywhere;
}

.menu__transcript {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: min(50vh, 460px);
  overflow-y: auto;
  padding: 10px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 40%, transparent);
}

.menu__msg {
  max-width: 82%;
  padding: 8px 10px;
  border-radius: 0;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 70%, transparent);
}

.menu__msg--user {
  align-self: flex-end;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
}

.menu__msg--assistant {
  align-self: flex-start;
}

.menu__msg--system {
  align-self: center;
}

.menu__msg-role {
  display: block;
  margin-bottom: 3px;
  color: var(--text-muted);
  font: 700 11px/1 var(--font-pixel);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.menu__msg-text {
  margin: 0;
  color: var(--text);
  font: 400 13px/1.45 var(--font-pixel);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.menu__setting {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.menu__setting:first-child {
  border-top: 0;
}

.menu__op-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.menu__secret-box {
  padding: 10px 12px;
  border-radius: 0;
  border: 1px dashed var(--accent-border);
  background: var(--accent-soft);
  color: var(--accent-text);
  font: 600 13px/1.5 ui-monospace, "SFMono-Regular", Menlo, monospace;
  overflow-wrap: anywhere;
  user-select: all;
}

/* A capability the partner holds: the tick and the tint are the whole state, so the
   button needs no second label to read as on. */
.menu__action--on {
  border-color: color-mix(in srgb, var(--accent) 44%, transparent);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--text);
}
