/* endmin's only stylesheet. One hand-written file, no framework, no build
   step, no CDN.

   The token set, the control shapes and the motion are lifted from the mail
   app at /srv/email so the two feel like one system on this box. Dark only:
   there is no light palette and no theme switch, because a terminal is dark
   and a light chrome wrapped around a dark terminal never stopped looking
   like an accident. */

/* ------------------------------------------------------------------- font
   Commit Mono 1.143, vendored under the SIL OFL (see vendor/). Self-hosted
   rather than pulled from a CDN, because script-src/style-src are 'self' and
   because a terminal that waits on someone else's network to draw text is not
   a terminal. Only the four faces xterm can ask for: the browser fetches bold
   or italic only when the pty actually emits SGR 1 or 3.

   The paths are relative, so they resolve under the versioned /static/v-<hash>
   prefix this file is served from and inherit its cache lifetime. */

@font-face {
  font-family: "Commit Mono";
  src: url("vendor/CommitMono-400-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Commit Mono";
  src: url("vendor/CommitMono-400-Italic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "Commit Mono";
  src: url("vendor/CommitMono-700-Regular.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Commit Mono";
  src: url("vendor/CommitMono-700-Italic.woff2") format("woff2");
  font-weight: 700; font-style: italic; font-display: swap;
}

/* ------------------------------------------------------------------ tokens */

:root {
  color-scheme: dark;

  --bg:          #0f1216;
  --surface:     #161a20;
  --surface-2:   #1c2128;
  --surface-3:   #252b34;
  --border:      #262c35;
  --border-2:    #333b46;
  --text:        #e7eaf0;
  --text-soft:   #9aa3b2;
  --text-faint:  #7b8494;
  --accent:      #5b9dff;
  --accent-ink:  #08111f;
  --accent-soft: #17233a;
  --danger:      #ff6b6b;
  --ok:          #4ade80;
  --warn:        #f0b429;
  --shadow-sm:   0 1px 2px rgba(0, 0, 0, .4);
  --shadow-md:   0 8px 28px rgba(0, 0, 0, .55);
  --radius:      10px;
  --radius-sm:   7px;

  /* Motion. One place to slow everything down, and one place for the
     reduced-motion block to switch it all off. */
  --dur-fast: 120ms;
  --dur:      190ms;
  --dur-slow: 280ms;
  --ease:     cubic-bezier(.2, .7, .3, 1);
  --ease-out: cubic-bezier(0, 0, .2, 1);

  /* The terminal's own colours. term-xterm.js reads these back out with
     getComputedStyle and hands them to xterm as its theme, so the terminal
     palette lives in css like every other colour in the app. */
  --term-bg: #0c0f13;
  --term-fg: #dde2ea;
  --term-cursor: #5b9dff;
  --term-sel: rgba(91, 157, 255, .32);

  --ansi-black: #2a313b;         --ansi-bright-black: #5b6472;
  --ansi-red: #ff6b6b;           --ansi-bright-red: #ff9490;
  --ansi-green: #4ade80;         --ansi-bright-green: #7ff0a5;
  --ansi-yellow: #f0b429;        --ansi-bright-yellow: #ffd166;
  --ansi-blue: #5b9dff;          --ansi-bright-blue: #8bb9ff;
  --ansi-magenta: #c48cff;       --ansi-bright-magenta: #dcb6ff;
  --ansi-cyan: #45d0d8;          --ansi-bright-cyan: #6fe3ea;
  --ansi-white: #c2c9d4;         --ansi-bright-white: #eef1f6;

  /* Geometry. Tokens rather than magic numbers, because the terminal's height
     falls out of these and the two must never drift apart. */
  --rail-w: 216px;
  --topbar-h: 48px;
  --termbar-h: 46px;
  /* Two rows of 38px keys, plus the 6px gutter and 5px of padding each side. */
  --keybar-h: 40px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
  /* The home-bar inset belongs to the key bar: on a phone it is the last row
     on screen, with nothing fixed below it. */
  --kb-safe: var(--safe-b);

  /* Published by viewport.js onto <html>. Declared here too, because a calc()
     against an undefined custom property makes the whole declaration invalid
     and the page would have no height at all before script runs. */
  --vvh: 100vh;
  --kb: 0px;

  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "Commit Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, "DejaVu Sans Mono", monospace;
}

@media (prefers-reduced-motion: reduce) {
  :root { --dur-fast: 0ms; --dur: 0ms; --dur-slow: 0ms; }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------------- base */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  /* No pull-to-refresh. A swipe up in the terminal must never reload the page
     and destroy the view of a running session. */
  overscroll-behavior: none;
}

body {
  font: 14px/1.5 var(--sans);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { margin: 0 0 8px; font-weight: 600; line-height: 1.25; }
p { margin: 0 0 10px; }
strong { font-weight: 650; }
code, pre { font-family: var(--mono); font-size: .92em; }
code { color: var(--text); }
pre {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 10px 12px; overflow-x: auto;
}
ul { margin: 0 0 10px; padding-left: 20px; }
li { margin-bottom: 3px; }
.muted { color: var(--text-soft); font-size: 13px; }
.icon { width: 17px; height: 17px; stroke-width: 1.9; display: block; flex: 0 0 auto; }
[hidden] { display: none !important; }

/* The <symbol> sprite html.ts prints as the first child of <body>. It carries
   no visible geometry of its own, and it must occupy no space. */
.icon-sprite { display: none; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* --------------------------------------------------------------- controls */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 7px 13px; font: inherit; font-weight: 500; line-height: 1.2;
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  cursor: pointer; white-space: nowrap; touch-action: manipulation;
  transition: background var(--dur-fast), border-color var(--dur-fast),
              transform var(--dur-fast);
}
.btn:hover { background: var(--surface-2); }
.btn:active { background: var(--surface-3); transform: scale(.97); }
.btn:disabled { opacity: .55; cursor: default; transform: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent); filter: brightness(1.08); }
.btn-primary:active { filter: brightness(.94); }
.btn.danger, .nav-menu-list button.danger { color: var(--danger); }

.icon-button {
  display: grid; place-items: center;
  width: 32px; height: 32px; padding: 0;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-soft);
  cursor: pointer; touch-action: manipulation;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.icon-button:hover { background: var(--surface-3); color: var(--text); }

input, textarea, select {
  width: 100%; padding: 8px 10px; font: inherit;
  /* 16px is the anti-zoom floor: iOS zooms the page whenever a focused field
     computes under it. */
  font-size: 16px;
  border: 1px solid var(--border-2); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  transition: border-color var(--dur-fast), background var(--dur-fast);
}
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--accent); }
textarea { resize: vertical; line-height: 1.6; }

label { font-weight: 500; }
.field { display: block; margin: 0 0 12px; }
.field > span { display: block; margin-bottom: 5px; font-size: 13px; font-weight: 400; color: var(--text-soft); }

/* A form is read left to right on a laptop but must not stretch a password
   field across a 27 inch monitor. */
.form-narrow { max-width: 340px; }

.notice {
  padding: 9px 11px; margin: 0 0 12px;
  border-radius: var(--radius-sm); font-size: 13px;
  animation: notice-in var(--dur) var(--ease-out);
}
.notice-ok { background: color-mix(in srgb, var(--ok) 13%, transparent); color: var(--ok); }
.notice-bad { background: color-mix(in srgb, var(--danger) 13%, transparent); color: var(--danger); }
@keyframes notice-in { from { opacity: 0; transform: translateY(-4px); } }

/* -------------------------------------------------------------- the shell */

.shell {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
  /* dvh, not vh: on iOS vh is the tall-URL-bar height, so the bottom of the
     page sits underneath the browser toolbar. */
  height: 100dvh;
  overflow: hidden;
}

.rail {
  grid-column: 1;
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px 10px;
  background: var(--surface); border-right: 1px solid var(--border);
  overflow-y: auto;
}
.rail-brand {
  display: block; padding: 6px 12px 16px;
  font-size: 15px; font-weight: 650; letter-spacing: .2px;
  color: var(--accent);
}
.rail-brand:hover { text-decoration: none; filter: brightness(1.1); }
.rail-items { display: flex; flex-direction: column; gap: 1px; }

/* Pill rows, the same shape the mail app's folder list uses. */
.rail-item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 12px; border-radius: 999px;
  color: var(--text-soft); user-select: none;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.rail-item:hover { background: var(--surface-3); color: var(--text); text-decoration: none; }
/* The accessibility attribute IS the state. There is no second source of
   truth to keep in sync. */
.rail-item[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.rail-spacer { flex: 1 1 auto; }

.rail-account { margin-top: auto; }
.rail-account > summary {
  display: flex; align-items: center; gap: 6px; justify-content: space-between;
  padding: 9px 12px; border-radius: 999px;
  color: var(--text-soft); cursor: pointer; list-style: none;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.rail-account > summary::-webkit-details-marker { display: none; }
.rail-account > summary:hover { background: var(--surface-3); color: var(--text); }

.nav-menu { position: relative; }
.nav-menu-list {
  position: absolute; bottom: 100%; left: 0; min-width: 190px; z-index: 40;
  display: flex; flex-direction: column; gap: 1px;
  padding: 5px; margin-bottom: 6px;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--border-2); border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  animation: menu-in var(--dur) var(--ease-out);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(6px); } }
.nav-menu-list a, .nav-menu-list button {
  display: block; width: 100%; text-align: left;
  padding: 8px 11px; font: inherit;
  border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--text); cursor: pointer;
  transition: background var(--dur-fast);
}
.nav-menu-list a:hover, .nav-menu-list button:hover { background: var(--surface-3); text-decoration: none; }
.nav-menu-list a:active, .nav-menu-list button:active { background: var(--border-2); }
/* Inset: the row is flush with the panel's edge and a ring drawn outside it
   would be clipped by the panel's own radius. Same as gigit. */
