/* Fixed white-on-black theme, deliberately not adapting to OS light/dark --
   mirrors the toast device's own simple monochrome UI rather than blending
   into whatever browser theme happens to be active. */
:root {
  --bg: #000000;
  --fg: #ffffff;
  --muted: #8a8a8a;
  --border: #3a3a3a;
  --card-bg: #0a0a0a;
  --ok: #4ade80;
  --bad: #f87171;
  --bubble-me: #ffffff;
  --bubble-me-fg: #000000;
  --bubble-them: transparent;
  --bubble-them-fg: #ffffff;
  --topbar-h: 3.4rem;
  --player-h: 4rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  line-height: 1.4;
}

/* Base link color -- without this, any <a> not already covered by a more
   specific rule (.tile, .badge, .back-link, ...) falls back to the
   browser's default blue, which is nearly unreadable against black and
   was never part of this app's palette to begin with. No blanket a:hover
   rule here on purpose: components with their own hover treatment
   (.tile:hover, .badge:hover, ...) set background/color but not always
   text-decoration/opacity, and a bare `a:hover` would still win on
   whichever property those don't explicitly re-set (same specificity
   category, but with an extra element-selector point) -- narrower,
   per-context hover rules are safer than one global one here. */
a { color: var(--fg); }

main {
  max-width: 860px;
  margin: 0 auto;
  padding: 1.5rem 1rem 3rem;
}

h1 { font-size: 1.4rem; margin-bottom: 0.75rem; }
/* Pairs an <h1> with a settings cog (a plain .badge link, see below) on
   pages whose old inline "Advanced" section moved to /system/settings/*. */
.page-title-row { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; }
.page-title-row h1 { margin-bottom: 0; }
h2 { font-size: 1.1rem; margin-top: 2rem; }
h3 { font-size: 0.98rem; margin-top: 1.25rem; }

/* Explicit height (not padding-driven auto height) so --topbar-h is exact,
   not guessed -- .chat-screen relies on it to fill the rest of the
   viewport precisely. */
.topbar {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0 1rem;
  border-bottom: 1px solid var(--border);
}
.topbar-left { display: flex; align-items: center; gap: 1.25rem; min-width: 0; }
.topbar-right { display: flex; align-items: center; gap: 0.9rem; flex-shrink: 0; }
.topbar .brand { color: var(--fg); font-weight: 600; text-decoration: none; letter-spacing: 0.02em; }

/* Lives at the top of #page-content now, not the topbar (see base.html) --
   a bordered, badge-like button rather than a small muted inline text link,
   specifically so it reads as an obvious, tappable control instead of
   something easy to miss next to the brand logo. */
.page-back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 1.2rem;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--fg);
  border-radius: 4px;
  color: var(--fg);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 600;
}
@media (hover: hover) {
  .page-back-link:hover { background: var(--fg); color: #000; }
}

/* Text badges, not icons -- emoji render as full-color glyphs on every
   platform and clash with the rest of this app's monochrome outline/text
   language (buttons, tiles, dots), so status/label widgets are small
   bordered text instead, matching .filter-tab. Originally topbar-only,
   now reused anywhere a compact label fits (e.g. a model name in chat). */
.badge {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--fg);
  border-radius: 4px;
  padding: 0.15rem 0.5rem;
  font-size: 0.78rem;
  color: var(--fg);
  text-decoration: none;
  line-height: 1.5;
  white-space: nowrap;
}
.badge.dummy { opacity: 0.4; cursor: default; }
a.badge:hover, button.badge:hover { background: var(--fg); color: #000; }
button.badge { background: none; font: inherit; cursor: pointer; }
#topbar-connectivity.dot { width: 0.65rem; height: 0.65rem; margin-right: 0; }

/* Scaffold only (fleet-foundation-plan.md section I) -- deliberately no
   `display` rule on #session-switcher/.session-switcher-menu themselves:
   topbar.js toggles their `hidden` attribute, and this app already hit the
   bug where an author `display` rule on the same selector silently
   overrides the UA's [hidden]{display:none} regardless of specificity (see
   the terminal fullscreen toolbar fix). */
.session-switcher { position: relative; }
.session-switcher-menu {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 0.4rem;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.4rem;
  min-width: 10rem;
  z-index: 10;
}
.session-switcher-item { display: block; padding: 0.3rem 0.5rem; font-size: 0.85rem; text-decoration: none; }
.session-switcher-item:hover { background: var(--fg); color: #000; }

.ok { color: var(--ok); font-weight: 600; }
.bad { color: var(--bad); font-weight: 600; }
.hint { color: var(--muted); font-size: 0.9rem; }

.banner {
  padding: 0.6rem 0.9rem;
  border-radius: 4px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  margin-bottom: 1rem;
}
.banner.error { border-color: var(--bad); color: var(--bad); }

table.status-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0.75rem 0 1.25rem;
}
table.status-table th, table.status-table td {
  text-align: left;
  padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.92rem;
}

.button-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 1rem; }
.inline-form { display: inline; }