.nav-menu-list a:focus-visible, .nav-menu-list button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.nav-menu-list a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); }
.nav-menu-caret { width: 14px; height: 14px; opacity: .7; transition: transform var(--dur-fast); }
/* An open menu says so at its summary, as gigit's does: the button stays lit
   and the caret turns while the panel is down. */
.nav-menu[open] > summary { background: var(--surface-3); color: var(--text); }
.nav-menu[open] .nav-menu-caret { transform: rotate(180deg); opacity: 1; }

.main { grid-column: 2; min-width: 0; min-height: 0; overflow-y: auto; }
.pad { padding: 24px 20px 48px; max-width: 780px; }
.page-title { font-size: 20px; margin: 0 0 18px; }
/* A module that owns its whole box: no page padding, no page scroll. */
.main-flush { padding: 0; overflow: hidden; display: flex; }
/* …except when such a module renders an ordinary page of content instead of
   its box, which still has to be able to scroll. */
.main-flush > .pad { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
.main-center {
  display: flex; align-items: center; justify-content: center;
  min-height: 100dvh; padding: 20px;
}

/* The phone bar. Hidden here; the rail is the wide-screen navigation. */
.topbar { display: none; }

/* A menu row that carries an icon: the module list inside the burger. */
.nav-menu-list .menu-row {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 11px; font: inherit; text-align: left;
}
.nav-menu-list .menu-row[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.menu-rule { height: 1px; margin: 5px 4px; border: 0; background: var(--border-2); }

/* ------------------------------------------------------------------ cards */

.box {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-sm);
  margin: 0 0 16px;
}
.box-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
}
.box-title { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase; color: var(--text-faint); }
.box-actions { margin-left: auto; }
.box-body { padding: 16px; }
.box-body > :last-child { margin-bottom: 0; }