.files-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }

button, .filter-tab, input[type="submit"] {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--fg);
  border-radius: 4px;
  padding: 0.5rem 0.9rem;
  font-size: 0.9rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
/* (hover: hover) scopes this to devices with a real hover-capable pointer
   (mouse/trackpad) -- touch devices have no way to "unhover", so without
   this a tapped button's :hover state visually sticks until something else
   is tapped, using the exact same invert as an actually-selected/locked
   state (see .mod-key.locked etc.) and making the two indistinguishable. */
@media (hover: hover) {
  button:hover, .filter-tab:hover { background: var(--fg); color: #000; }
}
.filter-tab.active { background: var(--fg); color: #000; }

.stacked-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  max-width: 420px;
}
.stacked-form input, .stacked-form textarea, .stacked-form select {
  padding: 0.5rem 0.6rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-family: inherit;
}
.stacked-form button { align-self: flex-start; }
/* The toggle sits inside the input itself (not beside it) -- an absolutely
   positioned icon button over the input's own right padding, rather than a
   separate flex sibling. Deliberately NOT `display: flex` on
   `.password-field` with `flex: 1` on the input (an earlier version of
   this did that): a flex item's `flex-basis` wins over its own `width`
   whenever basis isn't `auto`, which would have silently overridden
   system_wifi.html's fixed-width password field. `top: 50%` +
   `translateY(-50%)` centers on the wrapper's actual midpoint instead,
   independent of both the input's width and any line-box height slack a
   plain block wrapper has around an inline-block input. */
.password-field { position: relative; }
.password-field input { width: 100%; padding-right: 2.25rem; }
.password-toggle {
  position: absolute;
  top: 50%;
  right: 0.3rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  border: none;
  border-radius: 4px;
  background: none;
  padding: 0;
  color: var(--muted);
}
/* Firefox gives buttons their own internal focus padding that isn't
   removed by `padding: 0` alone -- same reset already used for
   .tile-button elsewhere in this file. */
.password-toggle::-moz-focus-inner { border: 0; padding: 0; }
@media (hover: hover) {
  .password-toggle:hover { background: none; color: var(--fg); }
}
.stacked-form label { margin-bottom: -0.25rem; }

/* Sign in, create account, local unlock, and local passcode setup all share
   this centered bordered card -- the one family of pages in the app that
   isn't left-aligned inside <main> like everything else (.hero-card's same
   border/radius language, just centered and narrower, since a standalone
   auth screen reads as more standard centered than sharing every other
   page's top-left, list-like layout). */
.auth-shell { display: flex; justify-content: center; padding-top: 1.5rem; }
.auth-card {
  width: 100%;
  max-width: 360px;
  border: 1px solid var(--fg);
  border-radius: 6px;
  padding: 1.75rem 1.75rem 1.5rem;
}
.auth-card h1 { text-align: center; }
.auth-card .hint { text-align: center; margin-bottom: 1.25rem; }
.auth-card .stacked-form { max-width: none; }
.auth-card .stacked-form > button[type="submit"] { align-self: center; }
.auth-card .banner { margin-top: 1rem; margin-bottom: 0; }
.auth-switch { text-align: center; margin-top: 1.25rem; font-size: 0.9rem; }

.pair-columns {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
  margin: 1rem 0 1.5rem;
}
.pair-column { flex: 1; min-width: 240px; }
.pair-column h2 { margin-top: 0; }

/* Home page's big System summary -- a full-width entry point above the
   regular tile grid, replacing the topbar's old "System" link. Deliberately
   doesn't invert on hover the way .tile does: the meter fills already use
   --fg as their color, and a full background/foreground swap would make
   them vanish against the inverted background, so hover is a subtle tint
   instead. */
.hero-card {
  display: block;
  border: 1px solid var(--fg);
  border-radius: 6px;
  padding: 1.3rem 1.5rem;
  margin-top: 1rem;
  background: var(--bg);
  color: var(--fg);
  text-decoration: none;
}
.hero-card:hover { background: rgba(255, 255, 255, 0.06); }
.hero-card-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.hero-card-title { font-size: 1.25rem; font-weight: 700; }
.hero-card-sub { color: var(--muted); font-size: 0.85rem; }
.meter-row {
  display: grid;
  /* auto-fit rather than a fixed repeat(3, 1fr): the temp meter is
     conditional (only shown when a thermal zone is actually available), so
     this needs to look right with either 3 or 4 items rather than assuming
     a fixed count. */
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1.2rem;
}
.meter-label {
  display: flex;
  justify-content: space-between;
  font-size: 0.85rem;
  margin-bottom: 0.35rem;
}
.meter-label span:first-child { color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; font-size: 0.75rem; }
.meter-label span:last-child { font-weight: 700; }
.meter-bar {
  height: 0.5rem;
  border: 1px solid var(--border);
  border-radius: 3px;
  overflow: hidden;
  background: var(--bg);
}
.meter-fill { height: 100%; background: var(--fg); }
.meter-detail { color: var(--muted); font-size: 0.75rem; margin-top: 0.3rem; }

/* Home page tiles + the Agents/Chat picker share this look. Fixed 2-column
   grid rather than auto-fit -- deliberately blocky/simple rather than
   reflowing to however many columns happen to fit a given width. */
.tile-grid {
  display: grid;
  /* minmax(0, 1fr), not plain 1fr -- a grid item's implicit min-width is
     "auto" (its content's own min-content size) unless the track itself
     caps it. Plain 1fr only splits the *remaining* space after that
     minimum is honored, so a tile with wide content (the terminal session
     tiles' rename input + Open/Fullscreen/Close badge row, especially with
     a long session title) grows its column -- and the whole grid, and the
     page -- past the viewport on a narrow phone instead of wrapping. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.9rem;
  margin-top: 1rem;
}
.tile {
  display: block;
  padding: 1.4rem 1.2rem;
  border: 1px solid var(--fg);
  border-radius: 4px;
  background: var(--bg);
  color: var(--fg);
  text-decoration: none;
}
.tile:hover { background: var(--fg); color: #000; }
.tile-title {
  display: flex;
  align-items: center;
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
}
.tile-status { color: var(--muted); font-size: 0.88rem; display: flex; align-items: center; }
.tile-status + .tile-status { margin-top: 0.2rem; }

/* Apps screen: one .filter-tab-styled button per connected device (only
   one exists today, see routes/apps.py). Each app tile is a <form>
   (launching an app is a POST, not a navigation) carrying .tile directly,
   so it's a completely normal grid item sized like any other .tile -- a
   <button> can't reliably be promoted to a grid item via display:contents
   on its parent <form> (inconsistent across browsers, confirmed live: tile
   sizes came out content-dependent instead of uniform), so the form itself
   must be the grid item, with a fully-reset <button> filling it. */
.apps-tabs { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 1rem 0; }
.tile:hover .tile-status { color: #333; }

/* Terminal's session-list tiles hold several independent actions (Open,
   Fullscreen, Close) instead of being one big link -- .tile:hover's
   invert would otherwise wash out the still explicitly-colored .badge
   children nested inside, so tile-static opts out of it. */
.tile.tile-static:hover { background: var(--bg); color: var(--fg); }
.tile.tile-static:hover .tile-status { color: var(--muted); }
.tile-actions { margin-top: 0.7rem; }

.session-rename-form { display: flex; align-items: center; gap: 0.4rem; }
.session-title-input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 1.1rem;
  font-weight: 600;
  background: transparent;
  color: inherit;
  border: none;
  border-bottom: 1px dashed var(--border);
  padding: 0 0 0.2rem;
}
.session-title-input:focus { outline: none; border-bottom-color: var(--fg); }

/* A tile that's a <form> whose whole area is the click target -- same
   reasoning as .app-tile-button below (a <button> can't be reliably
   promoted to a grid item via display:contents on its parent <form>), but
   usable anywhere, not just the Apps panel. */
.tile-button {
  all: unset;
  appearance: none;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  cursor: pointer;
  font: inherit;
  font-weight: 600;
  color: inherit;
}
.tile-button::-moz-focus-inner { border: 0; padding: 0; }

/* Every app tile is the same fixed box regardless of name length -- long
   names step down through these font sizes (chosen by name length in
   apps.html) rather than overflowing or growing the tile. */
.apps-panel .app-tile {
  height: 4.5rem;
  padding: 0;
  overflow: hidden;
}
.app-tile-button {
  all: unset;
  appearance: none;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0.6rem 0.9rem;
  border: none;
  background: none;
  cursor: pointer;
  text-align: center;
}
/* Firefox keeps an inner dotted focus box with its own border/padding on
   buttons that "all: unset" alone doesn't reach -- without this it reads
   as a second, smaller box nested inside the tile. */
.app-tile-button::-moz-focus-inner {
  border: 0;
  padding: 0;
}
.apps-panel .tile-title {
  margin-bottom: 0;
  font-size: 1rem;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  word-break: break-word;
}
.apps-panel .tile-title.name-long { font-size: 0.85rem; }
.apps-panel .tile-title.name-xlong { font-size: 0.72rem; }

.apps-settings-list { columns: 2; column-gap: 1.5rem; max-width: 640px; margin: 0.75rem 0 1rem; }
.apps-settings-item { display: block; padding: 0.15rem 0; break-inside: avoid; font-size: 0.9rem; }
.apps-settings-item input { margin-right: 0.4rem; }

.dot {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  margin-right: 0.5rem;
  flex-shrink: 0;
}

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem;
  margin: 1rem 0 1.5rem;
}
.stat-tile {
  border: 1px solid var(--fg);
  border-radius: 4px;
  padding: 1rem 1.1rem;
}
.stat-tile.dummy { opacity: 0.5; border-style: dashed; }
.stat-tile:not(.dummy) { cursor: pointer; }
/* Fired by system-stats.js on tap and on its 10s auto-refresh -- a light
   color-mix pulse against --bg rather than a full var(--fg)/var(--bg)
   invert (like .tile:hover elsewhere): full inversion firing on its own
   every 10s read as too jarring for something the user didn't just do. */
@keyframes stat-flash {
  0% { background: var(--bg); }
  25% { background: color-mix(in srgb, var(--fg) 22%, var(--bg)); }
  100% { background: var(--bg); }
}
.stat-tile.stat-flash { animation: stat-flash 0.5s ease-out; }
.stat-label {
  color: var(--muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.3rem;
}
.stat-value { font-size: 1.7rem; font-weight: 700; }
.stat-detail { color: var(--muted); font-size: 0.8rem; margin-top: 0.3rem; }
.dot-ok { background: var(--ok); }
.dot-bad { background: var(--bad); }
.dot-neutral { background: var(--muted); }

.conv-list { list-style: none; padding: 0; margin: 1rem 0; }
.conv-list li {
  padding: 0.6rem 0.2rem;
  border-bottom: 1px solid var(--border);
  display: flex;
  justify-content: space-between;
  gap: 1rem;
}
.conv-list a { color: var(--fg); text-decoration: none; }
.conv-list a:hover { text-decoration: underline; }

/* Fills the viewport below the topbar so the chat feels like an actual
   chat screen (input pinned at the bottom) rather than a form with a log
   above it -- especially noticeable on a brand-new/empty conversation,
   which otherwise just showed a couple of lines up top and a lot of dead
   space below. No position:fixed anywhere here on purpose: nested flex +
   overflow reflows naturally when the mobile keyboard resizes the visual
   viewport, avoiding the class of bug the terminal's fullscreen mode hit. */
.chat-screen {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--topbar-h));
  height: calc(100dvh - var(--topbar-h));
  /* main's own padding, canceled out so the chat screen truly fills its space */
  margin: -1.5rem -1rem -3rem;
  padding: 1rem;
}
.chat-header { flex-shrink: 0; }
.chat-header h1 { margin-bottom: 0.25rem; }
.chat-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  /* Short/empty transcripts sit at the bottom, right above the input, like
     a real chat -- not floating near the top with empty space below. Once
     content overflows, this also starts the view scrolled to the latest
     message with no JS needed. */
  justify-content: flex-end;
}
.chat-screen .chat-form { flex-shrink: 0; margin-top: 0.75rem; }
.chat-empty { text-align: center; margin: auto 0 0.5rem; }