.session-list { list-style: none; margin: -6px 0; padding: 0; }
.session-list li + li { border-top: 1px solid var(--border); }
.session-list a {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 4px; color: var(--text);
  transition: color var(--dur-fast);
}
.session-list a:hover { text-decoration: none; color: var(--accent); }
.session-name { font-weight: 550; }
.session-cwd { color: var(--text-faint); font-family: var(--mono); font-size: 12px; margin-left: auto; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: 0 0 auto; }

/* ------------------------------------------------------------------ login */

.login {
  width: min(380px, 100%); padding: 26px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-md);
  animation: dialog-in var(--dur-slow) var(--ease);
}
.login-title { margin: 0 0 20px; font-size: 19px; text-align: center; color: var(--accent); }
.login-error {
  padding: 9px 11px; margin-bottom: 14px; border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--danger) 13%, transparent);
  color: var(--danger); font-size: 13px;
  animation: notice-in var(--dur) var(--ease-out);
}
.login .btn { width: 100%; margin-top: 6px; }

/* --------------------------------------------------------------- terminal */

.term {
  display: flex; flex-direction: column;
  flex: 1 1 auto;
  /* The flexbox rule everyone forgets. Without it the screen refuses to shrink
     and overflows the shell, which on a phone puts the key bar off screen. */
  min-height: 0;
  min-width: 0;
  background: var(--term-bg);
  position: relative;
}

.term-bar {
  display: flex; align-items: center; gap: 8px;
  flex: 0 0 auto; height: var(--termbar-h);
  padding: 0 10px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
/* The phone menu's slot in the bar; the rail has the nav on a wide screen. */
.term-burger { display: none; }
.term-sessions > summary {
  display: flex; align-items: center; gap: 5px;
  padding: 6px 10px; border-radius: var(--radius-sm);
  cursor: pointer; list-style: none; font-weight: 600;
  max-width: 40vw; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  transition: background var(--dur-fast);
}
.term-sessions > summary::-webkit-details-marker { display: none; }
.term-sessions > summary:hover { background: var(--surface-2); }
.term-sessions .nav-menu-list { top: 100%; bottom: auto; margin: 6px 0 0; }
.term-cwd {
  color: var(--text-faint); font-family: var(--mono); font-size: 12px;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}

.term-state {
  font-size: 12px; font-weight: 500; padding: 3px 10px; border-radius: 999px;
  background: var(--surface-3); color: var(--text-soft); white-space: nowrap;
  transition: background var(--dur), color var(--dur);
}
.term-state[data-state="open"] { background: var(--accent-soft); color: var(--accent); }
.term-state[data-state="offline"],
.term-state[data-state="error"] {
  background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger);
}
.term-state[data-state="exited"] {
  background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn);
}
.term-state[data-state="stopped"] {
  background: color-mix(in srgb, var(--danger) 15%, transparent); color: var(--danger);
  cursor: pointer;
}
.term-state[data-state="stopped"]:hover { text-decoration: underline; }

.term-screen {
  flex: 1 1 auto; min-height: 0;
  padding: 6px 4px 6px 8px;
  /* A swipe that reaches the top of the scrollback must not turn into a
     pull-to-refresh on the page behind it. */
  overscroll-behavior: contain;
  /* The browser never scrolls or zooms this; term-scroll.js turns a touch
     drag into wheel reports for tmux, and the page itself does not scroll. */
  touch-action: none;
}
.term-screen .xterm { height: 100%; }
.term-screen .xterm-viewport { background-color: transparent !important; }

/* Fullscreen is requested on the terminal section itself, which is what lets
   the keyboard be locked. The UA pins it to the layout viewport with
   !important, and on Android the layout viewport does not shrink for the
   soft keyboard; the keyboard's height is padded off the bottom instead so
   the key bar stays above it. --kb is 0 everywhere else. */
.term:fullscreen { padding-bottom: var(--kb); }
.term-fullscreen .when-fullscreen { display: none; }
.term[data-fullscreen="on"] .term-fullscreen .when-windowed { display: none; }
.term[data-fullscreen="on"] .term-fullscreen .when-fullscreen { display: block; }

/* Input is visibly dead while the socket is down, and it is obvious why:
   keystrokes are dropped rather than queued, because replaying them into a
   Claude Code prompt that has moved on could approve a tool call nobody read. */
.term[data-conn="offline"] .term-screen,
.term[data-conn="stopped"] .term-screen,
.term[data-conn="reconnecting"] .term-screen {
  opacity: .55; pointer-events: none;
  transition: opacity var(--dur);
}