.transcript {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin: 1rem 0;
  min-height: 2rem;
}
.bubble-row { display: flex; }
.bubble-row.me { justify-content: flex-end; }
.bubble-row.them { justify-content: flex-start; }
.bubble {
  max-width: 75%;
  padding: 0.55rem 0.8rem;
  border-radius: 4px;
  white-space: pre-wrap;
  word-wrap: break-word;
  font-size: 0.94rem;
  border: 1px solid var(--fg);
}
.bubble-row.me .bubble { background: var(--bubble-me); color: var(--bubble-me-fg); }
.bubble-row.them .bubble { background: var(--bubble-them); color: var(--bubble-them-fg); }

.chat-form {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 1rem;
}
.chat-form input[type="text"] {
  flex: 1;
  min-width: 200px;
  padding: 0.55rem 0.7rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
}
.chat-form select {
  padding: 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
}

/* ---- Terminal ---- */

.term-container {
  height: 65vh;
  min-height: 320px;
  background: #000000;
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.5rem;
  overflow: hidden;
}

/* Set by terminal.js while fullscreen is active -- see the comment in
   setFullscreen() for why this is needed (mobile keyboard focus-scroll). */
body.term-fs-lock {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  height: 100dvh;
  overflow: hidden;
}

#term-wrap.fullscreen {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  max-width: 100vw;
  max-height: 100vh;
  max-height: 100dvh;
  overflow: hidden;
  box-sizing: border-box;
  z-index: 1000;
  background: #000;
  padding: 0.5rem;
  display: flex;
  flex-direction: column;
}
#term-wrap.fullscreen .term-container {
  flex: 1;
  width: 100%;
  height: auto;
  /* Flex items default to min-width/min-height: auto, which lets a child's
     intrinsic content size (xterm's canvas) refuse to shrink and push this
     box past the viewport -- explicit 0 is what actually lets it clamp to
     the space flex allocates it. */
  min-width: 0;
  min-height: 0;
  border: none;
  overflow: hidden;
}
/* Belt-and-suspenders alongside the body.term-fs-lock scroll lock above:
   pins the toolbar to the top of whatever scrolls, if anything ever does. */
#term-wrap.fullscreen .term-fs-toolbar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #000;
}

/* Always visible now, fullscreen or not, wide screen or narrow -- Close
   session and the modifier/arrow keys are just as useful outside
   fullscreen (a desktop user still wants Ctrl-C without reaching for a
   physical key, and Close session shouldn't require entering fullscreen
   first to reach it). Used to be hidden on wide screens until the
   "Fullscreen" button revealed it (.visible class, set by JS); the
   fullscreen-only bits (#btn-exit-fullscreen) still gate themselves
   separately below. */
.term-fs-toolbar {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: 0.2rem 0.2rem 0.6rem;
}
@media (max-width: 700px) {
  .term-container { height: 50vh; }
  .meter-row { grid-template-columns: 1fr; }
}

/* Always-visible row: the handle that toggles the Ctrl/Alt/Shift/Tab/Esc/
   arrow group below (reclaiming vertical space -- particularly useful with
   the keyboard up, where every row of chrome competes with the terminal
   itself for the little room left; collapsed state is remembered in
   localStorage, see terminal.js) plus the Exit fullscreen/Reconnect/Close
   session actions, which aren't modifier keys and so stay visible
   regardless of collapsed state even though they live in this same row. */
.term-fs-toolbar-handle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}
.term-fs-toolbar-handle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.5rem;
  font-size: 0.82rem;
  background: none;
  border: none;
  color: var(--muted);
}
.term-fs-toolbar-arrow { display: inline-block; width: 1em; }
.term-fs-toolbar-content {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
}
.term-fs-toolbar.collapsed .term-fs-toolbar-content { display: none; }