.term-search {
  flex: 0 0 auto; padding: 7px 10px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.term-search input { background: var(--surface); }

.term-drop {
  position: absolute; inset: 0; z-index: 20;
  display: none; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border: 2px dashed var(--accent);
  font-weight: 600; pointer-events: none;
}
.term[data-drop="over"] .term-drop { display: flex; animation: fade-in var(--dur-fast) var(--ease-out); }
@keyframes fade-in { from { opacity: 0; } }
.term-drop span {
  padding: 9px 18px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  box-shadow: var(--shadow-md);
}

/* ------------------------------------------------------------- the key bar
   Shown by capability, not by width: a tablet at 1400px with a touchscreen
   wants it, a 700px desktop window does not. */

.term-keys { display: none; }

@media (pointer: coarse) {
  .term-keys[data-role="keybar"]:not([hidden]) {
    display: flex; flex: 0 0 auto; flex-direction: column; gap: 0;
    /* One row is --keybar-h; a pinned row adds its own height. The sheet
       anchors to the top edge of the bar, whatever that height. */
    min-height: calc(var(--keybar-h) + var(--kb-safe));
    padding: 0 0 var(--kb-safe);
    /* The terminal's own background, so the bar reads as part of the screen
       the way Termux's extra-keys row does, not as a second toolbar. */
    background: var(--term-bg); border-top: 1px solid var(--border);
    position: relative;
    /* Fixed columns, so nothing here scrolls or swallows a drag. */
    touch-action: manipulation;
    /* A held key pins; it must not select text or open a callout. */
    -webkit-touch-callout: none; user-select: none;
  }
}

/* Termux's extra-keys row: flat, borderless, every key an equal share of the
   width however many there are, and only a hairline between rows. */
.tkey-row {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 0;
  min-width: 0;
}
.tkey-row + .tkey-row { border-top: 1px solid var(--border); }

.tkey {
  display: flex; align-items: center; justify-content: center; gap: 1px;
  min-width: 0; height: var(--keybar-h); padding: 0 1px;
  font-family: var(--mono); font-size: 12px; font-weight: 600; letter-spacing: .2px;
  border: 0; border-radius: 0;
  background: transparent; color: var(--text-soft);
  /* Kills the 300ms tap delay and double-tap-to-zoom on the bar. */
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  user-select: none; cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.tkey .icon { width: 17px; height: 17px; }
.tkey-label { line-height: 1; }
/* The label a narrow key cannot fit whole shrinks rather than overflowing. */
.tkey-row .tkey-label { overflow: hidden; text-overflow: clip; }
.tkey:active { background: var(--surface-3); color: var(--text); }
/* An armed modifier is the accent colour, as Termux tints its held CTRL. */
.tkey[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
/* Locked by a double tap: same colour, plus a bar under it to say it stays on. */
.tkey[data-lock="on"] { box-shadow: inset 0 -2px 0 var(--accent); }

/* --------------------------------------------------------------- dialogs */

.modal {
  padding: 20px; width: min(460px, 92vw);
  border: 1px solid var(--border-2); border-radius: var(--radius);
  background: var(--surface); color: var(--text);
  box-shadow: var(--shadow-md);
  animation: dialog-in var(--dur-slow) var(--ease);
}
.modal::backdrop { background: rgba(12, 16, 22, .6); }
@keyframes dialog-in { from { opacity: 0; transform: translateY(10px) scale(.98); } }

/* The "more keys" sheet. A panel hung from the top edge of the key bar and
   laid over the bottom of the screen, not a <dialog>: a dialog takes focus
   when it opens, the soft keyboard goes with it, and the terminal refits
   twice for one tap. Overlaid rather than in flow for the same reason — the
   screen must not change size, or tmux gets a SIGWINCH and Claude Code
   repaints. */
.key-sheet {
  position: absolute; left: 0; right: 0; bottom: 100%; z-index: 15;
  padding: 8px 6px 10px;
  background: var(--surface); color: var(--text);
  border-top: 1px solid var(--border-2);
  box-shadow: 0 -8px 28px rgba(0, 0, 0, .45);
  animation: sheet-in var(--dur) var(--ease-out);
}
@keyframes sheet-in { from { opacity: 0; transform: translateY(6px); } }
.key-sheet-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 8px; padding-left: 4px;
}
.key-sheet-hint { font-size: 12px; color: var(--text-faint); }
.key-sheet-close { width: 34px; height: 30px; flex: 0 0 auto; }
.key-sheet-grid {
  display: grid; grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.key-sheet-grid .tkey { background: var(--surface-2); border-radius: var(--radius-sm); }
/* Pinned in the sheet: a dot in the corner, the same accent as an armed
   modifier, so the sheet says which keys already live on the bar. */
.tkey[data-pinned="on"] { border-color: var(--accent); }
.tkey[data-pinned="on"]::after {
  content: ""; position: absolute; top: 3px; right: 3px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--accent);
}
.tkey { position: relative; }
.modal-actions { display: flex; gap: 9px; justify-content: flex-end; margin-top: 16px; }
.modal-error { color: var(--danger); font-size: 13px; }

.picker-roots { display: flex; gap: 7px; flex-wrap: wrap; }
.chip {
  padding: 5px 12px; font: inherit; font-size: 13px; font-weight: 500;
  border: 1px solid var(--border-2); border-radius: 999px;
  background: var(--surface-2); color: var(--text-soft); cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.chip:hover { background: var(--surface-3); color: var(--text); }
.picker-path { font-family: var(--mono); font-size: 12px; color: var(--text-faint); margin: 10px 0 5px; }
.picker-list {
  max-height: 220px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--bg);
}
.picker-item {
  display: block; width: 100%; text-align: left;
  padding: 7px 11px; font: inherit; font-family: var(--mono); font-size: 13px;
  border: 0; background: none; color: var(--text); cursor: pointer;
  transition: background var(--dur-fast);
}
.picker-item:hover { background: var(--surface-2); }

/* THE anti-zoom rule. iOS zooms whenever a focused field computes under 16px,
   and xterm's helper textarea inherits the terminal's font size — 13px on a
   phone. It is 0x0 and transparent, so this size is never seen; it exists only
   to stop the zoom. */
.xterm-helper-textarea { font-size: 16px !important; }

/* ----------------------------------------------------------------- phone */

@media (max-width: 820px) {
  .shell {
    /* One column, two rows: the top bar and everything else. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    /* The visual viewport, not the layout viewport. When the iOS keyboard
       opens innerHeight does not change but visualViewport.height does, and
       this is the number that keeps the last terminal line above it. */
    height: var(--vvh);
  }

  .rail { display: none; }

  /* The rail is gone and the grid is one column wide, so main has to move into
     it. Left at column 2 it lands in an implicit second column instead, which
     pushes every page sideways off the screen. */
  .main { grid-column: 1; }

  /* Row one of the shell grid on a phone. In flow rather than fixed: the grid
     already reserves its height, so no page pads around it and nothing ends
     up hidden underneath it. */
  .topbar {
    grid-row: 1; grid-column: 1;
    display: flex; align-items: center; gap: 4px;
    height: calc(var(--topbar-h) + var(--safe-t));
    padding: 0 8px; padding-top: var(--safe-t);
    background: var(--surface); border-bottom: 1px solid var(--border);
  }
  .main { grid-row: 2; }

  /* Left end of the bar, where a thumb on a big phone actually reaches. */
  .topbar-menu > summary {
    display: flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; border-radius: var(--radius-sm);
    color: var(--text-soft); cursor: pointer; list-style: none;
    transition: background var(--dur-fast), color var(--dur-fast);
  }
  .topbar-menu > summary::-webkit-details-marker { display: none; }
  .topbar-menu > summary .icon { width: 22px; height: 22px; }
  .topbar-menu[open] > summary { background: var(--surface-3); color: var(--text); }
  /* Under the bar, not above it: this menu hangs from the top of the screen. */
  .topbar .nav-menu-list { top: 100%; bottom: auto; left: 0; margin: 6px 0 0; }
  .topbar-brand {
    font-size: 15px; font-weight: 650; letter-spacing: .2px; color: var(--accent);
  }
  .topbar-brand:hover { text-decoration: none; }

  /* With the keyboard up the bar is noise and the key bar needs the room.
     This is state, not size, which is why it is an attribute and not a query. */
  body[data-kb="open"] .topbar { display: none; }

  /* The terminal page has one bar, not two. The shell's top bar is dropped
     and its menu is rendered inside the terminal bar (web.ts); the safe-area
     inset the top bar used to absorb moves with it. */
  .shell:has(.term) .topbar { display: none; }
  .shell:has(.term) .term-bar {
    height: calc(var(--termbar-h) + var(--safe-t));
    padding: var(--safe-t) 6px 0 4px; gap: 4px;
  }
  .term-burger { display: block; }
  .term-burger .nav-menu-list { top: 100%; bottom: auto; left: 0; margin: 6px 0 0; }
  /* No text selection on touch (tmux has the mouse), so no copy button. */
  .term-bar [data-action="copy"] { display: none; }
  /* "Live" is the normal state and not worth four letters of a narrow bar:
     a dot. Every other state is news and keeps its words. */
  .term-state[data-state="open"] {
    width: 9px; height: 9px; padding: 0; margin: 0 4px;
    border-radius: 50%; overflow: hidden; color: transparent;
    background: var(--accent);
  }

  .pad { padding: 18px 14px 36px; }
  .page-title { font-size: 18px; margin-bottom: 14px; }
  .term-cwd { display: none; }
  .term-sessions > summary { max-width: 45vw; }
  .btn { min-height: 40px; }
}