.term-fs-group { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.term-fs-toolbar button {
  padding: 0.6rem 0.8rem;
  font-size: 1rem;
  min-width: 2.6rem;
  text-align: center;
}
/* Exit fullscreen / Reconnect / Close session: icon-only now instead of
   text labels, specifically to keep this row narrow -- every row of
   toolbar chrome eats into space the terminal itself could use, especially
   with the keyboard up. */
.term-fs-toolbar button.icon-btn {
  padding: 0.5rem 0.6rem;
  min-width: 2.2rem;
  font-size: 1.2rem;
}
/* Matches .trackpad-modifier-btn's armed/locked treatment exactly (see
   below) -- same off -> armed -> locked model, same highlighting, so the
   two remote-keyboard-style modifier UIs in this app read as one system.
   armed and locked deliberately look identical (full invert) -- the
   locked-only dot (below) is the sole visual distinction between them. */
.term-fs-toolbar .mod-key.armed,
.term-fs-toolbar .mod-key.locked {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}

/* Locked-state indicator added by modifier-keys.js (shared by both the
   terminal's Ctrl/Alt/Shift and the trackpad's remote keyboard modifiers)
   -- reuses the platform's existing green "ok" status-dot (.dot/.dot-ok)
   rather than a new indicator, per that convention. Element+class selector
   deliberately used (not just .mod-key-lock-dot) so this default-hidden
   rule reliably beats .dot's own `display: inline-block` regardless of
   stylesheet order, since both are otherwise equal specificity. */
span.mod-key-lock-dot { display: none; margin: 0 0 0 0.35rem; vertical-align: middle; }
button.locked > span.mod-key-lock-dot { display: inline-block; }

/* Unlike the modifier/arrow-key group (always useful on a phone, fullscreen
   or not -- see the comment above), Enter/Exit fullscreen are each only
   meaningful in one of the two states; the mobile media query above forces
   the whole toolbar visible regardless of that state, so these need their
   own narrower guard rather than inheriting the toolbar's visibility.
   Exact mirror images of each other so exactly one of the two ever shows. */
#btn-exit-fullscreen { display: none; }
#term-wrap.fullscreen #btn-exit-fullscreen { display: inline-block; }
#term-wrap.fullscreen #btn-fullscreen { display: none; }

.danger { border-color: var(--bad); color: var(--bad); }
.danger:hover { background: var(--bad); color: #000; }

/* Persistent music player bar (see base.html) -- lives outside #page-content
   so it survives boosted navigation. Hidden until player.js has something
   to play (toggled via the .visible class, deliberately not the `hidden`
   attribute -- see base.html's comment on why). body.has-player adds
   bottom padding so the fixed bar never covers page content underneath it. */
.music-player-bar {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: var(--player-h);
  align-items: center;
  gap: 0.75rem;
  padding: 0 1rem;
  background: var(--bg);
  border-top: 1px solid var(--border);
  z-index: 100;
}
.music-player-bar.visible { display: flex; }
body.has-player main { padding-bottom: calc(var(--player-h) + 1.5rem); }
.music-player-info { flex: 1; min-width: 0; }
.music-player-expand {
  display: block;
  font-size: 0.7rem;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  margin-bottom: 0.1rem;
}
.music-player-expand:hover { color: var(--fg); }
.music-player-title { font-weight: 600; font-size: 0.92rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-player-subtitle { color: var(--muted); font-size: 0.8rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.music-player-controls { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.music-transport-btn { padding: 0.4rem 0.5rem; font-size: 0.85rem; line-height: 1; border: none; background: none; }
.music-transport-btn:hover { background: rgba(255, 255, 255, 0.1); border-radius: 4px; }
/* Never shown -- just a playback engine for streaming mode, driven by
   player.js's own custom transport below (#music-playpause-btn/#music-seek),
   not the browser's native <audio controls> UI. */
#music-audio { display: none; }
.music-seek {
  width: 90px;
  accent-color: var(--fg);
}
.music-local-indicator {
  display: none;
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 120px;
}
.music-local-indicator.visible { display: inline; }
.music-output-select {
  flex-shrink: 0;
  max-width: 130px;
  padding: 0.35rem 0.4rem;
  font-size: 0.78rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
}
@media (max-width: 700px) {
  .music-player-bar { gap: 0.5rem; padding: 0 0.6rem; }
  .music-player-subtitle { display: none; }
  .music-output-select { max-width: 90px; }
  .music-seek { width: 50px; }
  .music-local-indicator { display: none; }
}

.music-art {
  display: block;
  object-fit: cover;
  border-radius: 3px;
  background: var(--card-bg);
  flex-shrink: 0;
}
.music-art-sm { width: 2.6rem; height: 2.6rem; }
.music-art-lg { width: 100%; max-width: 220px; aspect-ratio: 1; margin-bottom: 1rem; }
/* No art cached/found -- a plain dashed box (same convention as
   .stat-tile.dummy), not an icon or placeholder image: this app has a
   standing rule against pictographic icons anywhere in its UI. */
.music-art-placeholder {
  border: 1px dashed var(--border);
  background: none;
}

.music-album-header { display: flex; align-items: flex-start; gap: 1.2rem; margin-bottom: 1rem; }
.music-album-header h1 { margin-bottom: 0.2rem; }
.music-album-header .music-art-lg { margin-bottom: 0; width: 140px; }
.track-play-btn {
  background: none;
  border: none;
  color: var(--fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
  padding: 0;
}
.track-play-btn:hover { text-decoration: underline; }

.settings-list { display: flex; flex-direction: column; gap: 1.1rem; margin-top: 1rem; }
.setting-toggle-form { border-bottom: 1px solid var(--border); padding-bottom: 1rem; }
.setting-toggle { display: flex; align-items: center; gap: 0.6rem; cursor: pointer; }
.setting-toggle input[type="checkbox"] { width: 1.1rem; height: 1.1rem; }
.setting-toggle-label { font-weight: 600; }
.setting-hint { margin: 0.3rem 0 0 1.7rem; }

/* Same input treatment as .stacked-form (border/background/radius) --
   this one field was still bare browser-default styling, narrowed but
   otherwise unstyled, the only such gap outside fleet_connect.html's
   already-fixed add-device form. Stays inline-in-the-row rather than
   promoted to its own page: unlike adding a device (a multi-step
   provisioning flow), connecting to one already-scanned network is a
   single self-contained action scoped to that row. */
.wifi-connect-form input[type="password"] {
  width: 130px;
  padding: 0.4rem 0.5rem;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-family: inherit;
}

/* Remote trackpad (see templates/trackpad.html, static/trackpad.js). Mobile
   browsers flash their own default tap-highlight overlay on every tap
   regardless of our state classes, which made armed/locked visually
   indistinguishable from a plain tap -- turned off everywhere in here so
   the classes below are the only thing that visibly changes. */
.trackpad-toolbar, .trackpad-modifier-bar { -webkit-tap-highlight-color: transparent; }
.trackpad-toolbar button, .trackpad-modifier-btn { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
.trackpad-toolbar { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-bottom: 0.6rem; }
.trackpad-toolbar .music-transport-btn { border: 1px solid var(--border); border-radius: 4px; }
.trackpad-modifier-bar {
  display: none;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.4rem;
  position: fixed;
  top: 4rem;
  left: 0.6rem;
  max-width: calc(100vw - 1.2rem);
  z-index: 50;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.6rem;
}
.trackpad-modifier-bar.visible { display: flex; }
.trackpad-modifier-btn { border: 1px solid var(--border); border-radius: 4px; background: transparent; }
/* Armed fires once with the next key, then clears. Locked stays held until
   tapped again. The two deliberately look identical (full invert) -- the
   locked-only dot (see .mod-key-lock-dot) is the sole visual distinction,
   matching the terminal's Ctrl/Alt/Shift modifiers exactly. */
.trackpad-modifier-btn.armed,
.trackpad-modifier-btn.locked {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}
#trackpad-surface {
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  height: 55vh;
  min-height: 280px;
  border: 1px dashed var(--border);
  border-radius: 6px;
  background: var(--card-bg);
}
#trackpad-surface.trackpad-dragging { border-color: var(--fg); border-style: solid; }
#trackpad-page:fullscreen { background: var(--bg); padding: 0.6rem; display: flex; flex-direction: column; }
#trackpad-page:fullscreen #trackpad-surface { flex: 1; height: auto; }
.trackpad-keyboard-input {
  position: absolute;
  left: -9999px;
  top: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
}
